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

Posted

技术标签:

【中文标题】如何在 csproj 模板中使用相对导入?【英文标题】:How do I use a relative import in a csproj template? 【发布时间】:2011-08-22 08:18:40 【问题描述】:

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

<?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\找不到第三方.targets”。确认声明中的路径正确,并且该文件存在于磁盘上。

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

有什么建议吗?

【问题讨论】:

我遇到了完全相同的问题,除了我导入的文件用于执行自定义构建任务。我需要这个路径是相对的,因为它是相对于我的源代码管理文件夹层次结构的。 @Christo:您能否将完整的 pah 提供给您从命令行运行构建的目录? 【参考方案1】:

您应该在 vstemplate 中将 CreateInPlace 属性设置为 true。 documentation 说

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

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

【讨论】:

【参考方案2】:

我选择使用Wizards with Project Templates 的解决方案主要是因为我的一些模板已经需要向导。

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

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 很好地解释了如何将向导与项目(或项目)模板相关联。

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

我不喜欢这种方法和/或我的实现的地方:

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

如果有人有其他想法,我仍然全神贯注。

【讨论】:

【参考方案3】:

我想我可能会改用environment variable...这对您的情况有用吗?如果您必须在开发人员之间共享项目模板,您可以在 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++,但您可能有 to use msbuild 用于 c#/vb.net 项目。

【讨论】:

【参考方案4】:

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

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

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

【讨论】:

导入 Microsoft.CSharp.targets 不会解决问题。

以上是关于如何在 csproj 模板中使用相对导入?的主要内容,如果未能解决你的问题,请参考以下文章

如何在我的新CSPROJ类库中包含带有本机dll的NuGet包?

如何在 Python 模块中正确使用相对或绝对导入?

如何避免在 Angular 2 中使用非常长的相对路径进行导入?

如何在 C# 中快速处理 csproj 文件

在 .csproj 文件中手动添加 Elmah 和 ApplicationInsights

如何在python中完成相对导入