Transform an ItemGroup into a Delimited String - msbuild

My question is almost identical to Create an ItemGroup of strings in MSBuild however the solution offered there still seems to carry over the existing delimiter. Here's a simplified snippet of what I'm attempting to do:
<Target Name="Testing">
<ItemGroup>
<Files Include="$(RootDirectory)\*.*"/>
</ItemGroup>
<Message Text="#(Files->'%(Filename)%(Extension) ')"/>
</Target>
My desired output is something that looks like this:
file1.cs file2.cs file3.cs
However the snippet above produces the following output
file1.cs ;file2.cs ;file3.cs
What have I done wrong?

Try to use MSBuild transforms like this:
<Message Text="#(Files->'%(Filename)%(Extension)', ' ')"/>

Related

Get wildcard reference in csproj node

Take the following example:
<EmbeddedResource Include="Resources\files\main-*.png">
<LogicalName>Images\main\???.resources</LogicalName>
</EmbeddedResource>
How can I get the part from "*" inside a child of a node? I have found %Filename%, but that's too much.
It is possible, but requires some property functions kung fu.
<Project>
<!--
Create files:
Resources\files\main-foo.png
Resources\files\main-bar.png
Run:
MSBuild.exe foo.proj /t:Test
Output:
LogicalName: Images\main\foo.resources
LogicalName: Images\main\bar.resources
-->
<ItemGroup>
<EmbeddedResource Include="Resources\files\main-*.png">
<_DashPosition>$([System.String]::Copy('%(FileName)').IndexOf('-'))</_DashPosition>
<_LogicalNameStart>$([MSBuild]::Add(%(_DashPosition), 1))</_LogicalNameStart>
<LogicalName>Images\main\$([System.String]::Copy('%(FileName)').Substring(%(_LogicalNameStart))).resources</LogicalName>
</EmbeddedResource>
</ItemGroup>
<Target Name="Test">
<Message Text="LogicalName: %(EmbeddedResource.LogicalName)" Importance="high"/>
</Target>
</Project>
The general approach is like it would be in C# or other language. Find the delimiter (here - in the filename) and take everything after that as LogicalName.
Note that I used "temporary" item metadata (the '_' is just a convention here) to keep things a little more clear. In theory, you could cramp all into the <LogicalName> itself.

Use uglifyjs in Publish Profile

This is a snippet of my Publish Profile:
<Exec WorkingDirectory="$(_PackageTempDir)"
Command="uglifyjs ..\..\..\..\js\file1.js ..\..\..\..\js\file2.js --mangle --reserved "$" --compress > js\outfile.min.js" />
Certain files (say file1.js) is located outside my project and therefore is not copied to the _PackageTempDir. Here I have to ..\ up several levels to get there. I'm wondering if there is a good way to use an ItemGroup or full path that will allow me the same results.
The above code "works". It is just complicated and difficult to maintain. Looking for a better solution.
EDIT:
Based on Sayed's suggestions, I refined my profile to:
<ItemGroup>
<UglifyJSFiles Include="$(MSBuildThisFileDirectory)..\..\js\mcm\mcm.js" />
<UglifyJSFiles Include="$(_PackageTempDir)\js\main.js" />
</ItemGroup>
<Exec WorkingDirectory="$(_PackageTempDir)"
Command="uglifyjs #(UglifyJSFiles,' ') > js\app.min.js" />
But I am running into an issue because the paths contain spaces. How can I either quote the path strings or escape the spaces?
Here is an example showing a better approach
<PropertyGroup>
<JsFilesToUglifyRoot Condition=" '$(JsFilesToUglifyRoot)'=='' ">$(MSBuildThisFileDirectory)\..\..\..\..\js\</JsFilesToUglifyRoot>
</PropertyGroup>
<ItemGroup>
<JsFilesToUglify Include="$(JsFilesToUglifyRoot)**\*.js" />
</ItemGroup>
<Target Name="AfterBuild">
<Message Text="Files: [#(JsFilesToUglify,' ')]" Importance="high" />
<!-- If you need to quote the file paths use the transform below -->
<Message Text="Files: [#(JsFilesToUglify->'"%(FullPath)"',' ')]" Importance="high" />
</Target>
Here I define a new property JsFilesToUglify that is populated with the path you indicated above. Note the usage of the MSBuildThisFileDirectory reserved property. You should not rely on just ..\ as its value may be different in VS versus outside of VS. Also do not use the MSBuildProjectDirectory property, only MSBuildThisFileDirectory.
Then inside of the target I transform the list of files with #(JsFilesToUglify,' ') the ,' ' makes a space the separator between values like your command above.

Using the Zip task in MSBuild

