Condition on "PropertyGroup" in Directory.build.props not working - msbuild

I've created a Directory.build.props file so I can set the C# language version in there.
But I also have Visual Basic Projects, so i wanted to limit the setting to C# projects.
<Project>
<PropertyGroup Condition="'$(ProjectExt)'=='.csproj'">
<LangVersion>7.2</LangVersion>
</PropertyGroup>
</Project>
But my project is not loading it / the UI is not displaying the language version 7.2.
I've tried to apply the same condition inside the csproj file, also not working.
<PropertyGroup>
<LangVersion Condition="'$(ProjectExt)'=='.csproj'">7.2</LangVersion>
</PropertyGroup>
However, this will work:
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Message Text="Condition working" Importance="high" Condition="'$(ProjectExt)'=='.csproj'"/>
</Target>
The build will output my message
Why is the condition not working on my LanguageVersion? Any Clues?

You will need to use a property to condition on that is available very early in the build. In your case, you should condition on MSBuildProjectExtension:
<PropertyGroup>
<LangVersion Condition="'$(MSBuildProjectExtension)'=='.csproj'">7.2</LangVersion>
</PropertyGroup>
See MSBuild reserved and well-known properties for the complete set of available properties.
ProjectExt is only defined late in the build definition and is therefore not available in Directory.Build.props, which is imported very early into the project.

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>

Stopping Post Build events on project when building directly from MSBuild

I have a project which has some post build events that do some copying around for other projects. I unfortunately cannot change that, and have been asked to write a build script for use on a CI server.
Problem is that the post build steps run off the debug/release bin folders and I compile through the build script to a different folder. So one solution would be to let the project build as is, and then manually copy all files from the bin folders to the output folder I am using. However that feels like a bit of a hack, so I was wondering if there is a way for an MSBuild task to tell the solution it is building to ignore PostBuild events, I believe you could set a property PostBuildEvent='' but it didnt seem to stop them from happening...
Here is an example of the build script target:
<Target Name="Compile" DependsOnTargets="Clean;">
<MSBuild Projects="$(SourceDirectory)\SomeSolution.sln"
Properties="Configuration=Release; OutputPath=$(CompilationDirectory); PostBuildEvent=''" />
</Target>
Anyone had to do anything similar before?
To disable all PostBuildEvents, set the CustomAfterMicrosoftCommonTargets to C:\PostBuild.config (or whatever you name the file) and have PostBuild.config to be:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="PostBuildEvent"/>
</Project>
Add /p:CustomAfterMicrosoftCommonTargets="C:\PostBuild.config" to your msbuild command line
Or update your MsBuild task properties:
<MsBuild Projects="$(ProjectTobuild)" Properties="Configuration=$(Configuration);Platform=$(Platform);CustomAfterMicrosoftCommonTargets='C:\PostBuild.config'" Targets="Build"/>
To disable PostBuildEvents at project level for MSBuild, simply put these codes inside .csproj:
<Target Name="BeforeBuild">
<PropertyGroup>
<PostBuildEvent Condition="'$(BuildingInsideVisualStudio)' == 'false' Or '$(BuildingInsideVisualStudio)' != 'true'"></PostBuildEvent>
</PropertyGroup>
</Target>

Property scope using msbuild extension pack detokenise

