Read text file and split every line in MSBuild - msbuild

I am stuck at the following issue I have in MSBuild.
I have a text file (buildsolutions1.txt) containing the list (line by line) with all the solutions I need to build and the related developers emails separated by comma :
Common\Common.sln,am#email,com
ExcGw/ExcDataService.sln,pm#email.com;am#email,com;jk#email.com;mk#email.com;Ppp#email.com
MessB/MessB/Message.sln,am#email,com
RiskS/RiskS2.sln,jp#email.com;mz#email.com;mk#email.com;jk#email.com;ps#email.com
I need to read this file line by line,compile each solution and in case it fails –send email to related developer(s)
My idea is to create an item group Lines where every item is a line from this file and it has 2 metadata values:
Solution –first part of the line until comma
Emails –second part of the line from comma to the end of line
So I created a Property Grroup and a target ReadSolutions like below.
I read the file line by line but I do not know how to set the metadata for each line item:
FirstPartOfTheCurrentLine(%(LinesFromFile.Identity))
SecondPartOfTheCurrentLine(%(LinesFromFile.Identity))
This syntax does not work:
%(LinesFromFile.Identity.Split(',')[0])
%(LinesFromFile.Identity.Split(',')[1])
Maybe someone would know how to set the metadata correctly or maybe has another approach to this task.
Thanks
Here is the code:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
ToolsVersion="4.0" DefaultTargets="CoreBuild">
<PropertyGroup>
<TPath>$(MSBuildProjectDirectory)\..\tools\MSBuild Extension Pack Binaries\MSBuild.ExtensionPack.tasks</TPath>
</PropertyGroup>
<Import Project="$(TPath)"/>
<PropertyGroup>
<!-- Default working folder -->
<RootFolder Condition=" '$(RootFolder)' == '' " >c:\ff\</RootFolder>
<BuildSolutionsFile >buildsolutions1.txt</BuildSolutionsFile>
<BuildSolutionsFileFullPath >$(RootFolder)$(BuildSolutionsFile)</BuildSolutionsFileFullPath>
</PropertyGroup>
<Target Name="ReadSolutions">
<Message Text=" Build solutions text file is : $(BuildSolutionsFileFullPath)" />
<!—Read the file and store each line as an item into LinesFromFile item group-->
<ReadLinesFromFile
File="$(BuildSolutionsFileFullPath)" >
<Output
TaskParameter="Lines"
ItemName="LinesFromFile"/>
</ReadLinesFromFile>
<Message Text="Current line : %(LinesFromFile.Identity)" />
<Message Text="===================================" />
<!—Create the other item group where each item is a line and has the metadata Solution and Emails -->
<ItemGroup>
<Lines Include="%(LinesFromFile.Identity)" >
<Solution>FirstPartOfTheCurrentLine(%(LinesFromFile.Identity))</Solution>
<Emails>SecondPartOfTheCurrentLine(%(LinesFromFile.Identity)) </Emails>
</Lines>
</ItemGroup>
<Message Text="All the Lines :%0A#(Lines,'%0A')" />
</Target>

Here is the data I was working with:
Common\Common.sln,am#email,com
ExcGw/ExcDataService.sln,pm#email.com;am#email,com;jk#email.com;mk#email.com;Ppp#email.com
MessB/MessB/Message.sln,am#email,com
RiskS/RiskS2.sln,jp#email.com;mz#email.com;mk#email.com;jk#email.com;ps#email.com
Slightly modified your sample to include a line break for the fourth solution.
Here is the modified code:
<ItemGroup>
<Lines Include="#(LinesFromFile)" >
<Solution>$([System.String]::Copy('%(LinesFromFile.Identity)').Split(',')[0])</Solution>
<Emails>$([System.String]::Copy('%(LinesFromFile.Identity)').Split(',')[1])</Emails>
</Lines>
</ItemGroup>
<Message Text="Solutions to Emails-> %(Lines.Solution) -> %(Lines.Emails)" />
We're copying the value to a property so we can use a property function to split the value and get the part we need.
Here is the output:
Solutions to Emails-> Common\Common.sln -> am#email
Solutions to Emails-> ExcGw/ExcDataService.sln -> pm#email.com;am#email
Solutions to Emails-> MessB/MessB/Message.sln -> am#email
Solutions to Emails-> RiskS/RiskS2.sln -> jp#email.com;mz#email.com;mk#email.com;jk#email.com;ps#email.com

