Passing array to custom MSBuild task - c#

I thought this would be quite simple but then realised that I couldnt find any information on it anywhere.
I have a custom task like so:
public class MyCustomTask : Task
{
[Required]
public string[] SomeStrings {get;set;}
public override bool Execute()
{
// Do something with strings...
}
}
The matching MSBuild stuff is basically like so:
<UsingTask TaskName="MyCustomTask" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildBinPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<SomeStrings ParameterType="System.String[]" Required="true" />
</ParameterGroup>
<Task>
...
</Task>
</UsingTask>
<Target Name="DoSomething">
<MyCustomTask SomeStrings="????" />
</Target>
Dont have any idea of what to put in the SomeStrings parameter, thought maybe it would understand if I did "xxx,xxx,xxx" so can anyone shed any light on this. The basic scenario is alot like tokenizing so I require a list of strings then some comparison strings so I need to pass in 2 lists/arrays, but just stumped.

#BrianKretzler is exactly dead on in using ITaskItem, since it's what MSBuild uses when you declare an <ItemGroup>.
I just wanted to flush out the answer with a full working example, since I found this post while I was trying to accomplish the same thing and it helped me out. (It's very hard to search for these problems, because the keywords are used in different contexts, so hopefully this will help someone else).
<UsingTask TaskName="MyCustomTask" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<SomeStrings ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
</ParameterGroup>
<Task>
<Code Type="Class" Language="cs"><![CDATA[
using System;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
public class MyCustomTask : Task
{
public ITaskItem[] SomeStrings { get; set; }
public override bool Execute()
{
foreach (var item in SomeStrings)
{
Log.LogMessage(MessageImportance.High,
"Got item {0}",
item.ItemSpec);
Log.LogMessage(" -> {0} -> {1}",
item.GetMetadata("Comparison"),
item.GetMetadata("MoreDetail"));
}
return true;
}
}
]]></Code>
</Task>
</UsingTask>
Now you can call this task with:
<Target Name="DoSomething">
<ItemGroup>
<SomeStrings Include="first string">
<Comparison>first</Comparison>
</SomeStrings>
<SomeStrings Include="second string">
<Comparison>2nd</Comparison>
<MoreDetail>this is optional</MoreDetail>
</SomeStrings>
</ItemGroup>
<MyCustomTask SomeStrings="#(SomeStrings)" />
</Target>
and the output is:
Microsoft (R) Build Engine Version 4.0.30319.1
[Microsoft .NET Framework, Version 4.0.30319.269]
Copyright (C) Microsoft Corporation 2007. All rights reserved.
Build started 2012-10-19 5:41:22 PM.
Got first string
-> first ->
Got second string
-> 2nd -> this is optional
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:00.12
You can of course also use something like <ItemGroup><SomeStrings Include="**\*.txt" /></ItemGroup> and you'll get the list of filenames that are matched, and of course you can use GetMetadata() to access the well-known file metadata

It isn't quite clear what you are trying to do; you have the C# code for a custom task, but also the MSBuild code for the same task as an inline task -- you do realize you only need to do one of those, correct? If you are trying to create a task in an assembly, the <UsingTask> in your MSBuild should be an empty element, without the <ParameterGroup> and <Task> children. If you are trying to use an inline task, you don't need the C# code and need to specify your own assembly as the AssemblyFile, and not specify the TaskFactory as you have.
I'd declare the parameter as type ITaskItem[], so you can then pass in the value(s) as,
<MyCustomTask SomeStrings="#(SomeStrings)" />
You could set up the comparison strings as a second item array in a second parameter, or as metadata on the first parameter, e.g.
<ItemGroup>
<SomeStrings Include="first string">
<Comparison>first</Comparison>
</SomeStrings>
<SomeStrings Include="second string">
<Comparison>2nd</Comparison>
</SomeStrings>
</ItemGroup>
If you are using inline code, you'll need to <Reference> the proper MSBuild assemblies and fully qualify the ParameterType. Get it working in a compiled assembly first even if your eventual intent is to use inline code.

Since this is currently the first hit on Google, here's the other way of doing it (as alluded to by #alastair-maw's comment) as answered in another SO thread:
MSBuild tasks can accept ITaskItem, primitives, string or an array of any of those for parameters. You declare the type in your task and then the values will be converted before passed to the task. If the value cannot convert to the type, then an exception will be raised and the build will be stopped.
For example, if you have a task which accepts an int[] named Values then you could do:
<Target Name="MyTarget">
<MyTask Values="1;45;657" />
<!-- or you can do -->
<ItemGroup>
<SomeValues Include="7;54;568;432;79" />
</ItemGroup>
<MyTask Values="#(SomeValues) />
</Target>

