如何在 csproj 模板中使用相对导入?

发布于 2024-11-06 07:34:07 字数 1120 浏览 5 评论 0原文

我创建了一个项目模板,其中包含一个 csproj,其中包含一个指向项目文件的导入,我在其中列出了所有第三方项目位置。我总是使用此项目模板在同一相对目录中创建项目。

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="../../../3rdParty/ThirdParty.targets" />
  ...
  <ItemGroup>
    <Reference Include="Library, Version=$(LibraryVersion), Culture=neutral, PublicKeyToken=$(LibraryPublicKeyToken), processorArchitecture=MSIL">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>$(LibraryDir)LibraryDll.dll</HintPath>
    </Reference>
  </ItemGroup>
  ...
</Project>

csproj 文件在 Visual Studio 中以及从命令提示符运行 msbuild 时可以正常工作。当我尝试使用项目模板创建项目时,出现以下错误:

C:\Users...\AppData\Local\Temp\534cylld.o0p\Temp\MyModule.csproj(3,3):导入的项目“C:\Users...\AppData\Local\3rdParty\未找到 ThirdParty.targets”。确认声明中的路径正确,并且该文件存在于磁盘上。

Visual Studio 似乎首先尝试在临时位置打开该项目。 我尝试将 $(MSBuildProjectDirectory) 添加到导入位置,希望它可以强制它使用我想要的位置,但这也不起作用。

有什么建议吗?

I've create a project template which contains a csproj which contains an Import pointing to a project file where I list all the third party project locations. I'm always using this project template to create projects in the same relative directory.

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="../../../3rdParty/ThirdParty.targets" />
  ...
  <ItemGroup>
    <Reference Include="Library, Version=$(LibraryVersion), Culture=neutral, PublicKeyToken=$(LibraryPublicKeyToken), processorArchitecture=MSIL">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>$(LibraryDir)LibraryDll.dll</HintPath>
    </Reference>
  </ItemGroup>
  ...
</Project>

The csproj file works properly in Visual Studio and when running msbuild from the command prompt. When I try to create a project using the project template I get the following error:

C:\Users...\AppData\Local\Temp\534cylld.o0p\Temp\MyModule.csproj(3,3): The imported project "C:\Users...\AppData\Local\3rdParty\ThirdParty.targets" was not found. Confirm that the path in the declaration is correct, and that the file exists on disk.

It seems that Visual Studio is trying to open the project in a temporary location first.
I've tried adding $(MSBuildProjectDirectory) to the import location hoping that it might force it to use the location I intended, but that also didn't work.

Any suggestions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

诗化ㄋ丶相逢 2024-11-13 07:34:07

您应该在 vstemplate 中将 CreateInPlace 属性设置为 true文档

指定是创建工程并在指定位置进行参数替换,还是在临时位置进行参数替换,然后将工程保存到指定位置。

如果您希望相对路径起作用,则需要在创建项目的位置进行参数替换,而不是在临时位置。

You should set the CreateInPlace property to true in the vstemplate. The documentation says

Specifies whether to create the project and perform parameter replacement in the specified location, or perform parameter replacement in a temporary location and then save the project to the specified location.

If you want relative paths to work, you need the parameter replacement to occur in the place where you're creating the project, not in a temporary location.

千寻… 2024-11-13 07:34:07

我选择了使用 带有项目模板的向导的解决方案 主要是因为我的一些模板已经需要向导了。

我创建了一个基类,所有其他向导都应该扩展该基类,或者可以单独使用该基类来实现基本功能:

public class AddTargetsWizard : IWizard
{
    private const string RELATIVE_PATH_TO_TARGETS = @"..\..\..\..\PATH\TO\Custom.Tasks.Targets";

    private const string TASK_NOT_FOUND_MESSAGE = @"A project of this type should be created under a specific path in order for the custom build task to be properly executed.

The build task could not be found at the following location:
    {0}

Including the build task would result in unexpected behavior in Visual Studio.

The project was created, but the build task WILL NOT BE INCLUDED.
This project's builds WILL NOT benefit from the custom build task.";

    private string _newProjectFileName;

    private bool _addTaskToProject;

    private Window _mainWindow;

    public AddTargetsWizard()
    {
        this._addTaskToProject = true;
    }

    protected Window MainWindow
    {
        get
        {
            return this._mainWindow;
        }
    }

