Expand MSBuild Properties containing wildcard into Items - c#

I am trying to write MSBuild script that will perform some action (eg. print its path) on an arbitrary files (specified as a property on a command line) in some predefined directory (F:\Files).
Given the following directory structure
F:\Files\TextFile.txt
F:\Files\Subdir1\ImageFile.bmp
F:\Files\Subdir1\SubSubdir\ImageFile2.bmp
F:\Files\Subdir1\SubSubdir\TextFile2.txt
And MSBuild Script
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="PrintNames" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<TargetDir>F:\Files</TargetDir>
</PropertyGroup>
<ItemGroup>
<Files Include="$(TargetDir)\$(InputFiles)"/>
</ItemGroup>
<Target Name="PrintNames">
<Message Text="Files: #(Files, ', ')" />
</Target>
</Project>
running the script with InputFiles set to "**\*.bmp;**\*.txt" works fine only for bmp files. Txt files are taken from the current working directory, not from "F:\Files"

There are two problems that you have to solve:
$(InputFiles) is specifed as a scalar property, but you want to interprete it as an array
$(InputFiles) contains wildcards you want to expand after you do transformation on the list of patterns in $(InputFiles).
It is easy to solve either of two problems separately, but combination of the two is actually tricky. I have one possible solution, and it works, but the downside is you have to encode '*' characters in your pattern definition.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="PrintNames" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<TargetDir>c:\temp\MyContent</TargetDir>
<InputFilesRelativeEsc>%2A%2A\%2A.bmp;%2A%2A\%2A.txt</InputFilesRelativeEsc>
</PropertyGroup>
<Target Name="PrintNames">
<ItemGroup>
<_TempGroup Include="$(InputFilesRelativeEsc)" />
</ItemGroup>
<CreateItem Include="#(_TempGroup->'$(TargetDir)\%(Identity)')">
<Output TaskParameter="Include" ItemName="_EvaluatedGroup" />
</CreateItem>
<Message Text="_EvaluatedGroup: %(_EvaluatedGroup.FullPath)" />
</Target>
</Project>
It works as follows. Property InputFilesRelativeEsc is a list of relative file patterns. Notice wildcard characters are encoded (%2A is a hex code for asterisk). Since the wildcards encoded, the group _TempGroup does not attempt to search for and extract list of files while you Include this patterns into this group. Now _TempGroup is a group which consists of two elements: **\*.bmp and **\*.txt. Now that you have a real group you can transform it. The only complication is that the normal MSBuild mechanism of running transform does not expand wildcards. You have to use older CreateItem task. The CreateItem task is actually declared deprecated by MSBuild team, but it still works.

Related

Vary the build output path depending on the current OS

