Need some help with this MSBuild code.
I want to generate 4 app.config files with different settings and create 2 setup files for QA and production.
Each setup file will have 2 physical installations (Production lines).
So QA setup should include 2 app.configs with qa settings for production line 1 and 2, the same for production setup.
Here is a extract of the msbuild I have so far.
<ItemGroup>
<BuildEnvironment Include="QA">
<Server>qa-server</Server>
<BuildEnvironment/>
<BuildEnvironment Include="Prod">
<Server>prod-server</Server>
<BuildEnvironment/>
<Line Include="1">
<Setting>A</Setting>
</Line>
<Line Include="2">
<Setting>B</Setting>
</Line>
<ItemGroup>
<Target Name="PublishSetup" Inputs="#(BuildEnvironment)" Outputs="%(BuildEnvironment.Identity)">
<!-- Doesn't work at all -->
<ItemGroup>
<AppConfig Include="#(BuildEnvironment);#(Line)">
<Path>$(MyOutDir)\App.Config-%(Identity)</Path>
</AppConfig>
</ItemGroup>
<!-- Copy app.config to the four new files -->
<Copy SourceFiles="$(AppConfigFile)" DestinationFiles="%(AppConfig.Path)" />
<!-- Update each new app.config with XmlUpdate (community task), something like the following -->
<XmlUpdate XmlFileName="%(AppConfig.Path)" XPath=".." Value="%(AppConfig.Server)" />
<XmlUpdate XmlFileName="%(AppConfig.Path)" XPath=".." Value="%(AppConfig.Setting)" />
<!-- Build 2 setup.exe, one for qa and one prod using a Exec-task passing in qa and prod as command line argument -->
<Exec Command="setupcompiler.exe /d%(BuildEnvironment.Identity)" />
</Target>
The 4 resulting app.configs should be like this
app.config-QA-1
<connectionstring datasource="qa-server" ../>
<applicationSetting name="aName" value="A" />
app.config-QA-2
<connectionstring datasource="qa-server" ../>
<applicationSetting name="aName" value="B" />
app.config-Prod-1
<connectionstring datasource="prod-server" ../>
<applicationSetting name="aName" value="A" />
app.config-Prod-2
<connectionstring datasource="prod-server" ../>
<applicationSetting name="aName" value="B" />
The idea is to first build a 'cross-product', an ItemGroup containing the 4 combinations. Can be done by combining # and % for the two groups, as shown here. Then in a second step populate the ItemGroup with extra metadata based on existing metadata (adding metadata is just declaring the group again and adding metadata). It's a bit tricky here, because from Line you both want Identity and Setting - I don't know a nice msbuild way of doing this so I resorted to building a string with Identity|Setting, then splitting on the | later on.
<Target Name="PublishSetup">
<ItemGroup>
<AppConfig Include="#(BuildEnvironment)">
<Mod>%(Line.Identity)|%(Line.Setting)</Mod>
</AppConfig>
<AppConfig>
<Line>$([System.String]::Copy('%(Mod)').Split('|')[0])</Line>
<Setting>$([System.String]::Copy('%(Mod)').Split('|')[1])</Setting>
</AppConfig>
<AppConfig>
<Path>app.config-%(Identity)-%(Line)</Path>
</AppConfig>
</ItemGroup>
<Message Text="Path=%(AppConfig.Path) Server=%(AppConfig.Server) Setting=%(AppConfig.Setting)" />
</Target>
Output:
Path=app.config-QA-1 Server=qa-server Setting=A
Path=app.config-Prod-1 Server=prod-server Setting=A
Path=app.config-QA-2 Server=qa-server Setting=B
Path=app.config-Prod-2 Server=prod-server Setting=B
Related
I have two files that I want to configure by environment: App.config and ApplicationInsights.config. I have created the files App.Debug.config and ApplicationINsights.Debug.config and added the following tasks to the csproj file:
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="AfterCompile" Condition="exists('app.$(Configuration).config')">
<TransformXml Source="app.config" Destination="$(IntermediateOutputPath)$(TargetFileName).config" Transform="app.$(Configuration).config" />
<ItemGroup>
<AppConfigWithTargetPath Remove="app.config" />
<AppConfigWithTargetPath Include="$(IntermediateOutputPath)$(TargetFileName).config">
<TargetPath>$(TargetFileName).config</TargetPath>
</AppConfigWithTargetPath>
</ItemGroup>
</Target>
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="AfterCompile" Condition="exists('ApplicationInsights.$(Configuration).config')">
<Message Text="Transforming app insights config file to $(OutputPath)\ApplicationInsights.config" Importance="high" />
<TransformXml Source="ApplicationInsights.config" Transform="ApplicationInsights.$(Configuration).config" Destination="$(OutputPath)\ApplicationInsights.config" />
</Target>
Both tasks work when they are the only task in the file, but when both are included only the second transform is executed. I have tried giving the tasks different Names, but to no avail. What can I do to get both tasks to run?
You have to give the two tasks different Names and then hook into the existing AfterCompile target:
<Target Name="SomeUniqueName1" AfterTargets="AfterCompile" …>
…
</Target>
<Target Name="SomeUniqueName2" AfterTargets="AfterCompile" …>
…
</Target>
The <UsingTask> only needs to be there once to define the imported TransformXml task.
I have two buildtargets to check my code quality.
I run the following buildtargets every time i compile. This takes up too much time and i would like them to only check the files that did change.
In other words i want to filter files that did not change from the ItemGroup CppCheckFiles / LinterFiles.
<Target Name="CppCheck">
<ItemGroup>
<CppCheckFiles Include="*main.c" />
<CppCheckFiles Include="Source/*/*.c" />
</ItemGroup>
<Message Text="$(Configuration) starting." Importance="High" />
<Exec Command="C:\Cppcheck\cppcheck.exe %(CppCheckFiles.FullPath) --enable=style --template="{file}({line}): error:{severity}-{id}: {message}"" />
</Target>
<Target Name="SPLint">
<ItemGroup>
<LinterFiles Include="*main.c" />
<LinterFiles Include="Source/*/*.c" />
<LinterFiles Include="Source/*/*.h" />
</ItemGroup>
<Message Text="$(Configuration) starting." Importance="High" />
<Exec Command="splintCaller %(LinterFiles.FullPath)" />
</Target>
I know that the regular build process does this and i wonder if i have to go so fas as to write my own task.
hmm.. this sounds interesting. I can't help you. But it would be nice if the cppcheck wiki or manual had some small example project that did this.
Some people use cppcheck in commit hooks. I've tried it with GIT myself (I added a linux shell script). And there is a TortoiseSVN plugin you can try (http://sourceforge.net/apps/phpbb/cppcheck/viewtopic.php?f=3&t=443).
The solution is incremental Build. Where MSBuild compares Timestamps to exclude complete Buildtargets if nothing changed.
The following target creates a timesstamp for each file and skippes those files that did not change.
cppcheck.exe returns -1 if an error was detected and the timestamp is not written.
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="CppCheck" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<CppCheckFiles Include="*main.c" />
<CppCheckFiles Include="Source/*/*.c" />
</ItemGroup>
<Target Name="CppCheck"
Inputs="#(CppCheckFiles)"
Outputs="CCPCheck\%(CppCheckFiles.Filename)%(CppCheckFiles.Extension).stamp">
<Exec Command="C:\Cppcheck\cppcheck.exe %(CppCheckFiles.FullPath) --enable=style --template="{file}({line}): error:{severity}-{id}: {message}"" />
<MakeDir Directories="CCPCheck"/>
<Touch Files="CCPCheck\%(CppCheckFiles.Filename)%(CppCheckFiles.Extension).stamp" AlwaysCreate = "true" />
</Target>
</Project>
I have a Silverlight project with multiple configuration files, and am using the transformation approach shown here:
App.Config Transformation for projects which are not Web Projects in Visual Studio 2010?
This approach doesn't work as-is for Silverlight projects though. I've re-written the MSBuild project to look like this:
<ItemGroup>
<None Include="App.config" />
<None Include="App.QABuild.config">
<DependentUpon>App.config</DependentUpon>
</None>
</ItemGroup>
....
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="BeforeCompile" Condition="Exists('App.$(Configuration).config')">
<!-- Generate transformed app config in the output directory -->
<Message Importance="high" Text="Transforming 'App.$(Configuration).config' to output config file..." />
<TransformXml Source="App.config" Destination="$(OutputPath)App.config" Transform="App.$(Configuration).config" />
<ItemGroup>
<Content Include="$(OutputPath)App.config" />
</ItemGroup>
</Target>
<Target Name="BeforeCompile" Condition="!Exists('App.$(Configuration).config')">
<Message Importance="high" Text="Using default 'App.config' as output config file..." />
<Copy SourceFiles="App.config" DestinationFiles="$(OutputPath)App.config" />
<ItemGroup>
<Content Include="$(OutputPath)App.config" />
</ItemGroup>
</Target>
This code generates the correct output file for the correct configuration, however it is never included in the XAP file, even though I am putting the output config into the Content item group. All I need to happen is for the output config to get included in the output XAP but I can't get this to happen.
Can anyone tell me what I'm doing wrong? I'm not an MSBuild expert by any means!
Found the solution by digging into the Silverlight 4 targets. Turns out the XAP packager target actually takes an item called XapFilesInputCollection, which is where the input files come from. The Content item looks likes it is copied to this item before my target runs, so modifying the Content item afterwards is the wrong approach.
All I did was add the transformed files directly to the XapFilesInputCollection item and it worked as I expected.
<ItemGroup>
<!-- Unit Test Projects-->
<MyGroup Include="Hello.xml" />
<MyGroup Include="GoodBye.xml" />
</ItemGroup>
How do I make a task that iterates through this list and does something?
<XmlPeek XmlInputPath="%(MyGroup.Identity)"
Query="/results">
<Output TaskParameter="Result"
ItemName="myResult" />
</XmlPeek>
I want to thow an error message if myresult has a certain text inside of it. However for the life of me I can't figure out how to iterate through arrays in Msbuild... anyone know how to accomplish this?
You need to use batching for this. Batching will iterate over a set of items based on a metadata key. I've put together a bunch of material on this at http://sedotech.com/Resources#batching. For example take a look at this simple MSBuild file.
<Project DefaultTargets="Demo" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Files Include="one.txt"/>
<Files Include="two.txt"/>
<Files Include="three.txt"/>
<Files Include="four.txt"/>
</ItemGroup>
<Target Name="Demo">
<Message Text="Not batched: #(Files->'%(Identity)')"/>
<Message Text="========================================"/>
<Message Text="Batched: %(Files.Identity)"/>
</Target>
</Project>
When you build the Demo target the results are
Not batched: one.txt;two.txt;three.txt;four.txt
========================================
Batched: one.txt
Batched: two.txt
Batched: three.txt
Batched: four.txt
Batching always uses the syntax %(Xyz.Abc). Take a look thorough those links for more info about batching then you ever wanted to know.
You could use batching on an inner target, like that :
<ItemGroup>
<!-- Unit Test Projects-->
<MyGroup Include="Hello.xml" />
<MyGroup Include="GoodBye.xml" />
</ItemGroup>
<Target Name="CheckAllXmlFile">
<!-- Call CheckOneXmlFile foreach file in MyGroup -->
<MSBuild Projects="$(MSBuildProjectFile)"
Properties="CurrentXmlFile=%(MyGroup.Identity)"
Targets="CheckOneXmlFile">
</MSBuild>
</Target>
<!-- This target checks the current analyzed file $(CurrentXmlFile) -->
<Target Name="CheckOneXmlFile">
<XmlPeek XmlInputPath="$(CurrentXmlFile)"
Query="/results/text()">
<Output TaskParameter="Result" ItemName="myResult" />
</XmlPeek>
<!-- Throw an error message if Result has a certain text : ERROR -->
<Error Condition="'$(Result)' == 'ERROR'"
Text="Error with results $(Result)"/>
</Target>
We are migrating from MSTests to NUnit. The first step was to migrate all our UnitTests projects which was accomplished using the following msbuild task:
<Target Name="RunTests">
<!-- The location of the necessary tools to run nunit tests -->
<PropertyGroup>
<NUnitToolPath>C:\Program Files\NUnit 2.5.2\bin\net-2.0</NUnitToolPath>
<NUnitResultTool>C:\Program Files\NUnit For Team Build Version 1.2</NUnitResultTool>
</PropertyGroup>
<!-- Create a build step representing running nunit tests -->
<BuildStep TeamFoundationServerUrl="$(TeamFoundationServerUrl)" BuildUri="$(BuildUri)" Name="NUnitTestStep" Message="Running Nunit Tests">
<Output TaskParameter="Id" PropertyName="NUnitStepId" />
</BuildStep>
<!-- Specify which dll's to include when running tests -->
<CreateItem Include="$(OutDir)\Profdoc.UnitTests*.dll">
<Output TaskParameter="Include" ItemName="TestAssembly" />
</CreateItem>
<NUnit
Assemblies="#(TestAssembly)"
ToolPath="$(NUnitToolPath)"
OutputXmlFile="$(OutDir)\NUnit_TestResults.xml"
ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="NUnitResult" />
</NUnit>
<!-- Update the build step result based on the output from the NUnit task -->
<BuildStep Condition="'$(NUnitResult)'=='0'" TeamFoundationServerUrl="$(TeamFoundationServerUrl)" BuildUri="$(BuildUri)" Id="$(NUnitStepId)" Status="Succeeded" />
<BuildStep Condition="'$(NUnitResult)'!='0'" TeamFoundationServerUrl="$(TeamFoundationServerUrl)" BuildUri="$(BuildUri)" Id="$(NUnitStepId)" Status="Failed" />
<!-- Upload the results to TFS. -->
<Exec Command=""$(NUnitResultTool)\NUnitTFS.exe" -n "$(OutDir)\NUnit_TestResults.xml" -t "$(TeamProject)" -b "$(BuildNumber)" -f "%(ConfigurationToBuild.FlavorToBuild)" -p "%(ConfigurationToBuild.PlatformToBuild)" -x "$(NUnitResultTool)\NUnitToMSTest.xslt"" />
<!-- Indicate build failure if any tests failed -->
<Error Condition="'$(NUnitResult)'!='0'" Text="Unit Tests Failed" />
</Target>
But i'm at a loss as to how we are going to accomplish the same with out integration tests, because we need to deploy settings and licence files to the binary folder before running the tests. So, how can i deploy files to the binary folder, preferably as a part of the NUnit task (because i want to run the IntegrationTests against different configuration setups)?
I would suggest to create new one target which will copy all required files and make target RunTests dependent on new one, basically:
<PropertyGroup>
<LicenseFiles>$(PathToLicenseFiles)\**\*.lcx</LicenseFiles>
<SettingsFiles>$(PathToConfigFiles)\**\*.config</SettingsFiles>
</PropertyGroup>
<ItemGroup>
<Files Include="$(LicenseFiles);$(SettingsFiles)"
Exclude="*.tmp"/>
</ItemGroup>
<Target Name="CopyDependencyFiles">
<CopyFiles Inputs="#(Files)" Outputs="..." />
</Target>
<!-- Run Integration tests after all files were copied -->
<Target Name="RunIntegrationTests" DependsOnTargets="CopyDependencyFiles">
<NUnit .. />
</Target>