MS Build: Access compiler settings in a subsequent task - visual-studio-2010

I'm writing my own MS build scripts which I import in the project file (*.vcxproj)
I would like to execute a task conditionally depending on the C runtime being used dynamically. I tried the following:
Condition = " '$(RuntimeLibrary)' == 'MultiThreadedDLL' "
But $(RunitmeLibrary) is not a Property but an argument to ClCompile.
Is there any other way to write a condition that checks whether the runtime is liked dynamically or statically?
Regards

The value you are looking for is metadata of the ClCompile item group. Use this:
Condition=" '%(ClCompile.RuntimeLibrary)' == 'MultiThreadedDll' "
I added this to the bottom of a vcxproj to see what the current setting was:
<Target Name="BeforeClCompile">
<Message Text="BeforeCompile: RuntimeLibrary=[%(ClCompile.RuntimeLibrary)]" Importance="high" />
</Target>

Related

Why does overriding the Build target in msbuild work for C++ projects but fail for C# projects?

I am overriding the Build target like this in my file OverrideBuild.targets:
<Target Name="OriginalBuild" DependsOnTargets="$(BuildDependsOn)">
<Message Text="Finished running target OriginalBuild" Importance="High" />
</Target>
<Target Name="Build" >
<CheckArtifacts ProjectGuid = "$(ProjectGuid)" SolutionPath = "$(SolutionPath)" >
<Output PropertyName = "ArtifactsHaveChanged" TaskParameter = "Result" />
</CheckArtifacts>
<Message Text="ArtifactsHaveChanged = $(ArtifactsHaveChanged)" Importance="high" />
<!-- if the artifacts.props file has not just been updated then we can run the original build target -->
<Message Condition="'$(ArtifactsHaveChanged)' == 'false'" Text="Running target OriginalBuild" Importance="High" />
<CallTarget Condition="'$(ArtifactsHaveChanged)' == 'false'" Targets="OriginalBuild" />
<!-- Otherwise we need to run a new msbuild to avoid using an out-of-date cached version of the artifacts.props file.
To force the msbuild process not to use the cached values from this process we must pass at least one property.
-->
<Message Condition="'$(ArtifactsHaveChanged)' == 'true'" Text="Running target OriginalBuild in nested msbuild" Importance="High" />
<MSBuild Condition="'$(ArtifactsHaveChanged)' == 'true'" Targets="OriginalBuild"
Projects="$(MSBuildProjectFullPath)" Properties="InNestedMsbuild=true" />
<!-- Visual Studio doesn't pick up on the modified artifacts.props file unless we force it to reload the solution -->
<Touch Condition="'$(ArtifactsHaveChanged)' == 'true' and '$(BuildingInsideVisualStudio)' == 'true'" Files = "$(SolutionPath)" />
<Message Text="Finished running build target override" Importance="High" />
</Target>
and each of my .vcxproj or .csproj files includes this file at the end:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\OverrideBuild.targets" />
</Project>
This works as I want it to for the C++ projects but fails with the C# projects. When building a C# project via msbuild it fails because the command line to the C# compiler is missing reference arguments for local assemblies. For example, a C# file that has a line like this at the top of the file:
using My.Utils.Common;
fails with the following error message:
error CS0234: The type or namespace name 'Common' does not exist in the namespace 'My.Utils' (are you missing an assembly reference?)
And looking at the compiler command used it is missing this line:
/reference:C:\Code\scratch\Build\My.Utils.Common\Bin\Release\My.Utils.Common.dll
That missing line is present when I comment out my override of the Build target. And weirdly enough it will build fine from within Visual Studio even with my Build override in place. It only fails when building using msbuild from the command line and only for C# projects.
I thought that the way I had overriden the Build target would be completely transparent but apparently it isn't. Can anybody shed some light on what is going wrong ?
It seems that when project A depends on project B with a project reference, the outputs of the Build target of B are used to deduce what should be passed as a reference to the compiler when building A. This is presumably somewhere in the ResolveAssemblyReferences logic.
Therefore to get your replacement Build target working, you need to make its outputs match those of the standard Build.
Here is how you can achieve this:
<Target
Name="Build"
Condition=" '$(_InvalidConfigurationWarning)' != 'true' "
DependsOnTargets="GetTargetPathWithTargetPlatformMoniker"
Returns="#(TargetPathWithTargetPlatformMoniker)" >
</Target>
Here Returns="#(TargetPathWithTargetPlatformMoniker)" is what the Returns of the standard Build in the SDK is. But the item array #(TargetPathWithTargetPlatformMoniker) is initially empty, so you need to run the Target GetTargetPathWithTargetPlatformMoniker to populate it before hand.
These are implementation details of the build system, so they may vary by SDK version, but you can always inspect the logic in C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\Microsoft.Common.CurrentVersion.target or equivalent.
Note that this cannot be used directly with C++ projects, their default Build target is a bit different. You may need to vary by the project type to support both. The Condition on a Target does not stop it from overwriting the existing one, it only stops it from executing, so if you need a target overwrite to differ, you need to put the alternatives in files and import them conditionally. I don't know of a more convenient way, but that at least works.
Why does overriding the Build target in msbuild work for C++ projects but fail for C# projects?
After test your sample, I found the error not comes from the overriden the Build target, it should be related to the project type which you referenced.
Because I have tried comment the import line in the HelloWorld project file:
<Import Project="..\..\OverrideBuild.targets" />
Then MSBuild command line still throw that error.
Besides, I found your referenced project HelloWorldHelper is a Console application project, which output type is Class library.
To resolve this issue, I have created a new Class library instead of Console application, then build it from MSBuild command line, it works fine.
So, please try to convert your referenced project to Class library.
Hope this helps.

