Append constant string to TargetName when building list of projects? - msbuild

I have a .proj file that contains a list of c++ project files and a couple of targets that builds the projects with different preprocessor macros, like so:
<ItemGroup>
<MyProjectItems Include="$(SourceDir)\Project1\Project1.vcxproj"/>
<MyProjectItems Include="$(SourceDir)\Project2\Project2.vcxproj"/>
<MyProjectItems Include="$(SourceDir)\Project3\Project3.vcxproj"/>
<MyProjectItems Include="$(SourceDir)\Project4\Project4.vcxproj"/>
<MyProjectItems Include="$(SourceDir)\Project5\Project5.vcxproj"/>
</ItemGroup>
<Target Name="NormalConfiguration">
<MSBuild Projects="#(MyProjectItems)"
Targets="$(BuildTarget)"
Properties="Configuration=Release;Platform=x86;/>
</Target>
<Target Name="SpecialConfigurationTarget">
<MSBuild Projects="#(MyProjectItems)"
Targets="$(BuildTarget)"
Properties="Configuration=Release;Platform=x86;SpecialConfiguration=SPECIAL_CONFIGURATION;TargetName=$(ProjectName)_SpecialConfiguration"/>
</Target>
The NormalConfiguration target works as expected. Run that target and the output folder contains
Project1.exe
Project2.exe
Project3.exe
Project4.exe
Project5.exe
What I'm trying to do is append the string "_SpecialConfiguration" to the executable name when the SpecialConfigurationTarget is run. So after running the SpecialConfigurationTarget, the output folder contains
Project1_SpecialConfiguration.exe
Project2_SpecialConfiguration.exe
Project3_SpecialConfiguration.exe
Project4_SpecialConfiguration.exe
Project5_SpecialConfiguration.exe
The above SpecialConfigurationTarget builds all of the MyProjectItems, but uses "_SpecialConfiguration.exe" as the output name. So after running the SpecialConfigurationTarget, the output folder only contains
_SpeicalConfiguration.exe
How can I get the name of the current project being build inside of an MSBuild task when that task is building multiple projects?

Related

How to change Build directory dynamically in MSBuild

I'm stuck with a problem I want to create each days build in different folders means if the build is created on 17/5/2013 I want the build in a folder named 17/05/2013 and so on How can I achieve that
With MSBUILD 4 you can use Property Functions to construct a custom output folder.
In C# this would generate the folder name:
System.DateTime.Today.ToString("MM/dd/yyyy");
Using that same logic in MSBUILD
<Target Name="DateStampTest">
<PropertyGroup>
<OutputRoot>$(MSBuildThisFileDirectory)</OutputRoot>
<DateTimeStamp>$([System.DateTime]::Today.ToString("yyyy.dd.MM"))</DateTimeStamp>
</PropertyGroup>
<ItemGroup>
</ItemGroup>
<Message Text="Override the output folder with: '$(OutputRoot)$(DateTimeStamp)'" />
</Target>
Saved this to a project file in "C:\Test\" and running msbuild /t:DateStampTest gives us the following output:
1>Project "C:\Test\" on node 1 (target1 target(s)).
1>Target1:
Override the output folder with: 'C:\Test\2013.17.05'
1>Done Building Project "C:\Test\" (target1 target(s)).

MSBUILD web deploy package does not include the project reference DLL's

When I try to create a package for web deploy using msbuild it only includes the projects dll. The package zip file or the temp directory does not include the referenced project's dlls.
I've looked at this post and that is not my problem. I am definitely using the code from the referenced projects in my main project that I'm creating the deployment package for.
I'm using MSBUILD 4 to create the package.
When I create the package using VS2010 using the exact same project file it works fine. All the referenced projects have their dlls included in the package.zip file.
I've tried changing the location of the _PackageTempDir and that did not solve the problem either.
I've tried taking out the ExcludeFilesFromDeployment property and set the PackageAsSingleFile setting to false to see if that would change the results.
Here is my target for Package. All the regex is so I can pull my project file name off the end of a search path and then use that name for the name of the output folder and the name of the zip file. The PackageOutputDir is a propertyI am importing.
<Target Name="Package">
<MSBuild Projects="#(PackageProject)" Targets="Package" Properties="Platform=$(Platform);
Configuration=$(Configuration);
DeployOnBuild=true;
DeployTarget=Package;
PackageLocation=$(PackageOutputDir)\$([System.Text.RegularExpressions.Regex]::Split($(ProjectName), '(.*\\)([a-z,A-Z,0-9,_,-]+)(\.\*proj;)')[2])\$([System.Text.RegularExpressions.Regex]::Split($(ProjectName), '(.*\\)([a-z,A-Z,0-9,_,-]+)(\.\*proj;)')[2]).zip;
PackageAsSingleFile=true;
ExcludeFilesFromDeployment=Web.config;
_PackageTempDir=$(PackageOutputDir)\temp;">
</MSBuild>
</Target>
Any ideas as to why it is not including my referenced project dlls?
You could do the following in your MasterBuild.proj.
<Target Name="Package">
<ConvertToAbsolutePath Paths="$(PackageOutputDir)">
<Output TaskParameter="AbsolutePaths" PropertyName="Source_Dir_Abs"/>
</ConvertToAbsolutePath>
<MSBuild Projects="#(PackageProject)" Targets="Package"
properties="Platform=$(Platform);
Configuration=$(Configuration);
DeployOnBuild=false;
DeployTarget=Package;
PackageLocation=$(Source_Dir_Abs)\$(PackageProjectName).zip;
PackageAsSingleFile=true;
ExcludeFilesFromDeployment=Web.config;
_PackageTempDir=$(PackageOutputDir)\temp;">
</MSBuild>
</Target>
Where you are calling msbuild you will need to add a property that will be used in $(PackageProjectName) by doing the following:
msbuild.exe /property:PackageProjectName=$project

