MSBuild Task CreateItem with files that exist - msbuild

In a msbuild target, I am trying to create an item and it should include a list of files, only if they exist.
<CreateItem Include="$(IntermediateOutputPath)$(OutputName).*.xyz" Condition="'$(IntermediateXyzFiles)' == ''">
<Output TaskParameter="Include" ItemName="IntermediateXyzFiles"/>
</CreateItem>
All the variables shown are defined.
Does anyone know how to add a condition for the file existence?
<CreateItem Include="$(IntermediateOutputPath)$(OutputName).*.xyz" Condition="'$(IntermediateXyzFiles)' == '' and Exists('%(FullPath)')">
I tried the one above and it doesn't work for me.
Or is there another way to do this?

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.

Pass list item to Properties when calling reusable msbuild target

I'm trying to create a reusable Target in msbuild, following the basic model outlined in How to invoke the same msbuild target twice?
I'm stuck trying to pass a property that I want interpreted as a list. I haven't found an example online that deals with this situation. As I understand it, the problem is that Properties is already treated as a list item, so it doesn't like having a list item passed in as well. Is there a way to get msbuild to pack and unpack the list correctly here?
Msbuild is complaining with:
error MSB4012: The expression "FilesToZip=#(Scripts)" cannot be used in this context. Item lists cannot be concatenated with other strings where an item list is expected. Use a semicolon to separate multiple item lists.
Here's an example caller:
<Target Name="BuildMigrationZip">
<MSBuild Projects="BuildZip.msbuild"
Targets="BuildZip"
Properties="FilesToZip=#(Scripts);OutputZipFile=$(MigrationPackageFilePath);OutputFolder=$(MigrationPackagePath);Flatten=true"/>
<Message Text="Created database migration zip: $(MigrationPackageFilePath)" Importance="high"/>
</Target>
And the base target:
<Target Name="BuildZip">
<MakeDir Directories="$(OutputFolder)"/>
<Zip Files="#(FilesToZip)"
ZipFileName="$(OutputZipFile)"
Flatten="$(Flatten)"
ParallelCompression="false" />
</Target>
I'm basically at the point of just going back to cut and paste for these, although I want to package up a number of zips here.
UPDATE: The same issue applies to setting Inputs on the reusable target. My question up to this point addresses the raw functionality, but it would be nice to keep dependencies working. So for example:
<Target Name="BuildZip"
Inputs="#(FilesToZip)"
Outputs="$(OutputZipFile)">
<MakeDir Directories="$(OutputFolder)"/>
<Zip Files="#(FilesToZip)"
ZipFileName="$(OutputZipFile)"
Flatten="$(Flatten)"
ParallelCompression="false" />
</Target>
They key is to pass the list around as a property. So when your Scripts list is defined as
<ItemGroup>
<Scripts Include="A"/>
<Scripts Include="B"/>
<Scripts Include="C"/>
</ItemGroup>
then you first convert it into a property (which just makes semicolon seperated items, but msbuild knows how to pass this via the Properties of the MSBuild target) then pass it to the target:
<Target Name="BuildMigrationZip">
<PropertyGroup>
<ScriptsProperty>#(Scripts)</ScriptsProperty>
</PropertyGroup>
<MSBuild Projects="$(MSBuildThisFile)" Targets="BuildZip"
Properties="FilesToZip=$(ScriptsProperty)" />
</Target>
(note I'm using $(MSBuildThisFile) here: you don't necessarily need to create seperate build files for every single target, in fact for small targets like yours it's much more convenient to put it in the same file)
Then in your destination target you turn the property into a list again:
<Target Name="BuildZip">
<ItemGroup>
<FilesToZipList Include="$(FilesToZip)"/>
</ItemGroup>
<Message Text="BuildZip: #(FilesToZipList)" />
</Target>
Output:
BuildZip: A;B;C
Update
When working with Inputs, you cannot pass #(FilesToZip) since that expands to nothing because is not a list: it's a property - which happens to be a number of semicolon-seperated strings. And as such, it is usable for Inputs you just have to expand it as the property it is i.e. $(FilesToZip):
<Target Name="BuildZip"
Inputs="$(FilesToZip)"
Outputs="$(OutputZipFile)">
...
</Target>
Output of second run:
BuildZip:
Skipping target "BuildZip" because all output files are up-to-date with respect to the input files.

MSBuild combine files

I'm trying to combine all javascript files in a project during the build process, but it just isn't working for me. Here's what I have:
<Target Name="CombineJS">
<CreateItem Include=".\**\*.js">
<Output TaskParameter="Include" ItemName="jsFilesToCombine" />
</CreateItem>
<ReadLinesFromFile File="#(jsFilesToCombine)">
<Output TaskParameter="Lines" ItemName="jsLines" />
</ReadLinesFromFile>
<WriteLinesToFile File="all.js" Lines="#(jsLines)" Overwrite="true" />
</Target>
MSBuild is throwing an error on the ReadLinesFromFile line saying there's an invalid value for the "File" parameter. (No error when there's only one file to combine)
So, two questions:
What am I doing wrong?
Is there a better way to combine files within an MSBuild task? I ask this question because I know that my current process removes all tabs and blank lines, which for me isn't that big of a deal, but still kind of annoying.
Change line 6 to:
<ReadLinesFromFile File="%(jsFilesToCombine.FullPath)">
The # operator is used when the input is ItemGroup which is essentially a semicolon-delimited list of strings.
The % operator is for expanding ItemGroups into strings (properties).
The ReadLinesFromFileTask you are using to read the files takes a single file as input to the File property (MSDN). You can't use this task to read lines from multiple files at once. You may however be able to use batching to run the task several times for each file.

