MSBuild: Output properties from imported projects - msbuild

Let's say I have a build.proj like this:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0"
DefaultTargets="AfterBuild"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<CustomAfterMicrosoftCSharpTargets>$(MSBuildThisFileDirectory)Common.Build.targets</CustomAfterMicrosoftCSharpTargets>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<ProjectProperties>
Configuration=$(Configuration);
Platform=$(Platform);
CustomAfterMicrosoftCSharpTargets=$(CustomAfterMicrosoftCSharpTargets);
</ProjectProperties>
</PropertyGroup>
<ItemGroup>
<ProjectToBuild Include="$(MSBuildThisFileDirectory)src\Proj\MyApp.csproj" />
</ItemGroup>
<Target Name="Build">
<MSBuild Targets="Build"
Projects="#(ProjectToBuild)"
Properties="$(ProjectProperties)" />
</Target>
<Target Name="AfterBuild" DependsOn="Build">
<Message Text="ChildProperty: $(ChildProperty)" />
</Target>
</Project>
In Common.Build.targets, I have a Target that creates a property:
<Target Name="DoSomethingUseful">
<!-- Do something useful -->
<CreateProperty Value="SomeComputedThingy">
<Output TaskParameter="Value" PropertyName="ChildProperty"/>
</CreateProperty>
</Target>
Now if I build build.proj, I do not see the value of ChildProperty in the message. The output is blank: ChildProperty:.
I was under the impression that any output for a target is merged back to global context after its execution. But it seems that it only applies to anything within that target file.
How do I make ChildProperty bubble up to the parent build.proj?

When you are calling <MSBuild> task on dependent projects, read TargetOutputs output parameter of the task. See example from MSDN:
<Target Name="BuildOtherProjects">
<MSBuild
Projects="#(ProjectReferences)"
Targets="Build">
<Output
TaskParameter="TargetOutputs"
ItemName="AssembliesBuiltByChildProjects" />
</MSBuild>
</Target>
You will also need to ensure the target you are calling in dependent projects correctly populates Returns or Output parameter (Returns takes precedence if used). E.g.:
<Target Name="MyTarget" Inputs="..." Outputs="..." Returns="$(MyOutputValue)">
<PropertyGroup>
<MyOutputValue>set it here</MyOutputValue>
</PropertyGroup>
</Target>

Related

How to extract directory from property?

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>

Add output or transfer data from child to parent project

I use msbuild in main.proj to build a project like this:
<MSBuild Projects="outs.proj" Targets="Build">
<Output ItemName="CustomOutputs" TaskParameter="TargetOutputs"/>
</MSBuild>
Inside outs.proj I have a custom Target, I need to add an output from this target to get .dll,.pdb,..., and .mycustomfiles
How can I send data from child project to parent project ?
Thanks in advance for your help.
I'd recommend you simply Import the dependant project, however the basic scenario you described can be achieved with Target's Outputs or Returns and corresponding Output's TargetOutputs although there are few caveats as it's designed for incremental builds and not as a data transfer object.
foo.build
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Foo1">
<MSBuild Projects="bar.build">
<Output TaskParameter="TargetOutputs" ItemName="Bar" />
</MSBuild>
<Message Text="%(Bar.Identity)" />
</Target>
<Import Project="bar.build" />
<Target Name="Foo2" DependsOnTargets="Bar">
<Message Text="%(Bar.Identity)" />
</Target>
</Project>
bar.build
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Bar" Outputs="#(Bar)">
<ItemGroup>
<Bar Include="**\*.dll" />
</ItemGroup>
</Target>
</Project>

Test if an MSBuild property constains a substring

