How to change verbosity of the MSBuild task? - msbuild

I'd like to have a different verbosity for the msbuild project invoked from the commandline, and those invoked by the MSBuild task from within the project. For example:
Inside my.proj:
<Target Name=Foo>
<MSBuild Projects="a.csproj;b.csproj;c.csproj"/>
</Target>
On the commandline:
msbuild /v:d my.proj
now when the MSBuild task builds the .csproj files, it does it with detailed verbosity as well. However I'd want to build it with minimal verbosity.
I know it is possible to invoke msbuild manually like so:
<Target Name=Foo>
<Exec Command="msbuild /v:m a.csproj"/>
<Exec Command="msbuild /v:m b.csproj"/>
<Exec Command="msbuild /v:m c.csproj"/>
</Target>
or in practice
<Target Name=Foo>
<Exec Command="msbuild /v:m %(Projectlist.Identity)"/>
</Target>
and this works well off course, but then I cannot get the functionality of the BuildInParallel switch anymore (I do not think it is possible to invoke msbuild from the commandline with multiple projects without them being contained in a solution?)
Update
I went with Ludwo's option: basically create a custom logger that holds two ConsoleLoggers as a member. One has the verbosity passed at command line, the other one is 'minimal'. The logger registers for all events and passes them to one of the loggers depending on whether a csproj file is currently being built or not. Output looks exactly like normal, except it doesn't include thousands of lines from the csproj files.

You have two options (at least) :)
Create one additional msbuild script for building abc projects
"BuildABC.proj"
<Target Name="BuildABC">
<MSBuild Projects="a.csproj;b.csproj;c.csproj" BuildInParallel="true"/>
</Target>
In your parent script execute MSBuild using Exec task and call
"BuildABC.proj" with minimal verbosity
<Target Name=Foo>
<Exec Command="msbuild /v:m /m:2 BuildABC.proj"/>
</Target>
You have to pass explicitly all parent properties needed in the BuildABC project to msbuild /p parameter.
Use custom logger. See this how to do it. In this case you can use your original script:
<Target Name=Foo>
<MSBuild Projects="a.csproj;b.csproj;c.csproj"/>
</Target>
In your custom logger do not log anything related to e.g. "a.csproj" project between ProjectStarted and ProjectFinished events where e.ProjectFile == "a.csproj" (to disable diagnostic logging on "a.csproj" project while building parent project with diagnostic verbosity)

Related

.NET Core msbuild ProjectReference

I have a solution that contains a console application with a .csproj file like the this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
</Project>
I also have a library project that uses the console application to generate a heap of C# code that get compiled into the library, the library .csproj file looks like this.
<Project Sdk="Microsoft.NET.Sdk" InitialTargets="RunGenerator">
<PropertyGroup>
<TargetFramework>netstandard1.4</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../generator/generator.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
</ItemGroup>
<Target Name="RunGenerator">
<Exec Command="dotnet run -p "../generator/generator.csproj" input output" />
</Target>
</Project>
This fails because the dependency analysis says that a netstandard1.4 assembly cannot reference a netcoreapp1.1 assembly. That is correct except that I am not referencing the assembly.
I can work around that issue by building the generator project like this:
<Project Sdk="Microsoft.NET.Sdk" InitialTargets="RunGenerator">
<PropertyGroup>
<TargetFramework>netstandard1.4</TargetFramework>
</PropertyGroup>
<Target Name="RunGenerator">
<Exec Command="dotnet build "../generator/generator.csproj"" />
<Exec Command="dotnet run -p "../generator/generator.csproj" input output" />
</Target>
</Project>
The problem is that the generator project no longer takes part in the dependency analysis when these projects are built using the containing solution file and the explicit build of the generator project sometimes runs concurrently with another build of the same project initiated by the solution build and this results in errors because files are locked etc.
Is it possible to have a project dependency without checking the target framework?
Can anyone suggest a workaround?
Thanks.
Here are some MSBuild tips. You might need to combine a few of these ideas.
You can use your solution file to add an explicit project dependency. See https://learn.microsoft.com/en-us/visualstudio/ide/how-to-create-and-remove-project-dependencies (This question was originally asked here: Visual Studio 2010: How to enforce build order of projects in a solution?). Unfortunately, this is really hard to do if you don't have VS. The format is .sln files is kinda a nightmare.
To avoid the concurrent build issue, use the MSBuild task instead of the Exec task. See https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-task
<Target Name="CompileAnotherProject">
<MSBuild Projects="../generator/generator.csproj" Targets="Build" />
</Target>
dotnet-run invokes "dotnet build" automatically. This is actually problematic in concurrent builds. You can instead add a target to your generator.csproj that runs the app after it has been built. "dotnet filepath.dll" runs the compiled app without building it.
<Target Name="RunCodeGen" AfterTargets="Build">
<Exec Command="dotnet $(AssemblyName).dll input output"
WorkingDirectory="$(OutDir)" />
</Target>

MSBuild target with MSBuild task pass in properties from command line

I have a targets file which uses the MSBuild task to compile bunch of .csproj files. This works as expected.
Is it possible to take the properties from the commandline?
<Target Name="MyBuild">
<MSBuild Projects="#(Projects)" Properties="FROM COMMAND LINE"/>
</Target>
msbuild mybuild.proj /p:myProperty=true
You can do something like this:
<Target Name="MyBuild">
<MSBuild Projects="#(Projects)" Properties="$(MyProperties)"/>
</Target>
and call MSBuild this way:
msbuild mybuild.proj /p:MyProperties="MyProperty=true;MyOtherProperty=false"
Environment variables can be used to set MSBuild properties. We use batch files to set env variables based on command line parameters, which then invokes MSBuild after setting env variables based on the command line parameters.

