How to output the duration of certain msbuild targets? - msbuild

Today I copy/paste the following in each and every target I want to measure and log its duration:
<Target Name="YabaDabaDoo" ...>
<PropertyGroup>
<Start>$([System.DateTime]::Now.Ticks)</Start>
</PropertyGroup>
...
<PropertyGroup>
<End>$([System.DateTime]::Now.Ticks)</End>
<Ticks>$([MSBuild]::Subtract($(End), $(Start)))</Ticks>
<Elapsed>$([MSBuild]::Divide($(Ticks), 10000000))</Elapsed>
</PropertyGroup>
<Message Text="Duration: $(Elapsed) seconds" Importance="High"/>
</Target>
I do not like it because it is too verbose and sometimes the target logic proper takes less space than duration logging.
Is there a way to implement it more concisely?

Have you considered implementing a logger that would do that? See http://msbuildlog.com for an example.
Here's the documentation on how to implement your own logger:
https://learn.microsoft.com/en-us/visualstudio/msbuild/build-loggers?view=vs-2017
If you run msbuild.exe /bl and then open the resulting .binlog in MSBuild Structured Log Viewer, it will show the durations of all targets in a timeline view:

Related

How to add an item only when an incrementally executed target is run?

Say we have the following MSBuild project that defines a target which can be partially run:
<Project DefaultTargets="Foo">
<ItemGroup>
<MyInputs Include="**/*.json"/>
</ItemGroup>
<Target Name="Foo"
Condition="'#(MyInputs)' != ''"
Inputs="#(MyInputs)"
Outputs="#(MyInputs->'%(FileName).cs')">
<MyCustomTask FileToProcess="%(MyInputs.Identity)"/>
<ItemGroup>
<Compile Include="%(MyInputs.FileName).cs"/>
</ItemGroup>
</Target>
</Project>
The problem is that all items are included into ProcessedFiles; even these whose respective MyCustomTasks are not run, due to incremental building. Apparently, MSBuild always processes ItemGroups inside targets.
Is there a way to add an item inside a target, only when the respective target batch is run? I tried using CreateItem, because it is a task and might not get executed just like MyCustomTask, but it didn't work.
My specific problem was that when the source files had already existed, they were included twice in the Compile item, which raised a warning. It was then when I learned about the KeepDuplicates attribute that saved me.
But the question still stands.

Can a task ItemGroup glob files?

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>

Proper use of msbuild inputs and outputs with Targets

We have an in house compiler and linker tool that we're trying to make MSBUILD compatible (i.e. proper behavior in build/incremental builds/rebuild/clean scenarios)
In the first step we actually call CL task to preprocess our files. The issue is I can't seem to figure out how to set up the tasks properly to do that so it detects if the output is deleted or if one of the inputs is modified.
Second step is call our Compiler with it's proper parameters.
Third step is to call our Linker with it's proper parameters.
I think once step one works making step two and three will be simple; I'm stuck on step one. Example code below. The main MFT contains "#includes" which reference all the other MFT files named in _MFTFiles - so we only need to process the main file; but we need to monitor them all so if we change them incremental builds work properly. If anyone has any idea I'd love to hear it. I have the MSBUILD book and of course scoured here but I don't see an example of what I'm trying to accomplish.
Thanks in advance.
<ItemGroup Label="_MainMFT">
<MainMFT Include="MFTSystem.MFT"/>
</ItemGroup>
<ItemGroup Label="_MFTFiles">
<MFTFiles Include="MFTbject.MFT;DebuggerSupport.MFT;enumerations.MFT;collections.MFT;DataStream.MFT;Version.MFT"/>
</ItemGroup>
<Target Name="_PreprocessFiles"
BeforeTargets="Build"
DependsOnTargets=""
Inputs="#(MFTFiles)"
Outputs="#(MFTFiles->'%(Filename).MFTpp')">
<Message Text="PlatformToolsetVersion is $(PlatformToolsetVersion)" Importance="high"/>
<CL Sources="#(MainMFT)" PreprocessorDefinitions="_DEBUG;EL_DIAG_ENABLED" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" PreprocessToFile="true" PreprocessOutputPath="$(ProjectDir)%(Filename).MFTpp" />
<CL Sources="#(MainMFT)" PreprocessorDefinitions="" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" PreprocessToFile="true" PreprocessOutputPath="$(ProjectDir)%(Filename).MFTpp"/>
</Target>
<Target Name="_ObjectCompiler" AfterTargets="_PreprocessFiles;Build">
<Message Text="Calling ObjectCompiler...." Importance="high"/>
</Target>
<Target Name="_ObjectLinker" AfterTargets="_ObjectCompiler;Link">
<Message Text="Calling ObjectLinker...." Importance="high"/>
</Target>

MSBuild WriteLinesToFile without new line at the end

I want to write a number in a text file using WriteLinesToFile but the task is putting a line feed at the end which causes me trouble when i want to read or combine in other places
Example:
<WriteLinesToFile File="$(TextFile)" Lines="#(BuildNumber)" Overwrite="true"/>
UPDATE as the user comment below:
The problem that I had was that I was using a very simple command in Property to read the content of a file $([System.IO.File]::ReadAllText("$(TextFile)")) and I really want to use this one but it also included the line feed from WriteLinesToFiles. I ended up using similar solution like yours using ReadLinesFromFile.
There is a slight dissconnect between the title and the description. I would have liked to post this "answer" as an edit, but do not have enough reputation points :)
Do you have a problem with the newline at the end of a file, or do you have a problem ignoring that newline? Could you please clarify?
One way how I suppose you could ignore that newline follows.
This small snippet of code writes a build number to a file, then reads it out and then increments the number read by 1.
<Target Name="Messing_around">
<PropertyGroup>
<StartBuildNumber>1</StartBuildNumber>
</PropertyGroup>
<ItemGroup>
<AlsoStartBuildNumber Include="1"/>
</ItemGroup>
<!-- Use a property
<WriteLinesToFile File="$(ProjectDir)test.txt" Lines="$(StartBuildNumber)" Overwrite="true"/>
-->
<WriteLinesToFile File="$(ProjectDir)test.txt" Lines="#(AlsoStartBuildNumber)" Overwrite="true"/>
<ReadLinesFromFile File="$(ProjectDir)test.txt">
<Output
TaskParameter="Lines"
ItemName="BuildNumberInFile"/>
</ReadLinesFromFile>
<PropertyGroup>
<OldBuildNumber>#(BuildNumberInFile)</OldBuildNumber>
<NewBuildNumber>$([MSBuild]::Add($(OldBuildNumber), 1))</NewBuildNumber>
</PropertyGroup>
<Message Importance="high" Text="Stored build number: #(BuildNumberInFile)"/>
<Message Importance="high" Text="New build number: $(NewBuildNumber)"/>
</Target>
And this is what I see
Output:
1>Build started xx/xx/xxxx xx:xx:xx.
1>Messing_around:
1> Stored build number: 1
1> New build number: 2
1>
1>Build succeeded.
If you attempting to read, in an MSBuild Task, a single line containing only a number from a file with a trailing line feed, then you should not have a problem.
As a side note: With the little information at hand I'd assume that BuildNumber is an Item in an ItemGroup. If you have only one build number to deal with, perhaps Property may have been an option. But then, again, I haven't been tinkering with MSBuild for too long. So, I am open to feedback on the Item vs Property issue.

Eliminate repetition in msbuild Targets? (especially repetition of item identifiers, which can't expand Property's in Exec Command attributes)

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>