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...
Related
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.
Tools
MSBuild v14
Visual Studio 2013
Jenkins v2.111 running on Windows Server 2012
Git (bare repo on local file server)
Windows Batch
My goal
Build a c# Visual Studio project using MSBuild that pulls back the major and minor version numbers from the projects AssemblyInfo.cs for use during the build. The build would produce something like 1.2.$BUILD_NUMBER resulting in something like 1.2.121, 1.2.122, 1.2.123 and so on. Once the user opts to 'release' the build, a clickonce deployment with correct version in the folder name is copied to its target destination and a tag applied to the Git repository.
Pipeline example
Below is a 'work in progress' of what I've got up to. Any suggestions to improve are welcome. For those that are wondering why I'm coping the codebase out to a temporary folder. I'm using a multi-branch job in Jenkins and the folders that are auto-generated are extremely long! This gave me errors along the lines that my file name, project name or both are too long (because the entire path is above the 255 or so character length). So the only way to get around this was to copy out contents so the build and publish would work.
pipeline {
agent none
stages {
stage ('Checkout'){
agent any
steps
{
checkout scm
}
}
stage ('Nuget Restore'){
agent any
steps
{
bat 'nuget restore "%WORKSPACE%\\src\\Test\\MyTestSolution.sln"'
}
}
stage('Build Debug') {
agent any
steps
{
bat "xcopy %WORKSPACE%\\src\\* /ey d:\\temp\\"
bat "\"${tool 'MSBuild'}\" d:\\temp\\Test\\MyTestSolution.sln /p:Configuration=Debug /target:publish /property:PublishUrl=d:\\temp\\ /p:OutputPath=d:\\temp\\build\\ /p:GenerateBootstrapperSdkPath=\"C:\\Program Files (x86)\\Microsoft SDKs\\Windows\\v8.1A\\Bootstrapper\" /p:VersionAssembly=1.0.$BUILD_NUMBER /p:ApplicationVersion=1.0.$BUILD_NUMBER"
}
}
stage('Deploy to Dev'){
agent none
steps {
script {
env.DEPLOY_TO_DEV = input message: 'Deploy to dev?',
parameters: [choice(name: 'Deploy to dev staging area?', choices: 'no\nyes', description: 'Choose "yes" if you want to deploy this build')]
}
}
}
stage ('Deploying to Dev')
{
agent any
when {
environment name: 'DEPLOY_TO_DEV', value: 'yes'
}
steps {
echo 'Deploying debug build...'
}
}
stage ('Git tagging')
{
agent any
steps
{
bat 'd:\\BuildTargets\\TagGit.bat %WORKSPACE% master v1.0.%BUILD_NUMBER%.0(DEV) "DEV: Build deployed."'
}
}
}
}
At the moment I've hard coded the major and minor version in the above script. I want to pull these values out of the AssemblyInfo.cs so that developers can control it from there without editing the Jenkinsfile. Any suggestions/best practice to achieve this?
Because I'm doing a clickonce deployment for a winforms app I've had to use MSBuild's VersionAssembly and ApplicationVersion switches to pass in the version. This seems to help with correctly labelling folders when MSBuild publishes the files. Have I have missed something in my setup which would negate these switches and make life simpler?
The last action in my pipeline is to trigger a .bat file to add a tag back into the master branch of the repository. This is another reason that I need to make the major and minor version accessible to the pipeline script.
MSBuild target for editing AssemblyInfo.cs
This code was taken from here: http://www.lionhack.com/2014/02/13/msbuild-override-assembly-version/
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<CompileDependsOn>
CommonBuildDefineModifiedAssemblyVersion;
$(CompileDependsOn);
</CompileDependsOn>
</PropertyGroup>
<Target Name="CommonBuildDefineModifiedAssemblyVersion" Condition="'$(VersionAssembly)' != ''">
<!-- Find AssemblyInfo.cs or AssemblyInfo.vb in the "Compile" Items. Remove it from "Compile" Items because we will use a modified version instead. -->
<ItemGroup>
<OriginalAssemblyInfo Include="#(Compile)" Condition="%(Filename) == 'AssemblyInfo' And (%(Extension) == '.vb' Or %(Extension) == '.cs')" />
<Compile Remove="**/AssemblyInfo.vb" />
<Compile Remove="**/AssemblyInfo.cs" />
</ItemGroup>
<!-- Copy the original AssemblyInfo.cs/.vb to obj\ folder, i.e. $(IntermediateOutputPath). The copied filepath is saved into #(ModifiedAssemblyInfo) Item. -->
<Copy SourceFiles="#(OriginalAssemblyInfo)"
DestinationFiles="#(OriginalAssemblyInfo->'$(IntermediateOutputPath)%(Identity)')">
<Output TaskParameter="DestinationFiles" ItemName="ModifiedAssemblyInfo"/>
</Copy>
<!-- Replace the version bit (in AssemblyVersion and AssemblyFileVersion attributes) using regular expression. Use the defined property: $(VersionAssembly). -->
<Message Text="Setting AssemblyVersion to $(VersionAssembly)" />
<RegexUpdateFile Files="#(ModifiedAssemblyInfo)"
Regex="Version\("(\d+)\.(\d+)(\.(\d+)\.(\d+)|\.*)"\)"
ReplacementText="Version("$(VersionAssembly)")"
/>
<!-- Include the modified AssemblyInfo.cs/.vb file in "Compile" items (instead of the original). -->
<ItemGroup>
<Compile Include="#(ModifiedAssemblyInfo)" />
</ItemGroup>
</Target>
<UsingTask TaskName="RegexUpdateFile" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<Files ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
<Regex ParameterType="System.String" Required="true" />
<ReplacementText ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Reference Include="System.Core" />
<Using Namespace="System" />
<Using Namespace="System.IO" />
<Using Namespace="System.Text.RegularExpressions" />
<Using Namespace="Microsoft.Build.Framework" />
<Using Namespace="Microsoft.Build.Utilities" />
<Code Type="Fragment" Language="cs">
<![CDATA[
try {
var rx = new System.Text.RegularExpressions.Regex(this.Regex);
for (int i = 0; i < Files.Length; ++i)
{
var path = Files[i].GetMetadata("FullPath");
if (!File.Exists(path)) continue;
var txt = File.ReadAllText(path);
txt = rx.Replace(txt, this.ReplacementText);
File.WriteAllText(path, txt);
}
return true;
}
catch (Exception ex) {
Log.LogErrorFromException(ex);
return false;
}
]]>
</Code>
</Task>
</UsingTask>
</Project>
Git tagging
This bat file is kicked off and passed values used to create and push a tag to the defined repository.
echo off
set gitPath=%1
set gitBranchName=%2
set gitTag=%3
set gitMessage=%4
#echo on
#echo Adding tag to %gitBranchName% branch.
#echo Working at path %gitPath%
#echo Tagging with %gitTag%
#echo Using commit message: %gitMessage%
d:
cd %gitPath%
git checkout %gitBranchName%
git pull
git tag -a %gitTag% -m %gitMessage%
git push origin %gitBranchName% %gitTag%
If there are any other gold nuggests that would help streamline or improve this overall workflow, would welcome those too!
I recently had the same problem which i solved by creating a Windows Script.
for /f delims^=^"^ tokens^=2 %%i in ('findstr "AssemblyFileVersion" %1\\AssemblyFile.cs') DO SET VERSION=%%i
This script extracts the version number from the AssemblyInfo.cs and put it inside an variable so it can be used later to tag the commit (in the same step though) :
CALL FindAssemblyVersion .\Properties
git tag %VERSION%
git push http://%gitCredentials%#url:port/repo.git %VERSION%
Not exactly from the assembly file but a very handy workaround to get the file version from the DLL while working with Jenkins, and using batch (or powershell) command:
Goto the directory where your DLL exists [CD Foo/Bar ]
FOR /F "USEBACKQ" %F IN (`powershell -NoLogo -NoProfile -Command (Get-Item "myApi.dll").VersionInfo.FileVersion`) DO (SET fileVersion=%F )
echo File version: %fileVersion%
I'm having a problem getting xbuild to compile a web application project.
We have some resource files, which are .html files.
The one that's failing currently is 'KwasantCore\Resources\HTMLEventInvitation.html'
The resource is defined in KwasantCore.csproj as
<Content Include="Resources\HTMLEventInvitation.html" />
When building on ubuntu, the file is located here:
/home/gitlab_ci_runner/gitlab-ci-runner/tmp/builds/project-1/KwasantCore/Resources/HTMLEventInvitation.html
When running xbuild, I get this error:
/home/gitlab_ci_runner/gitlab-ci-runner/tmp/builds/project-1/Kwasant.sln (default targets) ->
(Build target) ->
/home/gitlab_ci_runner/gitlab-ci-runner/tmp/builds/project-1/KwasantCore/KwasantCore.csproj (default targets) ->
/usr/lib/mono/xbuild/12.0/bin/Microsoft.Common.targets (GenerateResources target) ->
/usr/lib/mono/xbuild/12.0/bin/Microsoft.Common.targets: error : Tool exited with code: 1. Output: Error: Invalid ResX input.
Position: Line 123, Column 5.
Inner exception: Could not find a part of the path "/home/gitlab_ci_runner/gitlab-ci-runner/tmp/builds/project-1/KwasantCore/resources/htmleventinvitation.html".
I checked the file, and it's there - the problem is case sensitivity. The resource is correctly referenced in the .csproj, so somewhere along the line, the resource is getting lowercased from 'Resources/HTMLEventInvitation.html' to 'resources/htmleventinvitation.html'
I've taken a look at the Microsoft.Common.targets file on the ubuntu box. Line 125 is something completely unrelated (it shows me </PropertyGroup>). Looking at the GenerateResources target, it shows me this:
<Target Name = "GenerateResources">
<GenerateResource
Sources = "#(ResxWithNoCulture)"
UseSourcePath = "true"
OutputResources = "#(ManifestResourceWithNoCultureName->'$(IntermediateOutputPath)%(Identity).resources')"
Condition = "'#(ResxWithNoCulture)' != '' ">
<Output TaskParameter = "OutputResources" ItemName = "ManifestResourceWithNoCulture"/>
<Output TaskParameter = "FilesWritten" ItemName = "FileWrites"/>
</GenerateResource>
<GenerateResource
Sources = "#(ResxWithCulture)"
UseSourcePath = "true"
OutputResources = "#(ManifestResourceWithCultureName->'$(IntermediateOutputPath)%(Identity).resources')"
Condition = "'#(ResxWithCulture)' != '' ">
<Output TaskParameter = "OutputResources" ItemName = "ManifestResourceWithCulture"/>
<Output TaskParameter = "FilesWritten" ItemName = "FileWrites"/>
</GenerateResource>
</Target>
with the referenced targets being:
<CreateItem Include="#(ResourcesWithNoCulture)" Condition="'%(Extension)' == '.resx'">
<Output TaskParameter="Include" ItemName="ResxWithNoCulture"/>
</CreateItem>
<CreateItem Include="#(ResourcesWithNoCulture)" Condition="'%(Extension)' != '.resx'">
<Output TaskParameter="Include" ItemName="NonResxWithNoCulture"/>
</CreateItem>
<CreateItem Include="#(ResourcesWithCulture)" Condition="'%(Extension)' == '.resx'">
<Output TaskParameter="Include" ItemName="ResxWithCulture"/>
</CreateItem>
<CreateItem Include="#(ResourcesWithCulture)" Condition="'%(Extension)' != '.resx'">
<Output TaskParameter="Include" ItemName="NonResxWithCulture"/>
</CreateItem>
Now, this looks suspicious to me, but I can't figure out what these Include="#(ResourcesWithNoCulture)" lines are doing - a search for them elsewhere doesn't give me any hints.
The fact that it's a .html file (and not .resx), makes me suspicious of the GenerateTargets target, as it's only calling the resx versions of the targets.
I'm not an expert on .targets files - can anyone give me a hand? I've googled around, but found no help. I would assume that it would be a fairly common bug, as resources aren't extremely rare (but perhaps without .resx they are).
Edit: Having looked at it again, the error related to 'GenerateResources' doesn't exactly make sense: it should be failing at 'CopyNonResxEmbeddedResources', as the resources are not .resx. They GenerateResources target shouldn't be touching the .html files - as it's only looking at 'ResxWithNoCulture' and 'ResxWithCulture'
<Target Name = "CopyNonResxEmbeddedResources"
Condition = "'#(NonResxWithCulture)' != '' or '#(NonResxWithNoCulture)' != '' or '#(ManifestNonResxWithCulture)' != '' or '#(ManifestNonResxWithNoCulture)' != ''">
<MakeDir Directories="$(IntermediateOutputPath)%(ManifestNonResxWithCulture.Culture)"/>
<Copy SourceFiles = "#(NonResxWithCulture)"
DestinationFiles = "#(ManifestNonResxWithCulture->'$(IntermediateOutputPath)%(Identity)')"
SkipUnchangedFiles="$(SkipCopyUnchangedFiles)">
<Output TaskParameter = "DestinationFiles" ItemName = "ManifestNonResxWithCultureOnDisk"/>
<Output TaskParameter = "DestinationFiles" ItemName = "FileWrites"/>
</Copy>
<Copy SourceFiles = "#(NonResxWithNoCulture)"
DestinationFiles = "#(ManifestNonResxWithNoCulture->'$(IntermediateOutputPath)%(Identity)')"
SkipUnchangedFiles="$(SkipCopyUnchangedFiles)">
<Output TaskParameter = "DestinationFiles" ItemName = "ManifestNonResxWithNoCultureOnDisk"/>
<Output TaskParameter = "DestinationFiles" ItemName = "FileWrites"/>
</Copy>
</Target>
The target 'CopyNonResxEmbeddedResources' is called directly before 'GenerateResources'
I don't know why this happens (my brain just can't hold any more build systems' configuration nuances), but one of the tricks I've picked up along the way is:
MONO_IOMAP=case xbuild ...
That environment variable tells Mono to be case-insensitive when searching for files. The Mono documentation uses this for solving case sensitivity porting across Windows <-> Mac <-> Linux filesystems, but the MONO_IOMAP facility provides several other filesystem and I/O mapping operations.
In the event that doesn't work, you could try ciopfs, which is a Linux user-space case-insensitive filesystem. I've never used it, though.
I can't tell you WHY it's doing that, but my solution would be to change the name of the resource to match what it's looking for.
It DOES look like it's trying to process something as Resx...
Tool exited with code: 1. Output: Error: Invalid ResX input.
Maybe check your settings?
The compiler is trying to interpret this as a resource file rather than a resource. A resource file is a .txt or .resx file which is used to specify resources (e.g. strings, images) in a specific format, rather than a resource itself.
The GenerateResource task shouldn't be run on resources at all, because its purpose is to convert .txt or .resx files into .resource files to be embedded into an assembly.
If there are no actual resource files (.txt or .resx), then you should remove that task from the project's build altogether. Otherwise you just need to make sure that only the correct files are passed to it. I can't tell you exactly how to do this without being able to see more of your configuration, but this is a common configuration task, so you should be able to find guidance on Google.
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.
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>