I have a set of tools that need to be deployed on NET 3.5 or NET 4.0 depending on an MsBuild condition. At the moment we would like to change the project file of those utilities to handle this.
We are aware that we can do something like this:
<TargetFrameworkVersion Condition="">v3.5</TargetFrameworkVersion>
What is not clear for us is how can we specify different versions of NET depending on the condition. The condition property is an int that returns a number, between 1 and 4 and depending on that value we should target a different NET framework and of course change also this property in the app.config
<startup>
<supportedRuntime version="v2.0.50727"/>
</startup>
I want to know what is the right way of handling this type of problem.
You can give the condition like this one after other in your MSBuild file.
<TargetFrameworkVersion Condition="$(ConditionProperty) == '1'">v1.1.xxxx</TargetFrameworkVersion>
<TargetFrameworkVersion Condition="$(ConditionProperty) == '2'">v2.0.xxxx</TargetFrameworkVersion>
<TargetFrameworkVersion Condition="$(ConditionProperty) == '3'">v3.5.xxxx</TargetFrameworkVersion>
<TargetFrameworkVersion Condition="$(ConditionProperty) == '4'">v4.0.xxxx</TargetFrameworkVersion>
Accordingly you can write the code to change the value of
<startup>
<supportedRuntime version="v2.0.50727"/>
</startup>
in your app.cofig file as well by using the value of the variable $(TargetFrameworkVersion) using the code below:
<XmlUpdate XmlFileName="app.config"
XPath="//startup/supprtedRuntime[#version]"
Value="$(TargetFrameworkVersion)" />
Related
The MSBuild documentation hints in several places that Items aren't necessarily the same as files.
"MSBuild items are inputs into the build system, and they typically represent files."
"Items are objects that typically represent files."
However, I can't seem to find any examples where Items do not represent files. In particular, I would like to perform batching over a set of non-file items. But every item that I create, even from a custom build task, somehow acquires file-like metadata (FullPath, RootDir, Filename, Extension, etc.). Furthermore, I'm confused about the ramifications of setting the Inputs of a target to a set of items that aren't files, and what to use as that target's Outputs.
Does anybody have an example of using non-file Items to perform batching in MSBuild?
edit
Sorry for taking so long to come up with an example. I understand things a bit more, but I'm still uncertain (and there seems to be a complete lack of documentation about this). Everything here is going off my recollection; I'm not at my work computer right now, so I can't verify any of it.
In my experience, MSBuild doesn't like to build multiple configurations of a .sln file in one go. So, this:
msbuild.exe SampleMSBuild.sln /p:Configuration=Debug%3BRelease
(The encoded semicolon being necessary so that it doesn't try to define multiple properties.)
Produces this:
"D:\src\SampleMSBuild\SampleMSBuild.sln" (default target) (1) ->
(ValidateSolutionConfiguration target) ->
D:\src\SampleMSBuild\SampleMSBuild.sln.metaproj : error MSB4126: The
specified solution configuration "Debug;Release|Any CPU" is invalid.
Please specify a valid solution configuration using the Configuration
and Platform properties (e.g. MSBuild.exe Solution.sln
/p:Configuration=Debug /p:Platform="Any CPU") or leave those properties
blank to use the default solution configuration.
[D:\src\SampleMSBuild\SampleMSBuild.sln]
So, it seems like it should be possible to use batching and items to handle this.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
DefaultTarget="Rebuild"
ToolsVersion="4.0">
<ItemGroup>
<Configurations Include="Debug" />-->
<Configurations Include="Release" />-->
</ItemGroup>
<UsingTask TaskName="LogMetadata"
TaskFactory="CodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<Items ParameterType="Microsoft.Build.Framework.ITaskItem[]"
Required="true" />
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs">
<![CDATA[
foreach (var item in Items) {
Console.Write(item.ItemSpec);
Console.Write(" {");
foreach (string metadataKey in item.MetadataNames) {
Console.Write(metadataKey);
Console.Write("=\"");
Console.Write(item.GetMetadata(metadataKey).
ToString().Replace("\"", "\\\""));
Console.Write("\" ");
}
Console.WriteLine("}");
}]]>
</Code>
</Task>
</UsingTask>
<Target Name="Rebuild">
<LogMetadata Items="%(Configurations.Identity)" />
</Target>
</Project>
Which produces this:
Debug {
FullPath="D:\src\SampleMSBuild\Debug"
RootDir="D:\"
Filename="Debug"
Extension=""
RelativeDir=""
Directory="src\SampleMSBuild\"
RecursiveDir=""
Identity="Debug"
ModifiedTime=""
CreatedTime=""
AccessedTime=""
}
Release {
FullPath="D:\src\SampleMSBuild\Release"
RootDir="D:\"
Filename="Release"
Extension=""
RelativeDir=""
Directory="src\SampleMSBuild\"
RecursiveDir=""
Identity="Release"
ModifiedTime=""
CreatedTime=""
AccessedTime=""
}
As you can see, the items have all kinds of file metadata attached to them. I can't drop the Include attribute, since it's required, but I could synthesize the items in a custom task. HOWEVER, when I do that, they still somehow magically gain all the same file metadata.
What are the ramifications of this? Since I haven't specified these as Inputs or Outputs to a target, will the file metadata cause any problems? Will the build system skip over targets, or build more than it needs to, because the files specified in the Items' FullPath metadata do not exist? What if those files did exist? Would it cause any problems?
I use items to build several solutions after each other and doing some "manual" task with them, therefore I define my own items:
<ItemGroup>
<MergeConfigurations Include="project1\project1.SDK.sln">
<MergeOutAssemblyName>product1.dll</MergeOutAssemblyName>
<MergePrimaryAssemblyName>project1.Interfaces.dll</MergePrimaryAssemblyName>
<SolutionBinaries>project1\bin\$(FlavorToBuild)</SolutionBinaries>
</MergeConfigurations>
<MergeConfigurations Include="project1\project1.Plugin.sln">
<MergeOutAssemblyName>product1.dll</MergeOutAssemblyName>
<MergePrimaryAssemblyName>project1.Interfaces.dll</MergePrimaryAssemblyName>
<SolutionBinaries>project1\bin\plugin\$(FlavorToBuild)</SolutionBinaries>
</MergeConfigurations>
<ItemGroup>
Then I use a target to take the information and do what is necessary:
<Target Name="MergeSolution"
Inputs="%(MergeConfigurations.Identity)"
Outputs="%(MergeConfigurations.Identity)\Ignore_this">
<PropertyGroup>
<MergeSolution>%(MergeConfigurations.Identity)</MergeSolution>
<MergeOutAssemblyName>%(MergeConfigurations.MergeOutAssemblyName)</MergeOutAssemblyName>
<MergePrimaryAssemblyName>%(MergeConfigurations.MergePrimaryAssemblyName)</MergePrimaryAssemblyName>
<SolutionBinaries>%(MergeConfigurations.SolutionBinaries)</SolutionBinaries>
</PropertyGroup>
[....]
</Target>
Hope this helps to point you in the direction you need.
Tricky question. I'm an MSBuild noob, and found myself needing to understand batching recently, so figured I'd share some of what I found.
In general, yes, it seems that most everywhere you see samples and discussions about ITaskItems they tend to be about files. But the underlying implementation is very flexible and can deal with many other things as well. In my case, I've been working with strings and XML data.
This MS article gives some great examples of non-file ItemGroups and metadata.
This article was the best summary I can find that talks about the mechanics of Items and how they are different than Properties. It also covers the whole bit about # and % syntax, converting between strings and Items, and a hint as to where those file metadata properties are coming from - MSBuild is optimized for it.
Whenever you have an interface used as a parameter to a task or whatever, there is going to be a default implementation of that interface somewhere. My guess is that your code sample is newing up a number of these default objects under the hood and those define the metadata that are created by default. If you were to implement this interface yourself, I'll wager you could change this behavior. Probably beyond the scope of the question though =)
i need your help. I am running into a situation. I am trying to copy certain binaries into a particular folder. I am adding those task into "AfterCompileSolution" . I know it is incorrect, bcos it's gonna execute this step after every solution is compiled.
Here is my situation, i tried adding a condition like a SolutionFileName, but i get empty result. The target doesn't get executed because the SolutionFileName parameter is empty.
So do you know of any parameter that i can use between solutiontobuild i.e i want to copy certain binaries only after solution "A" is completed and i want these parameters to be part of "AfterCompileSolution" or maybe "BeforeCompileSolution"
Please suggest
Thanks
Satesh
It's been a while since I've done this but I believe you reference the file name with a syntax such as:
<Target Name="AfterCompileSolution" DependsOnTargets="RandomPreReqTarget">
<SomeTask Condition="'%(SolutionToBuildItem.Identity)' == 'ConditionValue'" />
</Target>
Another cool thing you can do is product extra properties in your SolutionToBuild item and reference them as metadata also like:
<SolutionToBuild Include="$(SolutionRoot)\$(SourceBranch)\RandomDirectory\Project.csproj">
<Targets>Build</Targets>
<Properties>OutDir=$(RandomDirectory);Configuration=$(Configuration);Platform=AnyCPU</Properties>
<GAC>True</GAC>
</SolutionToBuild>
You would then be able to access the metadata like this:
<Target Name="AfterCompileSolution" DependsOnTargets="RandomPreReqTarget">
<SomeTask Condition="'%(SolutionToBuildItem.GAC)' == 'True'" />
</Target>
I am running the MSBuild task with ContinueOnError=true:
<MSBuild Projects="#(ComponentToDeploy)"
Targets="$(DeploymentTargets)"
Properties="$(CommonProperties);%(AdditionalProperties)"
ContinueOnError="true"
Condition="%(Condition)"/>
So my build always succeeds.
Is there a way to find out if any error occurs?
I could not find any Output of the MSBuild task containing this information.
The only way I know is to parse the log file for errors but it looks like a workaround for me.
(I am using MSBuild 4.0)
This is an answer to the last feedback of #Ilya.
I'm using feedback/answer because of the length and formatting restrictions of the comments.
Log is scoped to individual targets or to be more specific tasks...
This was indeed the first question arose when I was reading your comment with the suggestion to use Log.HasLoggedErrors: "Was is the scope of the Log?".
Unfortunately I was not be able to finde a proper documentation. MSND does not help much...
Why did you know it is scoped to the task?
I'm not in doubt about your statement at all! I'm just wondering if there is a proper documentation somewhere..
(I haven't been using MSBuild for years ;-)
In any case, what are you building as project?
My test projects are very simple.
MyTest.project
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="ElenasTarget" ToolsVersion="4.0">
<UsingTask AssemblyFile="$(MSBuildProjectDirectory)\MyCompany.Tools.MSBuild.Tasks.dll" TaskName="MSBuildWithHasLoggedErrors" />
<ItemGroup>
<MyProjects Include="CopyNotExistingFile.proj" />
</ItemGroup>
<Target Name="ElenasTarget">
<MSBuildWithHasLoggedErrors Projects="#(MyProjects)" ContinueOnError="true" >
<Output TaskParameter="HasLoggedErrors" PropertyName="BuildFailed" />
</MSBuildWithHasLoggedErrors>
<Message Text="BuildFailed=$(BuildFailed)" />
</Target>
</Project>
The CopyNotExistingFile.proj just tries to copy a file that does not exist:
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Target1" ToolsVersion="4.0">
<Target Name="Target1">
<Copy SourceFiles="C:\lalala.bum" DestinationFiles="C:\tralala.bam" />
</Target>
</Project>
And this is my custom task MSBuildWithHasLoggedErrors
namespace MyCompany.Tools.MSBuild.Tasks
{
public class MSBuildWithHasLoggedErrors : Microsoft.Build.Tasks.MSBuild
{
[Output]
public bool HasLoggedErrors { get; private set; }
public override bool Execute()
{
try
{
base.Execute();
HasLoggedErrors = Log.HasLoggedErrors;
}
catch (Exception e)
{
Log.LogErrorFromException(e, true);
return false;
}
return true;
}
}
}
If I build my MyTest.proj the HasLoggedErrorswill be set to false although an error (MSB3021) was logged(?) to the console logger:
Project "C:\Users\elena\mytest.proj" on node 1 (default targets).
Project "C:\Users\elena\mytest.proj" (1) is building "C:\Users\elena\CopyNotExistingFile.proj" (2) on node 1 (default targets).
Target1:
Copying file from "C:\lalala.bum" to "C:\tralala.bam".
C:\Users\elena\CopyNotExistingFile.proj(5,4): error MSB3021: Unable to copy file "C:\lalala.bum" to "C:\tralala.bam". Could not find file 'C:\lalala.bum'.
Done Building Project "C:\Users\elena\CopyNotExistingFile.proj" (default targets) -- FAILED.
ElenasTarget:
BuildFailed=False
Done Building Project "C:\Users\elena\mytest.proj" (default targets).
Build succeeded.
My expectation was HasLoggedErrors would be set to true.
one way is to build self but with different target, for example your DefaultTargets one launches your custom MSBuildWrapper task pointing to itself (ie $(MSBuildProjectFile)) but with a different target that does other builds, copies
I've already tried it (that were my investigations I meant in my post). Unfortunately it doesn't work either :-(
(I am aware you said in theory).
My new single project looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="ElenasTarget" ToolsVersion="4.0">
<UsingTask AssemblyFile="$(MSBuildProjectDirectory)\MyCompany.Tools.MSBuild.Tasks.dll" TaskName="MSBuildWithHasLoggedErrors" />
<Target Name="ElenasTarget">
<MSBuildWithHasLoggedErrors Projects="$(MSBuildProjectFile)" Targets="CopyNotExistingFile" ContinueOnError="true" >
<Output TaskParameter="HasLoggedErrors" PropertyName="BuildFailed" />
</MSBuildWithHasLoggedErrors>
<Message Text="BuildFailed=$(BuildFailed)" />
</Target>
<Target Name="CopyNotExistingFile" >
<Copy SourceFiles="C:\lalala.bum" DestinationFiles="C:\tralala.bam" />
</Target>
</Project>
If I build this project HasLoggedErrors will still be set to false.
(Furthermore, my "real" build I'm currently maintaining is much complexer containing several project files with targets... so I can't pack them all in a single project file ).
or writing custom logger and passing it through command line
That was my last hope!
My "real" build has a custom logger passed through the command line (I didn't use it for my test project for the sake of simplicity). That is actually producing the log (a XML file) I'm going to parse to find out if any errors have been logged.
BTW, I thought the console logger is a kind of "global" logger. Am I wrong?
Anyway, the custom logger does not help neither, the Log.HasLoggedErrors is still set to false.
Is there some way I am not aware of to reference a particular logger (e.g. my custom logger) to ask if it has logged any errors?
It really looks like Log is scoped to individual targets.
Hmm... if the reflection on the buildengine instance is the last resort I would still prefer parsing the log.
(Don't blame me! :-) )
My decision
After some investigations I've decided to stick with my initial solution: parse the log to find out if the build failed.
Check my comments to see why I prefer that to the suggestions have been provided so far.
If someone has some other ideas do not hesitate to share :-)
(Otherwise this question can be closed, I suppose...)
The MSBuildLastTaskResult reserved property will be set to True if the last task succeeded and False if the last task failed:
<MSBuild Projects="#(ComponentToDeploy)"
Targets="$(DeploymentTargets)"
Properties="$(CommonProperties);%(AdditionalProperties)"
ContinueOnError="true"
Condition="%(Condition)" />
<Message Text="MSBuild failed!" Condition="'$(MSBuildLastTaskResult)' == 'False'" />
I believe this was introduced with MSBuild v4.0.
I know this thread is a bit old, but another possible solution, as I presume you needed to know that build failed in order to execute some "final task", is to use:
<OnError ExecuteTargets="FinalReportTarget;CleanupTarget" />
That would fail the build in case of error, but execute the "FinalReportTarget" and "CleanupTarget".
ContinueOnError="true" is not needed in this case.
You could capture TargetOutputs and check them for error conditions afterwards, but that's still quite hackish.
If you only want to check if MSBuild task failed, use Exec task. Set IgnoreExitCode to true and check ExitCode output value. If not zero, something is wrong.
If you need the list of build errors, use /fileloggerparameters command line switch to log errors only to some specific file:
/flp1:logfile=errors.txt;errorsonly
But if another task inside some target (e.g. Copytask) raised an error the Log.HasLoggedErrors returns false.
Didn't know comments have length limits...
Log is scoped to individual targets or to be more specific tasks, and (as far as I'm aware) there is no way to get a "global" one, may be through reflection on the buildengine instance, or writing custom logger and passing it through command line. In any case, what are you building as project? HasLoggedErrors works as expected (and has been working unchanged for years), it shows if project being built logged any errors. It doesn't, and shouldn't, have any control over logging of other tasks (that might use other types of loggers). If you want a global one, one way is to build self but with different target, for example your DefaultTargets one launches your custom MSBuildWrapper task pointing to itself (ie $(MSBuildProjectFile)) but with a different target that does other builds, copies, etc, in theory it should simulate a global HasLoggedErrors...
I want to hard-code the date of build into my assembly.
I can easily do that manually but is there some way it can be achieved by the build process?
See http://social.msdn.microsoft.com/Forums/ar/tfsbuild/thread/7fdeabcc-2ef1-4c4f-9798-b69ebee0c3a3
Once you are able to get the timestamp, you can place it in your property like so:
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>BUILD_TIME=$(Timestamp);%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
So I'm kinda getting the hang of writing in MSBuild.
I like the idea storing things in ItemGroups because its easy to iterate, and you can have multple fields.
That makes them sort of like a class (more like a struct) but they respond to the iteration syntax of targets and tasks in a way that feels like a lambda expression.
However I continue to run into situation where I want to access a value of a particular item in an item group, that is, to access it like a property.
In that case I run into the issue of how to isolate a single item within the group.
When the item group is batching in a target or task, because of addressing using a metadata name or because of addressing with the common itemgroup name, you can utilize the value of the 'current' item i.e. #(ItemsName) or %(MetaDataName).
But I want to do things like: use an Item group as a System.Configuration class that contains the values of the entries in a section of a config file. Therefore the normal thing would be to name the ItemGroup itself to match the section name in the config file. however, the ItemGroup is not an addressable element that is accessible through the build engine interface, only the items themselves are addressable.
It might be nice to individually name the items in an ItemGroup rather than name them all the same and use the Include or a metadata field like to distinguish among them. This makes them behave like properties in that they are individually addressable as distinct items. so you could easily use their values in Conditions this way: '#(UniqueItemName->'%(Value)').
However, then the iterable features are essentially lost.
To narrow this down, presume i have a config file that gets read into an Item group by and xml task so that the element names in a section become the name of the items in the item group and attributes of each config file element are attributes that become metadata:
<configItemFlag name="displayDebugMessages" value="true" note="use with abandon" />
<configItemFlag name="displaySecurityValueMessages" value="false" note="use with caution" />
When I want to test this in a Condition, I need to narrow it down to something like this:
<Messge Text="Debug Message: you are debugging!" Condition="'#(configItemFlag->'%(Name)')' == 'displayDebugMessages' AND '#(configItemFlag->'%(Value)')' == 'true'/>
But this only evaluates the comparison and frequently does not evaluate to a single boolean.
So is there any way to syntacticly get this down to a dependable test?
Does this work for what you are trying to do?
<ItemGroup>
<ConfigItemFlag Include="displayDebugMessages">
<Value>true</Value>
<Note>use with abandon</Note>
</ConfigItemFlag>
<ConfigItemFlag Include="displaySecurityValueMessages">
<Value>false</Value>
<Note>use with caution</Note>
</ConfigItemFlag>
</ItemGroup>
<Target Name="Build">
<Message
Condition="
'%(ConfigItemFlag.Identity)' == 'displayDebugMessages' AND
'%(Value)' == 'true'"
Text="Debug Message: you are debugging, %(Note)!"
/>
</Target>
Output:
Build:
Debug Message: you are debugging, use with abandon!
(response to comment)
...the only thing I can offer to be able to use meta as properties isn't all that great, unless the target will make heavy use of them throughout. Basically it involves flattening each item to properties by batching on the item and creating local properties with each batch.
<Target Name="BuildOne"
Outputs="%(ConfigItemFlag.Identity)">
<!-- flatten this batch to properties -->
<PropertyGroup>
<_Identity>%(ConfigItemFlag.Identity)</_Identity>
<_Value>%(ConfigItemFlag.Value)</_Value>
<_Note>%(ConfigItemFlag.Note)</_Note>
</PropertyGroup>
<!-- use meta as properties -->
<Message
Condition="
'$(_Identity)' == 'displayDebugMessages' AND
'$(_Value)' == 'true'"
Text="Debug Message: you are debugging, $(_Note)!"
/>
</Target>
<Target Name="Build" DependsOnTargets="BuildOne"
/>
It seems like you are running into some of the limitations of the msbuild scripting language. Have you thought about writing a custom task to perform what you are looking for? That way you would be able to bring the full power of a full programming language to bear against the simple conditional check you want to perform.