How to convert NAnt function "path::combine(path1, path2)" to MSBuild? - msbuild

I need to convert the function "path::combine(path1, path2)". Please help me if you have some idea. Thank you!

Use the CombinePath Task:
<Project DefaultTargets="DefaultTarget" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MyBasePath>.\a\b</MyBasePath>
<MySecondPath>c\d</MySecondPath>
</PropertyGroup>
<Target Name="Combine">
<PropertyGroup>
<MySecondPath Condition="$(MySecondPath)==''">.\</MySecondPath>
</PropertyGroup>
<CombinePath BasePath="$(MyBasePath)" Paths="$(MySecondPath)">
<Output TaskParameter="CombinedPaths" PropertyName="CombineOutput" />
</CombinePath>
</Target>
<Target Name="DefaultTarget" DependsOnTargets="Combine">
<Message Text="Result from Combine is $(CombineOutput)" />
</Target>
</Project>

Updating this post for newer MsBuild versions. From MSBuild 4.0 and up, you can use property functions
$([System.IO.Path]::Combine($(Path1),$(Path2)))

Related

Deleting parent folder msbuild

Am passing the the below path as parameter to msbuild project.
"D:\Tools\TestTools\Folder1\Folder2\Folder3"
How to I delete the "Folder1" by traversing this parameter using msbuild?
Thanks...
You could just split the path twice:
<Target Name="DeleteSubDir" DependsOnTargets="">
<PropertyGroup>
<Dir>D:\Tools\TestTools\Folder1\Folder2\Folder3</Dir>
<DirToDelete>$([System.IO.Path]::GetDirectoryName('$(Dir)'))</DirToDelete>
<DirToDelete>$([System.IO.Path]::GetDirectoryName('$(DirToDelete)'))</DirToDelete>
</PropertyGroup>
<RemoveDir Directories="$(DirToDelete)" />
</Target>
Just explicitly go two directories above:
<Target Name="DeleteSubDir" DependsOnTargets="">
<PropertyGroup>
<Dir>D:\Tools\TestTools\Folder1\Folder2\Folder3</Dir>
<DirToDelete>$([System.IO.Path]::GetFullPath('$(Dir)\..\..'))</DirToDelete>
</PropertyGroup>
<RemoveDir Directories="$(DirToDelete)" />
</Target>

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.

In MSBuild, can I use the String.Replace function on a MetaData item?