    public virtual void BeforeOpeningFile(EnvDTE.ProjectItem projectItem)
    {
    }

    public virtual void ProjectFinishedGenerating(EnvDTE.Project project)
    {
        this._newProjectFileName = project.FullName;

        var projectDirectory = Path.GetDirectoryName(this._newProjectFileName);

        var taskPath = Path.GetFullPath(Path.Combine(projectDirectory, RELATIVE_PATH_TO_TARGETS));

        if (!File.Exists(taskPath))
        {
            MessageBox.Show(
                this.MainWindow,
                string.Format(TASK_NOT_FOUND_MESSAGE, taskPath),
                "Project Creation Error",
                MessageBoxButton.OK,
                MessageBoxImage.Error,
                MessageBoxResult.OK,
                MessageBoxOptions.None);

                this._addTaskToProject = false;
        }
    }

    public virtual void ProjectItemFinishedGenerating(EnvDTE.ProjectItem projectItem)
    {
    }

    public virtual void RunFinished()
    {
        if (this._addTaskToProject)
        {
            var project = new Microsoft.Build.Evaluation.Project(this._newProjectFileName);

            project.Xml.AddImport(RELATIVE_PATH_TO_TARGETS);

            project.Save();
        }
    }

    public void RunStarted(object automationObject, Dictionary<string, string> replacementsDictionary, WizardRunKind runKind, object[] customParams)
    {
        var dte = (EnvDTE80.DTE2)automationObject;

        var mainWindow = dte.MainWindow;

        foreach (var proc in System.Diagnostics.Process.GetProcesses())
        {
            if (proc.MainWindowTitle.Equals(mainWindow.Caption))
            {
                var source = HwndSource.FromHwnd(proc.MainWindowHandle);
                this._mainWindow = source.RootVisual as System.Windows.Window;
                break;
            }
        }

        this.OnRunStarted(automationObject, replacementsDictionary, runKind, customParams);
    }

    protected virtual void OnRunStarted(object automationObject, Dictionary<string, string> replacementsDictionary, WizardRunKind runKind, object[] customParams)
    {
    }

    public virtual bool ShouldAddProjectItem(string filePath)
    {
        return true;
    }
}

本演练 很好地解释了如何将向导关联到项目(或项)模板。

您会注意到,我为需要提供附加功能的子向导提供了一个虚拟 OnRunStarted 方法,例如显示向导窗口、填充替换字典等。

我不喜欢的事情关于这种方法和/或我的实现:

  • 它比普通的项目模板要复杂得多。
  • 为了使我的向导窗口(所有 WPF)成为以 Visual Studio 作为其所有者的真正模式窗口,我发现没有比使用当前实例的标题来确定 HWND 和关联的窗口更好的方法了。
  • 当项目在预期的文件夹层次结构中创建时,一切都很好,但 Visual Studio 的行为会很奇怪(即弹出无用的对话框)。这就是为什么我选择在当前项目的位置与我的相对路径不兼容时显示错误消息并避免插入 Import

如果有人有其他想法,我仍然洗耳恭听。

I have opted for a solution using Wizards with Project Templates mainly because some of my templates already required wizards.

I created a base class that all my other wizards are supposed to extend or that can be used by itself for just basic functionality:

public class AddTargetsWizard : IWizard
{
    private const string RELATIVE_PATH_TO_TARGETS = @"..\..\..\..\PATH\TO\Custom.Tasks.Targets";

    private const string TASK_NOT_FOUND_MESSAGE = @"A project of this type should be created under a specific path in order for the custom build task to be properly executed.

The build task could not be found at the following location:
    {0}

Including the build task would result in unexpected behavior in Visual Studio.

The project was created, but the build task WILL NOT BE INCLUDED.
This project's builds WILL NOT benefit from the custom build task.";

    private string _newProjectFileName;

    private bool _addTaskToProject;

    private Window _mainWindow;

    public AddTargetsWizard()
    {
        this._addTaskToProject = true;
    }

    protected Window MainWindow
    {
        get
        {
            return this._mainWindow;
        }
    }

    public virtual void BeforeOpeningFile(EnvDTE.ProjectItem projectItem)
    {
    }

