MSBuild import project dynamically - msbuild

On my .csproj I would like to import a .target file depending on a path calculated from a task.
Is it possible to do something like this?
<PropertyGroup>
<TargetPath>/*Some calculation from task*/</TargetPath>
</PropertyGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(TargetPath)\Custom.targets" />
I tried to do it from another target, but it doesn't work as the import is called before the target evaluation.

You cannot invoke a target before targets are imported, but you can still dynamically generate a path to import from a property group.
Visual Studio does this when you create a Web project, as in this example from one of my projects:
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">12.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0\WebApplications\Microsoft.WebApplication.targets" />
So you could definitely define properties using conditions:
<PropertyGroup>
<ImportPath Condition="Exists('path\to\some\thing.targets')">path\to\some\thing.targets</ImportPath>
</PropertyGroup>
<Import Project="$(ImportPath)" Condition=" '$(ImportPath)' != '' "/>
Microsoft.Bcl.Build does this, so you can too.

No, firstly MSBuild import all "extensions", then build dependency graph, and finally run the tasks

Related

MSBuild Update property in csproj

I have been trying to update the ApplicationVersion property in my csproj file.witch works fine; i have added a Target that runs an custom task to extract the AssemblyFileVersion from my assemblyinfo.cs; this works there is no doubt about that.
But then when i want to use my updated ApplicationVersion to determan where to put my newly build files, i get the default value set in the property.
<PropertyGroup>
...
<ApplicationVersion>1.0.0.0</ApplicationVersion>
...
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\media-converter-BUILD\debug\$(ApplicationVersion)\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>..\media-converter-BUILD\debug\$(ApplicationVersion)\MediaConverter.XML</DocumentationFile>
</PropertyGroup>
My Targets
<UsingTask AssemblyFile="GetAssemblyFileVersion.dll" TaskName="GetAssemblyFileVersion.GetAssemblyFileVersion" />
<Target Name="MainAfterCompile">
<CallTarget Targets="AfterCompile" />
<CallTarget Targets="VerifyParam" />
</Target>
<Target Name="AfterCompile">
<GetAssemblyFileVersion strFilePathAssemblyInfo="Properties\AssemblyInfo.cs">
<Output TaskParameter="strAssemblyFileVersion" PropertyName="ApplicationVersionModded" />
</GetAssemblyFileVersion>
<PropertyGroup>
<ApplicationVersion>$(ApplicationVersionModded)</ApplicationVersion>
</PropertyGroup>
</Target>
<Target Name="VerifyParam">
<Message Text="New $(ApplicationVersionModded)" Importance="high"/>
<Message Text="Old Updated $(ApplicationVersion)" Importance="high"/>
</Target>
the GetAssemblyFileVersion.dll i more or less stole from some post i found on the internet, just can't find it again, so i can't add a link, sorry.
My theory on why it does not work is that the transforms and parameters in PropertyGroups are rendered before both InitailTagets and DefaultTargets is run. And there for will my plan never work
but if anyone knows of a way to make it work, i will be grateful to here it
My theory on why it does not work is that the transforms and parameters in PropertyGroups are rendered before both InitailTagets and DefaultTargets is run indeed, that's how the evaluation order works: msbuild evaluates global properties in the first pass of the file, you define OutputPath, that is used by the Microsoft.Common.CurrentVersion.targets file to derive OutDir/BaseIntermediateOutputPath/.... Then in another pass your targets run and update the version number, but there isn't another pass which evaluates the global OutputPath property again.
You can however override the value of OutputPath and derived paths in a Target, and it will take effect, you just have to take care of running it early in the build so that other targets use the updated version. This does the trick:
<Target Name="GetApplicationVersion">
<GetAssemblyFileVersion strFilePathAssemblyInfo="Properties\AssemblyInfo.cs">
<Output TaskParameter="strAssemblyFileVersion" PropertyName="ApplicationVersion" />
</GetAssemblyFileVersion>
</Target>
<Target Name="SetOutputPaths" DependsOnTargets="GetApplicationVersion"
BeforeTargets="PrepareForBuild">
<PropertyGroup>
<OutputPath>bin\$(Configuration)\$(ApplicationVersion)\</OutputPath>
<OutDir>$(OutputPath)</OutDir>
</PropertyGroup>
<Message Text="Set OutDir to $(OutDir)" Importance="high" />
</Target>
Another way to deal with this is doing things the other way around: define the application version as a global msbuild property, then use it to define OutputPath and to update the number in AssemblyVersion.cs before it is compiled.

Conditionally build Post-Deploy in SSDT SQLPROJ