Call MSBuild passing it a single task to run

We can define a target in a csproj file and then specify that target when we call msbuild from the command line. That looks like this:
my.csproj
<Target Name="CopyFiles">
<Copy
SourceFiles="#(MySourceFiles)"
DestinationFolder="c:\MyProject\Destination" />
</Target>
msbuild
msbuild my.csproj /t:CopyFiles
The CopyFiles targets asks msbuild to run the Copy task.
What if we don't want to edit the csproj file. How can we define a target just from the command line? Alternatively, using only the command line, how can we ask msbuild to run just one or maybe two tasks?
Pseudo-Code
msbuild my.csproj /t:"Copy SourceFiles=#(MySourceFiles) DestinationFolder=..."
Based on the MSBuild Command-Line Reference, this isn't possible exactly as you describe it, i.e. MSBuild will not take Target definitions from the command-line input. They have to be defined in a file somewhere.
But, you can have a script (maybe even a .bat?) that does something like:
Create a new, essentially empty, project file.
Import the .csproj, with <Import Project="foo.csproj" />
Add the targets
Call MSBuild with the new project file with the targets you want. Multiple targets can be specified by multiple /t: switches. Properties can be specified with /p:
The final, programmatically/script generated project file might look something like:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="foo.csproj" />
<Target Name="CopyFiles">
<Copy
SourceFiles="#(MySourceFiles)"
DestinationFolder="$(Destination)" />
</Target>
</Project>
The actual MSBuild command called might then be:
msbuild temp.proj /t:CopyFiles /p:"Destination=c:\MyProject\Destination"

How to make only one target BuildInParallel?

I am having a target which will build then checkin the assemblies.
<Target Name="CoreBuildCheckinSubSystem" DependsOnTargets="BuildDotNETSolutions;CheckinSubSystemDos">
</Target>
In this builddotnetSolution will build the list of solution in an order. So i do not want it to be buildinParallel but the CheckinSubsystemDos will checkin all the dlls. If the checkin process is being done in parallel it would save time for me.
How to make CheckinSubSystemDos alone in BuildInParallel?
An important thing to note about MSBuild, is that parallelization is done on the level of project. Targets within the same project are always executed sequentially, while two different project might (or might not) be executed in parallel. To rephrase it another way, if you want MSBuild to execute something concurrently, you have to create multiple projects and invoke them within the same <MSBuild> task.
In your case, the code would look like this. You have a list of projects or solutions to build:
<ItemGroup>
<MyProjects Include="One.proj"/>
<MyProjects Include="Two.proj"/>
<MyProjects Include="Three.proj"/>
</ItemGroup>
The build target would invoke <MSBuild> target sequentially:
<Target Name="BuildDotNETSolutions" ...>
<MSBuild Projects="#(MyProjects)" Targets="Build" BuildInParallel="false" />
</Target>
Your checkin target would invoke another target that is defined in the same projects -- call it MyCheckin:
<Target Name="CoreBuildCheckinSubSystem" DependsOnTargets="BuildDotNETSolutions;CheckinSubSystemDos">
<MSBuild Projects="#(MyProjects)" Targets="MyCheckin" BuildInParallel="true" />
</Target>
Another option for you is to create a sibling set of projects -- call it MyCheckinProjects and use them in you checkin target:
<ItemGroup>
<MyCheckinProjects Include="Checkin_One.proj"/>
<MyCheckinProjects Include="Checkin_Two.proj"/>
<MyCheckinProjects Include="Checkin_Three.proj"/>
</ItemGroup>
However I would suggest simply inserting a new target into existing projects.

How can I hide the command I'm using in an MSBuild Exec task from console output?

I've got a task in my MSBuild file like so:
<Exec command="net use $(DeploymentServerName) /user:username passwd" ContinueOnError="false" />
But in the console output it will dump the command:
...
net use $(DeploymentServerName) /user:username passwd
...
But I'd like to hide the credentials, if possible. I don't care about where the output of the command goes, I just care that the command itself is not echo'd to the console. Any ideas?
Starting with .NET 4.0 Exec MSBuild task got property EchoOFF which allows to achieve exactly that - suppress echoing of the command itself (not the command output). Full documentation is here. Just add EchoOff="true" to the list of Exec properties.
There are a couple of possible approaches, here is one
<Target Name="DoHideCommand">
<Exec Command="MSBuild $(MsBuildThisFile) /t:SpecialCommand /nologo /noconsolelogger"/>
</Target>
<PropertyGroup>
<MyCommand>dir c:</MyCommand>
</PropertyGroup>
<Target Name="SpecialCommand">
<Exec Command="dir"/>
</Target>
This invokes a seperate msbuild process to invoke the actual target, and hides all output resulting in
...
DoHideCommand:
MSBuild test.targets /t:SpecialCommand /nologo /noconsolelogger
...
And here is another one
<Target Name="SpecialCommandViaFile">
<PropertyGroup>
<TmpFile>tmp.bat</TmpFile>
</PropertyGroup>
<WriteLinesToFile File="$(TmpFile)" Lines="$(MyCommand)"/>
<Exec Command="$(TmpFile) > NUL 2>&1" WorkingDirectory="$(MsBuildThisFileDirectory)"/>
<Delete Files="$(TmpFile)"/>
</Target>
This creates a batch file to run the actual command, then redirects all output to NUL so only this is shown:
...
SpecialCommandViaFile:
tmp.bat > NUL 2>&1
Deleting file "tmp.bat".
...
Note though that the one executing your msbuild file can always simply open your file to look at the credentials, even if they are hidden from the output when running it.