Test if an MSBuild property constains a substring - msbuild

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>

Related

MSBuild: Output properties from imported projects

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>

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>

Copy a single file in MSBuild without using Exec or ItemGroup

Is there any way to do this? I just need to copy a single file and thought there may be some syntax for the SourceFiles parameter of the Copy task that means you don't need to define an ItemGroup beforehand, I'd rather stick with ItemGroup than use Exec though.
Copy files also takes a straight propertygroup as input:
<PropertyGroup>
<SourceFile>Some file</SourceFile>
</PropertyGroup>
<Copy SourceFiles="$(SourceFile)" DestinationFolder="c:\"/>
Or even just a string
<Copy SourceFiles="Pathtofile" DestinationFolder="c:\"/>
Just put the single file name as the value for "SourceFiles".
Easy-Peezey.
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="AllTargetsWrapper" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<WorkingCheckout>.</WorkingCheckout>
</PropertyGroup>
<Target Name="AllTargetsWrapper">
<CallTarget Targets="CopyItTarget" />
</Target>
<Target Name="CopyItTarget">
<Copy SourceFiles="c:\windows\system.ini" DestinationFolder="$(WorkingCheckout)\"/>
<Error Condition="!Exists('$(WorkingCheckout)\system.ini')" Text="No Copy Is Bad And Sad" />
</Target>
</Project>
For what it's worth, I needed to do the same thing, and wanted to put some version information in the file name. Here is how I did it for a project in $(SolutionDir) that references an executable created by another project in another solution that I can easily express the path to:
<Target Name="AfterBuild">
<GetAssemblyIdentity AssemblyFiles="$(SolutionDir)..\bin\$(Configuration)\SomeExectuable.exe">
<Output TaskParameter="Assemblies" ItemName="AssemblyVersions" />
</GetAssemblyIdentity>
<CreateProperty Value="$(TargetDir)$(TargetName)-%(AssemblyVersions.Version)$(TargetExt)">
<Output TaskParameter="Value" PropertyName="NewTargetPath" />
</CreateProperty>
<Copy SourceFiles="$(TargetPath)" DestinationFiles="$(NewTargetPath)" />
</Target>

Setting properties' values in MSBuild

Let's consider the below example.
There, I have:
target MAIN calls target t and then calls target tt.
target t calls target ttt, and target tt calls target tttt.
target t defines property aa, and target ttt modifies aa.
target tttt tries to print property aa's value.
In short, we have: MAIN -> {t -> {ttt->modifies aa, defines aa}, tt -> tttt -> prints aa}
But in target tttt, we can't "see" aa's updated value (by ttt)! How do I make that value visible to target tttt?
The whole script is as below:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="MAIN" >
<Target Name="MAIN" >
<CallTarget Targets="t" />
<CallTarget Targets="tt" />
</Target>
<Target Name="t">
<Message Text="t" />
<PropertyGroup>
<aa>1</aa>
</PropertyGroup>
<CallTarget Targets="ttt" />
</Target>
<Target Name="tt">
<Message Text="tt" />
<CallTarget Targets="tttt" />
</Target>
<Target Name="ttt">
<PropertyGroup>
<aa>122</aa>
</PropertyGroup>
<Message Text="ttt" />
</Target>
<Target Name="tttt">
<Message Text="tttt" />
<Message Text="tttt:$(aa)" />
</Target>
</Project>
As already said in an answer to another post you should model your MSBuild project with dependencies between your Targets rather than calling Targets one after another.
<Project DefaultTargets="tttt" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="t">
<Message Text="t" />
<PropertyGroup>
<aa>1</aa>
</PropertyGroup>
</Target>
<Target Name="tt" DependsOnTargets="t">
<Message Text="tt" />
</Target>
<Target Name="ttt" DependsOnTargets="t;tt">
<PropertyGroup>
<aa>122</aa>
</PropertyGroup>
<Message Text="ttt" />
</Target>
<Target Name="tttt" DependsOnTargets="t;tt;ttt">
<Message Text="tttt" />
<Message Text="tttt:$(aa)" />
</Target>
</Project>
An approach I use, is to define a Target as my final goal, putting it into the projects DefaultTargets.
Then add all the things that need to happen to achieve this goal.

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.