Our C++ project uses MSBuild to build on Windows and GNU make on *nix. I'm trying to recreate the functionality of the following single line of GNU make in MSBuild:
GENN_PATH:=$(abspath $(dir $(shell which genn-buildmodel.sh))../userproject/include)
Essentially setting a variable to a path relative to an executable in the path. However, this is proving to be a battle to implement in MSBuild...
The following are the (hopefully) pertinent sections from my vcxproj. For testing purposes I am first setting the variable I want to override to something obvious:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
...
<PropertyGroup Label="Configuration">
...
<GeNNUserProject>UNDEFINED</GeNNUserProject>
</PropertyGroup>
Then, in my ClCompile item definitions, I am adding the value of this property to the additional include directories
<ItemDefinitionGroup>
<ClCompile>
...
<AdditionalIncludeDirectories>include;$(GeNNUserProject)</AdditionalIncludeDirectories>
</ClCompile>
...
</ItemDefinitionGroup>
In order to find this path, I'm using the where command and redirecting it's output to a property. Then, from this, I'm finding the include directory and printing it out - this works!
<Target Name="FindUserProjects">
<Exec Command="where genn-buildmodel.bat" ConsoleToMsBuild="true">
<Output TaskParameter="ConsoleOutput" PropertyName="GeNNBuildModelPath" />
</Exec>
<PropertyGroup>
<GeNNUserProject>$([System.IO.Path]::GetFullPath($([System.IO.Path]::GetDirectoryName($(GeNNBuildModelPath)))\..\userproject\include))</GeNNUserProject>
</PropertyGroup>
<Message Text="MAGIC GENN-FINDING! $(GeNNBuildModelPath) -> $(GeNNUserProject)"/>
</Target>
I've tried a variety of ways of making this a dependency of ClCompile including setting the Target as BeforeTargets="PrepareForBuild" and the following:
<PropertyGroup>
<BeforeClCompileTargets>
FindUserProjects;
$(BeforeClCompileTargets);
</BeforeClCompileTargets>
</PropertyGroup>
</Project>
Whatever I do, my custom target runs but the property is not being overriden. Google suggests that if properties are overriden in depencies they should be visible from targets and from digging into Microsoft.CPP*.targets this is what setting BeforeClCompileTargets is doing.
The problem here was not that the target wasn't setting the property, it's that the AdditionalIncludeDirectories item metadata was being set from the original value. The solution is to set this directly from the target instead:
<ItemGroup>
<ClCompile>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$([System.IO.Path]::GetFullPath($([System.IO.Path]::GetDirectoryName($(GeNNBuildModelPath)))\..\userproject\include))</AdditionalIncludeDirectories>
</ClCompile>
</ItemGroup>
Related
I'd like to store ccnet.config file (or other cc.net configuration file for this project) in the repository (git) of my project and make CC.NET use it when I force building from dashboard. How can I do it?
Thank you!
Your "ccnet.config" should remain fairly static.
If you need different "logic" for your solution/project building, then I suggest:
1. Write your ccnet.config code to pull source code from repository. (aka, Task #1)
2. In your repository, include a MasterBuild.proj (msbuild definition).
3. Have cc.net call msbuild.exe on MasterBuild.proj (aka, Task #2).
4. Have the majority of your logic inside the MasterBuild.proj file. That is what you check in/out of source control.
If you think of CC.NET as a "super fancy msbuild.exe executor", you're world will make more sense IMHO.
Here is a very basic msbuild (definition) file.
You can call it
MySolutionMasterBuild.proj (or similar)
Put this in the same directory as your .sln file (in source control).
Use CC.NET to download the code.
Then wire up msbuild.exe to call the below file.
Then have any extra logic inside the .proj file.
You can do some of the other CC.NET stuff, like post build emailing and merging any results xml, but the majority of the logic (my preference anyways)..........would be in the file below.
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="AllTargetsWrapped">
<PropertyGroup>
<!-- Always declare some kind of "base directory" and then work off of that in the majority of cases -->
<WorkingCheckout>.</WorkingCheckout>
<ArtifactDestinationFolder>$(WorkingCheckout)\ZZZArtifacts</ArtifactDestinationFolder>
</PropertyGroup>
<Target Name="AllTargetsWrapped">
<CallTarget Targets="CleanArtifactFolder" />
<CallTarget Targets="BuildItUp" />
<CallTarget Targets="CopyFilesToArtifactFolder" />
</Target>
<Target Name="BuildItUp" >
<MSBuild Projects="$(WorkingCheckout)\MySolution.sln" Targets="Build" Properties="Configuration=$(Configuration)">
<Output TaskParameter="TargetOutputs" ItemName="TargetOutputsItemName"/>
</MSBuild>
<Message Text="BuildItUp completed" />
</Target>
<Target Name="CleanArtifactFolder">
<RemoveDir Directories="$(ArtifactDestinationFolder)" Condition="Exists($(ArtifactDestinationFolder))"/>
<MakeDir Directories="$(ArtifactDestinationFolder)" Condition="!Exists($(ArtifactDestinationFolder))"/>
<Message Text="Cleaning done" />
</Target>
<Target Name="CopyFilesToArtifactFolder">
<ItemGroup>
<MyExcludeFiles Include="$(WorkingCheckout)\**\*.doesnotexist" />
</ItemGroup>
<ItemGroup>
<MyIncludeFiles Include="$(WorkingCheckout)\bin\$(Configuration)\**\*.*" Exclude="#(MyExcludeFiles)"/>
</ItemGroup>
<Copy
SourceFiles="#(MyIncludeFiles)"
DestinationFiles="#(MyIncludeFiles->'$(ArtifactDestinationFolder)\%(Filename)%(Extension)')"
/>
</Target>
</Project>
Take a look at the scenario's at
http://www.cruisecontrolnet.org/projects/ccnet/wiki/Build_Server_Scenarios
Step 1 Setting up Source Control
Step 2 Build on Check-in
Step 3 Add unit tests
Step 4 Add Coverage
Step 5 Add source code analysis
There are build scripts foreseen in each step where you can base yourself on.
I'm trying to add an AfterBuild task to my VC++2012 project that only gets executed if the output changes. If I'm correct, in C# when you're adding a post-build-event, you have the option to specify when to run the post-build-event. I tried adding condition such as _SourceItemsToCopyToOutputDirectory but it doesn't help, it's empty whether I'm doing a null build or not. The only property I found is LinkSkippedExecution but I'm not sure it's the best option.
Thanks for your help
Check out this stack overflow question and answer:
You can create a property in the before build target based on the timestamp of the output files.
<Target Name="BeforeBuild">
<PropertyGroup>
<MyBeforeCompileTimestamp>%(IntermediateAssembly.ModifiedTime)
</MyBeforeCompileTimestamp>
</PropertyGroup>
</Target>
<Target Name="AfterBuild">
<CallTarget Condition="$(MyBeforeCompileTimestamp) !=
%(IntermediateAssembly.ModifiedTime)" Targets="MyTarget" />
</Target>
May be a silly question, but does anybody know how to specify the output filename of a VSTEST.Console.exe run? My command line is as follows:
vstest.console.exe [assembly] /logger:trx
At the end of the run, the following comes up in the console:
ResultsFile: somepath\TestResults\{username}_{workstation} {timestamp}.trx
I tried using the .runsettings file to specify the output location, but that only seems to control the output directory, but not the output file. Have not found anything else that would seem to control it.
I want to parse the TRX file and generate a report out of it (this already works, but if I can't specify the output path of the TRX file, I won't know where to pick it up from in the scripts!)
I have to be missing something here...
EDIT: See #AnaFranco's answer - apparently since VS2017 the file name can be configured like so:
vstest.console.exe [assembly] /logger:trx;LogFileName=[filename].trx
I'll leave the old answer for posterity and pre-2017 versions.
Nope, you're not missing anything. The TRX logger doesn't support any parameters (unlike the TFS publisher logger).
The logger assembly is located in "C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\CommonExtensions\Microsoft\TestWindow\Extensions\Microsoft.VisualStudio.TestPlatform.Extensions.TrxLogger.dll". If you check it out in your favorite .NET decompiler, you'll see the method TrxLogger.GetTrxFileName. It uses some basic knowledge about the current test run to produce the mangled name of form {username}_{workstation} {timestamp}.trx and is in no appreciable way configurable.
As far as I can tell, the TRX file is created in the TestResults\ folder under the current working directory unless otherwise configured. What you can do is:
Create a new temporary folder
Change the current directory to it
Run the test runner
Scan the folder for the result .trx file using your favorite recursive file search method and you're done
At least that is what I do in our build (MSBuild, sob):
<ItemGroup>
<TestResult Include="**\*.trx"/>
</ItemGroup>
I.e, gather all .trx files under the current directory and stuff them into the #(TestResult) item group for further processing.
This has worked for me for testing .net core I haven't tried it with .net framework:
vstest.console.exe [assembly] /logger:trx;LogFileName=[filename].trx
Maybe its a new thing
Update: This works for .net framework projects too using the latest Test platform and vstest.console.exe
Apparently, you can specify a directory where to put the *.trx file (not of the file itself though). This is, however, done via .runsettings file rather than via command line.
Excerpt from Bhuvaneshwari's blog:
If the default location of results need to be overriden user need to
pass this value using a runsettings file.
Example:
Mstest.exe /testcontainer:abc.dll /results:C:\Results.trx
Vstest.console.exe abc.dll /settings:output.runsettings
where the context of the
.runsettings file would be something like below :
<?xml version="1.0" encoding="UTF-8"?>
<RunSettings>
<RunConfiguration>
<ResultsDirectory>c:\</ResultsDirectory>
</RunConfiguration>
</RunSettings>
I had this issue as well. I decided to write a MSBuild target that executes vstest.console via the EXEC task, handling all its outputs, including the coverage results.
Basically, I captured the vstest output and used a regex to capture the *.trx and *.coverage portion of the output, which turned out to be really easy. Plus, it removes the TestResults directory to keep the workspace nice and clean.
In the end, you will get the *.trx file and the *.coverage file (optionally).
The script may look a bit complex, but it was necessary to fit our needs. I tried to clean it up a bit.
Hope this helps.
<Target Name="Test" DependsOnTargets="Build">
<!-- Declare the defaults and arrange parameters -->
<PropertyGroup>
<ArtifactsPath Condition=" '$(ArtifactsPath)' == '' ">Artifacts</ArtifactsPath>
<VSTestSessionName Condition=" '$(VSTestSessionName)' == ''">TestResults</VSTestSessionName>
<VSTestExe Condition=" '$(VSTestExe)' == '' ">C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe</VSTestExe>
<VSTestFailBuildOnTestFail Condition=" '$(VSTestFailBuildOnTestFail)' == '' ">false</VSTestFailBuildOnTestFail>
<VSTestInIsolation Condition=" '$(VSTestInIsolation)' == '' ">true</VSTestInIsolation>
<VSTestUseVsixExtensions Condition=" '$(VSTestUseVsixExtensions)' == '' ">true</VSTestUseVsixExtensions>
<VSTestFramework Condition=" '$(VSTestFramework)' == '' ">framework45</VSTestFramework>
<VSTestLogger Condition=" '$(VSTestLogger)' == '' ">trx</VSTestLogger>
<ErrorCode>0</ErrorCode>
</PropertyGroup>
<ItemGroup>
<VSTestResultsPath Include="$(VSTestResultsPath)" />
<VSTestParams Include="#(VSTestFiles ->'"%(FullPath)"', ' ')" />
<VSTestParams Condition="$(VSTestEnableCodeCoverage)" Include="/EnableCodeCoverage" />
<VSTestParams Condition="$(VSTestInIsolation)" Include="/InIsolation" />
<VSTestParams Include="/UseVsixExtensions:$(VSTestUseVsixExtensions)" />
<VSTestParams Include="/Framework:$(VSTestFramework)" />
<VSTestParams Include="/Logger:$(VSTestLogger)" />
<VSTestParams Condition="$(VSTestCaseFilter) != ''" Include="/TestCaseFilter:"$(VSTestCaseFilter)"" />
<VSTestParams Condition="$(VSTestRunSettings) != ''" Include="/Settings:"$(VSTestRunSettings)"" />
</ItemGroup>
<Message Text="TestAssembly: %(VSTestFiles.Identity)" Importance="high"/>
<Exec ContinueOnError="!$(VSTestFailBuildOnTestFail)" ConsoleToMSBuild="true" WorkingDirectory="$(WorkingDirectory)" Condition=" '#(VSTestFiles)' != ''"
Command=""$(VSTestExe)" #(VSTestParams, ' ')">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode"/>
<Output TaskParameter="ConsoleOutput" PropertyName="OutputOfExec" />
</Exec>
<Message Importance="high" Text="VSTest exitcode: $(ErrorCode)"/>
<!-- Use the VSTest output to discover the Results & Coverage files respectively -->
<PropertyGroup>
<!-- Unencoded Regex: (?<=(Results file: )).*?(?=\;)|(?<=(Attachments:;)).*?(?=\;) -->
<ResultsFileRegexPattern>(?<=(Results File: )).*.trx</ResultsFileRegexPattern>
<CoverageFileRegexPattern>(?<=(Attachments:;)).*.coverage</CoverageFileRegexPattern>
<SourceResultsFile>$([System.Text.RegularExpressions.Regex]::Match($(OutputOfExec), $(ResultsFileRegexPattern)))</SourceResultsFile>
<SourceCoverageFile Condition="$(VSTestEnableCodeCoverage)">$([System.Text.RegularExpressions.Regex]::Match($(OutputOfExec), $(CoverageFileRegexPattern)))</SourceCoverageFile>
</PropertyGroup>
<ItemGroup>
<TestArtifact Include="$(SourceResultsFile)" />
<TestArtifact Include="$(SourceCoverageFile)" />
</ItemGroup>
<Warning Condition=" '$(SourceResultsFile)' == '' " Text=".trx file not found" />
<Warning Condition=" $(VSTestEnableCodeCoverage) and '$(SourceCoverageFile)' == '' " Text=".coverage file not found" />
<!-- Copy files to the artifact directory -->
<Copy SourceFiles="#(TestArtifact)" DestinationFiles="#(TestArtifact->'$(ArtifactsPath)\$(VSTestSessionName)%(Extension)')" />
<!-- Clear the test results temporary directory -->
<RemoveDir Directories="#(TestResults)" />
<ItemGroup>
<TestFile Include="$(ArtifactsPath)\**\$(VSTestSessionName).trx" />
<CoverageFile Include="$(ArtifactsPath)\**\$(VSTestSessionName).coverage" />
</ItemGroup>
<Message Text="TestReport: #(TestFile)" />
<Message Text="CoverageReport: #(CoverageFile)" />
</Target>
I created a new template, which I can use. However it only uses some project settings
I made a project with a working libSDL hi world example. I exported as a template, but template doesn't save some of my settings: ( It 'saves' them, but new projects ignore them. )
Ignored settings:
include header folders:
for .h files
for .lib files
linker args: SDLmain.lib SDL.lib
windows subsystem: /SUBSYSTEM:WINDOWS
Here's the saved /template/sdl/sdl.vcxproj file, and the settings actually appear in it yet they are ignored.
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{8DDA73A6-86DD-4B03-BA9B-54BE878B648C}</ProjectGuid>
<RootNamespace>$safeprojectname$</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v110</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v110</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<IncludePath>C:\cpp_libs\SDL-1.2.15\include;$(IncludePath)</IncludePath>
<ReferencePath>C:\cpp_libs\SDL-1.2.15\lib\x86;$(ReferencePath)</ReferencePath>
<LibraryPath>C:\cpp_libs\SDL-1.2.15\lib\x86;$(LibraryPath)</LibraryPath>
<SourcePath>C:\cpp_libs\SDL-1.2.15\include;$(SourcePath)</SourcePath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>SDLmain.lib;SDL.lib;%(AdditionalDependencies)</AdditionalDependencies>
<SubSystem>Windows</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="Source.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
It's likely that the project system is actually removing these items as the file is being created: when you create a new project, Visual Studio runs a wizard in the background which can affect what actually ends up in the newly-created .vcxproj file.
These wizards are specific to the project type so you can actually supply your own via the <WizardExtension> element in the .vstemplate file. Of course by overriding the project creation logic, you may lose certain functionality/behaviour (unless there's a way of inheriting the original wizard's base assembly).
Alternately, a solution which I've used in the past is to create a VSPackage which handles the SolutionEvents_ProjectAdded method (in DTE.SolutionEvents). This method gets called whenever a new project is created or added to the solution, so you could potentially use that to set your project up as needed.
Note that you would need a way to ensure that it only affects projects of your specific type; a flag in the .vcxproj template file would do this.
I was hoping to find a way to set a value in my csproj file during my build to a value. Is there a task in MSBuild that I can use to set a property permanently to a value? In the example below, can I set CustomValue = Yes permanently?
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
.....
<CustomValue>XXXX</CustomValue
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids></ProjectTypeGuids>
<SccProjectName>SAK</SccProjectName>
<SccLocalPath>SAK</SccLocalPath>
<SccAuxPath>SAK</SccAuxPath>
<SccProvider>SAK</SccProvider>
</PropertyGroup>
You can use the XmlPoke task to do that. It seems a little odd to be altering projects this way though. Alternatively, you can set up a tiny import file,
<!-- in your main project file, right below the PropertyGroup -->
<Import
Condition="Exists('Custom.props')"
Project="Custom.props"
/>
Then dynamically create this property file, as,
<?xml version="1.0" encoding="utf-8"?>
<Project
xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
ToolsVersion="4.0">
<PropertyGroup>
<CustomValue>True</CustomValue>
</PropertyGroup>
</Project>
You can either use XmlPoke on just this .props file, or use WriteLinesToFile to create the entire file. This secondary file wouldn't need to be checked into source control, the condition on the import makes the project functional when the file doesn't exist.
The XmlPoke task would look like this,
<XmlPoke
XmlInputPath="./Custom.props"
Namespaces="<Namespace Prefix='x'
Uri='http://schemas.microsoft.com/developer/msbuild/2003'/>"
Query="//x:PropertyGroup/x:CustomValue/#Value"
Value="True"
/>