I have written a custom task that builds files by creating a parallel file with a different extension.
When MSBuild goes and creates such a file I'd like to add it to the project file itself. I'd also like to nest the built file under the source (with DependentUpon).
Can MSBuild do this? Will it need to reload the project? Can I automate all that?
The following is my .targets file that gets installed by NuGet when my package is added:
<UsingTask TaskName="PreCompiler" AssemblyFile="..\tools\Compiler.dll">
</UsingTask>
<UsingTask TaskName="GenerateDependencies" AssemblyFile="..\tools\Compiler.dll">
</UsingTask>
<PropertyGroup>
<BuildDependsOn>
$(BuildDependsOn);
BuildCoffeeFiles;
BuildLessFiles
</BuildDependsOn>
<ContentDir>Content\</ContentDir>
</PropertyGroup>
<ItemGroup Condition="'$(BuildingInsideVisualStudio)'=='true'">
<AvailableItemName Include="CoffeeScript" />
<AvailableItemName Include="Less" />
</ItemGroup>
<Target Name="GenerateCoffeeDependencies">
<GenerateDependencies Include="#(CoffeeScript->'%(FullPath)')">
<Output TaskParameter="Dependencies" ItemName="InputCoffeeFiles"/>
</GenerateDependencies>
<ItemGroup>
<InputCoffeeFiles
Include="#(InputCoffeeFiles->Distinct())"
KeepDuplicates='false' />
</ItemGroup>
</Target>
<Target Name="BuildCoffeeFiles"
DependsOnTargets="GenerateCoffeeDependencies"
Inputs="#(InputCoffeeFiles)"
Outputs="#(CoffeeScript->'%(RelativeDir)%(Filename).js')">
<PreCompiler Include="#(CoffeeScript->'%(FullPath)')" />
</Target>
<Target Name="GenerateLessDependencies">
<GenerateDependencies Include="#(Less->'%(FullPath)')">
<Output TaskParameter="Dependencies" ItemName="InputLessFiles"/>
</GenerateDependencies>
<ItemGroup>
<InputLessFiles
Include="#(InputLessFiles->Distinct())"
KeepDuplicates='false' />
</ItemGroup>
</Target>
<Target Name="BuildLessFiles"
DependsOnTargets="GenerateLessDependencies"
Inputs="#(InputLessFiles)"
Outputs="#(Less->'%(RelativeDir)%(Filename).css')">
<PreCompiler Include="#(Less->'%(FullPath)')" />
</Target>
You can apply a XslTransformation msbuild task in your project, before you build it, adding the required file.
This is the sample transformation file:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ms="http://schemas.microsoft.com/developer/msbuild/2003">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<!-- Identity. -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="//ms:Compile[#Include='Program.cs']">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
<xsl:element name="DependentUpon"
namespace="http://schemas.microsoft.com/developer/msbuild/2003">
<xsl:text>Output.cs</xsl:text>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
And here is the msbuild target sample file:
<Project DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<Target Name="Build">
<XslTransformation
XmlInputPaths="ConsoleApplication1.csproj"
OutputPaths="ConsoleApplication1.transformed.csproj"
XslInputPath="proj.xslt">
</XslTransformation>
<MSBuild Projects="ConsoleApplication1.transformed.csproj" Targets="Build" />
</Target>
</Project>
You will need to build with .NET 4.0 at least. For this specify the ToolsVersion in msbuild file.
Related
I want a msbuild task to remove all sub-directories with a specified name, I tried this but it's not working.
What am I doing wrong?
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Main" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<directory></directory>
<subDirectory></subDirectory>
</PropertyGroup>
<Target Name="Main">
<RemoveDir Directories="$(directory)\**\subDirectory\*" />
</Target>
</Project>
I have a property GroupProj storing a full path name. How can I extract the directory of the property?
I have the following code, but it doesn't work as expected:
<PropertyGroup>
<GroupProj>C:\development\project\default.groupproj</GroupProj>
</PropertyGroup>
<Target Name="Default">
<Message Text="Echo: $(GroupProj->'%(RootDir)')" />
</Target>
I will describe my actual intention of doing so. Perhaps there is a way to do the job that I am not aware of.
I have a Delphi groupproj (MSBuild project) file, C:\development\project\default.groupproj:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Projects Include="project1.dproj">
<Dependencies/>
</Projects>
<Projects Include="project2.dproj">
<Dependencies/>
</Projects>
<Projects Include="project3.dproj">
<Dependencies/>
</Projects>
</ItemGroup>
...
</Project>
There are other 3 MSBuild files (project1.dproj, project2.dproj and project3.dproj) stored in same folder as default.groupproj.
I create a MSBuild project file (c:\test.targets):
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build" ToolsVersion="3.5">
<Import Project="$(GroupProj)" />
<Target Name="Build">
<MSBuild BuildInParallel="True" Projects="project1.dproj;project2.dproj;project3.dproj"/>
</Target>
</Project>
And execute as:
c:\> msbuild /p:GroupProj="C:\development\project\default.groupproj" test.targets
The execution shall fail as MSBuild can't find projectN.dproj file. The issue shall be the working directory isn't set to default.groupproj.
One straight solution come into my mind is to extract directory of $(GroupProj) and concat to there projectN.dproj file.
That's the whole story of my question.
Try something like this:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<GroupProj>C:\development\project\default.groupproj</GroupProj>
</PropertyGroup>
<Target Name="Build">
<CreateItem Include="$(GroupProj)">
<Output TaskParameter="Include" ItemName="ItemFromProp"/>
</CreateItem>
<Message Text="1. #(ItemFromProp -> '%(RootDir)%(Directory)')"/>
<Message Text="2. %(ItemFromProp.RootDir)%(ItemFromProp.Directory)"/>
<Message Text="3. %(ItemFromProp.Identity)"/>
<Message Text="4. %(ItemFromProp.FullPath)"/>
<Message Text="5. %(ItemFromProp.FileName)"/>
<Message Text="6. %(ItemFromProp.Extension)"/>
</Target>
</Project>
EDIT:
To build the projects in parallel try this:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="GetGroupProjPath">
<ItemGroup>
<GroupProj Include="$(GroupProj)" />
<GroupProjPath Include="#(GroupProj->'%(Directory)')" />
</ItemGroup>
<PropertyGroup>
<GroupProjPath>#(GroupProjPath->'%(RootDir)%(Identity)')</GroupProjPath>
</PropertyGroup>
</Target>
<Import Project="$(GroupProj)" />
<Target Name="GetDProjs" DependsOnTargets="GetGroupProjPath">
<ItemGroup>
<DProjs Include="#(Projects->'$(GroupProjPath)%(FileName)%(Extension)')" />
</ItemGroup>
</Target>
<Target Name="Build" DependsOnTargets="GetDProjs">
<Message Text="#(DProjs)" />
</Target>
</Project>
When I read the value from the XML file, it is correct, but when I use it (as an option to rc.exe), it is undefined. How can I fix that?
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Read custom build number from generated XML file -->
<Target Name="ReadCustomVersion" BeforeTargets="ResourceCompile">
<XmlPeek XmlInputPath="$(OutDir)\CustomVersionNumber.xml" Query="/CustomVersion/CustomBuildNumber/text()">
<Output TaskParameter="Result" ItemName="CustomBuildNumber" />
</XmlPeek>
**<!-- Print out the custom build number -- it is correct here -->**
<Message Text="CustomBuildNumber = #(CustomBuildNumber)">
</Target>
<!-- Add version resource -->
<ItemGroup>
**<!-- CustomBuildNumber will not be set here -->**
<ResourceCompile Include="..\..\build\CommonVersionResource.rc">
<AdditionalOptions>/DBUILD_NUMBER=$(CustomBuildNumber) %(AdditionalOptions)</AdditionalOptions>
</ResourceCompile>
</ItemGroup>
</Project>
Use PropertyName attribute in XmlPeek/Output element instead of ItemName.
Why you don't add AdditionalOptions directly in your ReadCusomVersion target?
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="SetCustomVersion" BeforeTargets="ResourceCompile">
<XmlPeek XmlInputPath="$(OutDir)\CustomVersionNumber.xml" Query="/CustomVersion/CustomBuildNumber/text()">
<Output TaskParameter="Result" PropertyName="CustomBuildNumber" />
</XmlPeek>
<ItemGroup>
<ResourceCompile>
<AdditionalOptions>/DBUILD_NUMBER=$(CustomBuildNumber) %(AdditionalOptions)</AdditionalOptions>
</ResourceCompile>
</ItemGroup>
</Target>
Given something like so..
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="test" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ConfigFiles Include="*.config" />
<DatabaseConfig Include="ABC">
<Database>DB1</Database>
<CsString>Database</CsString>
</DatabaseConfig>
<DatabaseConfig Include="DEF">
<Database>DB2</Database>
<CsString>Logging</CsString>
</DatabaseConfig>
</ItemGroup>
<Target Name="test" >
<!-- Some sort of join here (or somewhere)... -->
<Message Text=" %(Combined.ConfigFile) %(Combined.Database) " />
</Target>
</Project>
I'd like the Output to be something like this.. (given two files one.config & two.config)
one.config DB1
two.config DB1
one.config DB2
two.config DB2
(the order is not important, just the full cartesian product of the two ItemGroups)
This seems like a tidy solution:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="test" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ConfigFiles Include="*.config" />
<DatabaseConfig Include="ABC">
<Database>DB1</Database>
<CsString>Database</CsString>
</DatabaseConfig>
<DatabaseConfig Include="DEF">
<Database>DB2</Database>
<CsString>Logging</CsString>
</DatabaseConfig>
</ItemGroup>
<Target Name="test" >
<ItemGroup>
<Combined Include="#(DatabaseConfig)">
<ConfigFile>%(ConfigFiles.Identity)</ConfigFile>
</Combined>
</ItemGroup>
<Message Text=" %(Combined.ConfigFile) %(Combined.Database) " />
</Target>
</Project>
There is a way you can do this with minimal changes to your existing sample code. You can combine metadata from ConfigFiles items and DatabaseConfig items into a new "combined" item and then output that "combined" item.
To combine the metadata, use target batching with the batched target running once for each DatabaseConfig item. Then you can call another target to output the combined metadata to get the output you described. Take a look at my extension of your sample code to see how this would all be accomplished:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="test" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ConfigFiles Include="*.config" />
<DatabaseConfig Include="ABC">
<Database>DB1</Database>
<CsString>Database</CsString>
</DatabaseConfig>
<DatabaseConfig Include="DEF">
<Database>DB2</Database>
<CsString>Logging</CsString>
</DatabaseConfig>
</ItemGroup>
<Target Name="test" DependsOnTargets="test_setup;test_output" >
<!-- Logic here runs after targets listed in "DependsOnTargets". -->
</Target>
<!-- This will run once for each "DatabaseConfig" item. -->
<Target Name="test_setup" Outputs="%(DatabaseConfig.Identity)">
<PropertyGroup>
<!-- Specify the Database for the current DatabaseConfig item -->
<CurrentDb>%(DatabaseConfig.Database)</CurrentDb>
</PropertyGroup>
<ItemGroup>
<!-- Add a new CombinedOutput item with each run, combining metadata. -->
<CombinedOutput Include=" %(ConfigFiles.FileName)%(ConfigFiles.Extension) $(CurrentDb) " />
</ItemGroup>
</Target>
<Target Name="test_output">
<!-- Output the combined metadata from the CombinedOutput items -->
<Message Text=" %(CombinedOutput.Identity) " />
</Target>
</Project>
What's happening in the sample:
The test target now just serves as a way to call two other targets to perform the work: test_setup, and test_output
The test_setup target is batched and creates the new
CombinedOutput items.
The test_output target is called after test_setup
to output the CombinedOutput items' metadata.
Output from test_output:
one.config DB1
two.config DB1
one.config DB2
two.config DB2
I have a msbuild that calls a *.sln file when doing compilation. This solution file contains 10 csprojects, one of them ( let's call it main.csproject) has the AssemblyName as WinMusic. The content of the msbuild is as follows:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!-- Default value here -->
<DefineConstants Condition=" '$(DefineConstants)'==''" >TRACE</DefineConstants>
<SlnFiles Condition=" '$(SlnFiles)'==''" >FullProject.sln</SlnFiles>
</PropertyGroup>
<!-- <ItemGroup> -->
<!-- <SlnFiles Include="SlnFiles=$(SlnFiles2)"/> -->
<!-- </ItemGroup> -->
<Target Name="Build">
<MSBuild Projects="$(SlnFiles)"
Properties="DefineConstants=$(DefineConstants)"/>
</Target>
</Project>
My question is, how to set the AssemblyName property from the above msbuild task?
Just to clarify, I'm talking about AssemblyName in csproject, not in AssemblyInfo.cs.
Edit: This is the new build.proj file I tried, the FullProject.sln is a solution file with one exe and one dll, but the msbuild file renamed both the dll and the exe to NoMusic. What I want is just to rename the exe to NoMusic and the dll should retain the same name.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!-- Default value here -->
<DefineConstants Condition=" '$(DefineConstants)'==''" >TRACE</DefineConstants>
<SlnFiles Condition=" '$(SlnFiles)'==''" >FullProject.sln</SlnFiles>
</PropertyGroup>
<Target Name="Build">
<MSBuild Projects="$(SlnFiles)"
Properties="DefineConstants=$(DefineConstants)"/>
<MSBuild Projects="WindowsFormsApplication1\WindowsFormsApplication1.csproj"
Properties="DefineConstants=$(DefineConstants);Platform=ANYCPU;AssemblyName=NoMusic"/>
</Target>
</Project>
Just do this:
<Target Name="Build">
<MSBuild Projects="#(SlnFiles)"
Properties="DefineConstants=$(DefineConstants)"/>
<MSBuild Projects="main.csproject.csproj"
Properties="AssemblyName=NoMusic"/>
Love to know why though.