Is there a way to print a new-line when using <Message...>? - msbuild

I wanna write <Message Text="Line1\nLine2\nLine3" /> but \n seems not to be working. What should I replace \n with?
(I read in the books they said that to print # and % we use %40 and %25, so I guess the should be a number for the new-line).

Try this:
<Message Text="Line1%0aLine2%0aLine3%0a" />

CR = 0x0D or 13
LF = 0x0A or 10

Here's a better way. Use the "Escape()" property function [1] in a trivial msbuild file like so:
<Message Text="$([MSBuild]::Escape('A\r\nB\r\nC\r\n'))" />
The 'Escape()' property function should do all the magic in terms of escaping exotic characters.
[1] https://learn.microsoft.com/en-us/visualstudio/msbuild/how-to-escape-special-characters-in-msbuild?view=vs-2017 & https://learn.microsoft.com/en-us/visualstudio/msbuild/property-functions?view=vs-2017

You could put the lines into an item group
<ItemGroup>
<Lines Include="Line 1 " />
<Lines Include="Line 2 " />
<Lines Include="Line 3 " />
</ItemGroup>
<Message Text="%(Lines.Identity)"/>

Put the multiline message in a property (in the example below called MyMultilineMessage) and then use that property in the message text. (Works at least with MSBuild for VisualStudio 2019)
<PropertyGroup>
<MyMultilineMessage>
Lorum ipsum...
</MyMultilineMessage>
</PropertyGroup>
<Message Importance="High" Text="$(MyMultilineMessage)"/>
This also works for Error elements as well.
Edit: Modified XML example above after comment about missing PropertyGroup.

Related

MSBUILD Splitting text file into lines

Note that I have already went through:
Is there a way to print a new-line when using <Message...>?
Read text file and split every line in MSBuild
But for some strange reason I can't make it work.
I have:
<ReadLinesFromFile File="$(OutputPath)myfile.log">
<Output PropertyName="FileOutput" TaskParameter="Lines" />
</ReadLinesFromFile>
<Message Text="$(FileOutput)"/>
-- This works, entire file content is shown on the screen.
Now I would like for each line in that file to report a warning/error.
<ItemGroup>
<SplitVersion Include="$(FileOutput.Split('%0A%0D'))"/>
</ItemGroup>
<Warning Text="%(SplitVersion.Identity)" />
Whatever combination I try in Split (e.g. \n, \r\n, %0A etc.) I get only one warning instead of getting one warning per line.
You are storing the lines in a property (typo maybe? Anyway I didn't even know was possible until now - it is also not mentioned in the documentation), store them in an item list instead and you'll get the lines, split already by ReadLinesFromFile so you don't have to bother with it, and after all that's the main way it is supposed to be used. Note the ItemName where you had PropertyName:
<ReadLinesFromFile File="$(OutputPath)myfile.log">
<Output ItemName="FileOutput" TaskParameter="Lines" />
</ReadLinesFromFile>
<Message Text="%(FileOutput.Identity)"/>

Use uglifyjs in Publish Profile

This is a snippet of my Publish Profile:
<Exec WorkingDirectory="$(_PackageTempDir)"
Command="uglifyjs ..\..\..\..\js\file1.js ..\..\..\..\js\file2.js --mangle --reserved "$" --compress > js\outfile.min.js" />
Certain files (say file1.js) is located outside my project and therefore is not copied to the _PackageTempDir. Here I have to ..\ up several levels to get there. I'm wondering if there is a good way to use an ItemGroup or full path that will allow me the same results.
The above code "works". It is just complicated and difficult to maintain. Looking for a better solution.
EDIT:
Based on Sayed's suggestions, I refined my profile to:
<ItemGroup>
<UglifyJSFiles Include="$(MSBuildThisFileDirectory)..\..\js\mcm\mcm.js" />
<UglifyJSFiles Include="$(_PackageTempDir)\js\main.js" />
</ItemGroup>
<Exec WorkingDirectory="$(_PackageTempDir)"
Command="uglifyjs #(UglifyJSFiles,' ') > js\app.min.js" />
But I am running into an issue because the paths contain spaces. How can I either quote the path strings or escape the spaces?
Here is an example showing a better approach
<PropertyGroup>
<JsFilesToUglifyRoot Condition=" '$(JsFilesToUglifyRoot)'=='' ">$(MSBuildThisFileDirectory)\..\..\..\..\js\</JsFilesToUglifyRoot>
</PropertyGroup>
<ItemGroup>
<JsFilesToUglify Include="$(JsFilesToUglifyRoot)**\*.js" />
</ItemGroup>
<Target Name="AfterBuild">
<Message Text="Files: [#(JsFilesToUglify,' ')]" Importance="high" />
<!-- If you need to quote the file paths use the transform below -->
<Message Text="Files: [#(JsFilesToUglify->'"%(FullPath)"',' ')]" Importance="high" />
</Target>
Here I define a new property JsFilesToUglify that is populated with the path you indicated above. Note the usage of the MSBuildThisFileDirectory reserved property. You should not rely on just ..\ as its value may be different in VS versus outside of VS. Also do not use the MSBuildProjectDirectory property, only MSBuildThisFileDirectory.
Then inside of the target I transform the list of files with #(JsFilesToUglify,' ') the ,' ' makes a space the separator between values like your command above.

Read text file and split every line in 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

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

How to set 'condition' using a condition stored in a property?

I have a condition such as 'a==1' stored in property $(c) and I wanna used it as the condition for task Message like below code:
<PropertyGroup>
<aa>1>2</aa>
</PropertyGroup>
<Target Name="t">
<Message Text="122333" Condition="$(aa)" />
</Target>
Error was raised!
So, how can I do it? Please help!
You can easily use property values for evaluating conditions. Here is an example:
<PropertyGroup>
<aa>1</aa>
</PropertyGroup>
<Target Name="Build">
<Message Text="Some text" Condition=" $(aa) < 2 " />
</Target>
Note that:
Property values are strings, you must evaluate the condition in the Condition attribute. See MSDN Docs on evaluating conditions.
You must escape XML characters (replace < with < )