Execute custom MSBuild task always after build process

I created a custom Task that executes after the build operation.
<Target Name="AfterBuild" />
<Target Name="MyTarget"
AfterTargets="AfterBuild">
<MyTask ... />
</Target>
QUESTION: Is it possible to execute the task, if the build operation was triggered, but did not perform, because there are no changes in the project / no need to build again?
In other words: I want to execute the task always at the end of the build process, even if the project was not built again.
UPDATE: Using AfterTargets="Build" or setting the property <DisableFastUpToDateCheck>true</DisableFastUpToDateCheck> does not help.
After triggering the Build process a second time, I only get the Output: Build: 0 succeeded, 0 failed, 1 up-to-date, 0 skipped
Is it possible to execute the task, if the build operation was triggered, but did not perform, because there are no changes in the project / no need to build again?
If I understand you correctly, you can define this property in your project file:
<PropertyGroup>
<DisableFastUpToDateCheck>true</DisableFastUpToDateCheck>
</PropertyGroup>
Note: This method seems that Visual Studio is bypassing normal up-to-date checks of MSBuild and using some sort of custom check that is faster, but has a side effect of breaking customized build targets.
Update:
Not sure the reason why this method not work on your project, let me make the answer more detail:
Define the property in your project file:
Add the custom MSBuild task with some messages info.
Build the project, check the output(log file verbosity is Normal).
Build the project again, check the output again.
If I use AfterTargets="Build" instead of AfterBuild, the message is written to the Output window every time I build the solution (.NET Core Console App with a .NET Standard Class Libary).
<Target Name="MyAfterBuild" AfterTargets="Build">
<Message Importance="High" Text="Hello World!" />
</Target>

Sequentially build configurations in Visual Studio without MSBuild or plugins?