Getting the Content item from a csproj using the MSBuild task

I have an MSBuild file and I am building C# projects like this:
<ItemGroup>
<ProjectsToBuild Include="./source/ProjectA/ProjectA.csproj"/>
<ProjectsToBuild Include="./source/ProjectB/ProjectB.csproj"/>
</ItemGroup>
<Target Name="Build">
<MSBuild Projects="#(ProjectsToBuild)" Targets="Build">
<Output ItemName="ProjectOutputs" TaskParameter="TargetOutputs"/>
</MSBuild>
<Message Text="#ProjectOutputs"/>
</Target>
I successfully get an Item containing all of the .dll files that were built:
Build:
c:\code\bin\ProjectA.dll;c:\code\bin\ProjectB.dll
I would also like to get the Content item from each project without modifying the .csproj files. After digging around in the Microsoft .targets files, I was almost able to get it working with this:
<MSBuild Projects="#(ProjectsToBuild)" Targets="ContentFilesProjectOutputGroup">
<Output ItemName="ContentFiles" TaskParameter="TargetOutputs"/>
</MSBuild>
<Message Text="#(ContentFiles->'%(RelativeDir)')"/>
The problem with this approach is the RelativeDir is not being set correctly. I am getting the full path instead of relative:
Build:
c:\ProjectA\MyFolder\MyControl.ascx;c:\ProjectB\MyOtherFolder\MyCSS.css;
instead of:
Build:
MyFolder\MyControl.ascx;MyOtherFolder\MyCSS.css;
Is there a property I can pass to the MSBuild task that will make RelativeDir behave correctly?
Or, even better, is there an easier way to get the Content item?
You can do this but it is not very intutive. I've discussed this type of technique a few times on my blog ( which is currently down :( ).
So create a new file, I named it GetContentFiles.proj which is shown here.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Projects Include="WindowsFormsApplication1\WindowsFormsApplication1.csproj"/>
</ItemGroup>
<!-- This target will be executed once for each file declared in the Project target -->
<Target Name="PrintFiles" Outputs="%(Projects.Identity)">
<Message Text="PrintFiles" Importance="high"/>
<MSBuild Projects="$(MSBuildProjectFile)"
Targets="GetContentFiles"
Properties="ProjectToGetFiles=%(Projects.Identity)">
<Output ItemName="projContent" TaskParameter="TargetOutputs"/>
</MSBuild>
<Message Text="ProjContent: #(projContent)" Importance="high"/>
<!-- Transform the projContent to have correct path -->
<!--
Get the relative path to the project itself, this serves as the base for
the Content files path
-->
<PropertyGroup>
<_ProjRelativeDir>%(Projects.RelativeDir)</_ProjRelativeDir>
</PropertyGroup>
<!-- This item will contain the item with the corrected path values -->
<ItemGroup>
<ProjContentFixed Include="#(projContent->'$(_ProjRelativeDir)%(RelativeDir)%(Filename)%(Extension)')"/>
</ItemGroup>
<!-- Create a new item with the correct relative dirs-->
<Error Condition="!Exists('%(ProjContentFixed.FullPath)')"
Text="File not found at [%(ProjContentFixed.FullPath)]"/>
</Target>
<Import Project="$(ProjectToGetFiles)" Condition="'$(ProjectToGetFiles)'!=''"/>
<Target Name="GetContentFiles" Condition="'$(ProjectToGetFiles)'!=''" Outputs="#(Content)">
<Message Text="Content : #(Content)" Importance="high"/>
<Message Text="Inside GetContentFiles" Importance="high"/>
</Target>
</Project>
I will try and explain this, but it may be tough to follow. Let me know if you need me to expand on it. This file has two targets PrintFiles and GetContentFiles. The entry point into this file is the PrintFiles target, in the sense that this is the target that you are going to call. So you call the PrintFiles target which it then uses the MSBuild task to call the GetContentFiles target on itself, also it passes a value for the ProjectToGetFiles property. Because of that the Import elemnent will be executed. So what you are really doing is taking the project defined in the ProjectToGetFiles property and extending it to include the target GetContentFiles (and whatever other content is inside the GetContentFiles.proj file). So we are effectively extending that file. I'm calling this technique "MSBuild Inheritance" because. So inside the GetContentFiles target we can access all properties and items that are declared inthe ProjectToGetFiles property. So I take advantage of that by simply putting the content of the Content item into the outputs for the target, which can be accessed by the original file using the TargetOutputs from the MSBuild task.
You mentioned in your post that you wanted to correct the path values to be the right ones. The problem here is that in the .csproj file all items are declared relative to the original project file. So if you "extend" the project file in this way from a file in a different directory you must correct the file path values manually. I've done this inside the PrintFiles target, check it out.
If you execute the command msbuild GetContentFile.proj /fl /t:PrintFiles the result would be:
Build started 7/3/2009 12:56:35 AM.
Project "C:\Data\Development\My Code\Community\MSBuild\FileWrites\GetContentFile.proj" on node 0 (PrintFiles target(s)).
PrintFiles
Project "C:\Data\Development\My Code\Community\MSBuild\FileWrites\GetContentFile.proj" (1) is building "C:\Data\Development\My Co
de\Community\MSBuild\FileWrites\GetContentFile.proj" (1:2) on node 0 (GetContentFiles target(s)).
Content : Configs\Config1.xml;Configs\Config2.xml
Inside GetContentFiles
Done Building Project "C:\Data\Development\My Code\Community\MSBuild\FileWrites\GetContentFile.proj" (GetContentFiles target(s)).
PrintFiles:
ProjContent: Configs\Config1.xml;Configs\Config2.xml
Done Building Project "C:\Data\Development\My Code\Community\MSBuild\FileWrites\GetContentFile.proj" (PrintFiles target(s)).
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:00.03
Sayed Ibrahim Hashimi
My Book: Inside the Microsoft Build Engine : Using MSBuild and Team Foundation Build
In case this helps someone else - use TargetPath instead of RelativeDir:
<MSBuild Projects="#(ProjectsToBuild)" Targets="ContentFilesProjectOutputGroup">
<Output ItemName="ContentFiles" TaskParameter="TargetOutputs"/>
</MSBuild>
<Message Text="#(ContentFiles->'%(TargetPath)')"/>
This will give you the relative path for each item.