    public virtual void ProjectFinishedGenerating(EnvDTE.Project project)
    {
        this._newProjectFileName = project.FullName;

        var projectDirectory = Path.GetDirectoryName(this._newProjectFileName);

        var taskPath = Path.GetFullPath(Path.Combine(projectDirectory, RELATIVE_PATH_TO_TARGETS));

        if (!File.Exists(taskPath))
        {
            MessageBox.Show(
                this.MainWindow,
                string.Format(TASK_NOT_FOUND_MESSAGE, taskPath),
                "Project Creation Error",
                MessageBoxButton.OK,
                MessageBoxImage.Error,
                MessageBoxResult.OK,
                MessageBoxOptions.None);

                this._addTaskToProject = false;
        }
    }

    public virtual void ProjectItemFinishedGenerating(EnvDTE.ProjectItem projectItem)
    {
    }

    public virtual void RunFinished()
    {
        if (this._addTaskToProject)
        {
            var project = new Microsoft.Build.Evaluation.Project(this._newProjectFileName);

            project.Xml.AddImport(RELATIVE_PATH_TO_TARGETS);

            project.Save();
        }
    }

    public void RunStarted(object automationObject, Dictionary<string, string> replacementsDictionary, WizardRunKind runKind, object[] customParams)
    {
        var dte = (EnvDTE80.DTE2)automationObject;

        var mainWindow = dte.MainWindow;

        foreach (var proc in System.Diagnostics.Process.GetProcesses())
        {
            if (proc.MainWindowTitle.Equals(mainWindow.Caption))
            {
                var source = HwndSource.FromHwnd(proc.MainWindowHandle);
                this._mainWindow = source.RootVisual as System.Windows.Window;
                break;
            }
        }

        this.OnRunStarted(automationObject, replacementsDictionary, runKind, customParams);
    }

    protected virtual void OnRunStarted(object automationObject, Dictionary<string, string> replacementsDictionary, WizardRunKind runKind, object[] customParams)
    {
    }

    public virtual bool ShouldAddProjectItem(string filePath)
    {
        return true;
    }
}

This walkthrough provides a pretty good explanation on how to associate the wizard to a project (or item) template.

You'll notice that I am providing a virtual OnRunStarted method for child wizards that would need to provide additional functionality, such as displaying wizard windows, populating the replacements dictionary, etc.

The things I don't like about this approach and/or my implementation:

  • It is way more complicated than a plain project template.
  • In order for my wizard windows—all WPF—to be real modal windows with Visual Studio as their owner, I found no better way than to use the current instance's title to determine the HWND and associated Window.
  • All's well when the projects are created in the expected folder hierarchy, but Visual Studio behaves strangely (i.e. unhelpful dialogs pop up) otherwise. That's why I chose to display an error message and avoid inserting the Import if the current project's location does not work with my relative path.

If anyone has other ideas, I'm still all ears.

画中仙 2024-11-13 07:34:07

我想我可能会使用 环境变量相反......这对你的情况有用吗?如果您必须在开发人员之间共享项目模板,您可以在 powershell 脚本中做一些奇特的事情,它会自动设置环境变量,或者通过询问开发人员的模板目录在哪里。

[Environment]::SetEnvironmentVariable("3rdPartyTargets", "%ProgramFiles%/3rdParty/ThirdParty.targets", "User")

然后在 csproj 宏中:

<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(3rdPartyTargets)" />
  ...

哦等等。这适用于 C++,但您可能需要 使用 msbuild 对于 ac#/vb.net 项目。

I think I might use an environment variable instead... Would that work in your situation? And if you had to share the project templates between developers, you could do something fancy in a powershell script where it would set the environment variable either automatically or by asking the developer where his template directory is.

[Environment]::SetEnvironmentVariable("3rdPartyTargets", "%ProgramFiles%/3rdParty/ThirdParty.targets", "User")

And then in csproj macro:

<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(3rdPartyTargets)" />
  ...

Oh wait. That works for C++, but you might have to use msbuild for a c#/vb.net proj.

z祗昰~ 2024-11-13 07:34:07

默认行为是将模板解压到临时文件夹中。然后,在那里执行参数替换。不知何故,测试了相对路径,并且在临时位置中,文件不存在。

您是否尝试在导入之前添加以下行?

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

The default behavior is for the template to be unpacked in a temporary folder. Then, the parameter replacements are performed there. Somehow, the relative paths are tested and, in the temp location, the files do not exist.

Did you try to add the following line before your import?

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文