MSDN describes how to create a batch build, but does not provide a way to automate different batches (and one click solution for the GUI)
This question describes conditionally invoking a second build but doesn't appear to suffice for more than two sequential configurations
This question addresses the same situation, but again only for two configurations
In my test case, each configuration:
defines its own MACROS (which impact source code)
is applicable to multiple projects (class libraries). The projects are interdependent and require a specific build order in the context of the current configuration
I would like visual studio to build multiple configurations sequentially with a single build command.
Can child configurations be nested under a parent configuration, and be executed sequentially by visual studio when the parent configuration is built?
UPDATE : ATTEMPTED SOLUTION 1 [2016-03-11]
In response to Stijn's suggested answer I've tried the following:
Setup DotNetFramework 4.5 WinForms solution with 3 test projects and with 6 Configurations:
CORE_DEBUG
CORE_RELEASE
EXTENDED_DEBUG
EXTENDED_RELEASE
Debug
Release
The Debug Configuration must:
NOT trigger it's own configuration build (i.e. 'Debug')
must trigger the CORE_DEBUG and EXTENDED_DEBUG Configurations in sequence
I've added the following modified target to the first project's project file:
<Target Name="AfterBuild" Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
Building with the 'Debug' Configuration now, causes an EXTENDED_RELEASE build to trigger. Having a look at the solution file, I see that Visual Studio decided to automatically link 'Debug' to 'EXTENDED_RELEASE':
{4F9706AA-26A9-483C-81C4-22E301C54C89}.Debug|Any CPU.ActiveCfg = EXTENDED_RELEASE|Any CPU
{4F9706AA-26A9-483C-81C4-22E301C54C89}.Debug|Any CPU.Build.0 = EXTENDED_RELEASE|Any CPU
Removing the above two lines from the solution file doesn't help, since Visual Studio just regenerates them. In summary this now has two undesirable outcomes:
Visual Studio executes a 'Debug' build for Project1
Visual Studio then executes an 'EXTENDED_RELEASE' for Project2 and Project3
Conclusion: While this approach can work, it also (first) performs debug and release configuration builds respectively. Visual Studio
also lists all 6 Configurations in the build menu (we only want Debug
and Release to be visible, and behind the scenes Debug must trigger
CORE_DEBUG and EXTENDED_DEBUG, and Release must trigger CORE_RELEASE
and EXTENDED_RELEASE)
UPDATE : ATTEMPTED SOLUTION 2 [2016-03-16]
Moving on to a makefile project solution: I've created a makefile project as specified by stijn's answer below, and it worked perfectly!
Conclusion : This is the preferred solution in my opinion because it gives the user the most power and ability to control exactly how the build(s) must be executed and how the configurations must be handled.
The principle of the second SO question can be adjusted to build more than one configuration/platform sequentially by just invoking MsBuild multiple times. For instance:
<Target Name="AfterBuild" Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
<MSBuild Projects="$(MySolution)" Properties="Configuration=Release;Platform=x86"/>
<MSBuild Projects="$(MySolution)" Properties="Configuration=Debug;Platform=x64"/>
<MSBuild Projects="$(MySolution)" Properties="Configuration=Release;Platform=x64"/>
</Target>
This can be cleaned up by using item batching, removing the condition and instead automatically determining which config is invoked and then only building the others etc but that's a bit out of scope here.
I'm not really convinced doing this in an AfterBuild target is the best way though, because then you'd need to adjust one of your 'normal' projects to also trigger a build of everything else. An alternative is to add a MakeFile Project to your solution, set up it's dependencies so that it comes last in the build order (at least if that is what you need), and set it's command line to invoke msbuild in a way similar as described above. You can even keep all logic in the same project file: set the 'Build Command Line' to
msbuild $(MsBuildThisFile) /t:CustomBuild /p:Configuration=$(Configuration);Platform=$(Platform)
so building the project will 'recurse' and make it call itself again with the same properties as called with by VS, but executing the CustomBuild target where you can then build your other projects/solutions to taste.
EDIT re: update
You're almost there, but you have to go to Configuration Manager and make sure the configurations are setup properly to begin with. From the start:
create new solution, add 3 projects
right-click solution, select Configuration Manager
in the Active solution configuration combobox select new
enter CORE_DEBUG for name, select DEBUG under Copy settings from and make sure the Create new project configurations is checked like
repeat for other configurations
for EXTENDED_RELEASE for instance, it should now look like
you probably did most of this already, but somehow Debug got assigned to EXTENDED_RELEASE somehow so that is one thing you should fix; you could do that by editing the solution manually but instead of removing lines you'd have to edit them to be correct else VS just adds them again, as you noticed
Now open first project in a text editor and near the end of the file where AfterBuild is already inserted but commented out, add
<ItemGroup>
<Configurations Condition="'$(Configuration)'=='Debug'" Include="CORE_DEBUG;EXTENDED_DEBUG" />
<Configurations Condition="'$(Configuration)'=='Release'" Include="CORE_RELEASE;EXTENDED_RELEASE" />
<Projects Include="$(SolutionDir)WindowsFormsApplication1.csproj;$(SolutionDir)WindowsFormsApplication2.csproj;$(SolutionDir)WindowsFormsApplication3.csproj" />
</ItemGroup>
<Target Name="AfterBuild" Condition="'#(Configurations)' != ''">
<Message Text="Projects=#(Projects) Configuration=%(Configurations.Identity)" />
<MSBuild Projects="#(Projects)" Targets="Build" Properties="Configuration=%(Configurations.Identity)" />
</Target>
you might need to adjust the paths to the projects. This will build CORE_DEBUG and EXTENDED_DEBUG for Debug builds, and likewise for Release builds. AfterBuild is skipped when the Configurations ItemGroup is empty, i.e. when not building Debug or Release which is exactly the point.
EDIT re: makefile
You can specify multiple commands for the makefile commandline. Click the arrow next to the 'Build Command Line' box and select '' To be sure you have everything right, Configuration Manager has to be set up to only build the makefile project for Debug/Release like:
and the makefile project's commandline looks like
Alternatively, and I'd prefer this myself, you create an msbuild file with the same content as above:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Configurations Condition="'$(Configuration)'=='Debug'" Include="CORE_DEBUG;EXTENDED_DEBUG" />
<Configurations Condition="'$(Configuration)'=='Release'" Include="CORE_RELEASE;EXTENDED_RELEASE" />
<Projects Include="$(SolutionDir)WindowsFormsApplication1.csproj;$(SolutionDir)WindowsFormsApplication2.csproj;$(SolutionDir)WindowsFormsApplication3.csproj" />
</ItemGroup>
<Target Name="Build" Condition="'#(Configurations)' != ''">
<Message Text="Projects=#(Projects) Configuration=%(Configurations.Identity)" />
<MSBuild Projects="#(Projects)" Targets="Build" Properties="Configuration=%(Configurations.Identity)" />
</Target>
</Project>
and your makefile command then invokes that file like
msbuild /path/to/msbuildfile /t:Build /p:Configuration=Debug;SolutionDir=$(SolutionDir)