Related

MSBuild auto increment build version differently for release/debug

Context:
I use MSBuild to build my projects. Currently I use a date of release version number that, unfortunately, lacks clarity when multiple releases occur in the same day. In Directory.Build.props:
<PropertyGroup>
<Version>
$([System.DateTime]::Now.Year).
$([System.DateTime]::Now.Month).
$([System.DateTime]::Now.Day).
$([System.Convert]::ToUInt16(
$([MSBuild]::Divide(
$([System.DateTime]::Now.TimeOfDay.TotalSeconds),
1.32
))
))
</Version>
</PropertyGroup>
Goal:
Create a versioning scheme that looks something like this:
3/23/20:
Release Build: 2020.3.23.0
3/24/20:
Debug Build: 2020.3.24.0
Debug Build: 2020.3.24.1
Debug Build: 2020.3.24.2
Release Build: 2020.3.24.0
Debug Build: 2020.3.24.3
Release Build: 2020.3.24.1
Essentially: the first three numbers are year/month/day, because date of release is frequently important. Then use auto incrementing version numbers for releases within the same day. Incrementing on debug is useful so I can confirm the correct version of software is being loaded and run, but I don't want confusingly high numbers on release builds. I may play around with some additional indicator for debug builds, but I should be able to figure that out on my own.
Question:
How can I auto increment builds within the same day, having a separate version for debug and release? Ideally solutions that don't add additional dependencies are preferred, but if there is no way without, then it is acceptable.
MSBuild auto increment build version differently for release/debug
In general, MSBuild did not have a function to see the version number of the obvious incremental build but only used the timestamp of the system build determines the build order as you used before.
In fact, if you create a custom property in msbuild to record the version number of the incremental build, it still needs to use an entity to store the record, and if it is not used, the parameter is reinitialized for each build (the msbuild attribute can only be identified in msbuild).
So the ideal way it that use textfile as an intermediate. You can follow my solution:
Solution
1) create a custom msbuild task which does increment the value of the record property.
--a) Create a class library project called MyCustomTask then Right-click on the project-->Add Reference-->reference Microsoft.Build.Framework dll and Microsoft.Build.Utilities.v4.0 dll.
--b) add these into CustomTask.cs(this is the name of the task which will be used in xxx.csproj file).
public class CustomTask : Task
{
private int _number;
[Required]
public int number //input taskitem
{
get { return _number; }
set { _number = value; }
}
private int _lastnumber;
[Output]
public int LastNumber //output value
{
get { return _lastnumber; }
set { _lastnumber = value; }
}
public override bool Execute() // Execution logic
{
LastNumber = number + 1;
return true;
}
}
--c) Then build the project and remember to store its MyCustomTask dll.
2) Aim to your main project and then create two txt files called Debug.txt,Release.txt and give each of them an initial value of 0.
3) add these into your Directory.Build.props file:
<Project>
<UsingTask TaskName="CustomTask" AssemblyFile="xxxxxx\MyCustomTask\MyCustomTask\MyCustomTask\bin\Debug\MyCustomTask.dll(the local path of the dll)"> </UsingTask>
<PropertyGroup>
<Record></Record>
</PropertyGroup>
<Target Name="WriteToFile1" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<Record Condition="'$(Configuration)'=='Debug' and !Exists('$(TargetPath)')">
0
</Record>
<Record Condition="'$(Configuration)'=='Release'and !Exists('$(TargetPath)')">
0
</Record>
</PropertyGroup>
<ItemGroup Condition="'$(Configuration)'=='Debug'">
<MyTextFile Include="Debug.txt">
<Number>$(Record)</Number>
</MyTextFile>
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'=='Release'">
<MyTextFile Include="Release.txt">
<Number>$(Record)</Number>
</MyTextFile>
</ItemGroup>
<WriteLinesToFile
File="#(MyTextFile)"
Lines="$(Record)"
Overwrite="true"
Encoding="Unicode" Condition="'$(Configuration)'=='Debug'"/>
<WriteLinesToFile
File="#(MyTextFile)"
Lines="$(Record)"
Overwrite="true"
Encoding="Unicode" Condition="'$(Configuration)'=='Release'"/>
<PropertyGroup>
<Version>
$([System.DateTime]::Now.Year).
$([System.DateTime]::Now.Month).
$([System.DateTime]::Now.Day).
$(Record)
</Version>
</PropertyGroup>
</Target>
<Target Name="ReadLineFromFile" BeforeTargets="WriteToFile1">
<ReadLinesFromFile File="Debug.txt" Condition="'$(Configuration)'=='Debug'">
<Output TaskParameter="Lines" PropertyName="Record"/>
</ReadLinesFromFile>
<ReadLinesFromFile File="Release.txt" Condition="'$(Configuration)'=='Release'">
<Output TaskParameter="Lines" PropertyName="Record"/>
</ReadLinesFromFile>
<CustomTask number="$(Record)">
<Output TaskParameter="LastNumber" PropertyName="Record"/>
</CustomTask>
</Target>
</Project>
4) When you execute a task which depends on Build to show the property Version, it will work well as you hope.
Note that it will work for incremental build and if you click Rebuild(which execute Clean and then Build), it will set the version number to zero and start the rethrow.
Overall, this is an ideal solution which I try to realize it.