I want to conditionally build the post-deploy script in my SSDT project, but i don't understand how to do that. So normally the post-deploy script is built, but I want a way to NOT build or run the post-deploy script when doing a Debug build. I am running the build from the command-line, so i could pass in properties, but how could i use a property to not run the post-deploy script?
The options i see are SQLCMD, or editing the SQLPROJ file, or passing in properties, but I can't find any reference for what the available properties are and what not for SQLPROJ files.
The file i want to conditionally build is located here:
<ItemGroup>
<PostDeploy Include="PostDeploymentScripts\Script.PostDeployment1.sql" />
</ItemGroup>
My Debug build block looks like this:
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<OutputPath>bin\Debug\</OutputPath>
<BuildScriptName>$(MSBuildProjectName).sql</BuildScriptName>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<DefineDebug>true</DefineDebug>
<DefineTrace>true</DefineTrace>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
And my command-line looks like this:
msbuild $sqlprojFilePath /p:Configuration="Debug"
In your .sqlproj, add this line to occur after the import on Microsoft.Data.Tools.Schema.SqlTasks.targets
Find this line in your project file after the import on *SqlTasks.targets.
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(MSBuildToolsVersion)\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets" />
<PropertyGroup Condition="'$(Configuration)'=='debug'">
<DeployDependsOn />
<SqlDeployDependsOn />
</PropertyGroup>
This removes the deployment projects from the dependency chain and can be modified to use an alternate property.
<PropertyGroup Condition="'$(SkipDeployment)'=='true'">
<DeployDependsOn />
<SqlDeployDependsOn />
</PropertyGroup>
Command line:
msbuild.exe mydb.sqlproj /p:SkipDeployment=true
Big Edit:
Or you can take this:
<ItemGroup>
<PostDeploy Include="PostDeploymentScripts\Script.PostDeployment1.sql" />
</ItemGroup>
and change it to read like so:
<ItemGroup Condition="'$(Configuration)'=='debug'">
<PostDeploy Include="PostDeploymentScripts\Script.PostDeployment1.sql" />
</ItemGroup>
You can do this with a SQLCMD variable. Set one up in the project and check the value of that variable when publishing the project. I blogged about something like this here:
http://schottsql.blogspot.com/2013/05/trick-to-not-run-prepost-sql-on-publish.html
Not sure about the msbuild command-line as I've normally used sqlpackage.exe to push the changes.

Include .sln file in MsBuild file?

I'm trying to build Visual Studio project from .msbuild file. The idea is to do some things before the build, then execute build of .sln from the same place.
Something like this:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="BuildClient" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Release</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<Tools>$(MSBuildProjectDirectory)\tools</Tools>
<NugetExe>$(Tools)\nuget\nuget.exe</NugetExe>
<ClientFolder>$(MSBuildProjectDirectory)\src\Client</ClientFolder>
<ClientSolution>$(ClientFolder)\ClientProject.sln</ClientSolution>
</PropertyGroup>
<Target Name="BuildClient">
<!--Restore Nuget-->
<Message Text="Starting Restoring Nuget Packages" />
<Exec Command=" $(NugetExe) restore $(ClientSolution)" />
<Message Text="Finished Restoring Nuget Packages" />
<Import Project="$(ClientSolution)" />
<!--build project-->
</Target>
</Project>
But line with <Import> is underlined in VS, saying import should be on level up, not within <Target>. When I move <Import> a level up as suggested I get an execution error:
errorMSB4025: The project file could not be loaded. Data at the root level is invalid.
Any idea how to build whole project without major rewriting?
Silly me! the solution turns out to be very simple:
<MSBuild Projects="..\..\MySolution.sln" "/>
This did the trick with executing the build on .sln file
I got the pointer from this answer.

Using FsLex/Yacc in Vs2013

