I want to access a MSBuild variable inside an unit test, which is a .NET 4.5 class library project (classic csproj), but I failed to find any articles discussing a way to pass values from MSBuild into the execution context.
I thought about setting an environment variable during compilation and then reading that environment variable during execution, but that seems to require a custom task to set the environment variable value and I was a bit worried about the scope of the variable (ideally, I only wanted it to be available to the currently executing project, not globally).
Is there a known solution to reading an MSBuild property from inside a DLL project in runtime? Can MSBuild properties be "passed as parameters" during execution somehow?
I finally made it work by using the same code generation task that is used by default in .Net Core projects. The only difference is that I had to manually add the Target in the csproj file for it to work, as code creation is not standard for framework projects:
<Target Name="BeforeBuild">
<ItemGroup>
<AssemblyAttributes Include="MyProject.SolutionFileAttribute">
<_Parameter1>$(SolutionPath)</_Parameter1>
</AssemblyAttributes>
</ItemGroup>
<WriteCodeFragment AssemblyAttributes="#(AssemblyAttributes)" Language="C#" OutputDirectory="$(IntermediateOutputPath)" OutputFile="SolutionInfo.cs">
<Output TaskParameter="OutputFile" ItemName="Compile" />
<Output TaskParameter="OutputFile" ItemName="FileWrites" />
</WriteCodeFragment>
</Target>
The lines with Compile and FileWrites are there for it to play nicely with clean and such (see linked answers in my comments above). Everything else should be intuitive enough.
When the project compiles, a custom attribute is added to the assembly, that I can then retrieve using normal reflection:
Assembly
.GetExecutingAssembly()
.GetCustomAttribute<SolutionFileAttribute>()
.SolutionFile
This works really well and allows me to avoid any hardcoded searches for the solution file.
I think you have a couple of options:
Use environment variables, like you already suggested. A custom task maybe required to do that, but it is easy to do, without any extra assemblies on your part. The required global visibility might be an issue tough; consider parallel builds on a CI machine, for example.
Write a code fragment during build and include that into your resulting assembly (something akin to what you have already found under the link you suggested in your comments.
Write a file (even app.config) during build that contains settings reflecting the MSBuild properties you need to have; read those during test runs.
(BTW, what makes little sense, is to attempt to read the MSBuild project file again during runtime (using the Microsoft.Build framework). For once that is a whole lot of work to begin with, for little gain IMHO.
And even more important, you most likely - depending on the complexity and dependencies of your properties - need to make sure you invoke the MSBuild libraries with the same properties that where present during the actual build. Arguably, that might put you back were you started from.)
The last two options are best suited because they share equal traits: they are scoped only to the build/test run you currently have (i.e. you could have parallel running builds without interference).
I might go for the third, because that seems to be the easiest to realize.
In fact I have done so on a larger project I've been working on. Basically, we had different environments (database connection strings, etc.) and would select those
as a post build step by basically copying the specific myenv.config to default.config.
The tests would only ever look for a file named default.config and pick up whatever settings are set in there.
Another version, compiled from several internet sources, get environment variable when building, then use its value in code
file AssemblyAttribute.cs
namespace MyApp
{
[AttributeUsage(AttributeTargets.Assembly)]
public class MyCustomAttribute : Attribute
{
public string Value { get; set; }
public MyCustomAttribute(string value)
{
Value = value;
}
}
}
file MainForm.cs
var myvalue = Assembly.GetExecutingAssembly().GetCustomAttribute<MyCustomAttribute>().Value;
file MyApp.csproj, at the end (get %USERNAME% environment variable in build, generate SolutionInfo.cs file, automatically include it to build)
<Target Name="BeforeBuild">
<ItemGroup>
<AssemblyAttributes Include="MyApp.MyCustomAttribute">
<_Parameter1>$(USERNAME)</_Parameter1>
</AssemblyAttributes>
</ItemGroup>
<WriteCodeFragment AssemblyAttributes="#(AssemblyAttributes)" Language="C#" OutputFile="SolutionInfo.cs">
<Output TaskParameter="OutputFile" ItemName="Compile" />
<Output TaskParameter="OutputFile" ItemName="FileWrites" />
</WriteCodeFragment>
</Target>
Related
I have an existing Single-File Generator (housed in a C# Class Library). How do you add the VSIX project-level features to this project? The end goal is to compile my class library project and get a VSIX.
(I'm actually answering my own question. This is in relation to SIngle-file generator changes in Visual Studio 2017 - but that question wasn't asking what I'm answering here.)
First off,
your Single-File Generator class needs to have the appropriate class-level attributes:
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.Shell;
using VSLangProj80;
[ComVisible(true)]
[Guid("your-unique-identifier")]
[CodeGeneratorRegistration(
typeof(MyCustomTool),
"MyCustomTool",
vsContextGuids.vsContextGuidVCSProject,
GeneratesDesignTimeSource = true,
GeneratorRegKeyName = "MyCustomTool"
)]
[ProvideObject(
typeof(MyCustomTool),
RegisterUsing = RegistrationMethod.CodeBase
)]
public sealed class MyCustomTool : IVsSingleFileGenerator {
All of these attributes will ensure that a .pkgdef file is correctly generated within your VSIX. The .pkgdef file contains the registry entries that are used to register your single-file generator with Visual Studio.
Second,
add a text file "source.extension.vsixmanifest" to your project. Its "Build Action" should be "None." Give it some default text of:
<?xml version="1.0" encoding="utf-8"?>
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011">
<Metadata>
<Identity Id="MyCustomTool.MyCompany.another-random-guid" Version="1.0" Language="en-US" Publisher="MyCompany" />
<DisplayName>MyCustomTool</DisplayName>
<Description>Helpful information</Description>
</Metadata>
<Installation>
<InstallationTarget Id="Microsoft.VisualStudio.Community" Version="[15.0]" />
</Installation>
<Dependencies>
<Dependency Id="Microsoft.Framework.NDP" DisplayName="Microsoft .NET Framework" d:Source="Manual" Version="[4.5,)" />
</Dependencies>
<Prerequisites>
<Prerequisite Id="Microsoft.VisualStudio.Component.CoreEditor" Version="[15.0,16.0)" DisplayName="Visual Studio core editor" />
</Prerequisites>
</PackageManifest>
Most of this stuff is pretty esoteric. In the next step, we'll make it so you can edit this file with a designer.
Third
(and to answer the original question), you need to manhandle the .csproj file (your C# Class Library file). Specifically, you need to add the following:
<PropertyGroup>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ProjectTypeGuids>{82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
</PropertyGroup>
<ItemGroup>
<None Include="source.extension.vsixmanifest">
<SubType>Designer</SubType>
</None>
</ItemGroup>
<PropertyGroup>
<UseCodebase>true</UseCodebase>
</PropertyGroup>
<Import Project="$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets" Condition="'$(VSToolsPath)' != ''" />
So, what have we done here? Let's break it down.
First, we setup a path to the location of the Visual Studio toolset. Afterall, this is now an "extensions project." So, it needs to know where the VS-SDK is located.
Then, we changed the "ProjectTypeGuids" (which probably wasn't in your project file to begin with). Originally, it just included the guid for C# (which is the "FAE04EC0-..." guid). Now, it also includes the guid for VSIX (which is the "82b43b9b-..." guid).
We also made sure the "source.extension.vsixmanifest" file uses its new, fancy designer (instead of editing the file by hand).
The "UseCodeBase" element is important. This element prevents you from being forced to register your Generator with the system's COM registry. Instead, Visual Studio will simply load up your Generator from its installation location.
At the bottom, we import the MsBuild stuff for the VS-SDK.
Fourth,
Load your project back up. Go to the Project Properties screen and you'll see a new "VSIX" section at the bottom. Open that section and check the "Create VSIX Container during build" checkbox.
At this point, you can also double-check the "source.extension.vsixmanifest" file. Depending on how fancy your Generator is, you can change whatever you need. (The contents of the file that I pasted above is pretty much exactly what I used for my project.)
And finally,
you can compile your project. In the bin folder, you'll find MyCustomTool.dll and MyCustomTool.vsix. The .vsix file is simply a zip file. Inside the .vsix, you'll find MyCustomTool.pkgdef.
If we've done everything correctly, the .pkgdef file should look something like this:
[$RootKey$\Generators\{FAE04EC1-301F-11D3-BF4B-00C04F79EFBC}\MyCustomTool]
#="MyCustomTool"
"CLSID"="{your-unique-identifier}"
"GeneratesDesignTimeSource"=dword:00000001
[$RootKey$\CLSID\{your-unique-identifier}]
#="MyCustomTool"
"InprocServer32"="$WinDir$\SYSTEM32\MSCOREE.DLL"
"Class"="MyCustomTool"
"CodeBase"="$PackageFolder$\MyCustomTool.dll"
"ThreadingModel"="Both"
And, I think this is the longest SO answer I've written. And probably, only 5 people will ever read this :)
If, instead of implementing the IVsSingleFileGenerator on your class, your class inherits from the abstract class BaseCodeGeneratorWithSite that inherits from the abstract class BaseCodeGenerator that implements IVsSingleFileGenerator you need to add the following atribute to your class, to avoid the error message "Cannot find custom tool '...' on this system":
[ClassInterface(ClassInterfaceType.None)]
The reason is that the abstract class BaseCodeGeneratorWithSite is not COM visible.
I am new to MSBuild. Just started trying it two days ago, and now I am just testing it. I have run into a problem where I get this error:
"c:\Users\martinslot\Documents\Visual Studio 2010\Projects\MultifileAssembly\SpecializedBuild.xml" (BuildNumberUtil target) (1) ->
c:\Users\martinslot\Documents\Visual Studio 2010\Projects\MultifileAssembly\SpecializedBuild.xml(4,34): error MSB4006: There is a circular dependency in t
he target dependency graph involving target "BuildNumberUtil".
My MSBuild script look like this:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="BuildNumberUtil" DependsOnTargets="BuildStringUtil" >
<Message Text="=============Building modules for NumberUtil============="/>
<Csc TargetType="Module" Sources="NumberUtil/DoubleUtil.cs; NumberUtil/IntegerUtil.cs" AddModules="/StringUtil/StringUtil"/>
<Copy SourceFiles="#(NetModules)" DestinationFolder="../Output/Specialized"/>
</Target>
<Target Name="BuildStringUtil" DependsOnTargets="BuildNumberUtil" >
<Message Text="=============Building modules for StringUtil============="/>
<Csc TargetType="Module" Sources="StringUtil/StringUtil.cs;" AddModules="/NumberUtil/IntegerUtil;/NumberUtil/DoubleUtil"/>
<Copy SourceFiles="#(NetModules)" DestinationFolder="/Output/Specialized"/>
</Target>
</Project>
I understand the problem, actually I created this small example to see if MSBuild understood and could somehow correct the problem. How do I solve this?
My problem is that the two targets compile modules that rely on eachother. Does someone here have a solution on how to handle this kind of problem with MSBuild? Maybe I am constructing this in the wrong way?
You simply cannot build projects with circular dependencies. How could you? Which do you build first? There may be some esoteric, convoluted, incorrect way of doing so, but why do it? Circular dependencies usually indicate a design flaw. Fix the design, and you no longer have a circular dependency issue.
It is possible to construct Circular Modules within the scope of MSBuild and Visual Studio; however, doing so has a very limited set of situations where it would be valid to do so.
One key way to do this, if you're planning on using Xaml within your code, is to remove the Sources aspect of the Csc tag and generate your own .response file which actually points to the code you wish to inject. Within the Csc tag attributes you'd specify this file yourself in the ResponseFiles attribute.
Within your .response file, you would then break your application down into its assembly and netmodule components, making sure to include the core assembly's files first at all times. Typically the Csc tag's attributes are directly translated into Csc.exe command line parameters. The parameter names do not always match up. For the sake of resolution it's best to use full, non-relative, paths when referring to files (example, partial, .response below):
"X:\Projects\Code\C#\Solution Name\InternalName\ProjectName - InternalName\SearchContexts\StringSearchType.cs"
"X:\Projects\Code\C#\Solution Name\InternalName\ProjectName - InternalName\UI\Themes\Themes.cs"
/target:module /out:bin\x86\Debug\InternalName.UI.dll
"X:\Projects\Code\C#\Solution Name\InternalName\ProjectName - InternalName\UI\EditDatabaseImageControl.xaml.cs"
"X:\Projects\Code\C#\Solution Name\InternalName\ProjectName - InternalName\obj\x86\Debug\UI\EditDatabaseImageControl.g.cs"
You'll notice that this will end up with merging your multiple sets of Targets into one, and that I've included the xaml generated code myself. This is partly why you remove the Sources aspect, as the Xaml Page generator part of the MSBuild task automatically injects information into the #(Compile) set. Since there's a Debug/Release configuration, in the area where you define the response file to use, I create two versions of the response (since I'm using a T4 template):
ResponseFiles="$(CompilerResponseFile);InternalName.$(Configuration).response"
If you intended to include more than one platform in your code you'd likely need C*P response files where C is the number of configurations (Debug|Release) and P is the number of platforms (x86, x64, AnyCpu). This kind of solution would likely only be a sane method by using a generator.
The short version of this: it is possible to create circular modules so long as you can guarantee that you'll compile it all in one step. To ensure that you maintain the build functionality that is afforded to you with the Xaml build step, your best bet is to start with a normal C# project, and create your own .Targets file from the $(MSBuildToolsPath)\Microsoft.CSharp.targets in the <Import ... tag near the bottom. You'll also likely need a secondary csproj for design purposes since a large portion of intellisense is lost by using this workaround (or use a csproj Condition attribute where the target is selected by some flag you set). You'll also notice certain Xaml editors don't seem to like the binding to netmodule namespaces, so if you bind to types in a netmodule you'll likely have to do them in codebehind (I haven't tested workarounds for this since there's usually ways around static namespace binding)
For some reason within all this, the .baml compiled .xaml files are implicitly understood by the Csc compiler, I haven't been able to figure out where it's deriving this from a command argument, or if it's just implicit by design. If I had to guess they're inferred by the g.cs files associated to what you include in your list of included files.
Observe that this is occurred for web application (either ASP.NET standard web application or ASP.NET MVC application) and fix for this problem is to be removed the below line in ".csproj" file.
<PropertyGroup>
<BuildDependsOn>
$(BuildDependsOn);
Package
</BuildDependsOn>
</PropertyGroup>
Original Problem
In building our projects, I want the mercurial id of each repository to be embedded within the product(s) of that repository (the library, application or test application).
I find it makes it so much easier to debug an application being run by customers 8 timezones away if you know precisely what went into building the particular version of the application they are using. As such, every project (application or library) in our systems implements a way of getting at the associated revision information.
I also find it very useful to be able to see if an application has been compiled with clean (un-modified) changesets from the repository. 'Hg id' usefully appends a + to the changeset id when there are uncommitted changes in a repository, so this allows us to easily see if people are running a clean or a modified version of the code.
My current solution is detailed below, and fulfills the basic requirements, but there are a number of problems with it.
Current Solution
At the moment, to each and every Visual Studio solution, I add the following "Pre-build event command line" commands:
cd $(ProjectDir)
HgID
I also add an HgID.bat file to the Project directory:
#echo off
type HgId.pre > HgId.cs
For /F "delims=" %%a in ('hg id') Do <nul >>HgID.cs set /p = #"%%a"
echo ; >> HgId.cs
echo } >> HgId.cs
echo } >> HgId.cs
along with an HgId.pre file, which is defined as:
namespace My.Namespace {
/// <summary> Auto generated Mercurial ID class. </summary>
internal class HgID {
/// <summary> Mercurial version ID [+ is modified] [Named branch]</summary>
public const string Version =
When I build my application, the pre-build event is triggered on all libraries, creating a new HgId.cs file (which is not kept under revision control) and causing the library to be re-compiled with with the new 'hg id' string in 'Version'.
Problems with the current solution
The main problem is that since the HgId.cs is re-created at each pre-build, so every time we need to compile anything, all projects in the current solution are re-compiled. Since we want to be able to easily debug into our libraries, we usually keep many libraries referenced in our main application solution. This can result in build times which are significantly longer than I would like.
Ideally I would like the libraries to compile only if the contents of the HgId.cs file have actually changed, as opposed to having been re-created with exactly the same contents.
The second problem with this method is it's dependence on specific behaviour of the windows shell. I've already had to modify the batch file several times, since the original worked under XP but not Vista, the next version worked under Vista but not XP and finally I managed to make it work with both. Whether it will work with Windows 7 however is anyones guess and as time goes on, I see it more likely that contractors will expect to be able to build our apps on their Windows 7 boxen.
Finally, I have an aesthetic problem with this solution, batch files and bodged together template files feel like the wrong way to do this.
My actual questions
How would you solve/how are you solving the problem I'm trying to solve?
What better options are out there than what I'm currently doing?
Rejected Solutions to these problems
Before I implemented the current solution, I looked at Mercurials Keyword extension, since it seemed like the obvious solution. However the more I looked at it and read peoples opinions, the more that I came to the conclusion that it wasn't the right thing to do.
I also remember the problems that keyword substitution has caused me in projects at previous companies (just the thought of ever having to use Source Safe again fills me with a feeling of dread *8').
Also, I don't particularly want to have to enable Mercurial extensions to get the build to complete. I want the solution to be self contained, so that it isn't easy for the application to be accidentally compiled without the embedded version information just because an extension isn't enabled or the right helper software hasn't been installed.
I also thought of writing this in a better scripting language, one where I would only write HgId.cs file if the content had actually changed, but all of the options I could think of would require my co-workers, contractors and possibly customers to have to install software they might not otherwise want (for example cygwin).
Any other options people can think of would be appreciated.
Update
Partial solution
Having played around with it for a while, I've managed to get the HgId.bat file to only overwrite the HgId.cs file if it changes:
#echo off
type HgId.pre > HgId.cst
For /F "delims=" %%a in ('hg id') Do <nul >>HgId.cst set /p = #"%%a"
echo ; >> HgId.cst
echo } >> HgId.cst
echo } >> HgId.cst
fc HgId.cs HgId.cst >NUL
if %errorlevel%==0 goto :ok
copy HgId.cst HgId.cs
:ok
del HgId.cst
Problems with this solution
Even though HgId.cs is no longer being re-created every time, Visual Studio still insists on compiling everything every time. I've tried looking for solutions and tried checking "Only build startup projects and dependencies on Run" in Tools|Options|Projects and Solutions|Build and Run but it makes no difference.
The second problem also remains, and now I have no way to test if it will work with Vista, since that contractor is no longer with us.
If anyone can test this batch file on a Windows 7 and/or Vista box, I would appreciate hearing how it went.
Finally, my aesthetic problem with this solution, is even stronger than it was before, since the batch file is more complex and this there is now more to go wrong.
If you can think of any better solutions, I would love to hear about them.
I've just released a small open-source MSBuild task to do exactly what you need:
It puts your Mercurial revision number into your .NET assembly version
You can tell from the version if an assembly has been compiled with uncommitted changes
Does not cause unnecessary builds if the revision hasn't changed
Not dependent on Windows scripting
Nothing to install - you just add a small DLL to your solution, and edit some files in your project
http://versioning.codeplex.com
I think I have an answer for you. This will be a bit involved, but it gets you away from having to do any batch files. You can rely on MSBuild and Custom Tasks to do this for you. I've used the extension pack for MSBuild (Available at CodePlex) - but the second task you need is something you could just as easily write yourself.
With this solution, you can right click on the DLL and see in the file properties which Mercurial Version the DLL (or EXE) came from.
Here are the steps:
Get the MBBuildExtension Pack
OR Write Custom Task to overwrite
AssemblyInfo.cs
Create a Custom
Build Task in its own project to get
the Mercurial Id(code below).
Edit project files that need the
Mercurial Id to use Custom Task
(code below).
Custom Task to Get mercurial id: (This would need to be tested well and perhaps better generalized...)
using System;
using System.Diagnostics;
using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;
namespace BuildTasks
{
public class GetMercurialVersionNumber : Task
{
public override bool Execute()
{
bool bSuccess = true;
try
{
GetMercurialVersion();
Log.LogMessage(MessageImportance.High, "Build's Mercurial Id is {0}", MercurialId);
}
catch (Exception ex)
{
Log.LogMessage(MessageImportance.High, "Could not retrieve or convert Mercurial Id. {0}\n{1}", ex.Message, ex.StackTrace);
Log.LogErrorFromException(ex);
bSuccess = false;
}
return bSuccess;
}
[Output]
public string MercurialId { get; set; }
[Required]
public string DirectoryPath { get; set; }
private void GetMercurialVersion()
{
Process p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.WorkingDirectory = DirectoryPath;
p.StartInfo.FileName = "hg";
p.StartInfo.Arguments = "id";
p.Start();
string output = p.StandardOutput.ReadToEnd().Trim();
Log.LogMessage(MessageImportance.Normal, "Standard Output: " + output);
string error = p.StandardError.ReadToEnd().Trim();
Log.LogMessage(MessageImportance.Normal, "Standard Error: " + error);
p.WaitForExit();
Log.LogMessage(MessageImportance.Normal, "Retrieving Mercurial Version Number");
Log.LogMessage(MessageImportance.Normal, output);
Log.LogMessage(MessageImportance.Normal, "DirectoryPath is {0}", DirectoryPath);
MercurialId = output;
}
}
And the modified Project File: (The comments may help)
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!--this is the import tag for the MSBuild Extension pack. See their documentation for installation instructions.-->
<Import Project="C:\Program Files (x86)\MSBuild\ExtensionPack\MSBuild.ExtensionPack.tasks" />
<!--Below is the required UsingTask tag that brings in our custom task.-->
<UsingTask TaskName="BuildTasks.GetMercurialVersionNumber"
AssemblyFile="C:\Users\mpld81\Documents\Visual Studio 2008\Projects\LambaCrashCourseProject\BuildTasks\bin\Debug\BuildTasks.dll" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{D4BA6C24-EA27-474A-8444-4869D33C22A9}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>LibraryUnderHg</RootNamespace>
<AssemblyName>LibraryUnderHg</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Xml.Linq">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Data.DataSetExtensions">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Class1.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="Build" DependsOnTargets="BeforeBuild">
<!--This Item group is a list of configuration files to affect with the change. In this case, just this project's.-->
<ItemGroup>
<AssemblyInfoFiles Include="$(MSBuildProjectDirectory)\Properties\AssemblyInfo.cs" />
</ItemGroup>
<!--Need the extension pack to do this. I've put the Mercurial Id in the Product Name Attribute on the Assembly.-->
<MSBuild.ExtensionPack.Framework.AssemblyInfo AssemblyInfoFiles="#(AssemblyInfoFiles)"
AssemblyProduct="Hg: $(MercurialId)"
/>
<!--This is here as an example of messaging you can use to debug while you are setting yours up.-->
<Message Text="In Default Target, File Path is: #(AssemblyInfoFiles)" Importance="normal" />
</Target>
<Target Name="BeforeBuild">
<!--This is the custom build task. The Required Property in the task is set using the property name (DirectoryPath)-->
<BuildTasks.GetMercurialVersionNumber DirectoryPath="$(MSBuildProjectDirectory)">
<!--This captures the output by reading the task's MercurialId Property and assigning it to a local
MSBuild Property called MercurialId - this is reference in the Build Target above.-->
<Output TaskParameter="MercurialId" PropertyName="MercurialId" />
</BuildTasks.GetMercurialVersionNumber>
</Target>
<!--<Target Name="AfterBuild">
</Target>-->
</Project>
Last Note: The build tasks project only needs to be built once. Don't try to build it every time you do the rest of your solution. If you do, you will find that VS2008 has the dll locked. Haven't figured that one out yet, but I think the better thing to do is build the dll as you want it, then distribute ONLY the dll with your code, ensuring that the dll's location is fixed relative to every project you need to use it in. That way, no one has to install anything.
Good luck, and I hope this helps!
Audie
Have you considered using a string resource instead of a C# language string constant? String resources can be edited/replaced in the output binaries post-build using tools intended for localization.
You would emit your mercurial version number to a text file that is not used by the C# build, then using a post-build operation replace the version resource with the actual value from the emitted text file. If you strong-name sign your assemblies, the resource string replacement would need to happen before the signing.
This is how we handled this issue at Borland years ago for Windows products. The world has become more complicated since then, but the principle still applies.
Im my .hgrc I have this:
[extensions]
hgext.keyword =
hgext.hgk =
[keyword]
** =
[keywordmaps]
Id = {file|basename},v {node|short} {date|utcdate} {author|user}
And in my source file (Java) I do:
public static final String _rev = "$Id$";
After commit $Id$ gets expanded into something like:
public static final String _rev = "$Id: Communication.java,v 96b741d87d07 2010/01/25 10:25:30 mig $";
We have solved this issue with another source control system, subversion.
What we do is that we have a commonassemblyinfo.cs file and we plug the svn revision number into that using an msbuild script.
We literally call svninfo and scrape the output from the revision: part and then poke it into the CommonAssemblyInfo.cs file.
Every project in our solution has a link to the common file and is then compiled, meaning that the assemblies are versioned on compilation with the svn revision number, it also means all dependent libraries we have written are also versioned.
We acheived this quite easily using cruisecontrol .net and msbuild files.
I have not used mercurial but I beleive you could do something similar with the id command ?
but because of the format of the output you would need to approach how you used it somewhat differently.
I would have a standard xml file that is read on app startup and you could poke the information into that (works for resx files also). You can then read that value back out in code. A bit icky but it works.
My favourite solution is the way we do it but you can't do that in mercurial as the changeset info is not purely numeric like the svn revision number is.
There seems to be multiple possible approaches to this problem. The first and probably preferred solution would be to install a build server and only distribute builds generated there to customers. This has the advantage that you never ship uncommitted changes. By using MSBuild, NAnt or some other task-based build tool the entire process is very flexible. I was able to install TeamCity and get the first couple of builds up and running with very little effort, but there are other good build servers too. This really should be your solution.
If you for some reason insist that it's okay to distribute developer builds to clients ;) then you'll need a local solution.
A fairly easy solution would be to use the built-in support for auto-incrementing the build number of an assembly:
// major.minor.build.revision
[assembly:AssemblyVersion("1.2.*")]
The * makes the build number auto-increment every time you compile (and there are changes). The revision number is a random number. From here you can either keep track of the association to the Mercurial id by saving both pieces of information, e.g. by posting it to some internal web solution or whatever fits your particular needs, or update the generated assembly. I'd suspect you could use PostSharp or Mono.Cecil do rewrite the assembly, e.g. by patching the revision number to be the id. If your assemblies are signed the rewrite needs to happen before you sign them, which is a bit bothersome if you don't have a build file. Note that VS can be configured to compile using your custom build file instead of the default build procedure.
My final suggestion is to create a separate project just for the hg id, and use the post-build step to merge the generated assemblies into one. ILMerge supports re-signing of signed assemblies and this is therefore likely to be easier to make work. The downside is that redistribution of ILMerge is not allowed (although commercial use is).
It's not a solution but hopefully inspiration to get you going.
Here's what we do here: we do not embed the revision every time the project is built on developer's machines. At best this causes the problems you've mentioned above, and at worst it's misleading because you could have modified files in your workspace.
Instead we only embed the source control revision number when our projects are built under TeamCity on a separate build server. The revision is embedded into both AssemblyVersion and AssemblyFileVersion, split across the last two parts.
By default the version ends in 9999.9999, and we split the revision in such a way that revision 12345678 would become 1234.5678 (not that we're anywhere close to the 12 millionth revision...).
This process guarantees that a product whose version is 2.3.1.1256 was definitely a pristine build of revision 11,256. Anything developers build manually will instead look like this: 2.3.9999.9999.
The exact details of how the above is achieved are not directly relevant since we're not using Mercurial, but briefly: TeamCity handles checking out the required revision and passes its number to our MSBuild script, which then does the rewriting of AssemblyInfo.cs with the help of a custom task we wrote.
What I particularly like about this is that absolutely no files are modified by hitting F5 in Visual Studio - in fact, as far as Visual Studio is concerned, it's working with a plain old normal solution.
I have a need to reference two different versions of the Sharepoint API dll. I have a webservice that needs to run under both Sharepoint 2 and Sharepoint 3, but also needs to work with new features provided by the Sharepoint 3 API (Checkout and Content Approval)
What is the best way to acheive this - I'm currently leaning towards having two projects, with the code in a single file shared between the two with various sections of the code compiled in using conditional compilation.
Is there a better way ?
Thanks
Matt
This is how I spit out .NET 1.1 versions compiled against WSSv2 API and .NET 2.0 compiled against WSSv3 assembly. It will work for VS 2005 and 2008.
You will need to use MSBEE http://www.codeplex.com/Wiki/View.aspx?ProjectName=MSBee
Working with .NET 1.1 with Visual Studio 2008
Some tips
Open up *.csproj and find out where the SharePoint dll is referenced and change to something like this which changes the referenced assembly depending upon your target (FX1_1 means you are targeting .NET1.1 and therefore WSSv2)
<Reference Include="Microsoft.SharePoint">
<HintPath Condition="'$(TargetFX1_1)'!='true'">pathto\WSS3\Microsoft.SharePoint.dll</HintPath>
<HintPath Condition="'$(TargetFX1_1)'=='true'">pathto\WSS2\Microsoft.SharePoint.dll</HintPath>
</Reference>
Use conditional compilation for differences where necessary
#if FX1_1
// WSSv2 specific code
#else
// WSSv3 specific code
#endif
If you get a compiler error but the code looks right it may be that the error is only for .NET1.1 / WSSv2 and compiles fine in .NET2/WSSv3. Check the output tab to see for which target the error occurred
You will also need to master some MSBUILD ninja moves to keep a 1 step build process and keep yourself sane http://brennan.offwhite.net/blog/2006/11/30/7-steps-to-msbuild/ using MSBUILD you can get VS to compile both versions at the same time without resorting to the command line.
This will run the .NET1.1 compilation after .NET has finished and output some messages to the Output window to help you work out where errors occurred.
<Target Name="BeforeBuild">
<Message Text="--- Building for .NET 1.1 ---" Importance="high" Condition="'$(TargetFX1_1)'=='true'" />
<Message Text="--- Building for .NET 2.0 ---" Importance="high" Condition="'$(TargetFX1_1)'!='true'" />
</Target>
<Target Name="AfterBuild" Condition="'$(TargetFX1_1)'!='true'">
<MSBuild Projects="$(MSBuildProjectFile)" Properties="TargetFX1_1=true;" />
</Target>
You could give an "extern alias" a go.
This is one of those times when the VB late binding (option strict off) approach works well. Roll on C# 4.0 and dynamic.
You might try writing an interface for the bits you need (in a base library), and write 2 dlls: one referencing each version of the sharepoint dll. For both projects, implement the interface (throwing NotSupportedException for the bits you can't do), and load the appropriate dll at runtime? (factory approach)
Just try it with a single method before you get too absorbed... don't do the whole thing until you know it works for the simplest of simple methods.
I have a solution that contains several c# projects and I would like to be able to set the output path and other properties on all the projects together in a single place. Property Sheets (vsprops) do not seem to be able available for C# projects and the $(SolutionDir) variable is ignored. Are there any other methods to set properties across several C# projects?
Update
By Following the information in the answer by Bas Bossink I was able to set the output path of several projects by creating a common csproj and importing it into the individual project. A few other points:
When building in Visual Studio if changes are made to the common project it is necessary to touch/reload any projects that reference it for the changes to be picked up.
Any properties which are also set in a individual project will override the common properties.
Setting $(SolutionDir) as the output path via the Visual Studio UI does not work as expected because the value is treated as a string literal rather than getting expanded. However, Setting $(SolutionDir) directly into the csproj file with a text editor works as expected.
A csproj file is already an msbuild file, this means that csproj files can also use an import element as described here. The import element is
exactly what you require. You could create a Common.proj that contains something like:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5"xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<OutputPath>$(SolutionDir)output</OutputPath>
<WarningLevel>4</WarningLevel>
<UseVSHostingProcess>false</UseVSHostingProcess>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>
You can import this Common.proj in each of your csprojs, for instance like so:
<Import Project="..\Common.proj" />
The import statement should precede any tasks that depend on the properties defined in Common.proj
I hope this helps. I can't confirm your problems with the $(SolutionDir) variable I've used it many times. I do know however that this variable does not get set when you run an msbuild command via the commandline on a specific project that is contained in a solution. It will be set when you build your solution in Visual Studio.
Unfortunately, these bits of information such as output path are all stored inside the individual *.csproj files. If you want to batch-update a whole bunch of those, you'll have to revert to some kind of a text-updating tool or create a script to touch each of those files.
For things like this (apply changes to a bunch of text files at once) I personally use WildEdit by Helios Software - works like a charm and it's reasonably priced.
But I'm sure there are tons of free alternatives out there, too.
I would suggest you to use a build tool such as MSBuild or NAnt which would give you more flexibility on your builds. Basically the idea is to kick off a build using (in most cases) a single configurable build file.
I would personally recommend NAnt.
You could find an awesome tutorial on NAnt on JP Boodhoo's blog here
Set the $(OutputPath) property in a common property sheet. Then delete that entry in all the project files you want to it to affect. Then import that property sheet into all your projects.
For hundreds of projects that can be very tedious. Which is why I wrote a tool to help with this:
https://github.com/chris1248/MsbuildRefactor