Write Properties (name=value) to a file from an MSBuild project

For an MSBuild project, I would like to output some kind of a .config file that would be redistributed along the generated binary so the parameters used at build time can be checked by the users of the binary, programmatically.
Output file format:
PropertyName1=ValueA
PropertyName2=ValueB
...
Ideally, the list of properties to write would contain just their names. Maybe like:
<ItemGroup>
<MyExposedDictionary Include="Configuration" />
<MyExposedDictionary Include="Platform" />
<MyExposedDictionary Include="PropertyName1" />
...
</ItemGroup>
With MyExposedDictionary being the argument to give to some DotConfigFileWriter task, as well as the path of the destination file.
I found several ways to write down values to a file, including a sub-target with some C# code in it, but I'm new to MSBuild and I'm not sure how I can merge those requirements into a single Target to make it re-usable.
In case someone comes here with the same requirement, here is what I ended up with. Not really happy with the result as I was hoping for something more generic but at least it does the job and blends well in my project:
<Target Name="WriteBuildProperties" BeforeTargets="PreBuildEvent">
<WriteLinesToFile File="$(DotConfigFile)" Overwrite="true" Lines="" />
<WriteLinesToFile File="$(DotConfigFile)" Lines="ProjectName=$(ProjectName)" />
<WriteLinesToFile File="$(DotConfigFile)" Lines="Configuration=$(Configuration)" />
...
</Target>
If someone happen to have a more elegant solution, please jump in!
I am not sure where your problem is located. I have a similar requirement that a file is created by the program which just was compiled. I edited the properties of the project: in the build events enter a Post-build action like
REM create special file
"$(ProjectDir)$(OutDir)MyProgram.exe" /WriteFile MyFile.xml
Of course, you must also change your program such that it does the right thing when called with that parameter (and stops after having completed that action - does not show a GUI or start as a Windows Service).

