msbuild
msbuild copied to clipboard
Evaluate a project from the command line
This is a feature request: Add a command line switch which would make MSBuild evaluate a project and output the value of a property or a list of items. No target would be called. This would be useful for scripting purposes.
Some examples of what I mean:
C:\Project> msbuild SomeProject.csproj -evaluate:'$(TargetPath)'
C:\Project\SomeProject\bin\Debug\SomeProject.exe
C:\Project> msbuild SomeProject.csproj -evaluate:'@(Compile)'
Program.cs
Foo.cs
Bar.cs
C:\Project> msbuild SomeProject.csproj -evaluate:'@(Compile->%(FileName))'
Program
Foo
Bar
Or a simpler version:
C:\Project> msbuild SomeProject.csproj -evaluateProperty:TargetPath
C:\Project\SomeProject\bin\Debug\SomeProject.exe
C:\Project> msbuild SomeProject.csproj -evaluateItems:Compile
Program.cs
Foo.cs
Bar.cs
Take a look at MSBuildDumper and see if it fits your needs: https://github.com/KirillOsenkov/MSBuildTools
https://github.com/KirillOsenkov/MSBuildTools/blob/master/src/MSBuildDumper/MSBuildDumper.cs
@KirillOsenkov thanks, but I know that getting the evaluated values is pretty easy with the MSBuild NuGet package.
To clarify, I simply thought that having this possibility from MSBuild itself (without requiring additional tools) would be a useful feature, and I think it would make sense to support that. I wished several times that MSBuild offered that.
I could contribute that feature if the MSBuild team thinks it would be valuable.
Can you elaborate on "This would be useful for scripting purposes."? What specifically?
I would have sworn that we already had a work item for this but I don't see one. I think it's a fine idea and would help with various debugging scenarios. I would lean toward the simpler only-dump-values interface; I'm not sure how difficult plumbing the transform mechanism in would be and I think you could get most of the value from just items/properties. I'd suggest that the output should match the text logger, so combining requests would be more clear:
$ msbuild SomeProject.csproj -evaluateItems:Compile -evaluateProperty:TargetPath
TargetPath = s:\msbuild\artifacts\Debug\bin\Microsoft.Build.Framework\net472\Microsoft.Build.Framework.dll
Compile
..\Shared\BinaryWriterExtensions.cs
Link = Shared\BinaryWriterExtensions.cs
..\Shared\Constants.cs
Link = Shared\Constants.cs
BuildEngineResult.cs
BuildErrorEventArgs.cs
BuildEventArgs.cs
BuildEventContext.cs
Can you elaborate on "This would be useful for scripting purposes."? What specifically?
I originally needed to get the OutDir
property value in a script in order to zip the build output.
I couldn't just set my own OutDir
because some referenced projects use multi-targeting and all targets would end up in the same output directory, thus overwriting each other. I ended up solving this issue differently, but I wished I had that feature nevertheless.
I'd suggest that the output should match the text logger, so combining requests would be more clear
The problem I see with this approach is that it's well suited for human inspection, but less useful for scripting. My idea was to make MSBuild behave like a Unix utility in this case: only output the desired value so it can be easily consumed by a script or by another tool which could be piped to the MSBuild output.
With this approach, a script would additionally have to strip the TargetPath =
prefix, and there would probably be issues if the value is multi-line.
Also, given that the debugging experience is very good using the binary log viewer, I don't think a text output aimed at debuggability would provide a compelling benefit over that.
That's interesting. What would you do about item metadata?
Well, actually item metadata is the reason I suggested the "full" syntax (-evaluate
) in the first place. I supposed it shouldn't be too hard to evaluate MSBuild expressions since they're also evaluated in the execution phase, and that this mode could be an alternate "execution phase" where you basically evaluate a single expression.
So suppose I want a list of linked files from your example, here's how I could get it:
$ msbuild SomeProject.csproj -evaluate:"@(Compile->'%(Identity):%(Link)')"
..\Shared\BinaryWriterExtensions.cs:Shared\BinaryWriterExtensions.cs
..\Shared\Constants.cs:Shared\Constants.cs
BuildEngineResult.cs:
BuildErrorEventArgs.cs:
BuildEventArgs.cs:
BuildEventContext.cs:
Line breaks could still be an issue though, but a unique delimiter could be inserted at the end of each line to handle these if needed.
This would be super useful for us. We worked around that currently but our workaround broke and we now have lots of work backporting script-changes :-(
There's a related consideration here - properties and items are not static, they can be modified during the evaluation of a particular target. That implies to me that the request might look something like to specify a target to run (and maybe just evaluate the project if none is specified):
dotnet msbuild -t:TARGET_TO_RUN --evaluate "EVAL_EXPRESSION"
or
dotnet msbuild -t:TARGET_TO_RUN --evaluateProperty "PROP_NAME"
or
dotnet msbuild -t:TARGET_TO_RUN --evaluateItems "ITEMS_NAME"
(though labeling of the returned values might be interesting)
@baronfel Great point. We do this in the Docker extension for VSCode to determine the path of the Blazor static web assets manifest, in order to "containerize" it by adjusting the paths. The target "ResolveStaticWebAssetsConfiguration" has to be run before the necessary information is available.
More on that here: https://github.com/microsoft/vscode-docker/blob/main/resources/netCore/GetBlazorManifestLocations.targets
I just want to show our usecase. We do have a msbuild file thats integrated in most of our projects. It contains version info which is used to set assembly metadata like FileVersion, AssemblyVersion and so on. There are properties which are static and calculated using values of other properties. We do have Powershell scripts that have to read this values to do deployments and calculate paths and so on. We've used the msbuild api in the past to evaluate the properties in powershell but this broke with a new version of msbuild/powershell. So we now have a proxy msbuild-script which gets a path to another msbuild-script and a list of properties to evaluate and then does the evaluation and pretty prints the evaluated properties and so on. So we now use msbuild to run the proxy-msbuild script which then prints everything to console and we parse it in powershell.
Here is the script we want to have values of:
<Project xmlns="http://XXXs.microsoft.com/developer/msbuild/2003">
<Import Project="$(USERPROFILE)\Pre-XXX-VersionInfo.msb.xml" Condition="exists('$(USERPROFILE)\Pre-XXX-VersionInfo.msb.xml')" />
<PropertyGroup>
<!-- The XXX Release-Year -->
<XXXProductYear Condition="'$(XXXProductYear)' == ''">2022</XXXProductYear>
<!-- The XXX Service-Pack or empty for a major release -->
<XXXProductServicePack Condition="'$(XXXProductServicePack)' == ''">0</XXXProductServicePack>
<!-- The suffix appended to the product name, for example "SP1" -->
<XXXProductSuffix Condition="'$(XXXProductSuffix)' == '' and '$(XXXProductServicePack)' != '0'"> SP$(XXXProductServicePack)</XXXProductSuffix>
<!-- The full product name used in AssemblyInformationalVersion -->
<XXXProductName Condition="'$(XXXProductName)' == ''">XXX $(XXXProductYear)$(XXXProductSuffix)</XXXProductName>
<XXXCompanyName>XXX Company</XXXCompanyName>
<XXXCopyright>Copyright © XXX</XXXCopyright>
<!-- START: Release Version -->
<!-- This group of versions will be used as the release version and for help documentation -->
<!-- $(XXXVersionMajor).$(XXXVersionMinor).$(XXXVersionBuild).$(XXXVersionRevision) -->
<!-- Major version position -->
<XXXVersionMajor Condition="'$(XXXVersionMajor)' == ''">12</XXXVersionMajor>
<!-- Is never changed currently. Defaults to zero -->
<XXXVersionMinor Condition="'$(XXXVersionMinor)' == ''">0</XXXVersionMinor>
<!-- Service-Pack position -->
<XXXVersionBuild Condition="'$(XXXVersionBuild)' == ''">$(XXXProductServicePack)</XXXVersionBuild>
<!-- Is never changed currently. Defaults to zero -->
<XXXVersionRevision Condition="'$(XXXVersionRevision)' == ''">0</XXXVersionRevision>
<!-- END: Release Version -->
<!-- START: Assembly Version -->
<!-- This group of versions will be used in future to automatically manipulate assembly version of assemblyinfo.cs files -->
<!-- $(XXXVersionAssemblyMajor).$(XXXVersionAssemblyMinor).$(XXXVersionAssemblyBuild).$(XXXVersionAssemblyRevision) -->
<!-- Major verison used for references between assemblies -->
<XXXVersionAssemblyMajor Condition="'$(XXXVersionAssemblyMajor)' == ''">$(XXXVersionMajor)</XXXVersionAssemblyMajor>
<!-- Is never changed currently. Defaults to zero -->
<XXXVersionAssemblyMinor Condition="'$(XXXVersionAssemblyMinor)' == ''">0</XXXVersionAssemblyMinor>
<!-- Is never changed currently. Defaults to zero -->
<XXXVersionAssemblyBuild Condition="'$(XXXVersionAssemblyBuild)' == ''">$(XXXProductServicePack)</XXXVersionAssemblyBuild>
<!-- Is never changed currently. Defaults to zero -->
<XXXVersionAssemblyRevision Condition="'$(XXXVersionAssemblyRevision)' == ''">0</XXXVersionAssemblyRevision>
<!-- END: Assembly Version -->
<!-- START: File Version -->
<!-- This group of versions will be used in future to automatically manipulate file version of assemblyinfo.cs files -->
<!-- $(XXXVersionFileMajor).$(XXXVersionFileMinor).$(XXXVersionFileBuild).$(XXXVersionFileRevision) -->
<!-- Major version used for file details in windows explorer -->
<XXXVersionFileMajor Condition="'$(XXXVersionFileMajor)' == ''">$(XXXVersionMajor)</XXXVersionFileMajor>
<!-- Is never changed currently. Defaults to zero -->
<XXXVersionFileMinor Condition="'$(XXXVersionFileMinor)' == ''">0</XXXVersionFileMinor>
<!-- Is never changed currently. Defaults to zero -->
<XXXVersionFileBuild Condition="'$(XXXVersionFileBuild)' == ''">$(XXXProductServicePack)</XXXVersionFileBuild>
<!-- can be changed for each modified assembly manually. -->
<XXXVersionFileRevision Condition="'$(XXXVersionFileRevision)' == ''">0</XXXVersionFileRevision>
<!-- END: File Version -->
<!-- Make sure our target file changes will take effect when a new build is done -->
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<GenerateXXXAssemblyInfo Condition="'$(GenerateXXXAssemblyInfo)' == ''">true</GenerateXXXAssemblyInfo>
</PropertyGroup>
<PropertyGroup Condition="'$(GenerateXXXAssemblyInfo)' == 'true'">
<GenerateXXXAssemblyVersionAttribute Condition="'$(GenerateXXXAssemblyVersionAttribute)' == ''">true</GenerateXXXAssemblyVersionAttribute>
<GenerateXXXAssemblyFileVersionAttribute Condition="'$(GenerateXXXAssemblyFileVersionAttribute)' == ''">true</GenerateXXXAssemblyFileVersionAttribute>
<GenerateXXXAssemblyInformationalVersionAttribute Condition="'$(GenerateXXXAssemblyInformationalVersionAttribute)' == ''">true</GenerateXXXAssemblyInformationalVersionAttribute>
<GenerateXXXAssemblyCompanyAttribute Condition="'$(GenerateXXXAssemblyCompanyAttribute)' == ''">true</GenerateXXXAssemblyCompanyAttribute>
<GenerateXXXAssemblyCopyrightAttribute Condition="'$(GenerateXXXAssemblyCopyrightAttribute)' == ''">true</GenerateXXXAssemblyCopyrightAttribute>
</PropertyGroup>
<Target Name="GenerateXXXAssemblyInfoFile" BeforeTargets="CoreCompile" DependsOnTargets="PrepareForBuild;BeforeCoreGenerateXXXAssemblyInfoFile;CoreGenerateXXXAssemblyInfoFile" Condition="'$(GenerateXXXAssemblyInfo)' == 'true'" />
<!-- We have to create the property here, otherwise $(IntermediateOutputPath) would be empty -->
<Target Name="BeforeCoreGenerateXXXAssemblyInfoFile">
<PropertyGroup>
<XXXAssemblyInfoFilePath Condition="'$(XXXAssemblyVersionInfoFilePath)' == ''">$(IntermediateOutputPath)$(MSBuildProjectName).XXXAssemblyInfo$(DefaultLanguageSourceExtension)</XXXAssemblyInfoFilePath>
</PropertyGroup>
</Target>
<Target Name="CoreGenerateXXXAssemblyInfoFile" Condition="'$(Language)'=='VB' or '$(Language)'=='C#'" Inputs="$(MSBuildAllProjects)" Outputs="$(XXXAssemblyInfoFilePath)">
<PropertyGroup>
<GitShortSHA Condition="'$(CI_COMMIT_SHORT_SHA)' != ''">$(CI_COMMIT_SHORT_SHA)</GitShortSHA>
</PropertyGroup>
<Exec Command="git rev-parse --short=8 HEAD" ConsoleToMSBuild="true" EchoOff="true" WorkingDirectory="$(SourceCodePath)" ContinueOnError="true" Condition="'$(GitShortSHA)' == ''">
<Output TaskParameter="ConsoleOutput" PropertyName="GitShortSHA" />
</Exec>
<ItemGroup>
<XXXAssemblyAttribute Include="System.Reflection.AssemblyVersionAttribute" Condition="'$(GenerateXXXAssemblyVersionAttribute)' == 'true'">
<_Parameter1>$(XXXVersionAssemblyMajor).$(XXXVersionAssemblyMinor).$(XXXVersionAssemblyBuild).$(XXXVersionAssemblyRevision)</_Parameter1>
</XXXAssemblyAttribute>
<XXXAssemblyAttribute Include="System.Reflection.AssemblyFileVersionAttribute" Condition="'$(GenerateXXXAssemblyFileVersionAttribute)' == 'true'">
<_Parameter1>$(XXXVersionFileMajor).$(XXXVersionFileMinor).$(XXXVersionFileBuild).$(XXXVersionFileRevision)</_Parameter1>
</XXXAssemblyAttribute>
<XXXAssemblyAttribute Include="System.Reflection.AssemblyInformationalVersionAttribute" Condition="'$(GenerateXXXAssemblyInformationalVersionAttribute)' == 'true'">
<_Parameter1>$(XXXVersionAssemblyMajor).$(XXXVersionAssemblyMinor).$(XXXVersionAssemblyBuild).$(XXXVersionAssemblyRevision) $(XXXProductName) ($(GitShortSHA))</_Parameter1>
</XXXAssemblyAttribute>
<XXXAssemblyAttribute Include="System.Reflection.AssemblyCompanyAttribute" Condition="'$(GenerateXXXAssemblyCompanyAttribute)' == 'true'">
<_Parameter1>$(XXXCompanyName)</_Parameter1>
</XXXAssemblyAttribute>
<XXXAssemblyAttribute Include="System.Reflection.AssemblyCopyrightAttribute" Condition="'$(GenerateXXXAssemblyCopyrightAttribute)' == 'true'">
<_Parameter1>$(XXXCopyright)</_Parameter1>
</XXXAssemblyAttribute>
</ItemGroup>
<ItemGroup>
<!-- Ensure the generated assemblyinfo file is not already part of the Compile sources -->
<Compile Remove="$(XXXAssemblyInfoFilePath)" />
</ItemGroup>
<Error Text="Variable XXXVersionAssemblyMajor with value '$(XXXVersionAssemblyMajor)' is not a number.'" Condition="!$([System.Text.RegularExpressions.Regex]::IsMatch($(XXXVersionAssemblyMajor), '^\d+$'))" />
<Error Text="Variable XXXVersionAssemblyMinor with value '$(XXXVersionAssemblyMinor)' is not a number.'" Condition="!$([System.Text.RegularExpressions.Regex]::IsMatch($(XXXVersionAssemblyMinor), '^\d+$'))" />
<Error Text="Variable XXXVersionAssemblyBuild with value '$(XXXVersionAssemblyBuild)' is not a number.'" Condition="!$([System.Text.RegularExpressions.Regex]::IsMatch($(XXXVersionAssemblyBuild), '^\d+$'))" />
<Error Text="Variable XXXVersionAssemblyRevision with value '$(XXXVersionAssemblyRevision)' is not a number.'" Condition="!$([System.Text.RegularExpressions.Regex]::IsMatch($(XXXVersionAssemblyRevision), '^\d+$'))" />
<Error Text="Variable XXXVersionFileMajor with value '$(XXXVersionFileMajor)' is not a number.'" Condition="!$([System.Text.RegularExpressions.Regex]::IsMatch($(XXXVersionFileMajor), '^\d+$'))" />
<Error Text="Variable XXXVersionFileMinor with value '$(XXXVersionFileMinor)' is not a number.'" Condition="!$([System.Text.RegularExpressions.Regex]::IsMatch($(XXXVersionFileMinor), '^\d+$'))" />
<Error Text="Variable XXXVersionFileBuild with value '$(XXXVersionFileBuild)' is not a number.'" Condition="!$([System.Text.RegularExpressions.Regex]::IsMatch($(XXXVersionFileBuild), '^\d+$'))" />
<Error Text="Variable XXXVersionFileRevision with value '$(XXXVersionFileRevision)' is not a number.'" Condition="!$([System.Text.RegularExpressions.Regex]::IsMatch($(XXXVersionFileRevision), '^\d+$'))" />
<WriteCodeFragment AssemblyAttributes="@(XXXAssemblyAttribute)" Language="$(Language)" OutputFile="$(XXXAssemblyInfoFilePath)">
<Output TaskParameter="OutputFile" ItemName="Compile" />
<Output TaskParameter="OutputFile" ItemName="FileWrites" />
</WriteCodeFragment>
</Target>
<Import Project="$(USERPROFILE)\Post-XXX-VersionInfo.msb.xml" Condition="exists('$(USERPROFILE)\Post-XXX-VersionInfo.msb.xml')" />
</Project>
And thats the proxy-script:
<Project DefaultTargets="PrintPropertiesToEvaluate">
<PropertyGroup>
<ProjectFilePath></ProjectFilePath>
<PropertiesToEvaluate></PropertiesToEvaluate>
<PropertiesToInitialize></PropertiesToInitialize>
<IncludePropertyNames>false</IncludePropertyNames>
<PropertyValueSeparator>=</PropertyValueSeparator>
</PropertyGroup>
<Target Name="PrintPropertiesToEvaluate">
<Error Text="The property 'ProjectFilePath' is empty, but is required." Condition="'$(ProjectFilePath)' == ''" />
<Error Text="Project '$(ProjectFilePath)' does not exist." Condition="!Exists('$(ProjectFilePath)')" />
<Error Text="The property 'PropertiesToEvaluate' is empty, but is required." Condition="'$(PropertiesToEvaluate)' == ''" />
<EvaluateMSBuildProperties ProjectFilePath="$(ProjectFilePath)" PropertiesToEvaluate="$(PropertiesToEvaluate)" PropertiesToInitialize="$(PropertiesToInitialize)">
<Output TaskParameter="Result" ItemName="EvaluatedProperties"/>
</EvaluateMSBuildProperties>
<ConsoleWriteLine Text="%(EvaluatedProperties.Identity)$(PropertyValueSeparator)%(EvaluatedProperties.Value)" Condition="$(IncludePropertyNames)"/>
<ConsoleWriteLine Text="%(EvaluatedProperties.Value)" Condition="!$(IncludePropertyNames)"/>
</Target>
<UsingTask TaskName="EvaluateMSBuildProperties" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<ProjectFilePath ParameterType="System.String" Required="true" />
<PropertiesToEvaluate ParameterType="System.String" Required="true" />
<PropertiesToInitialize ParameterType="System.String" Required="false" />
<Result ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" />
</ParameterGroup>
<Task>
<Reference Include="System.Xml" />
<Reference Include="Microsoft.Build" />
<Using Namespace="System.Collections.Generic" />
<Using Namespace="Microsoft.Build.Evaluation" />
<Code Type="Fragment" Language="cs"><![CDATA[
Dictionary<string, string> initProperties = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
if (PropertiesToInitialize != null)
{
foreach (string initProperty in PropertiesToInitialize.Split(';'))
{
int equalSignIndex = initProperty.IndexOf('=');
if (equalSignIndex == -1)
throw new InvalidDataException(string.Format("Property definition '{0}' is invalid. It's missing a value (no equal sign).", initProperty));
initProperties[initProperty.Substring(0, equalSignIndex)] = initProperty.Substring(equalSignIndex + 1);
}
}
ProjectCollection projectCollection = new ProjectCollection(initProperties);
Project project = projectCollection.LoadProject(ProjectFilePath);
string[] propertyNamesToValuate = PropertiesToEvaluate.Split(';');
Result = new ITaskItem[propertyNamesToValuate.Length];
for (int i = 0; i < propertyNamesToValuate.Length; i++)
{
string propertyName = propertyNamesToValuate[i];
string propertyValue = project.GetPropertyValue(propertyName);
Result[i] = new TaskItem(propertyName, new Dictionary<string, string> { { "Value", propertyValue } });
}
]]>
</Code>
</Task>
</UsingTask>
<UsingTask TaskName="ConsoleWriteLine" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<Text Required="false" ParameterType="System.String"/>
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs"><![CDATA[ Console.WriteLine(Text); ]]></Code>
</Task>
</UsingTask>
</Project>
It would be nice to be able to skip this proxy script and just use msbuild with parameters. We dont use the msbuild api in powershell anymore because it broke suddenly and we had to do lots of backporting. Hope this helps in evaluating usecases.
Regards, Scordo
@Scordo for advanced scenarios like this I think a C# console app that uses MSBuild APIs would be a good approach.
I doubt we can ever put enough flexibility into MSBuild.exe to get what you want, and arguably, at this point just doing what you’re already doing with a proxy project is that kind of extensibility already.
I like the idea of rendering just one interesting property (as mentioned in the OP), but I also like the idea of rendering the entire xml (as in my closed dupe issue). The fact that some variables cannot be evaluated is true, but many can be, so it's still useful.
Quick elaboration:
When everything is done by msbuild, this feature request adds no value and seems unimportant.
But not everyone uses msbuild as the primary build mechanism. We use bash for everything, and call the msbuild/dotnet CLI to perform work for c# projects. But that means we are outside msbuild's "domain", and so lack data that msbuild doesn't naturally provide - so it would be helpful to have a feature where we can get msbuild to rendering the csproj.
In a multi-platform build environment, with many containers, different technologies (not just c#), etc., shell scripting is the lowest common denominator, but acquiring the info required to perform a build is quite hard.
Here's a proposal for this to see if we can get it moving forward. What do people think?
Command line evaluation of MSBuild properties
We will add command-line options to MSBuild to support getting the value of properties, items, or target return values.
-
-getProperty:<propertyName>
- Get the value of the specified property -
-getItem:<itemName>
- Get the value(s) of the specified item -
-getTargetResult
- Get the return values of the targets that were specified via the-target
option.
If no targets are specified on the command line via the -target
option, then the -getProperty
and -getItem
options will get the values from MSBuild evaluation, and no targets will be built. If the -target
option is specified, then the property or item values returned will be the values after the build is finished (all targets have run).
By default, the requested values will be printed to the console output in text format, and any other MSBuild output will be suppressed, unless there is an error. The text format for the values will simply put each value on a separate line, and won't include any item metadata. It will be possible to get the values for multiple properties, for example -getProperty:OutputPath;TargetPath
. In that case each property would be on a separate line in the text format output. It will also be possible to get values for multiple items this way, however this won't be very useful with the text format as there won't be a way to know when the values for one item stop and the next one begin.
The format for the values can be switched to json by specifying -resultsFormat:json
. This format will include item metadata values, as well as supporting multiple properties, items, and target results.
The values can be saved to a file instead of printed to the console with the -resultsFile:<fileName>
option. In this case the normal console log output will not be suppressed. If saving the results to a file, and the results format is not specified, it will default to using json unless the file extension of the results file is .txt
.
A possible format for the json output could be as follows:
{
"properties":
{
"PropertyName": "PropertyValue",
"PropertyName2": "PropertyValue2"
},
"items":
{
"Sources":
[
{
"ItemSpec": "Program.cs"
},
{
"ItemSpec": "obj\\Debug\\net6.0-windows10.0.19041.0\\ConsoleTest.AssemblyInfo.cs"
}
],
"References":
[
{
"ItemSpec": "C:\\Program Files\\dotnet\\packs\\Microsoft.WindowsDesktop.App.Ref\\6.0.14\\ref\\net6.0\\Accessibility.dll",
"FileVersion": "6.0.1423.7402",
"ReferenceSourceTarget": "ResolveAssemblyReference"
}
]
},
"targets":
{
"GetTargetPath":
{
{
"ItemSpec": "c:\\git\\repro\\ConsoleTest\\bin\\Debug\\net6.0-windows10.0.19041.0\\ConsoleTest.dll",
"TargetFrameworkIdentifier": ".NETCoreApp",
"ReferenceAssembly": "c:\\git\\repro\\ConsoleTest\\obj\\Debug\\net6.0-windows10.0.19041.0\\ref\\ConsoleTest.dll"
}
}
}
}
Comments
This doesn't support evaluating arbitrary expressions from the command line, so something like -evaluate:'@(Compile->%(FileName))'
isn't possible. However, the command line syntax is simpler, and you can still get more complex values by using the json format or calling a target.
The -getTargetResult
option may not be necessary as it is a bit redundant with just getting property or item values after running targets. However, it would allow for command-line builds to do the same thing as design-time builds do in Visual Studio, where a bunch of targets are run and the return values of each target are returned.
Thanks, I like it! 🙂
A few comments:
any other MSBuild output will be suppressed, unless there is an error
Just to clarify: will the error message go to stderr? I think it would be better for stdout to remain empty in that case.
The format for the values can be switched to json
Excellent idea. It becomes necessary when dealing with multi-line values.
The values can be saved to a file instead of printed to the console
Is there a real use case for this? When used in a script, the command output can be redirected to a file.
"ItemSpec": "Program.cs"
- Shouldn't it be
Identity
instead ofItemSpec
? - Should other well-known item metadata be included? Stuff like
FullPath
could be useful. - Since everything in MSBuild uses PascalCase, I'd also name the top-level JSON items that way:
Properties
,Items
,Targets
.
How would these options interact with the InitialTargets
and DefaultTargets
attributes of the Project element? I'd expect:
-
-getProperty
or-getItem
, without-target
, ignores bothInitialTargets
andDefaultTargets
. -
-getProperty
or-getItem
, with-target
, runs bothInitialTargets
and the-target
targets, but notDefaultTargets
. -
-getTargetResult
, without-target
, is an error and ignores bothInitialTargets
andDefaultTargets
. -
-getTargetResult
, with-target
, runs bothInitialTargets
and the-target
targets, but notDefaultTargets
. It outputs only the results of the-target
targets, not the results ofInitialTargets
.
Just to clarify: will the error message go to stderr? I think it would be better for stdout to remain empty in that case.
I'm not sure. I don't know if the error messages currently go to stdout or stderr, and if they don't go to stderr it might be tricky to change that.
The values can be saved to a file instead of printed to the console
Is there a real use case for this? When used in a script, the command output can be redirected to a file.
I think the normal MSBuild output can be useful to have in logs in case something goes wrong. This is more likely to matter if targets are being run instead of just evaluating the project.
- Shouldn't it be
Identity
instead ofItemSpec
?
We use both. ItemSpec
is what we use in the MSBuild .NET APIs, while Identity
is what we use as the metadata name. Probably Identity
would be better here.
- Should other well-known item metadata be included? Stuff like
FullPath
could be useful.
Yes, this is probably a good idea.
- Since everything in MSBuild uses PascalCase, I'd also name the top-level JSON items that way:
Properties
,Items
,Targets
.
I'm not sure about this, JSON typically uses camelCase.
How would these options interact with the
InitialTargets
andDefaultTargets
attributes of the Project element? I'd expect:
-getProperty
or-getItem
, without-target
, ignores bothInitialTargets
andDefaultTargets
.-getProperty
or-getItem
, with-target
, runs bothInitialTargets
and the-target
targets, but notDefaultTargets
.-getTargetResult
, without-target
, is an error and ignores bothInitialTargets
andDefaultTargets
.-getTargetResult
, with-target
, runs bothInitialTargets
and the-target
targets, but notDefaultTargets
. It outputs only the results of the-target
targets, not the results ofInitialTargets
.
Yes, all of this matches what I was thinking.
An explicit use case for this comes from our friends at the VSCode Docker tooling - they currently do evaluations looking for specific properties and items. They'd love to have a way to get that same information that doesn't require building and shipping an entire .NET application.
Using stdout in MSBuild for structured data output is something we have not done in past, all structured build artifacts are in form of files. Although convenient, there is lot of corner cases like errors during MSBuild execution which might make it less useful. I recommend to support just json file output. That should be sufficient for almost everybody.
I recommend to support just json file output. That should be sufficient for almost everybody.
That would be very inconvenient to use in scripts (you'd need to install and use something like jq
, then get rid of the output json file).
Retrieving info in scripts in the main goal behind this feature request.
That would be very inconvenient to use in scripts (you'd need to install and use something like
jq
, then get rid of the output json file).
Not an expert here, but I believed that it is quite simple for javascript to read json files. Anyway, I am just expecting whole kind of issues with it, and would rather not to implement it unless it is necessary. I would rather propose stable solution which is harder to use than easy but buggy solution.
Here are incomplete list of possible problems I fear (please take it with grain of salt):
- custom tasks using Console.WriteLine as oppose to ILogger
- error in execution will cause partial output while failure exit code will be not be handled
- cancellation will cause partial output while failure exit code will be not be handled
- stderr vs stdout confusion
- console out encoding mismatch (ansi vs utf-8 for example)
- console buffer width truncating lines
Especially the 1st point is very concerning as I am sure quite a few people do it. I even know few internal tasks which do so.
Please dont discuss if the output should be json or stdout. Just let the user decide when invoking msbuild. Just provide parameters like --std-out or --json-out xxx.json or --xml-out. The user knows his scenario and knows what can go wrong. For me using a json-file would be bad. Our scenario is to evaluate properties. We know that nothing additionally would be written to stdout in this case. Creating and deleting a json file is a pain in the ass for certain scenarios when you can just use stdout. So just implement both and use parameters. :-)
it is quite simple for javascript to read json files
It's easy in JavaScript, but not so much in shell scripts. Does anyone even call MSBuild from JS? 😅
- custom tasks using Console.WriteLine as oppose to ILogger
This can be mitigated with:
Console.Out = TextWriter.Null;
Console.Error = TextWriter.Null;
Custom tasks would then need to use Console.OpenStandardOutput
to write to stdout, and I wouldn't expect any task to do so.
Redirecting Console.Out
/Error
to an ILogger
instead of TextWriter.Null
by writing an adapter would be nice to have, but potentially unreliable due to missing line feeds for instance.
Also, as @Scordo stated above, this is not a problem if all you need is the evaluation phase.
- failure exit code will be not be handled
That would be a bug in the script which calls MSBuild, not in MSBuild itself.
- stderr vs stdout confusion
- console out encoding mismatch (ansi vs utf-8 for example)
Those points are valid for any command-line executable.
- console buffer width truncating lines
This shouldn't be an issue when redirecting MSBuild output.
There is already an open PR and there has been lots of discussion both here in the issue and in the PR.
Every target available on an MSBuild project is a potential 'endpoint' or 'sub-command'. You don't need to use the build
target for everything.
I have routinely implemented 'diagnostic' targets whose purpose is to report specific information (from properties and/or items). These targets can require other targets to execute, or not. A common set of 'diagnostic' targets can be applied to a set of projects with a Directory.Build.targets
file. For each project in the set the shared target can then be called. Creating a shared PrintTargetPath
target, as an example, is straightforward.
For a one-off ad-hoc circumstance, simple evaluations at the command line have value.
For some of the scripting use cases described, expanding the API surface (or the command surface if you prefer) by adding specialized targets is a better approach because it can be tailored to the specific requirements (including the returned data format) and can use the full capabilities of MSBuild.
There is a way in which the more complete and the more sophisticated the command line evaluation support is, the more redundant it will be with just executing a project.
Hi there, is this available in RC1?
If so, are there any more examples of how to use this?
I would be very interested to get the parameters of the FscTask
in CoreCompile
for F# projects;
Would this be possible to extract with these new flags?
This will be in RC 2, and we'll have proper documentation on learn.microsoft.com at that time.
I've been playing with it in the nightlys, it's very cool 😎
@slang25 got anything you want to share with the class? 🥹
-help
doesn't provide usage information for the new command line switches.
The meta project created from a solution file has properties, items, and targets. What is the rationale for the MSB1063 error?
MSBUILD : error MSB1063: Cannot access properties or items when building solution files or solution filter files. This feature is only available when building individual projects.
I didn't find anything in the discussion here or in the PR.