在MsBuild命令行参数中使用$(SolutionName)

5
为了在新的基于任务的Build 2015构建中模拟TFS 2013的XAML build中的“PerProject”选项,我希望能够在不必每次手动设置的情况下将SolutionName传递给msbuild命令行参数。
我想要做的事情类似于:
/p:OutputPath=$(Build.BinariesDirectory)\$(SolutionName)\

我希望MsBuild能够推断出$(SolutionName)参数的位置。但是在命令行中传递时,新的任务运行程序将使用正确的目标路径替换$(Build.BinariesDirectory), 并保持$(SolutionName)不变。不幸的是,MsBuild此后也会保持该属性不变:

Copying file from "obj\Debug\TFSBuild.exe" to "bin\debug\$(SolutionName)\TFSBuild.exe".
TFSBuild -> b\$(SolutionName)\TFSBuild.exe
Copying file from "obj\Debug\TFSBuild.pdb" to "b\$(SolutionName)\TFSBuild.pdb".

我不记得有一种方法可以将属性传递给命令行并进行延迟扩展...有什么提示吗?

对于那些想要模拟SingleFolderAsConfigured的人来说,这很容易:

SingleFolder -> /p:OutputPath="$(Build.BinariesDirectory)"
Asconfigured -> don't pass OutputPath
PerProject   -> /p:OutputPath="$(Build.BinariesDirectory)\HARDCODESOLUTIONNAME"

那么,将/p:MyOutputPathBaseDir=$(Build.BinariesDirectory)传递过去,然后在项目文件中将OutputPath属性设置为$(MyOutputPathBaseDir)\$(SolutionName)如何? - stijn
这将需要我编辑所有项目文件,这样做是可行的,但我也可以插入自己的变量并使用 AsConfigured。 - jessehouwing
这并不需要我编辑所有的项目文件,msbuild有多个扩展点可以导入你在命令行中传递给它的任意文件。在你的情况下,那个文件只需按照我描述的设置属性即可。 - stijn
我该如何从命令行导入一个额外的targets文件?这将会很有帮助。 - jessehouwing
4个回答

3

恐怕没有简单的方法可以从命令行覆盖属性,并在评估阶段将另一个属性的值“注入”到它中。

有一些方法可以绕过它,但它们不是理想的,对于MsBuild支持的每种语言都不是通用的。 真是遗憾。

我已经调试了MsBuild目标文件并找到了一个解决方案来复现2005/2008年的旧行为。 不完全是解决方案,但它确实将项目重定向到子文件夹中。

 /p:GenerateProjectSpecificOutputFolder=true /p:OutDirWasSpecified=true
 /p:OutputPath=$(Build.BinariesDirectory)

2

通常情况下,当执行解决方案级别的MSBuild管道(例如在根解决方案目录中运行dotnet restore)时,会定义$(SolutionName)

要使$(SolutionName)可用于项目级别的MSBuild管道,请在解决方案的根目录中添加一个Directory.Build.props文件,并将以下内容添加到其中:

<Project>
    <PropertyGroup>
        <SolutionName Condition="'$(SolutionName)' == ''">
            $([System.IO.Path]::GetFileNameWithoutExtension($([System.IO.Directory]::GetFiles("$(MSBuildThisFileDirectory)", "*.sln")[0])))
        </SolutionName>
    </PropertyGroup>
</Project>

现在,即使在执行项目级MSBuild管道时,$(SolutionName)也将被定义。

当解决方案目录的根目录中恰好有一个解决方案文件时,此答案最有效。对于其他项目结构,您需要稍微调整上面的内容。

当然,您也可以直接指定解决方案名称,但这会导致重构问题的可能性(如果解决方案名称更改,则需要记住更新此文件)。

<Project>
    <PropertyGroup>
        <SolutionName Condition="'$(SolutionName)' == ''">
            MySolutionName
        </SolutionName>
    </PropertyGroup>
</Project>

1

一种解决方案是通过在项目文件中更改OutputPath来模拟这种“延迟评估”。为了避免手动更改每个单独的项目文件,您可以使用CustomBeforeMicrosoftCSharpTargets扩展点。这只是一种属性,当找到并指向一个现有文件时,将导致该文件在所有实际构建逻辑之前的某个地方被导入。这是想法:在某个地方创建一个名为paths.targets的文件 - 可以将其包含在源代码控制中,或者作为构建过程的一部分动态生成它。内容:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <OutputPath Condition="'$(OutputPathBaseDir)'!=''">$(OutputPathBaseDir)\$(SolutionName)</OutputPath>
  </PropertyGroup>
</Project>

所以这只是将OutputPath覆盖为一些基本目录+解决方案名称。然后,如果您像这样构建解决方案
msbuild my.sln /p:CustomBeforeMicrosoftCSharpTargets=paths.targets;
                  OutputPathBaseDir=$(Build.BinariesDirectory)

每个项目都将导入paths.targets文件并将输出属性设置为valueOfBinariesDirectory\my,我认为这正是您想要的。

但在C#,VB.NET和C ++的混合中,这是行不通的。我知道它可能会,而且我怀疑还有其他3个属性可以尝试覆盖。 - jessehouwing
对于C++,有ForceImportBeforeCppTargets选项,至于VB我不确定,但应该类似。 - stijn

0

你说得对,TFS vNext构建无法识别OutputPath中的$(SolutionName),因为$(SolutionName)没有列在预定义变量中。

作为替代方案,我们可以使用解决方案名称命名构建定义,然后指定MSBuild参数为:/p:OutputPath="$(Build.BinariesDirectory)\$(Build.DefinitionName)"这样,我们就可以在解决方案名称下获取输出。


这并不是一个选项,因为“PerProject”选项只在构建多个解决方案时才有意义。而当构建多个解决方案时,这种方法是行不通的。 - jessehouwing
我知道在Build 2015中它不是预定义变量,而是在MsBuild本身中。因此,我的希望是诱导MsBuild对该变量进行某些操作。 - jessehouwing
当构建解决方案时(而不是构建单个项目时),确实在msbuild中定义了SolutionName。 - stijn
我知道,在Xaml Build中,“PerProject”名称选择得不好。拆分和注入是在XAML工作流中处理的,它只需要您传递给MsBuild的任何文件,剥离扩展名并创建一个以该名称命名的文件夹。我怀疑“Project”一词源于2008年时Team Build需要一个TfsBuild.proj文件的时代。 - jessehouwing

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接