How to transform files before adding them to an assembly?

I would like to do the following :
(project is a User Control library for WPF)
add a bunch of .FX (shader source code) files to the project as resources (Build action)
transform each to a .PS file (compiled shader) by invoking FXC.EXE utility
use the resulting file in place of the inputted file
I have been looking to write a CustomTool, unfortunately the tool is never seen by Visual Studio as it's mentioned in the article. In the article it is said that sometimes it is not seen but in my case it translates to every time.
I also looked at MSBuild Transforms but I'm not really sure if it would be appropriate for the task.
The goal of this is to include shader files source code and transform them at build time instead of manually building them from command line and dropping them every time to the project.
Do you know how one can achieve this ? Any methods are welcome
EDIT
Answer thanks to #Luaan :
public class CompileEffectTask : Task
{
public string[] Files { get; set; }
public override bool Execute()
{
if (Files != null)
{
foreach (string file in Files)
{
if (file != null)
{
Log.LogMessage(MessageImportance.High, file);
string s = #"C:\Program Files (x86)\Windows Kits\8.1\bin\x86\fxc.exe";
string changeExtension = Path.ChangeExtension(file, "ps");
string arguments = string.Format("/T ps_3_0 /Fo \"{0}\"" + " " + "\"{1}\"", changeExtension,
file);
Log.LogMessage(MessageImportance.High, arguments);
var process = new Process
{
StartInfo = new ProcessStartInfo(s, arguments)
};
process.Start();
process.WaitForExit();
}
}
}
return true;
}
}
And the MSBuild part :
<UsingTask TaskName="CompileEffectTask" AssemblyFile="D:\HLSLCompiler.dll" />
<PropertyGroup>
<BuildDependsOn>
MyCustomTarget1;
$(BuildDependsOn);
</BuildDependsOn>
</PropertyGroup>
<Target Name="MyCustomTarget1">
<Message Text="CompileEffectTask started" Importance="high" />
<Message Text="Compiling FX files ..." Importance="high" />
<CompileEffectTask Files="#(CompileEffectTask)"/>
<Message Text="Adding resulting .PS files as resources ..." Importance="high" />
<ItemGroup>
<Resource Include="**\*.ps" />
</ItemGroup>
</Target>
<Target Name="AfterBuild">
<CreateItem Include="**\*.ps">
<Output TaskParameter="Include" ItemName="DeleteAfterBuild" />
</CreateItem>
<Delete Files="#(DeleteAfterBuild)" />
</Target>
(still needs some cleaning but it works :D)
Custom tools do work, in fact, but they're rather tricky to setup - they're COM extensions to Visual Studio. However, the better solution for your case would be a custom build target or a pre-build event anyway - custom tools (code generators) are better suited for generating code (text) rather than binary files.
So, the pre-build event is the simple one. It's just some script that's run before the project starts building. You can find it in project properties. The simplest way would be to have all your .fx files in one directory, and in the pre-build event, you'd just call fxc.exe on each of them.
Now, build targets are cooler. They allow you to add your own build actions to files, among other things. So you'd just select CompileEffect in Build action of your files, and magic happens.
The target file can be quite simple:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<AvailableItemName Include="CompileEffect"></AvailableItemName>
</ItemGroup>
</Project>
Or you can just put the ItemGroup part inside of your project file directly (otherwise you'd want to include this target file).
Next, you want to set the task as part of your build:
<PropertyGroup>
<BuildDependsOn>
MyCompileTarget;
$(BuildDependsOn);
</BuildDependsOn>
</PropertyGroup>
This basically says "run my build target first, and after that whatever you'd want".
Now, for the building:
<Target Name="MyCompileTarget">
<CompileEffectTask
ProjectDirectory="$(ProjectDir)"
Files="#(CompileEffect)"
RootNamespace="$(RootNamespace)">
</CompileEffectTaskTask>
</Target>
How does Visual Studio know what CompileEffectTask is?
<UsingTask TaskName="MyAssembly.CompileEffectTask"
AssemblyFile="C:\MyAssembly.dll"/>
And then you just need to implement the compiler task itself.
Now, if you only want to call an executable or a batch script, you don't even need that custom task, because there's a lot of built-in tasks in MSBuild (and even more in MSBuild Community Tasks). Exec task should work:
<Target Name="MyCompileTarget">
<Exec Command="fxc.exe #(CompileEffect)" />
</Target>
You might have to write a for cycle there, I'm not entirely sure. There's a lot of things you can do to customize project builds, http://msdn.microsoft.com/en-us/library/0k6kkbsd.aspx (especially the Task refecence part) is a rather good start.

Set Version number of TFS Build in C# File

Hy,
is it possible to set the number of the TFS Build in a C# File?
The TFS Build should replace the version number with the current one.
public const string SERVICE_VERSION = "2.0 Build: 20130518.1";
EDIT:
The const is in a class called SharedMethods:
public static class SharedMethods
{
public const string SERVICE_VERSION = "2.0 Build: 20130518.1";
...
...
}
The TFS build should check out the element, update the version, check in the element and continue the normal build process.
Check out this article:
Create a Custom WF Activity to Sync Version and Build Numbers
It describes how to change the version number in the AssemblyInfo.cs file. You could tweak this to suit your needs.
If your file contains only theses lines, perhaps you could do something like that :
<WriteLinesToFile File="$(VersionFilePath)" Lines="public const string SERVICE_VERSION = %22$(Version)%22%3B" Overwrite="true" Encoding="ASCII"/>
Note that " should be replaced by %22 and ; by %3B (when you add a ';', a new line char is inserted).
Otherwise, you could write C# code in a msbuild to do what you want. An exemple :
<UsingTask TaskName="UpdateApplicationRevision" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<ProjectFilePath ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Reference Include="$(MSBuildToolsPath)\Microsoft.Build.dll" />
<Reference Include="System.Xml" />
<Code Type="Fragment" Language="cs">
<![CDATA[
var project = new Microsoft.Build.Evaluation.Project(ProjectFilePath);
var property = project.GetProperty("ApplicationRevision");
property.UnevaluatedValue = "" + (System.Int32.Parse(property.EvaluatedValue) + 1);
project.Save();
]]>
</Code>
</Task>
</UsingTask>
Here, I modify a csproj (xml) file, but feel free to write your own code to read your file and update it...

Unconstrained Melody error

I just installed the nuget package for Jon Skeet's Unconstrained Melody project, but when I attempt to use it, I get an error when I compile:
Type parameter 'T' inherits conflicting constraints 'UnconstrainedMelody.IEnumConstraint' and 'System.ValueType'
Function definition:
public void SetEnum<T>() where T : struct, IEnumConstraint {}
Am I missing something? Should I not use the nuget package?
I could be wrong, but it appears that while this library uses IEnumConstraint internally, and gets it to work with the postbuild steps described in the article, it does not provide any magic for you to just consume IEnumConstraint directly for your own methods.
The GetValues<T> method described in the post is one of several methods provided from the UnconstrainedMelody.Enums class. There are other objects and methods available as well.
If you wanted to constrain your own generic methods to enums, you could follow the same steps Jon used to build this library, but on your own library. There was also this example in the comments of how to do this with PostSharp.
The ideas of the "unconstrained melody" have been integrated into
ExtraConstraints
It comes with the necessary tooling to incorporate the IL weaving into the build process and is being actively maintained. Therefore, it should be preferrable to use this over the code from "unconstrained melody". For Enums, Enums.NET also offers additional improvements.
ExtraConstraints also supports adding constraints to delegates.
I haven't tried this, but it appears you can use this MSBuild task to accomplish the same thing as ConstraintChanger. You have to include a copy of the DelegateConstraint.cs and IEnumConstraint.cs code files in your project. After the build task is applied to the project, the constraints are swapped out, and your other projects which reference this project will be able to see the constraints.
So its essentially useful for creating a common library project within your solution, and can include things like your own custom generic extension methods which are type constrained to System.Enum and System.Delegate.
https://code.google.com/p/unconstrained-melody/issues/detail?id=13
All credit to: j...#friesen.us
I've found this project to be useful but wanted to have the build-time steps in MSBuild. Adding this to your *.csproj files in which you use the constraints should accomplish the same thing as the Constraintchanger app. Note the following:
1. I've added the two constraint types to my classes root namespace and the substitution looks for the types in the assembly's root namespace.
2. To make Resharper happier I've added the IEnumConstraint, DelegateConstraint type args into my project inside an #if UNCONSTRAINED ... #endif block like so:
public static T Parse<T>(string val) where T : struct
#if UNCONSTRAINED
, IEnumConstraint
#endif
{
}
This is purely optional but keeps resharper from complaining about the constraint not matching when using the project's code from another project in a common solution.
<PropertyGroup>
<BuildDependsOn>
$(BuildDependsOn);
SwapConstraints
</BuildDependsOn>
</PropertyGroup>
<ItemGroup>
<PreprocessorDefines Include="UNCONSTRAINED" />
</ItemGroup>
<UsingTask TaskName="FileReplace" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<FileName ParameterType="System.String" Required="true" />
<Source ParameterType="System.String" Required="true" />
<Replacement ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs"><![CDATA[
string content = File.ReadAllText(FileName);
content = content.Replace(Source, Replacement);
File.WriteAllText(FileName, content);
]]></Code>
</Task>
</UsingTask>
<Target Name="SwapConstraints">
<GetFrameworkPath>
<Output TaskParameter="Path" PropertyName="FW" />
</GetFrameworkPath>
<GetFrameworkSdkPath>
<Output TaskParameter="Path" PropertyName="SDK" />
</GetFrameworkSdkPath>
<PropertyGroup>
<ILDASM>"$(SDK)bin\NETFX 4.0 Tools\ildasm.exe"</ILDASM>
<ILASM>"$(FW)\ilasm.exe"</ILASM>
<IlFile>$(OutputPath)$(AssemblyName).il</IlFile>
<DllFile>$(OutputPath)$(AssemblyName).dll</DllFile>
</PropertyGroup>
<Exec Command="$(ILDASM) /OUT=$(IlFile) $(DllFile)" WorkingDirectory="$(ProjectDir)" />
<FileReplace FileName="$(IlFile)" Source="($(RootNamespace).DelegateConstraint)" Replacement="([mscorlib]System.Delegate)" />
<FileReplace FileName="$(IlFile)" Source="([mscorlib]System.ValueType, $(RootNamespace).IEnumConstraint)" Replacement="([mscorlib]System.Enum)" />
<FileReplace FileName="$(IlFile)" Source="($(RootNamespace).IEnumConstraint), [mscorlib]System.ValueType" Replacement="([mscorlib]System.Enum)" />
<FileReplace FileName="$(IlFile)" Source="($(RootNamespace).IEnumConstraint)" Replacement="([mscorlib]System.Enum)" />
<Exec Command="$(ILASM) /OUTPUT=$(DllFile) /DLL $(IlFile)" WorkingDirectory="$(ProjectDir)" />
</Target>
Nov 22, 2013
#1 j...#friesen.us
Sorry, didn't mean to say #if UNCONSTRAINED ... #endif should be around type args for DelegateConstraint. I've not messed with delegates to this point but I doubt that it would be necessary for them.

Categories