MSBuild - Comparing ItemGroups metadata

I am attempting to write a build script for our source tree. This tree consists of a (large) number of solutions with assembly references between them. I have created an ItemGroup containing all the solutions and am batching over this ItemGroup to build the solutions.
I also need to copy some project outputs to an "exes output" folder, each in their own folder. I've attached some metadata to the solution item pointing to the projects that I want to grab the output from. As I can have potentially more than one project to output from each solution, I am doing this by giving the metadata the value that is passed to an ItemGroup's Include to create an ItemGroup separately. This works happily and I am able to batch over this dynamically created ItemGroup.
The final step I want to do, which is causing me a headache, is that for some of those output projects, I want to specify a special folder name. Now, I can do this by altering the metadata inside the target that is doing the work, like this:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
DefaultTargets="Build">
<!-- Define all the solutions to build, and any projects we want to handle the output of -->
<ItemGroup>
<SolutionsToBuild Include="$(MSBuildProjectDirectory)\Solution1\Solution1.sln">
<ProjectsToOutput>
$(MSBuildProjectDirectory)\Solution1\Project1A\Project1A.csproj;
$(MSBuildProjectDirectory)\Solution1\Project1B\Project1B.csproj;
</ProjectsToOutput>
</SolutionsToBuild>
<SolutionsToBuild Include="$(MSBuildProjectDirectory)\Solution2\Solution2.sln">
<ProjectsToOutput>
$(MSBuildProjectDirectory)\Solution2\Project2A\Project2A.csproj;
$(MSBuildProjectDirectory)\Solution2\Project2B\Project2B.csproj;
</ProjectsToOutput>
</SolutionsToBuild>
</ItemGroup>
<Target Name="Build">
<CallTarget Targets="DoBuild" />
</Target>
<Target Name="DoBuild" Outputs="%(SolutionsToBuild.Identity)">
<Message Text="Building project: %(SolutionsToBuild.FullPath)" />
<!-- e.g. <MSBuild Projects="%(SolutionsToBuild.FullPath)" /> -->
<PropertyGroup>
<ProjectsToOutputIncludeMask>%(SolutionsToBuild.ProjectsToOutput)</ProjectsToOutputIncludeMask>
</PropertyGroup>
<ItemGroup>
<OutputProjects Include="$(ProjectsToOutputIncludeMask)" />
<!-- Now create the OutputTo metadata -->
<!-- Default to the same name as the project file -->
<OutputProjects>
<OutputTo>%(OutputProjects.FileName)</OutputTo>
</OutputProjects>
<!-- Now override specific projects OutputTo metadata -->
<OutputProjects Condition="'%(OutputProjects.FileName)' == 'Project1A'">
<OutputTo>ArbitraryFolder1</OutputTo>
</OutputProjects>
<OutputProjects Condition="'%(OutputProjects.FileName)' == 'Project2B'">
<OutputTo>ArbitraryFolder2</OutputTo>
</OutputProjects>
</ItemGroup>
<Message Text=" Outputting project: %(OutputProjects.FullPath) -> %(OutputTo)" />
</Target>
</Project>
However, this build script needs to be maintained by all developers on the team, not all of whom are familiar with MSBuild. Therefore, I'd like to define another ItemGroup at the top of the script that defines any special names. They can then ignore all the targets and tasks and just maintain the ItemGroups, like this:
<ItemGroup>
<SolutionsToBuild Include="$(MSBuildProjectDirectory)\Solution1\Solution1.sln">
<ProjectsToOutput>
$(MSBuildProjectDirectory)\Solution1\Project1A\Project1A.csproj;
$(MSBuildProjectDirectory)\Solution1\Project1B\Project1B.csproj;
</ProjectsToOutput>
</SolutionsToBuild>
<SolutionsToBuild Include="$(MSBuildProjectDirectory)\Solution2\Solution2.sln">
<ProjectsToOutput>
$(MSBuildProjectDirectory)\Solution2\Project2A\Project2A.csproj;
$(MSBuildProjectDirectory)\Solution2\Project2B\Project2B.csproj;
</ProjectsToOutput>
</SolutionsToBuild>
<OutputNames Include="Project1A">
<OutputFolder>ArbitraryFolder1</OutputFolder>
</OutputNames>
<OutputNames Include="Project2B">
<OutputFolder>ArbitraryFolder2</OutputFolder>
</OutputNames>
</ItemGroup>
However, any way I've tried to get the DoBuild target to update the metadata falls on its face. I thought I could do this:
<!-- Now override specific projects OutputTo metadata -->
<OutputProjects Condition="'%(OutputProjects.FileName)' == '%(OutputNames.Identity)'">
<OutputTo>%(OutputNames.OutputFolder)</OutputTo>
</OutputProjects>
But that code batches over The OutputProjects item group and then over the OutputNames item group, so the condition is never true (one of the arguments to the comparison is always empty).
I'm unfortunately, at this stage, unable to change either the solution structure or the output folder structure requirements. Is there any trick of MSBuild that I'm missing that could help me here? I'm not averse to including a custom task to do the job, but would prefer a straight MSBuild solution if possible.
If it makes a difference, I am using MSBuild v4.
Ah. Stumbled on an answer whilst playing around with this.
Firstly, I was investigating this post about getting the intersection of two item groups. I therefore changed my OutputNames item group to have the same Identity as the OutputProjects ItemGroup:
<OutputNames Include="$(MSBuildProjectDirectory)\Solution1\Project1A\Project1A.csproj">
<OutputFolder>ArbitraryFolder1</OutputFolder>
</OutputNames>
<OutputNames Include="$(MSBuildProjectDirectory)\Solution2\Project2B\Project2B.csproj">
<OutputFolder>ArbitraryFolder2</OutputFolder>
</OutputNames>
This let me batch on %(Identity) and get the intersection like this:
<Message Condition="'%(Identity)' != '' and
'#(OutputProjects)' != '' and
'#(OutputNames)' != ''"
Text="Found a match for %(Identity)" />
However, when also referring to the OutputFolder metadata in the same Task, that became part of the batching as well resulting in the following never printing to the output:
<Message Condition="'%(Identity)' != '' and
'#(OutputProjects)' != '' and
'#(OutputNames)' != ''"
Text="Found a match for %(Identity): %(OutputNames.OutputFolder)" />
But, by using a transform over the property instead of direct access, it isn't treated as part of the batching:
<Message Condition="'%(Identity)' != '' and
'#(OutputProjects)' != '' and
'#(OutputNames)' != ''"
Text="Found a match for %(Identity): #(OutputNames->'%(OutputFolder)')" />
Therefore, I can do the following to update my metadata:
<!-- Now override specific projects OutputTo metadata -->
<OutputProjects Condition="'%(Identity)' != '' AND
'#(OutputProjects)' != '' AND
'#(OutputNames)' != ''">
<OutputTo>#(OutputNames->'%(OutputFolder)')</OutputTo>
</OutputProjects>

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.