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.
Related
I have an ItemGroup declared as follows:
<ItemGroup>
<MyCustomProjectType Include="..\path_to_my_project">
<Name>MyProjectName</Name>
</MyCustomProjectType>
</ItemGroup>
This is a custom project type that I want to perform some specific manipulations on.
Later I have a Target (example only but it communicates what I am after):
<Target Name="MyTarget">
<ItemGroup>
<CustomProjectReferenceFiles
KeepMetadata="Name"
Include="#(MyCustomProjectType->'%(Identity)\%(Name)\**\*')"
Exclude="**\*.x;**\*.y"
/>
</ItemGroup>
<Message Text="#(CustomProjectReferenceFiles)" />
</Target>
So I have a Target based ItemGroup where I am attempting, using a transform, to create a new Include. This does run, but it appears the Include is literally set to:
..\path_to_my_project\MyProjectName\**\*
AKA that glob/wildcards are not expanded.
I'm pretty new to MSBuild so maybe I am missing something in my search of the documentation. One solution I thought of here would be just just create a new Custom Task that handles pulling out the files I need and setting that Output on an intermediate Target.
I also found this SO question:
https://stackoverflow.com/a/3398872/1060314
Which brings up the point about CreateItem being deprecated which leaves me with not knowing what the alternatives are.
The easiest way is to use an intermediate property so that the actual text is used and not the escaped transformed items:
<PropertyGroup>
<_CustomProjectReferenceFileIncludes>#(MyCustomProjectType->'%(Identity)\%(Name)\**\*')</_CustomProjectReferenceFileIncludes>
</PropertyGroup>
<ItemGroup>
<CustomProjectReferenceFiles
KeepMetadata="Name"
Include="$(_CustomProjectReferenceFileIncludes)"
Exclude="**\*.x;**\*.y"
/>
</ItemGroup>
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.
I would like to minimize repetition in my msbuild xml, particularly inside my Target's Exec Command attribute.
1) Most importantly, in the following Target I would like to un-duplicate the pair of "%(ShaderVertex.Identity)" constructs in the Exec Command attribute (for example, the Property VertexTargetName is defined to be "ShaderVertex", but %($(VertexTargetName).Identity) will not expand $(VertexTargetName)):
<Target Name="ShaderVertexBuild"
Inputs="#($(VertexTargetName))"
Outputs="#($(VertexTargetName)$(OutputRelPathAndFileName))">
<Exec Command="$(FxcPathFull)v$(ShaderArgNoFirstLetter) %(ShaderVertex.Identity)$(ShaderOutputFlagAndRelPath)%(ShaderVertex.Identity)$(OutputFileExtLetterToAppend)">
</Exec>
</Target>
2) It would also be nice not to duplicate the "ShaderVertex" identifier in the ItemGroup that precedes my Target, but again I can't expand a $(VertexTargetName) in the context of an ItemGroup.
<ItemGroup>
<ShaderPixel Include="p*.fx"/>
<ShaderVertex Include="v*.fx"/>
</ItemGroup>
3) Less importantly, it would be nice to be able to further reduce duplicate constructs between my Target's. Using the above example, I'd like to simply pass $(VertexTargetName) and the letter "v" (which tells the HLSL compiler to compile a vertex shader) to a magical Target generator that fills in all the data identical to all Targets -- or even better, just pass $(VertexTargetName) and allow the Target generator to infer from this argument that a "v" needs to be passed (instead of, say, a "p" for PixelShader, which could be inferred from $(PixelTargetName)).
Any ideas?
Caveats: I know that msbuild is not a functional programming language, and therefore may not be able to accommodate my desires here. I also know that VS2012 handles HLSL compilation automatically, but I'm on VS2010 for now, and would like to know enough about msbuild to accomplish this sort of task.
Here is the complete xml listing:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- TODO NTF: does not take into account *.inc dependency -->
<Target Name="Main">
<CallTarget Targets="ShaderPixelBuild"/>
<CallTarget Targets="ShaderVertexBuild"/>
</Target>
<ItemGroup>
<ShaderPixel Include="p*.fx"/>
<ShaderVertex Include="v*.fx"/>
</ItemGroup>
<PropertyGroup>
<ShaderModelNum>4</ShaderModelNum><!-- Radeon 8600 GTS doesn't support shader model 5 -->
<ShaderArgNoFirstLetter>s_$(ShaderModelNum)_0</ShaderArgNoFirstLetter><!-- first letter defines shader type; v=vertex, p=pixel-->
<FxcPathFull>"%DXSDK_DIR%\Utilities\bin\x64\fxc.exe" /T </FxcPathFull>
<ShaderOutputRelPath>..\..\x64\shadersCompiled\</ShaderOutputRelPath>
<ShaderOutputFlagAndRelPath> /Fo $(ShaderOutputRelPath)</ShaderOutputFlagAndRelPath>
<OutputFileExtLetterToAppend>o</OutputFileExtLetterToAppend>
<OutputRelPathAndFileName>->'$(ShaderOutputRelPath)%(identity)$(OutputFileExtLetterToAppend)'</OutputRelPathAndFileName>
<!-- Note that the content of each of these properties must be duplicated in the Exec Command twice, since %($(PropertyName)) will not expand there -->
<PixelTargetName>ShaderPixel</PixelTargetName>
<VertexTargetName>ShaderVertex</VertexTargetName>
</PropertyGroup>
<Target Name="ShaderPixelBuild"
Inputs="#($(PixelTargetName))"
Outputs="#($(PixelTargetName)$(OutputRelPathAndFileName))">
<Exec Command="$(FxcPathFull)p$(ShaderArgNoFirstLetter) %(ShaderPixel.Identity)$(ShaderOutputFlagAndRelPath)%(ShaderPixel.Identity)$(OutputFileExtLetterToAppend)">
</Exec>
</Target>
<Target Name="ShaderVertexBuild"
Inputs="#($(VertexTargetName))"
Outputs="#($(VertexTargetName)$(OutputRelPathAndFileName))">
<Exec Command="$(FxcPathFull)v$(ShaderArgNoFirstLetter) %(ShaderVertex.Identity)$(ShaderOutputFlagAndRelPath)%(ShaderVertex.Identity)$(OutputFileExtLetterToAppend)">
</Exec>
</Target>
</Project>
I'm trying to output the variable from one target, into the parent target which started it. For example,
Target 1 simply calls the task in file 2 and is supposed to be able to use the variable set within that. However, I just can't seem to get it to work (wrong syntax perhaps?). Target 1 looks like this:
<Target Name="RetrieveParameter">
<MSBuild Projects="$(MSBuildProjectFile)" Targets="ObtainOutput" />
<Message Text="Output = $(OutputVar)" />
</Target>
Target 2 is where it reads in the value of the text file and sets it to the property and sets the variable 'OutputVar' to match. This is supposed to be returned to the parent.
<Target Name="ObtainOutput" Outputs="$(OutputVar)">
<ReadLinesFromFile File="output.txt">
<Output TaskParameter="Lines"
PropertyName="OutputVar" />
</ReadLinesFromFile>
</Target>
I'm quite new to MSBuild tasks, so it could well be something obvious. All I want to do is set a variable in one task, and then have that available in the parent task which called it.
Julien has given you the right answer, but not explained why it is correct.
As you're new to MSBuild tasks, I'll explain why Julien's answer is correct.
All tasks in MSBuild have parameters - you will know them as the attributes that you put on the task. Any of these parameters can be read back out by placing an Output element within it. The Output element has three attributes that can be used:
TaskParameter - this is the name of the attribute/parameter on the task that you want to get
ItemName - this is the itemgroup to put that parameter value into
PropertyName - this is the name of the property to put that parameter value into
In your original scripts, you were invoking one from the other. The second script will execute in a different context, so any property or itemgroup it sets only exists in that context. Therefore when the second script completes, unless you have specified some Output elements to capture values they will be discarded.
Note that you can put more than one Output element under a task to capture multiple parameters or just set the same value to multiple properties/itemgroups.
You have to use TargetOutputs of the MSBuild task:
<Target Name="RetrieveParameter">
<MSBuild Projects="$(MSBuildProjectFile)" Targets="ObtainOutput">
<Output TaskParameter="TargetOutputs" ItemName="OutputVar"/>
</MSBuild>
<Message Text="Output = #(OutputVar)" />
</Target>
(More information on MSBuild task.)
I have a script that attempts to construct an ItemGroup out of all files in a certain directory while excluding files with certain names (regardless of extension).
The list of files to be excluded initially contains file extensions, and I am using Community Tasks' RegexReplace to replace the extensions with an asterisk. I then use this list in the item's Exclude attribute. For some reason the files do not get excluded properly, even though the list appears to be correct.
To try and find the cause I created a test script (below) which has two tasks: first one initialises two properties with the list of file patterns in two different ways. The second task prints both properties and the files resulting from using both these properties in the Exclude attribute.
The properties' values appear to be identical, however the resulting groups are different. How is this possible?
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
DefaultTargets="Init;Test" ToolsVersion="3.5">
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
<Target Name="Init">
<ItemGroup>
<OriginalFilenames Include="TestDir\SampleProj.exe"/>
<OriginalFilenames Include="TestDir\SampleLib1.dll"/>
</ItemGroup>
<RegexReplace Input="#(OriginalFilenames)" Expression="\.\w+$" Replacement=".*">
<Output TaskParameter="Output" ItemName="PatternedFilenames"/>
</RegexReplace>
<PropertyGroup>
<ExcludeFilesA>TestDir\SampleProj.*;TestDir\SampleLib1.*</ExcludeFilesA>
<ExcludeFilesB>#(PatternedFilenames)</ExcludeFilesB>
</PropertyGroup>
</Target>
<Target Name="Test">
<Message Text='ExcludeFilesA: $(ExcludeFilesA)' />
<Message Text='ExcludeFilesB: $(ExcludeFilesB)' />
<ItemGroup>
<AllFiles Include="TestDir\**"/>
<RemainingFilesA Include="TestDir\**" Exclude="$(ExcludeFilesA)"/>
<RemainingFilesB Include="TestDir\**" Exclude="$(ExcludeFilesB)"/>
</ItemGroup>
<Message Text="
**AllFiles**
#(AllFiles, '
')" />
<Message Text="
**PatternedFilenames**
#(PatternedFilenames, '
')" />
<Message Text="
**RemainingFilesA**
#(RemainingFilesA, '
')" />
<Message Text="
**RemainingFilesB**
#(RemainingFilesB, '
')" />
</Target>
</Project>
Output (reformatted somewhat for clarity):
ExcludeFilesA: TestDir\SampleProj.*;TestDir\SampleLib1.*
ExcludeFilesB: TestDir\SampleProj.*;TestDir\SampleLib1.*
AllFiles:
TestDir\SampleLib1.dll
TestDir\SampleLib1.pdb
TestDir\SampleLib2.dll
TestDir\SampleLib2.pdb
TestDir\SampleProj.exe
TestDir\SampleProj.pdb
PatternedFilenames:
TestDir\SampleProj.*
TestDir\SampleLib1.*
RemainingFilesA:
TestDir\SampleLib2.dll
TestDir\SampleLib2.pdb
RemainingFilesB:
TestDir\SampleLib1.dll
TestDir\SampleLib1.pdb
TestDir\SampleLib2.dll
TestDir\SampleLib2.pdb
TestDir\SampleProj.exe
TestDir\SampleProj.pdb
Observe that both ExcludeFilesA and ExcludeFilesB look identical, but the resulting groups RemainingFilesA and RemainingFilesB differ.
Ultimately I want to obtain the list RemainingFilesA using the pattern generated the same way ExcludeFilesB is generated. Can you suggest a way, or do I have to completely rethink my approach?
The true cause of this was revealed accidentally when a custom task threw an exception.
The actual value of ExcludeFilesA is TestDir\SampleProj.*;TestDir\SampleLib1.* like one might expect. However the actual value of ExcludeFilesB is TestDir\SampleProj.%2a;TestDir\SampleLib1.%2a.
Presumably Message unescapes the string before using it, but Include and Exclude do not. That would explain why the strings look the same but behave differently.
Incidentally, the execution order doesn't seem to have anything to do with this, and I'm pretty sure (following extensive experimentation) that everything gets executed and evaluated exactly in the order in which it appears in this script.
ItemGroups need to be evaluated before targets execution, and the PatternedFilenames ItemGroup is being created on the fly within its target container.
You could workaround this using the CreateItem task, which will ensure the PatternedFilenames scope throughout the execution:
<RegexReplace Input="#(OriginalFilenames)" Expression="\.\w+$" Replacement=".*">
<Output TaskParameter="Output" ItemName="PatternedFilenames_tmp"/>
</RegexReplace>
<CreateItem Include="#(PatternedFilenames_tmp)">
<Output TaskParameter="Include" ItemName="PatternedFilenames"/>
</CreateItem>