Checking if project has a specific target in MSBuild

Some of my .csproj project files have special target "AssembleJS" that merges all .js files included in project in one big file (i.e. webcontrols.csproj has target "AssembleJS" with output "webcontrols.js").
So if I have project parts.csproj
That has target AssembleJS.
References project webcontrols.csproj.
References utility project utils.csproj that does not have any JavaScript and does not have AssembleJS target.
I want target AssembleJS of parts.csproj execute AssembleJS in webcontrols.csproj (the same was as MSBuild works with standard Build target).
Something like
<MSBuild Project="#ReferencedProjects" Targets="AssembleJS"/>
does not work because utils.csproj does not have target AssembleJS.
Is there any way to filter #ReferencedProjects based on whether project has certain target?
Any other idea on how to handle this scenario?
You cannot do what you are requiring. But you might be able to acheive it with batching.
<Project xmlns=''>
<ItemGroup>
<ReferencedProjects Include="webcontrols.csproj">
<Type>Web</Type>
</ReferencedProjects>
<ReferencedProjects Include="utils.csproj">
<Type>NonWeb</Type>
</ReferencedProjects>
</ItemGroup>
<Target Name="BuildWebProjects">
<MSBuild Projects="#(ReferencedProjects)" Condition=" '%(ReferencedProjects.Type)' == 'Web' " />
</Target>
</Project>
Do a search for MSBuild Batching and find some results on sedodream.com for more
info.
Should I expand on this?

MSBuild ITaskItem array is out of date

I'm creating a custom ITask for MSBuild which uploads the output files of my build. I'm using a web deployment project to publish my app and hooking in to the AfterBuild target to do my custom work.
If I add a file to my web application, the first time I do a build, my custom task is not recognizing the recently added file. In order for that file to show up in my array of ITaskItems, I have to first do a build with my 'AfterBuild' target removed and then start the build again with my 'AfterBuild' target in place.
Here is what my build file looks like:
<ItemGroup>
<PublishContent Include="$(OutputPath)\**" />
</ItemGroup>
<Target Name="AfterBuild">
<UploadTask FilesToPublish="#(PublishContent)" />
</Target>
The list in #(PublishContent) seems to be initialized at the beginning of the build instead of reflecting any changes that might have taken place by the build process itself.
Any ideas?
Thanks
Your ItemGroup is going to get evaluated when the project file first loads (when you first open Visual Studio or you 'unload' and 'reload' the project in Solution Explorer).
What you probably need is to create an ItemGroup as a task in your 'AfterBuild' target. Example:
<CreateItem Include="$(OutputPath)\**">
<Output TaskParameter="Include" ItemName="OutputFiles"/>
</CreateItem>
followed by:
<Target Name="AfterBuild">
<UploadTask FilesToPublish="#(OutputFiles)" />
</Target>