Im trying to use the msbuild extensions pack to fix up the configuration of our app on deploy,
i want to be able to pass a property (ENV) which will load my environment specific config file to use with the detokeniser, and fix up my application configs.
Like this:
<UsingTask TaskName="MSBuild.ExtensionPack.FileSystem.Detokenise"
AssemblyFile=".\Tools\MSBuild Extension Pack 4.0.3.0\MSBuild.ExtensionPack.dll"/>
<Import Project=".\Environments\$(Env).properties"/>
<Target Name="Build" >
<ItemGroup>
<SourceTemplates Include=".\Templates\**\*.*"/>
</ItemGroup>
<RemoveDir Directories=".\Temp"/>
<MakeDir Directories=".\Temp"/>
<Message Text="#(SourceTemplates)"/>
<Copy SourceFiles="#(SourceTemplates)"
DestinationFolder=".\Temp\%(RecursiveDir)" />
<ItemGroup>
<TargetTemplates Include=".\Temp\**\*.*"/>
</ItemGroup>
<MSBuild.ExtensionPack.FileSystem.Detokenise
TaskAction="Detokenise"
TargetFiles="#(TargetTemplates)"/>
</Target>
So i call this using
msbuild Detokenise.msbuild /p:Env=Prod
Msbuild knows about my file and i have access to its properties, but when the detokeniser runs i get the error:
Detokenise Task Execution Completed [15:07:50]
C:\Source\1.2\Build\Detokenise.msbuild(27,3):
error : InvalidProjectFileException: The imported project "C:\Source\1.2\Build\Environments\.properties" was not found.
Confirm that the path in the <Import> declaration is correct, and that the file exists on disk.
C:\Source\1.2\Build\Detokenise.msbuild\r
C:\Source\1.2\Build\Detokenise.msbuild(27,3): error :
All works fine if i hard code it-
Any ideas how to solve this. I thought of doing some text replacement on the msbuild before i execute...
You could try to assign this parameter to a local property:
<PropertyGroup Condition="'$(Env)'=='Prod'">
<TargetEnv>Prod</TargetEnv>
</PropertyGroup>
<!-- add other environments as needed -->
<PropertyGroup Condition="'$(Env)'=='Test'">
<TargetEnv>Test</TargetEnv>
</PropertyGroup>
<Import Project=".\Environments\$(TargetEnv).properties"/>
You could also try to enclose your parameter value in quotes:
msbuild Detokenise.msbuild /p:"Env=Prod"
As is your problem can't be reproduced, so it may be a side effect of other parameters not shown in your sample code.
I've seen a number of other questions where a similar problems was happening:
Visual Studio Ignoring MSBuild file (csproj) Customizations

Creating MSBuild target hooks

Can someone please point me to a reference about target hooks in MSBuild?
I'm looking for something that will let me define targets to run before and after a specified target. I know this can be done using the DependsOnTargets property but I've seen references to using target hooks and I'd like to explore that area.
Thanks,
Zain
A good list of built-in overridable build process hooks can be found here. For custom targets, the only thing I can think of is to use either the DependsOnTarget attribute (like you mentioned) or the BeforeTargets/AfterTargets attribute (like #Ritch Melton mentioned.) Be careful, the BeforeTargets/AfterTargets are only available in MSBuild 4.0
If you understand the idea behind DependsOnTargets then open up the Microsoft.Common.targets file in the .Net SDK directory (C:\Windows\Microsoft.NET\Framework\v3.5). That file defines the build process for the MSBuild task and .Net projects created by Visual Studio. Look for tags called BeforeXXXX, and AfterXXXX. BeforeBuild and AfterBuild are referenced in the default.csproj file - Snippet:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
There are others, like Clean, Rebuild, etc..
Define a Target (or Targets) to execute inside those Target elements, like this (Creates a directory, or list of directories based on the value in the Directories Property:
<Target Name="CreateDir">
<MakeDir Directories="D:\Dogs.txt"/>
</Target>
Then include those Targets in the BeforeXXX Target:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="BeforeBuild" BeforeTargets="CreateDir">
</Target>
</Project>

MSBuild and _PublishedWebsites

After MSbuild has built my solution (with an asp.net website), and the webdeployment project has built and put the website in the directory _PublishedWebsites:
c:\mybuilds\buildName\Daily_20090519.3\Release_PublishedWebsites\MyWebsite.
How do I copy this to the fixed directory where IIS points to for the test website?
I have found loads of code snippets, but I cannot seem to find one that will take into account the fact that this directory name changes.
This is pretty easy. You can edit the project and insert something similar to the following.
<PropertyGroup>
<OutputDest>$(MSBuildProjectDirectory)\..\OutputCopy\</OutputDest>
</PropertyGroup>
<Target Name="AfterBuild">
<!-- Create an item with all the output files -->
<ItemGroup>
<_OutputFiles Include="$(OutputPath)**\*" Exclude="$(OutputPath)obj\**\*" />
</ItemGroup>
<!-- You probably don't want to include the files in the obj folder so exclude them. -->
<Message Text="OutputDest : $(OutputDest)" />
<Copy SourceFiles="#(_OutputFiles)"
DestinationFiles="#(_OutputFiles->'$(OutputDest)%(RecursiveDir)%(Filename)%(Extension)')"/>
</Target>
Is this what you are looking for?
My Book: Inside the Microsoft Build Engine : Using MSBuild and Team Foundation Build
I'm using different technique.
<PropertyGroup>
<BinariesRoot>c:\BinariesForIis\</BinariesRoot>
</PropertyGroup>
The c:\BinariesForIis\ will be used for direct output compiled binaries (before copy to ...\Daily_20090519.3\Release_ ...).