I'm currently working on my first .NET 6 cross-plattform project in VisualStduio 2022.
I want to change my build output (Base output path) to: ..\..\..\bin\.
I know that this only works for windows.
Is there a way to adjust this path so it works for both windows and unix?
I tried this and it only works for windows:
<PropertyGroup>
<IsWindows Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true'">true</IsWindows>
<IsLinux Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true'">true</IsLinux>
</PropertyGroup>
<PropertyGroup Condition="'$(IsWindows)'=='true'">
<BaseOutputPath>..\..\..\bin\</BaseOutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(IsLinux)'=='true'">
<BaseOutputPath>../../../bin/</BaseOutputPath>
</PropertyGroup>
In my project.cs I would use:
System.IO.Directory.GetParent(System.Environment.CurrentDirectory).Parent.FullName;
but not sure how to use it in the xml
MSBuild v15 and later has a NormalizeDirectory function that will ensure the correct directory separator for the current OS. (See "MSBuild property functions".) Your code can be changed to the following:
<PropertyGroup>
<BaseOutputPath>$([MSBuild]::NormalizeDirectory('..\..\..\bin\'))</BaseOutputPath>
</PropertyGroup>
In ItemGroups, MSBuild handles mapping paths based on the OS. In the following example the Include paths are equivalent, interchangeable, and work on both Windows and Unix.
<ItemGroup>
<Windows Include="..\..\..\bin\*.*" />
<Unix Include="../../../bin/*.*" />
</ItemGroup>
If for other reasons you need to know the current OS is Unix, use the IsOSUnixLike function.
<Message Text="Hello Windows" Condition="!$([MSBuild]::IsOSUnixLike())" />
<Message Text="Hello *nix" Condition="$([MSBuild]::IsOSUnixLike())" />

Substring under PropertyGroup in msbuild props not working when property is passed as argument to msbuild

In my props file, i pass the BUILD_VERSION to dotnet msbuild using properties as /p:BUILD_VERSION=1.2.3.4. However, i get errors on when performing Substring operation on property BuildVersion which gets its value from : BUILD_VERSION.
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<IntermediateOutputPath>./obj/</IntermediateOutputPath>
<BuildVersion>$(BUILD_VERSION)</BuildVersion>
<InformationalVersion>$(BuildVersion)</InformationalVersion>
<BuildMajorVersion>$([System.String]::Concat($(BuildVersion.Substring(0, $(BuildVersion.IndexOf('.')))), ".0.0.0"))</BuildMajorVersion>
<AssemblyVersion>$(BuildMajorVersion)</AssemblyVersion>
<AssemblyFileVersion>$(BuildMajorVersion)</AssemblyFileVersion>
</PropertyGroup>
The error that i get is : """.Substring(0,-1)" length should be inside the string
I checked that BUILD_VERSION is present by printing in it in Message.
<Target Name="SetBuildVersions" BeforeTargets="BeforeBuild">
<Message Importance="High" Text="BUILD_VERSION=$(BUILD_VERSION)"/>
</Target>
It prints BUILD_VERSION=1.2.3.4.
If i hardcode <BuildVersion>1.2.3.4</BuildVersion> instead of <BuildVersion>$(BUILD_VERSION)</BuildVersion> below, it works.
If i use the Substring code inside a Target it works :
<Target Name="SetBuildVersions" BeforeTargets="BeforeBuild">
<PropertyGroup>
<InformationalVersion>$(BuildVersion)</InformationalVersion>
<BuildMajorVersionOnly>$([System.String]::Concat($(BuildVersion.Substring(0, $(BuildVersion.IndexOf('.')))), ".0.0.0"))</BuildMajorVersionOnly>
<AssemblyVersion>$(BuildMajorVersionOnly)</AssemblyVersion>
<AssemblyFileVersion>$(BuildMajorVersionOnly)</AssemblyFileVersion>
</PropertyGroup>
</Target>
Why can't i use the Substring inside normal PropertyGroup when passing BUILD_VERSION as property argument to msbuild ?

How to manually evaluate msbuild condition?

I'm creating a custom msbuild task that will be processing a configuration from custom XML file. I want to allow to use Condition attribute in that xml file. Syntax of that attribute should be the same as MSBuild Conditions (https://msdn.microsoft.com/en-us/library/7szfhaft.aspx)
How can I evaluate value of that attribute? Is there an existing library that automate that or I'm forced to write my own parser?
So far I was able only to get value of all variables that probably will be necessary to evaluate that conditions (How to access the MSBuild 's properties list when coding a custom task?)
I’m not sure if this will be helpful for you. I was solving the similar problem c++ projects. I was thinking to use Microsoft.Build.BuildEngine.Project class but later changed my mind. Finally I’ve created mine config in msbuild style (including namespace). I’ve enforced importing my config by msbuild (I’ve misused ForceImportAfterCppTargets property). Msbuild evaluated everything for me. Mine injected config (or props/target file) contained target that was injected into build process by overriding some build property (at the project level) in a way my target was called. Mine custom target called mine custom task with passed all necessary properties and items by parameters.
Following content is response on Uriel Jun 12 at 16:26:
Because you've marked question with tag c# I tried to make sample with C# vs 2010.
I made sample really simple. I put task and xml configuration file into one file named my.props. My custom task just prints values of my configuration provided by item. It prints metadata of item.
One think you have to do is to manually modify your .csproj by adding one simple line. After the line where is Microsoft.CSharp.targets imported add import of custom my.props file.
This sample expects your my.props is in the same directory as .csproj.
Diff style change:
+
Content of my.props:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask TaskName="MyTool" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >
<ParameterGroup>
<Cfg ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs">
<![CDATA[
if (Cfg.Length > 0)
{
for (int i = 0; i < Cfg.Length; ++i)
{
ITaskItem item = Cfg[i];
string value1 = item.GetMetadata("Value1");
string value2 = item.GetMetadata("Value2");
Log.LogMessage(MessageImportance.High, "MyTool: {0} - {1}", value1, value2);
}
}
]]>
</Code>
</Task>
</UsingTask>
<ItemDefinitionGroup Condition=" '$(Configuration)' == 'Release' ">
<MyConfig>
<Value1>Hello</Value1>
<Value2>World</Value2>
</MyConfig>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition=" '$(Configuration)' == 'Debug' ">
<MyConfig>
<Value1>Hello</Value1>
<Value2>Debug world</Value2>
</MyConfig>
</ItemDefinitionGroup>
<ItemGroup>
<MyConfig Include="MyCfg" />
</ItemGroup>
<Target Name="AfterBuild">
<MyTool Cfg="#(MyConfig)" />
</Target>
</Project>

How can I set the LIB environment variable inside a csproj file?

I'm operating on a system where the environment variable LIB is set to "--must-override--". I cannot change the value of the variable on the system itself.
In Visual Studio, the LIB variable is checked during compilation. Because it's set to a junk value, I get a warning in the build:
Invalid search path '--must-override--' specified in 'LIB environment variable' -- 'The system cannot find the path specified.
I'd like to get rid of this warning. To do so, I need to override the value of the LIB environment variable that VS uses, either to NULL or to some value pointing to a real path.
Since I can't change the value of the variable in the environment, I need to do it within the csproj file itself. I've tried setting it in a property group to no avail:
<PropertyGroup>
<Lib></Lib>
</PropertyGroup>
Any ideas on how this variable can be set? Or if it's even possible?
You can mangle it in using the Exec Task, or you can write your own Task to set them - here's the "Let's mangle away with Exec" route:
<PropertyGroup>
<!--
need the CData since this blob is just going to
be embedded in a mini batch file by studio/msbuild
-->
<LibSetter><![CDATA[
set Lib=C:\Foo\Bar\Baz
set AnyOtherEnvVariable=Hello!
]]></LibSetter>
</PropertyGroup>
<Exec Command="$(LibSetter)" />
EDIT:
So I just threw this csproj together with the basics - I've confirmed they do set properly when I run them - I've added in the inline-Task approach as well.
<?xml version="1.0" encoding="utf-8" ?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask
TaskName="EnvVarSet"
TaskFactory="CodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<VarName ParameterType="System.String" Required="true"/>
<VarValue ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs">
<![CDATA[
Console.WriteLine("Setting var name {0} to {1}...", VarName, VarValue);
System.Environment.SetEnvironmentVariable(VarName, VarValue);
Console.WriteLine("{0}={1}", VarName, VarValue);
]]>
</Code>
</Task>
</UsingTask>
<Target Name="ThingThatNeedsEnvironmentVars">
<CallTarget Targets="FiddleWithEnvironmentVars"/>
<Message Text="LIB environment var is now: $([System.Environment]::GetEnvironmentVariable('LIB'))"/>
</Target>
<Target Name="FiddleWithEnvironmentVars">
<Message Text="LIB environment var is now: $([System.Environment]::GetEnvironmentVariable('LIB'))"/>
<EnvVarSet VarName="LIB" VarValue="C:\temp"/>
<Message Text="LIB environment var is now: $([System.Environment]::GetEnvironmentVariable('LIB'))"/>
</Target>
</Project>

How to call second target in Msbuild

I need to call the second target in the msbuild but when I'm calling it in the cmd it shows error my code is give below
MsBuild.csproj
<?xml version="1.0" encoding="utf-8" ?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<alen>123456</alen>
</PropertyGroup>
<Target Name="FirstTarget">
<Message Text="Hello World $(alen)" />
</Target>
<Target Name="SecondTarget">
<Message Text="The second target" />
</Target>
</Project>
The first target called successfully but I cant load the second Target...How it is possible???
Since you have not defined it, the default target is the first target in the file, FirstTarget. To call the second target from the command line you need call it explicitly with /t:SecondTarget. You can use /t:FirstTarget;SecondTarget if you want to run both.
You could also define SecondTarget to always come after first target. Use the AfterTargets attribute like so:
<Target Name="SecondTarget" AfterTargets="FirstTarget">
Now msbuild msbuild.proj would call both targets.
I know this is a really old post, but you can also have one target call other targets.
<Target Name="Build">
<CallTarget Targets="PreBuild"/>
<CallTarget Targets="Main"/>
<CallTarget Targets="AfterBuild"/>
</Target>
Have you tried
%SystemRoot%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe "D:\test_2\MsBuild\MsBuild\BuildScript\MsBuild.csproj" /t:SecondTarget
?
Another option is to define a default target in your build file and than define the order of targets using the DependsOnTargets:
<Project DefaultTargets="DefaultTarget" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
<Target Name="DefaultTarget" DependsOnTargets="FirstTarget;SecondTarget">
<Message Text="Executing DefaultTarget" />
</Target>
<!-- your targets -->
</Project>
The targets defined in DependsOnTargets will run before the target itself is running.
Doing it this way, you do not need to set the /t: parameter in your call.

Categories