msbuild+wix : how to get heatdirectory to use defineconstants's variable? - visual-studio-2012

hello everybody : my first post !
i am trying to get a wixproj using heatdirectory to take its source directory using visualstudio's "Define Constants" feature, in which i define a constant SourceBinaries=c:\someproject\bin\release.
The purpose is to use the same wixproj/setup for several projects and automate the whole with TFS-Build...
however, the Directory Tag never gets the SourceBinaries's value.
here's the xml code :
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
blah..
<OutputName>ProjectSetup</OutputName>
blah..
</PropertyGroup>
<PropertyGroup>
<OutputPath>bin\$(Configuration)\</OutputPath>
<IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath>
<DefineConstants>SourceBinaries=c:\someproject\bin\release\</DefineConstants>
</PropertyGroup>
<ItemGroup>
blah..
</ItemGroup>
<Import Project="$(WixTargetsPath)" />
<Target Name="BeforeBuild">
<HeatDirectory NoLogo="$(HarvestDirectoryNoLogo)"
Directory="$(SourceBinaries)"
PreprocessorVariable="var.SourceBinaries"
SuppressAllWarnings="$(HarvestDirectorySuppressAllWarnings)"
SuppressSpecificWarnings="$(HarvestDirectorySuppressSpecificWarnings)"
ToolPath="$(WixToolPath)"
TreatWarningsAsErrors="$(HarvestDirectoryTreatWarningsAsErrors)"
TreatSpecificWarningsAsErrors="$(HarvestDirectoryTreatSpecificWarningsAsErrors)"
VerboseOutput="$(HarvestDirectoryVerboseOutput)"
AutogenerateGuids="$(HarvestDirectoryAutogenerateGuids)"
GenerateGuidsNow="$(HarvestDirectoryGenerateGuidsNow)"
OutputFile="ProductFiles.wxs"
SuppressFragments="$(HarvestDirectorySuppressFragments)"
SuppressUniqueIds="$(HarvestDirectorySuppressUniqueIds)"
Transforms="Transforms.xsl"
ComponentGroupName="ProductFiles"
DirectoryRefId="INSTALLLOCATION"
KeepEmptyDirectories="false"
SuppressCom="%(HarvestDirectory.SuppressCom)"
SuppressRootDirectory="true"
SuppressRegistry="%(HarvestDirectory.SuppressRegistry)">
</HeatDirectory>
blah..
</Target>
<Target Name="AfterBuild">
blah..
</Target>
</Project>
whatever i tried lead me to "Error The "HeatDirectory" task was not given a value for the required parameter "Directory".
can somebody help me resolving this ?
thanks in advance...
Didier

I think your main source of confusion is that the variables defined in the <DefineConstants> element only work for the .wxs files, but they will not work in the .wixproj file itself.
To fix this you could do something like:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
blah..
<OutputName>ProjectSetup</OutputName>
blah..
</PropertyGroup>
<PropertyGroup>
<OutputPath>bin\$(Configuration)\</OutputPath>
<IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath>
<SourceBinaries>c:\someproject\bin\release</SourceBinaries>
<DefineConstants>SourceBinaries=$(SourceBinaries)</DefineConstants>
</PropertyGroup>
<ItemGroup>
blah..
</ItemGroup>
<Import Project="$(WixTargetsPath)" />
<Target Name="BeforeBuild">
<HeatDirectory NoLogo="$(HarvestDirectoryNoLogo)"
Directory="$(SourceBinaries)"
PreprocessorVariable="var.SourceBinaries"
SuppressAllWarnings="$(HarvestDirectorySuppressAllWarnings)"
SuppressSpecificWarnings="$(HarvestDirectorySuppressSpecificWarnings)"
ToolPath="$(WixToolPath)"
TreatWarningsAsErrors="$(HarvestDirectoryTreatWarningsAsErrors)"
TreatSpecificWarningsAsErrors="$(HarvestDirectoryTreatSpecificWarningsAsErrors)"
VerboseOutput="$(HarvestDirectoryVerboseOutput)"
AutogenerateGuids="$(HarvestDirectoryAutogenerateGuids)"
GenerateGuidsNow="$(HarvestDirectoryGenerateGuidsNow)"
OutputFile="ProductFiles.wxs"
SuppressFragments="$(HarvestDirectorySuppressFragments)"
SuppressUniqueIds="$(HarvestDirectorySuppressUniqueIds)"
Transforms="Transforms.xsl"
ComponentGroupName="ProductFiles"
DirectoryRefId="INSTALLLOCATION"
KeepEmptyDirectories="false"
SuppressCom="%(HarvestDirectory.SuppressCom)"
SuppressRootDirectory="true"
SuppressRegistry="%(HarvestDirectory.SuppressRegistry)">
</HeatDirectory>
blah..
</Target>
<Target Name="AfterBuild">
blah..
</Target>
</Project>
Above, we're creating a new <SourceBinaries> element with the path to the directory. This custom element can then be used as a variable in the rest of the .wixproj file. We then use this value to populate the SourceBinaries constant that is used in the .wxs files.
In conclusion, in the <HeatDirectory> element:
the Directory attribute gets its value from <SourceBinaries>
The PreProcessorVariable attribute gets its value from <DefineConstants> (or other variables available to .wxs files like project references)

You could use the HarvestDirectory target as described here http://wixtoolset.org/documentation/manual/v3/msbuild/target_reference/harvestdirectory.html.

Related

Overriding project properties with a custom task

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>

CruiseControl.NET use project name in project config

Is there any way to use the project name in the project config in CruiseControl.NET ?
I tried ${project.name} based on other posts which doesn't work.
<cruisecontrol xmlns:cb="urn:ccnet.config.builder">
<cb:define BuildArtifactsMainDir="D:\CCNet\BuildArtifacts\"/>
<project name="MyProject">
<artifactDirectory>$(BuildArtifactsMainDir)${project.name}</artifactDirectory>
<tasks>
<exec>
<executable>C:\Windows\System32\robocopy.exe</executable>
<buildArgs>D:\${project.name} F:\${project.name}</buildArgs>
<buildTimeoutSeconds>600</buildTimeoutSeconds>
<successExitCodes>0,1,3,4,8,16</successExitCodes>
</exec>
</tasks>
I don't think there's a parameter for project name. You can use scopes instead.
<cruisecontrol xmlns:cb="urn:ccnet.config.builder">
<cb:define BuildArtifactsMainDir="D:\CCNet\BuildArtifacts\"/>
<cb:scope ProjectName="MyProject">
<project name="$(ProjectName)">
<artifactDirectory>$(BuildArtifactsMainDir)$(ProjectName)</artifactDirectory>
...
</project>
</cb:scope>

How to setup building steps for CruiseControl.net from repository of the building project?

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.

Visual Studio template ignores some project settings that are in template

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.

MSBuild: Permanently Change PropertyGroup Property of a Project

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"
/>

Resources