Используйте $(SolutionName) в параметрах командной строки MsBuild

Чтобы эмулировать параметр "PerProject" в сборке XAML TFS 2013 в новых сборках на основе задач Build 2015, я хотел бы иметь возможность передавать 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".

Я не могу вспомнить, как передать свойство в командную строку и сделать ли это позднее расширение... Любые советы?

Для тех, кто хочет подражать SingleFolder или же AsConfiguredэто легко:

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

3 ответа

Решение

Как я и опасался, похоже, не существует простого способа переопределить свойство из командной строки и "внедрить" в него значение другого свойства на этапе оценки.

Есть несколько способов обойти это, но они не идеальны и, конечно, не универсальны для каждого языка, поддерживаемого MsBuild. Жалость.

Я отладил целевые файлы MsBuild и нашел решение для воспроизведения старого поведения эпохи 2005/2008. Не полностью для решения, но оно перенаправляет проекты в подпапку.

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

Как обычно, $(SolutionName) определяется при выполнении конвейеров MSBuild уровня решения, таких как запуск dotnet restore в корневом каталоге решения.

Делать $(SolutionName) доступно для конвейеров MSBuild на уровне проекта, добавьте Directory.Build.props файл в корне вашего решения со следующим содержимым:

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

Сейчас же $(SolutionName) будет определен даже при выполнении конвейеров MSBuild на уровне проекта.

Этот ответ работает лучше всего, когда в корне каталога решений есть ровно один файл решения. Вам нужно будет немного помассировать вышесказанное для других структур проекта.

Конечно, вы также можете полениться и указать имя решения напрямую, но это открывает возможность проблем с рефакторингом (не забудьте обновить этот файл, если имя решения изменится).

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

Одно из решений состоит в том, чтобы имитировать такую ​​"позднюю оценку" самостоятельно, изменяя 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 к некоторому базовому dir + имя решения. Тогда, если вы строите решение, как

msbuild my.sln /p:CustomBeforeMicrosoftCSharpTargets=paths.targets;
                  OutputPathBaseDir=$(Build.BinariesDirectory)

каждый проект импортирует файл paths.targets и устанавливает выходное свойство в valueOfBinariesDirectory\my, которое, я думаю, именно то, что вам нужно.

Вы правы, что сборка TFS vNext не может распознать $(SolutionName) в OutputPath, поскольку $(SolutionName) не отображается в предопределенных переменных.

В качестве альтернативы мы можем назвать определение сборки именем решения, а затем указать аргумент MSBuild: /p:OutputPath="$(Build.BinariesDirectory)\$(Build.DefinitionName)"таким образом, мы можем получить вывод под именем решения.

Другие вопросы по тегам