I have a property in an MSBuild project which is a semicolon-separated-list of string values. How can I test if the list constains a particular value?
In the example listing below, I want the target DeployToServer only to be executed if the property $(DCC_Define) constains 'WebDeploy'.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<DCC_Define>WebDeploy;DEBUG</DCC_Define>
</PropertyGroup>
<Target Name="DeployToServer" Condition="$(DCC_Define) constains 'WebDeploy'">
<Message Text="Do something." />
</Target>
</Project>
I've used a bit of pseudo logic in the #Condition attribute to indicate what I mean. I am using a .NET framework version of 2.0.50727.3655; and MSBuild version of 3.4.30729.1 .
How can I achieve this? I don't have the luxury of being able to upgrade to MSBuild 4.
Well, since you can't use property function you have to get creative.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<DCC_Define>WebDeploy;DEBUG;WebDeploy</DCC_Define>
</PropertyGroup>
<Target Name="DeployToServer">
<CreateItem Include="$(DCC_Define)">
<Output TaskParameter="Include" ItemName="DCC_Define" />
</CreateItem>
<!-- Not required since MSBuild doesn't execute targets twice -->
<!-- <CreateProperty Value="True" Condition="%(DCC_Define.Identity) == WebDeploy">
<Output TaskParameter="Value" PropertyName="WebDeploy" />
</CreateProperty> -->
<CallTarget Targets="_DeployToServer" Condition="%(DCC_Define.Identity) == WebDeploy" />
</Target>
<Target Name="_DeployToServer">
<Message Text="Do something." />
</Target>
</Project>

Using MSBuild to buld a solution (.sln) with many projects in how can I make each project build into its own folder?

I am trying to create a simple build process for a quite complex (many projects) vs2010 solution.
I wish for a folder structure such as this
-Build
-Proj1
-proj1.exe
-proj1.dll
-Proj2
-proj2.exe
-proj2.dll
......
-Projn
-projn.exe
-projn.dll
What I am getting from my attempts below is
-Build
-proj1.exe
-proj1.dll
-proj2.exe
-proj2.dll
-projn.exe
-projn.dll
I currently have this as a .proj file. (see below)
This builds things fine, however it puts everything in the "build" folder that I specify. I want each project to be in its own seperate folder within that 'build' folder. How can I achive this?
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<BuildOutputDir>C:\Projects\BuildScripts\Build</BuildOutputDir>
<SolutionToCompile>PathToSolution.sln</SolutionToCompile>
</PropertyGroup>
<Target Name="Clean">
<RemoveDir Directories="$(BuildOutputDir)" />
</Target>
<Target Name="Compile">
<MakeDir Directories="$(BuildOutputDir)" />
<MSBuild Projects="$(SolutionToCompile)"
properties = "OutputPath=$(BuildOutputDir)" Targets="Rebuild" />
</Target>
<Target Name="Build" DependsOnTargets="Clean;Compile">
<Message Text="Clean, Compile"/>
</Target>
</Project>
I call the .proj with a simple bat
"%windir%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe" /nologo externalBuild.proj /m:2 %*
pause
I have also tried a more complex version (copy and paste!) that looks more like it should work, but still puts things in a single folder.
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="BuildAll" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ProjectsToBuild Include="path to solution folder\**\*proj" Exclude="$(MSBuildProjectFile)"/>
</ItemGroup>
<PropertyGroup>
<Configuration>CI</Configuration>
</PropertyGroup>
<Target Name="CoreBuild">
<MSBuild Projects ="#(ProjectsToBuild)"
ContinueOnError ="false"
Properties="Configuration=$(Configuration)">
<Output ItemName="OutputFiles" TaskParameter="TargetOutputs"/>
</MSBuild>
</Target>
<PropertyGroup>
<DestFolder>Build\</DestFolder>
</PropertyGroup>
<Target Name="CopyFiles">
<Copy SourceFiles="#(OutputFiles)"
DestinationFiles="#(OutputFiles->'$(DestFolder)%(RecursiveDir)%(Filename)%(Extension)')" />
</Target>
<Target Name="CleanAll">
<!-- Delete any files this process may have created from a previous execution -->
<CreateItem Include="$(DestFolder)\**\*exe;$(DestFolder)\**\*dll">
<Output ItemName="GeneratedFiles" TaskParameter="Include"/>
</CreateItem>
<Delete Files="#(GeneratedFiles)"/>
<MSBuild Projects="#(ProjectsToBuild)" Targets="Clean" Properties="Configuration=$(Configuration);"/>
</Target>
<PropertyGroup>
<BuildAllDependsOn>CleanAll;CoreBuild;CopyFiles</BuildAllDependsOn>
</PropertyGroup>
<Target Name="BuildAll" DependsOnTargets="$(BuildAllDependsOn)"/>
</Project>
Using devenv.com to build from the command line will do what you want. It will use the output directories specified in the project files. This is what we're using, because at the moment we don't need more control over the build mechanism.