Related

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.

How to extract multiple lines to variables in msbuild

I came across the following post
MSBuild ReadLinesFromFile all text on one line
From this I haven't been able to figure out how to do the following.
What should I do if I want to ReadAllLines but want to store each line in a different variable without the semicolon?
<ReadLinesFromFile File="#(File)">
<Output TaskParameter="Lines" ItemName="FileContents" />
</ReadLinesFromFile>
<Line1>"What should I do here?" </Line1>
<Line2>"What should I do here?" </Line2>
Read the content into a property, then split that property and get an item out of it. This of course requires that you know on beforehand that the file will have (at least) as many lines as you have properties.
<Target Name="ReadFile">
<ReadLinesFromFile File="$(MyInputFile)">
<Output TaskParameter="Lines" PropertyName="FileContents"/>
</ReadLinesFromFile>
</Target>
<Target Name="CreateProperties" DependsOnTargets="ReadFile">
<PropertyGroup>
<Line0>$([System.String]::Copy( $(FileContents) ).Split( ';' )[ 0 ])</Line0>
<Line1>$([System.String]::Copy( $(FileContents) ).Split( ';' )[ 1 ])</Line1>
</PropertyGroup>
</Target>

Can't get MSBuild Community Task RegexReplace to work

I'm trying to copy a bunch of files whose names begin with the prefix DR__, but the copies must have that prefix removed. That is, DR__foo must be copied as foo. I'm trying this, which is based in the example provided in the documentation (the .chm):
<Target Name="CopyAuxiliaryFiles">
<MakeDir Directories="$(TargetDir)Parameters" Condition="!Exists('$(TargetDir)Parameters')" />
<ItemGroup>
<ContextVisionParameterFiles Include="$(SolutionDir)CVParameters\DR__*" />
</ItemGroup>
<Message Text="Files to copy and rename: #(ContextVisionParameterFiles)"/>
<RegexReplace Input="#(ContextVisionParametersFiles)" Expression="DR__" Replacement="">
<Output ItemName ="DestinationFullPath" TaskParameter="Output" />
</RegexReplace>
<Message Text="Renamed Files: #(DestinationFullPath)"/>
<Copy SourceFiles="#(ContextVisionParameterFiles)" DestinationFiles="#(DestinationFullPath)" />
</Target>
DestinationFullPath comes out empty (or that's what I see when I display it with Message). Thus, Copy fails because no DestinationFiles are specified. What's wrong here?
Edit: ContextVisionParameterFiles is not empty, it contains this:
D:\SVN.DRA.WorkingCopy\CVParameters\DR__big_bone.alut;D:\SVN.DRA.WorkingCopy\CVParameters\DR__big_medium.gop
They're actually 40 files, but I trimmed it for the sake of clarity
Got it! It seems to have been the combination of a stupid error and a seemingly compulsory parameter. As for the first one, there were two Targets called CopyAuxiliaryFiles. As for the second one, it seems the Count parameter is needed.
The final, working version:
<Target Name="CopyCvParameters">
<ItemGroup>
<CvParamFiles Include="$(SolutionDir)CVParameters\DR__*" />
</ItemGroup>
<Message Text="Input:
#(CvParamFiles, '
')"/>
<!-- Replaces first occurance of "foo." with empty string-->
<RegexReplace Input="#(CvParamFiles)" Expression="^.*DR__" Replacement="$(TargetDir)Parameters\" Count="1">
<Output ItemName ="RenamedCvParamFiles" TaskParameter="Output" />
</RegexReplace>
<Message Text="
Output RenamedCvParamFiles:
#(RenamedCvParamFiles, '
')" />
<Copy SourceFiles="#(CvParamFiles)" DestinationFiles="#(RenamedCvParamFiles)" SkipUnchangedFiles="True" />
</Target>
Notice that:
I renamed the Target to solve the name collision (Why doesn't Visual Studio detect this as an error?)
I pretty-printed the ItemGroups with the #(CvParamFiles, '
') syntax, which seems to replace ; with line breaks
My regex replaces the absolute path and the prefix
Count="1" is now passed to RegexReplace

Including files with directory specified separately in MSBuild

This seems like it should be simple but I can't work it out from the reference and my google-fu is apparently weak.
I just want to specify the file names and base folder separately in the build file...
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<TestFilesWithFolder>
B:\Root\Test1.*;
B:\Root\Test2.*
</TestFilesWithFolder>
<TestFiles>Test1.*;Test2.*</TestFiles>
<TestFileRoot>B:\Root</TestFileRoot>
</PropertyGroup>
<Target Name="Build">
<ItemGroup>
<TestFilesGroupWithFolder Include="$(TestFilesWithFolder)" />
<TestFilesGroup Include="$(TestFileRoot)\$(TestFiles)" />
</ItemGroup>
<Warning Text="Source files with folder: #(TestFilesGroupWithFolder)" />
<Warning Text="Source files: #(TestFilesGroup)" />
</Target>
</Project>
When I run this, the first warning shows both files as expected, but the second warning only shows the first file (since the straight string concat put the folder name on the first but not second).
How would I get the ItemGroup "TestFilesGroup" to include both the files given the "TestFiles" and "TestFileRoot" properties?
It is possible to convert a semicolon delimited list of things into an item, which would make this possible, except that the items in your property contain wildcards, so if you want to have MSBuild treat them as items in a list, at the moment MSBuild first sees it the path must be valid. There may be a way to do that but I can't think of one. In other words...
<ItemGroup>
<TestFiles Include="$(TestFiles)" />
</ItemGroup>
...only works if $(TestFiles) contains a delimited list of either things with no wildcards, or qualified paths that actually exist.
Further, MSBuild can't compose a path with a wildcard inside the Include attribute and evaluate it at the same time, so you need a trick to first compose the full path separately, then feed it into the Include attribute. The following will work, but it requires changing your delimited property into a set of items. It batches a dependent target on this item list, with each batched target execution calculating a meta value for one item, which is stored off in a new meta value. When the original target executes, it is able to use that meta value in a subsequent Include.
<PropertyGroup>
<TestFilesWithFolder>
D:\Code\Test1.*;
D:\Code\Test2.*
</TestFilesWithFolder>
<TestFileRoot>D:\Code</TestFileRoot>
</PropertyGroup>
<ItemGroup>
<TestFilePattern Include="TestFilePattern">
<Pattern>Test1.*</Pattern>
</TestFilePattern>
<TestFilePattern Include="TestFilePattern">
<Pattern>Test2.*</Pattern>
</TestFilePattern>
</ItemGroup>
<Target Name="Compose" Outputs="%(TestFilePattern.Pattern)">
<ItemGroup>
<TestFilePattern Include="TestFilePattern">
<ComposedPath>#(TestFilePattern->'$(TestFileRoot)\%(Pattern)')</ComposedPath>
</TestFilePattern>
</ItemGroup>
</Target>
<Target Name="Build" DependsOnTargets="Compose">
<ItemGroup>
<TestFilesGroupWithFolder Include="$(TestFilesWithFolder)" />
</ItemGroup>
<Warning Text="Source files with folder: #(TestFilesGroupWithFolder)" />
<ItemGroup>
<ComposedTestFiles Include="%(TestFilePattern.ComposedPath)" />
</ItemGroup>
<Warning Text="Source files: #(ComposedTestFiles)" />
</Target>
Which produces the following output:
(Build target) ->
d:\Code\My.proj(80,5): warning : Source files with folder:
D:\Code\Test1.txt;D:\Code\Test2.txt
d:\Code\My.proj(84,5): warning : Source files:
D:\Code\Test1.txt;D:\Code\Test2.txt

MsBuild run Exec for every item in a list

I'm trying to load a list of filenames from a text file and then run an Exec task for each entry retrieved from the text file.
So I have a file, let's call it SomeFile.txt containing the following:
FileA.file
FileB.file
FileC.file
The MsBuild code I have for this looks like this (which doesn't work:)
<Target Name="runScripts">
<ItemGroup>
<scriptsFile Include="SomeFile.txt" />
</ItemGroup>
<ReadLinesFromFile File="#(scriptsFile)">
<Output TaskParameter="Lines" ItemName="scriptItems" />
</ReadLinesFromFile>
<Message Text="Running Exec for each entry..." />
<Exec Command="$(someCommand) %(scriptItems)" />
</Target>
This gives me an error saying I need to specify an item name, but if I use anything like %(scriptItems.item) or %(itemname.scriptItems) MsBuild simply puts a blank instead of %(scriptItems).
Simply need to use %(scriptItems.Identity) to get an item name from the metadata. This is well documented at MSDN.