In MSBuild v4 one can use functions (like string.replace) on Properties. But how can I use functions on Metadata?
I'd like to use the string.replace function as below:
<Target Name="Build">
<Message Text="#(Files->'%(Filename).Replace(".config","")')" />
</Target>
Unfortunately this outputs as (not quite what I was going for):
log4net.Replace(".config","");ajaxPro.Replace(".config","");appSettings.Replace(".config","");cachingConfiguration20.Replace(".config","");cmsSiteConfiguration.Replace(".config","");dataProductsGraphConfiguration.Replace(".config","");ajaxPro.Replace(".config","");appSettings.Replace(".config","");cachingConfiguration20.Replace(".config","");cmsSiteConfiguration
Any thoughts?
You can do this with a little bit of trickery:
$([System.String]::Copy('%(Filename)').Replace('config',''))
Basically, we call the static method 'Copy' to create a new string (for some reason it doesn't like it if you just try $('%(Filename)'.Replace('.config',''))), then call the replace function on the string.
The full text should look like this:
<Target Name="Build">
<Message Text="#(Files->'$([System.String]::Copy("%(Filename)").Replace(".config",""))')" />
</Target>
Edit: MSBuild 12.0 seems to have broken the above method. As an alternative, we can add a new metadata entry to all existing Files items. We perform the replace while defining the metadata item, then we can access the modified value like any other metadata item.
e.g.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Files Include="Alice.jpg"/>
<Files Include="Bob.not-config.gif"/>
<Files Include="Charlie.config.txt"/>
</ItemGroup>
<Target Name="Build">
<ItemGroup>
<!--
Modify all existing 'Files' items so that they contain an entry where we have done our replace.
Note: This needs to be done WITHIN the '<Target>' (it's a requirment for modifying existing items like this
-->
<Files>
<FilenameWithoutConfig>$([System.String]::Copy('%(Filename)').Replace('.config', ''))</FilenameWithoutConfig>
</Files>
</ItemGroup>
<Message Text="#(Files->'%(FilenameWithoutConfig)')" Importance="high" />
</Target>
</Project>
Result:
D:\temp>"c:\Program Files (x86)\MSBuild\12.0\Bin\MSBuild.exe" /nologo test.xml
Build started 2015/02/11 11:19:10 AM.
Project "D:\temp\test.xml" on node 1 (default targets).
Build:
Alice;Bob.not-config;Charlie
Done Building Project "D:\temp\test.xml" (default targets).
I needed to do something similar, the following worked for me.
<Target Name="Build">
<Message Text="#(Files->'%(Filename)'->Replace('.config', ''))" />
</Target>
Those functions works in properties only (as I know). So create target which will perform operation throw batching:
<Target Name="Build"
DependsOnTargets="ProcessFile" />
<Target Name="ProcessFile"
Outputs="%(Files.Identity)">
<PropertyGroup>
<OriginalFileName>%(Files.Filename)</OriginalFileName>
<ModifiedFileName>$(OriginalFileName.Replace(".config",""))</ModifiedFileName>
</PropertyGroup>
<Message Text="$(ModifiedFileName)" Importance="High"/>
</Target>
Do you really need in your example such kind of task? I mean there exists MSBuild Well-known Item Metadata
EDIT: I should specify that this task processes all items in #(Files).
i dont think you can use functions directly with itemgroups and metadata (that would be easy)
However you can use batching:
Taking the ideas from this post:
array-iteration
I was trying to trim an itemgroup to send to a commandline tool (i needed to lose .server off the filename)
<Target Name="ProcessFile" DependsOnTargets="FullPaths">
<ItemGroup>
<Environments Include="$(TemplateFolder)\$(Branch)\*.server.xml"/>
</ItemGroup>
<MSBuild Projects=".\Configure.msbuild"
Properties="CurrentXmlFile=%(Environments.Filename)"
Targets="Configure"/>
</Target>
<Target Name="Configure" DependsOnTargets="FullPaths">
<PropertyGroup>
<Trimmed>$(CurrentXmlFile.Replace('.server',''))</Trimmed>
</PropertyGroup>
<Message Text="Trimmed: $(Trimmed)"/>
<Exec Command="ConfigCmd $(Trimmed)"/>
</Target>
For MSBuild 12.0, here's an alternative.
<Target Name="Build">
<Message Text="$([System.String]::Copy("%(Files.Filename)").Replace(".config",""))" />
</Target>
Got the same problem (except with MakeRelative), so I passed with another solution : Using good old CreateItem that take a string and transform to Item :)
<ItemGroup>
<_ToUploadFTP Include="$(PublishDir)**\*.*"></_ToUploadFTP>
</ItemGroup>
<CreateItem Include="$([MSBuild]::MakeRelative('c:\$(PublishDir)','c:\%(relativedir)%(filename)%(_ToUploadFTP.extension)'))">
<Output ItemName="_ToUploadFTPRelative" TaskParameter="Include"/>
</CreateItem>
<FtpUpload Username="$(UserName)"
Password="$(UserPassword)"
RemoteUri="$(FtpHost)"
LocalFiles="#(_ToUploadFTP)"
RemoteFiles="#(_ToUploadFTPRelative->'$(FtpSitePath)/%(relativedir)%(filename)%(extension)')"
UsePassive="$(FtpPassiveMode)" ></FtpUpload>

google closure and MSBUILD

looking to implement google closure with msbuild.
i found a few solutions but struggling to get it working.
any suggestions on how i can read multiple js files in and create .min versions during build?
thanks
EDIT: with the answer from below i was able to make some alterations with a view to fully implement what was recommended but in a simple form here is what i've wrote:
<ItemGroup>
<JSMin Include="$(OutputPath)Scripts\*.js"/>
</ItemGroup>
<Target Name="AfterBuild" Inputs="#(JSMin)" Outputs="#(JSMin ->'%(Directory)%(Filename).min%(Extension)')">
<Exec Command="java -jar C:\temp\compiler.jar --js %(JSMin.Identity) --js_output_file C:\temp\%(JSMin.Filename).min.js"/>
</Target>
the output path will be changed to a dynamic path later on.
many thanks
I haven't looked at Google Closure, but you can run any command through the <Exec> task:
<Project DefaultTargets="build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="build">
<Exec Command="echo Hello world"/>
</Target>
</Project>
Edit: To run a command over a set of files:
<Project DefaultTargets="build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<JSMin Include="jquery.js"/>
<JSMin Include="somethingelse.js"/>
</ItemGroup>
<Target Name="build" Inputs="#(JSMin)" Outputs="#(JSMin->'%(Directory)%(Filename).min%(Extension)')">
<Exec Command="java -jar C:\temp\compiler.jar --js %(JSMin.Identity) --js_output_file %(JSMin.Directory)%(JSMin.Filename).min%(JSMin.Extension)"/>
</Target>
</Project>

How to suppress logger output when using <MSBuild ...> task?

I need to run an external MSBuild thread from inside another MSBuild project. What I need to do is to pass the /noconsolelogger to the task . How can I do this, please help!
foo.csproj
<Project DefaultTargets="BuildAll" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="BuildAll">
<Message Text="foo"/>
<Exec Command="MSBuild.exe bar.csproj /noconsolelogger"/>
</Target>
</Project>
bar.csproj
<Project DefaultTargets="BuildAll" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="BuildAll">
<Message Text="bar"/>
</Target>
</Project>
Output contains the message 'foo', but not 'bar'.