Updating Assembly information with MSBuild failing

All
i am trying to automatically update the assembly information of a project using AssemblyInfo task, before build however the target appears to do nothing (no failure/error) just no update/creation
Below is the build.proj file I am using (obviously some contents altered)
Can anyone help?
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\AssemblyInfoTask\Microsoft.VersionNumber.targets"/>
<PropertyGroup>
<Major>1</Major>
<Minor>0</Minor>
<Build>0</Build>
<Revision>0</Revision>
</PropertyGroup>
<PropertyGroup>
<BuildDir>C:\svn\Infrastructure</BuildDir>
</PropertyGroup>
<ItemGroup>
<SolutionsToBuild Include="Infrastructure.sln"/>
</ItemGroup>
<Target Name="Build" DependsOnTargets="ChangeDataAccessAssemblyInfo">
<RemoveDir Directories="$(BuildDir)\Builds" Condition="Exists('$(BuildDir)\Builds')" />
<MSBuild Projects="#(SolutionsToBuild)" Properties="Configuration=Debug" Targets="Rebuild" />
</Target>
<ItemGroup>
<TestAssemblies Include="Build\Logging\Logging.UnitTests.dll" />
</ItemGroup>
<!--<UsingTask TaskName="NUnit" AssemblyFile="$(teamcity_dotnet_nunitlauncher_msbuild_task)" />
<Target Name="Test" DependsOnTargets="Build">
<NUnit NUnitVersion="NUnit-2.4.6" Assemblies="#(TestAssemblies)" />
</Target>-->
<Target Name="ChangeDataAccessAssemblyInfo" >
<Message Text="Writing ChangeDataAccessAssemblyInfo file for 1"/>
<Message Text="Will update $(BuildDir)\DataAccess\My Project\AssemblyInfo.vb" />
<AssemblyInfo CodeLanguage="VB"
OutputFile="$(BuildDir)\DataAccess\My Project\AssemblyInfo_new.vb"
AssemblyTitle="Data Access Layer"
AssemblyDescription="Message1"
AssemblyCompany="http://somewebiste"
AssemblyProduct="the project"
AssemblyCopyright="Copyright notice"
ComVisible="true"
CLSCompliant="true"
Guid="hjhjhkoi-9898989"
AssemblyVersion="$(Major).$(Minor).1.1"
AssemblyFileVersion="$(Major).$(Minor).5.7"
Condition="$(Revision) != '0' "
ContinueOnError="false" />
<Message Text="Updated Assembly File Info"
ContinueOnError="false"/>
</Target>
</Project>
I think you are missing the specification of the AssemblyInfoFiles attribute on your AssemblyInfo task. Here's how it looks on a project I'm working on...
<Target Name="AfterGet">
<Message Text="In After Get"/>
<CreateItem Include="$(SolutionRoot)\Source\SomeProject\My Project\AssemblyInfo.vb">
<Output ItemName="AssemblyInfoFiles" TaskParameter="Include"/>
</CreateItem>
<Attrib Files="#(AssemblyInfoFiles)"
ReadOnly="false"/>
<AssemblyInfo AssemblyInfoFiles="#(AssemblyInfoFiles)"
AssemblyDescription="$(LabelName)">
</AssemblyInfo>
</Target>
What we're doing is first using to create a property that contains the name of the file we'll be updating. We have to do this via createItem because when we start the build the file doesn't exist (and that is when MSBuild evaluates the and definitions in your build file.
We then take the readonly bit off the file.
Finally we invoke the AssemblyInfo task passing it the file(s) to update and a custom assembly name that we want to give it (in this case we put the TFS build label into the Assembly Description field so that we can easily tell which team build the assembly came from.