I'm trying to resurrect an old f# parser project I had working in vs 2008 to work with vs 2013. It uses FsLexYacc.
I got it building ok by using a prebuild step as thus:
fslex --unicode "$(ProjectDir)XpathLexer.fsl"
fsyacc --module XpathParser "$(ProjectDir)XpathParser.fsy"
But this is less than ideal, as it always executes whether or not the inputs have changed.
I then tried just using the old MsBuild actions:
<FsYacc Include="XpathParser.fsy">
<FsLex Include="XpathLexer.fsl">
but these appeared to be completely ignored during the build process. Is that right? Have these build tasks been removed somehow?
I then found some stuff documented under vs C++ that I thought might work:
<CustomBuild Include="XpathParser.fsy">
<Message>Calling FsYacc</Message>
<Command>fsyacc --module XpathParser "$(ProjectDir)XpathParser.fsy"</Command>
<Outputs>$(ProjectDir)XpathParser.fs</Outputs>
</CustomBuild>
and
<PropertyGroup>
<CustomBuildBeforeTargets>CoreCompile</CustomBuildBeforeTargets>
</PropertyGroup>
(I inspected the Microsoft.Fsharp.Targets file to come up with the "CoreCompile" target.)
Alas, still no cigar.
Is anyone able to shine a light on whether it is indeed possible to properly integrate fslex/yacc into a vs 2013 solution, and if so, how?
I don't think the those tools are included by default with the F# compiler that is installed with Visual Studio and so the tasks don't exist. I did the following with a Visual Studio 2012 project, but I expect it would be similar in VS 2013. Here were the steps I had to follow:
Install FSharp.Powerpack from nuget. This has the fslex and fsyacc tools as well as build tasks and targets.
Unload the project and edit the .fsproj file.
Add an import statement for the FSharp.Powerpack.target file. This will add the CallFsLex and CallFsYacc build targets. I added this after the import for Microsoft.FSharp.targets:
<Import Project="$(ProjectDir)\..\packages\FSPowerPack.Community.3.0.0.0\Tools\FSharp.PowerPack.targets" />
Add these three properties to main PropertyGroup at the top of the file:
<FsYaccToolPath>..\packages\FSPowerPack.Community.3.0.0.0\Tools</FsYaccToolPath>
<FsLexToolPath>..\packages\FSPowerPack.Community.3.0.0.0\Tools</FsLexToolPath>
<FsLexUnicode>true</FsLexUnicode> This tells the build tasks where to find the necessary tools and sets the unicode option for fslex.
To use the targets we've imported, you need to define the FsLex and FsYacc item groups with the input files to use. You also need to add Compile items for the output .fs files. You end up with something like this in an ItemGroup section:
<Compile Include="Sql.fs" />
<FsYacc Include="SqlParser.fsp">
<Module>SqlParser</Module>
</FsYacc>
<Compile Include="SqlParser.fsi" />
<Compile Include="SqlParser.fs" />
<FsLex Include="SqlLexer.fsl" />
<Compile Include="SqlLexer.fs" />
You might be able to use the FsLex and FsYacc build tasks directly by referencing the FSharp.Powerpack.Build.Tasks.dll, but for me this was easier to get going.
This is what works for me (Windows 7 x64, Visual Studio 2013 Ultimate RTM):
Get and install "PowerPack for FSharp 3.0 + .NET 4.x + VS2012" from CodePlex (https://fsharppowerpack.codeplex.com/downloads/get/625449)
Create the following Registry key: HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\.NETFramework\AssemblyFolders\FSharp.PowerPack-1.9.9.9 (for x64 versions of Windows, omit the Wow6432Node for 32bit versions) and set its (Default) value to the installation directory of the F# PowerPack (e.g. "C:\Program Files (x86)\FSharpPowerPack-4.0.0.0\bin"). [This is related to a long standing/regression bug in src/FSharp.PowerPack/CompilerLocationUtils.fs which basically breaks tool discovery.]
Import the PowerPack targets (AFTER importing the F# targets) in your *.fsproj file: <Import Project="$(MSBuildExtensionsPath32)\FSharp\1.0\FSharp.PowerPack.targets" />
Update your ItemGroup node to something like this (use FsYacc accordingly):
<ItemGroup>
<None Include="App.config" />
<FsLex Include="Lexer.fsl" />
<Compile Include="Lexer.fs">
<Visible>False</Visible>
</Compile>
<Compile Include="Program.fs" />
</ItemGroup>
Include a reference to FSharp.PowerPack.dll and build.
You should end up with a *.fsproj file similar to this:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>8c565f99-d6bc-43a9-ace9-eadfe429c0f7</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>FsYaccTest</RootNamespace>
<AssemblyName>FsYaccTest</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFSharpCoreVersion>4.3.1.0</TargetFSharpCoreVersion>
<Name>FsYaccTest</Name>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<!-- Snip -->
</PropertyGroup>
<ItemGroup>
<Reference Include="FSharp.PowerPack">
<HintPath>C:\Program Files (x86)\FSharpPowerPack-4.0.0.0\bin\FSharp.PowerPack.dll</HintPath>
</Reference>
<Reference Include="mscorlib" />
<Reference Include="FSharp.Core, Version=$(TargetFSharpCoreVersion), Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Numerics" />
</ItemGroup>
<PropertyGroup>
<MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">11</MinimumVisualStudioVersion>
</PropertyGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '11.0'">
<PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets')">
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets</FSharpTargetsPath>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets')">
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets</FSharpTargetsPath>
</PropertyGroup>
</Otherwise>
</Choose>
<Import Project="$(FSharpTargetsPath)" />
<Import Project="$(MSBuildExtensionsPath32)\FSharp\1.0\FSharp.PowerPack.targets" />
<PropertyGroup>
<FsLexUnicode>true</FsLexUnicode>
</PropertyGroup>
<ItemGroup>
<None Include="App.config" />
<FsLex Include="Lexer.fsl" />
<Compile Include="Lexer.fs">
<Visible>False</Visible>
</Compile>
<Compile Include="Program.fs" />
</ItemGroup>
</Project>
Note: You can probably omit creating the Registry key if you provide a proper FsYaccToolPath as described in mike z's answer.
This looks like it works - at least, in my experience, if you use the separate FsLexYacc nuget package as detailed here, and then put the following in your fsproj file (extracted from the github example):
Next to all the other imports:
<Import Project="..\packages\FsLexYacc.6.0.4\bin\FsLexYacc.targets" />
etc, etc
and then for the source files:
<FsYacc Include="Parser.fsp">
<OtherFlags>--module SqlParser</OtherFlags>
</FsYacc>
<FsLex Include="Lexer.fsl">
<OtherFlags>--unicode</OtherFlags>
</FsLex>
No need to do anything apart from edit the fsproj file, and install the nuget packages.

MSBUILD Unable to force PackageLocation for Package target

I'm trying to build a deployment package for my web service from msbuild just like you can do in Visual Studio by right-clicking on the project file.
The package gets created fine and is put in the /obj/Release/Packages folder under my source directory for the project file.
You should be able to specify where that package is created by setting the PackageLocation property in a PropertyGroup inside the project file. However, that is not working for me. It still puts the package in /obj/Release/Packages under the source directory.
Here is the snippet from my project file:
<Import Project="$(SrcTreeRoot)\Build\TaskInit.Tasks" />
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />
<Import Project="$(SrcTreeRoot)\Build\TaskOverrides.Tasks" />
<PropertyGroup>
<Platform>Any Cpu</Platform>
<Configuration>Dev</Configuration>
<PackageLocation>$(PackageOutputDir)\MatrixOnCdsService\MatrixOnCdsService.zip</PackageLocation>
<PackageAsSingleFile>True</PackageAsSingleFile>
<EnablePackageProcessLoggingAndAssert>True</EnablePackageProcessLoggingAndAssert>
<!--OutDir>$(PackageOutputDir)\MatrixOnCdsService\</OutDir-->
</PropertyGroup>
We are also using a MasterBuild.proj that has the following sections:
<PackageProject Include="..\Source\AnalysisSuite\MatrixOnCdsService\MatrixOnCdsService.csproj"/>
...
<Target Name="Package">
<MSBuild Projects="#(PackageProject)" Targets="Package" Properties="Platform=$(Platform);
Configuration=$(Configuration);
DeployOnBuild=true;
DeployTarget=Package;
PackageLocation=$(PackageLocation);"/>
</Target>
TaskInit.tasks is our own custom import file that contains the PackageOutputDir property that we use to tell the project where to put the package.
My question is why is the package still being placed in the /obj/Release/Packages folder even after specifying the PackageLocation?
Stick the property group you have in a target:
<Target Name="SetValues">
<PropertyGroup>
<Platform>Any Cpu</Platform>
<Configuration>Dev</Configuration>
<PackageLocation>$(PackageOutputDir)\MatrixOnCdsService\MatrixOnCdsService.zip</PackageLocation>
<PackageAsSingleFile>True</PackageAsSingleFile>
<EnablePackageProcessLoggingAndAssert>True</EnablePackageProcessLoggingAndAssert>
<!--OutDir>$(PackageOutputDir)\MatrixOnCdsService\</OutDir-->
</PropertyGroup>
</Target>
then add this as a DependsOnTarget for your Package Target and i think you will have your values passed.
e.g. <Target Name="Package" DependsOnTargets="SetValues">
You could do the following in your MasterBuild.proj.
<Target Name="Package">
<ConvertToAbsolutePath Paths="$(PackageOutputDir)">
<Output TaskParameter="AbsolutePaths" PropertyName="Source_Dir_Abs"/>
</ConvertToAbsolutePath>
<MSBuild Projects="#(PackageProject)" Targets="Package"
properties="Platform=$(Platform);
Configuration=$(Configuration);
DeployOnBuild=false;
DeployTarget=Package;
PackageLocation=$(Source_Dir_Abs)\$(PackageProjectName).zip;
PackageAsSingleFile=true;
ExcludeFilesFromDeployment=Web.config;
_PackageTempDir=$(PackageOutputDir)\temp;">
</MSBuild>
</Target>
Where you are calling msbuild you will need to add a property that will be used in $(PackageProjectName) by doing the following:
msbuild.exe /property:PackageProjectName=$project