I have been attempting to use the zip task of msbuild in a project I am working on at the moment.
My project file looks something like this:
<PropertyGroup> <MSBuildCommunityTasksPath>$(SolutionDir)\.build</MSBuildCommunityTasksPath> </PropertyGroup>
<Import Project="$(MSBuildCommunityTasksPath)\MSBuild.Community.Tasks.Targets" />
<ItemGroup>
<FileToZip include="C:\FilePath"></FilesToZip>
<FileToZip include="C:\FilePath"></FilesToZip>
</ItemGroup>
<Target Name="BeforeBuild">
<PropertyGroup>
<ReleasePath>\releasepath</ReleasePath>
<Zip Files="#(FilesToZip)" WorkingDirectory="$(ReleasePath)" ZipFileName="HTMLeditor.html" ZipLevel="9" />
</Target>
However, the zip file updates but does not contain the files specified in the item group FilesToZip. I cannot figure out why they aren't being recognised! I have double checked file paths and they are correct. Any ideas?
I think you want to do something like this:
<ItemGroup>
<FileToZip include="C:\FilePath;C:\FilePath"/>
</ItemGroup>
As I mentioned in my comment, simply creating a variable (FileToZip) and repeating it twice with different values does not give you an array that contains both of the values. You end up with only the last value (and not an array at all). Your include attribute is a selector which is used to build the array and it can contain multiple values, wildcards and other patterns which are used to build out that array for you.
Here's a link to MSDN that gives you more information on how to use the Include attribute: http://msdn.microsoft.com/en-us/library/ms171454.aspx
I ditched the ItemGroup in the end, and went with another way of doing it.
<Target Name="Zip">
<CreateItem Include="FilesToInclude" >
<Output ItemName="ZipFiles" TaskParameter="Include"/>
<Zip ZipFileName="ZipFile.zip" WorkingDirectory="FolderToWriteZipTo" Files="#(ZipFiles)" />
</Target>
This method seemed to be easier and wasn't adding files to the root of the file.
Thanks for the help though guys.

MSBuild: transform paths to namespaces

I have list of items like this:
<ItemGroup>
<ToCompile Include="clojure\core.clj;clojure\set.clj;clojure\zip.clj;clojure\test\junit.clj;"/>
</ItemGroup>
And I want to transform that to a list of items like this:
clojure.core clojure.set clojure.zip clojure.test.junit
Is there a way to do this with MSBuild transforms? I tried but I can only get at the file name; the extension and the root path, and I can change the separator. But not the path separators.
If not, any other solution that avoids using custom tasks is appreciated.
We can do it easily with fewer cheese:
<Message
Text="$([System.String]::Copy('%(ToCompile.Identity)').Replace('.clj','').Replace('\','.'))"/>
This is a bit cheesy, but it works in MSBuild 4.0+.
<Target Name="Namespaces">
<PropertyGroup>
<Cheesy>#(ToCompile -> '%(relativedir)%(filename)', ' ')</Cheesy>
</PropertyGroup>
<Message Text="$(Cheesy.Replace(`\`, `.`))" />
</Target>

MSBuild getting property substring before underscore symbol

In MSBuild I have a property which value is Name_Something. How can I get name part of this property.
With MSBuild 4
If you use MSBuild 4, you could use the new and shiny property functions.
<PropertyGroup>
<MyProperty>Name_Something</MyProperty>
</PropertyGroup>
<Target Name="SubString">
<PropertyGroup>
<PropertyName>$(MyProperty.Substring(0, $(MyProperty.IndexOf('_'))))</PropertyName>
</PropertyGroup>
<Message Text="PropertyName: $(PropertyName)"/>
</Target>
With MSBuild < 4
You could use the RegexReplace task of MSBuild Community Task
<PropertyGroup>
<MyProperty>Name_Something</MyProperty>
</PropertyGroup>
<Target Name="RegexReplace">
<RegexReplace Input="$(MyProperty)" Expression="_.*" Replacement="" Count="1">
<Output ItemName ="PropertyNameRegex" TaskParameter="Output" />
</RegexReplace>
<Message Text="PropertyNameRegex: #(PropertyNameRegex)"/>
</Target>
If I understand your question correctly you are trying to get the substring of a MSBuild property. There is no direct way to do string manipulation in MSBuild, like in NAnt. So you have two options:
1). Create separate variables for each part and combine them:
<PropertyGroup>
<Name>Name</Name>
<Something>Something</Something>
<Combined>$(Name)_$(Something)</Combined>
</PropertyGroup>
This works fine if the parts are known before hand, but not if you need to do this dynamically.
2). Write a customer MSBuild task that does the string manipulation. This would be your only option if it needed to done at runtime.
It looks like you could use Item MetaData instead of a Property:
<ItemGroup>
<Something Include="SomeValue">
<Name>YourName</Name>
<SecondName>Foo</SecondName>
</Something>
</ItemGroup>