How to execute custom exe in MSBuild to set environment variable during property evaluation phase

I am trying to import some props and targets file from a directory, which is a user-specified location. In order to determine this directory, I have some custom logic and I am trying to find a way to execute this in one of the props. I am unable to use the standard MSBUILD property functions to achieve this.
My end goal is to have a small UI that allows the user to specify this directory and the scope of this directory must be tied to a project i.e, there could be two different projects pointing to two different directory within a solution
What about calling your build script again after you have fiddled out all your requirements? Every call to the MSBuild task where you put $(MSBuildProjectFile) as your Projects value will call your build file again and that call will have a new copy of the environment.
<Project DefaultTargets="EntryPoint" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="14.0">
<Target Name="SetProperties">
<PropertyGroup>
<PropA>Something</PropA>
</PropertyGroup>
</Target>
<Target Name="EntryPoint">
<MSBuild
Projects="$(MSBuildProjectFile)"
Properties="Configuration=$(Configuration);OutputPath=$(OutputPath);"
Targets="SetProperties;EntryPoint"
Condition="$(PropA) == ''"/>
<Message
Text="Everything is set!"
Condition="$(PropA) != ''"/>
</Target>
</Project>
This setup can be easily extended to execute some Exec task logic and forwards that output to a new call to the entry point.

How to add a command line code generator to Visual Studio?

I'm working on a project that uses code generation to generate C# classes using a command line tool from a text-based description. We are going to start using these descriptions for javascript too.
Currently these classes are generated and then checked in, however, I would like to be able to make the code generate automatically so that any changes are propagated to both builds.
The step that is run manually is:
servicegen.exe -i:MyService.txt -o:MyService.cs
When I build I want MSBuild/VS to first generate the CS file then compile it. It is possible to do this using, by modifying the csproj, perhaps using a MSBuild Task with Exec, DependentUpon & AutoGen?
Normally I would recommend a pre-build command be placed in a pre-build event, but since your command line tool will be creating C# classes needed for compiling, this should be done in the BeforeBuild target in the .csproj file. The reason for this is because MSBuild looks for the files it needs to compile between the time BeforeBuild is called and the time when PreBuildEvent is called in the overall process (you can see this flow in the Microsoft.Common.targets file used by MSBuild).
Call the Exec task from within the BeforeBuild target to generate the files:
<Target Name="BeforeBuild">
<Exec Command="servicegen.exe -i:MyService.txt -o:MyService.cs" />
</Target>
See the Exec task MSDN documentation for more details about specifying different options for the Exec task.
Antlr has an example of a process that can be used to add generated code to a project. This has the advantage of showing the files that are generated nested under the source file, although it is more complex to add.
You need add an item group with the file to be generated from, for example:
<ItemGroup>
<ServiceDescription Include="MyService.txt"/>
</ItemGroup>
Then add the cs file to be generated to the ItemGroup containing the rest of the source code.
<ItemGroup>
...
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
...etc..
<Compile Include="MyService.txt.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>MyService.txt</DependentUpon> <!--note: this should be the file name of the source file, not the path-->
</Compile>
</ItemGroup>
And then finally add the build target to execute the code generation (using % to execute the command for each item in the ItemGroup). This could be put into a separate file, so that it can be included from many projects.
<Target Name="GenerateService">
<Exec Command="servicegen.exe -i:%(ServiceDescription.Identity) -o:%(ServiceDescription.Identity).cs" />
</Target>
<PropertyGroup>
<BuildDependsOn>GenerateService;$(BuildDependsOn)</BuildDependsOn>
</PropertyGroup>

Resources