I'm trying to build upon this question:
Reading a single value from a file in MSBuild
My goal is to have a single place to put the version number that's used in several projects, and I also want a portion of the version number in the DLL file name for one of the projects.
Based on the question above, I already got the first part, but I'm having difficulty with the second part and would appreciate some guidance.
In my solution, I set up a plain text file called Version.txt containing my full version number only:
1.1.0.0
In both of my projects, I opened their AssemblyInfo.cs files and removed the AssemblyVersion and AssemblyFileVersion items, then modified both projects to generate them in a separate file as described in the question above.
<ItemGroup>
<VersionFile Include="..\Version.txt" />
</ItemGroup>
<Target Name="BeforeBuild">
<ReadLinesFromFile File="#(VersionFile)">
<Output TaskParameter="Lines" PropertyName="VersionNumber" />
</ReadLinesFromFile>
<Delete Files="Properties\Version.cs" />
<WriteLinesToFile File="Properties\Version.cs" Lines="using System.Reflection%3B
[assembly: AssemblyVersion("$(VersionNumber)")]
[assembly: AssemblyFileVersion("$(VersionNumber)")]" />
</Target>
Now when I build, I get a generated Properties\Version.cs file for each project, which is used to build the EXE/DLL and shows up as "1.1.0.0" in their file properties. This is exactly what I want.
For the DLL, I would like to name the assembly "filename.v1.1.dll", where the "1.1" comes from the first two components in Version.txt above. I'm flexible on the format of Version.txt as long as I can get the full "1.1.0.0" in the EXE/DLL properties and "1.1" in the DLL file name.
To try this out, I modified the DLL's csproj file to have:
<RootNamespace>dllfile</RootNamespace>
<AssemblyName>dllfile.v$(VersionNumber)</AssemblyName>
Of course, this will insert the full version number in the file name, which I don't want.
Does anyone have any tips on how to proceed?
Thanks.
EDIT: I have been able to extract the major/minor components of the version number by adding the following to my .csproj BeforeBuild target:
<ReadLinesFromFile File="#(VersionFile)">
<Output TaskParameter="Lines" PropertyName="VersionNumber" />
</ReadLinesFromFile>
<PropertyGroup>
<VersionNumberFirstDotIndex>$(VersionNumber.IndexOf('.'))</VersionNumberFirstDotIndex>
<VersionNumberMajorStart>0</VersionNumberMajorStart>
<VersionNumberMajorLen>$(VersionNumberFirstDotIndex)</VersionNumberMajorLen>
<VersionNumberMinorStart>$([MsBuild]::Add(1, $(VersionNumberFirstDotIndex)))</VersionNumberMinorStart>
<VersionNumberSecondDotIndex>$(VersionNumber.IndexOf('.', $(VersionNumberMinorStart)))</VersionNumberSecondDotIndex>
<VersionNumberMinorLen>$([MSBuild]::Subtract($([MSBuild]::Subtract($(VersionNumberSecondDotIndex), $(VersionNumberFirstDotIndex))), 1))</VersionNumberMinorLen>
<VersionNumberMajor>$(VersionNumber.Substring($(VersionNumberMajorStart), $(VersionNumberMajorLen)))</VersionNumberMajor>
<VersionNumberMinor>$(VersionNumber.Substring($(VersionNumberMinorStart), $(VersionNumberMinorLen)))</VersionNumberMinor>
<VersionNumberShort>$(VersionNumberMajor).$(VersionNumberMinor)</VersionNumberShort>
</PropertyGroup>
<Message Text="DEBUG1 VersionNumberFull=$(VersionNumber)" Importance="High" />
<Message Text="DEBUG2 VersionNumberAbbrev=$(VersionNumberShort)" Importance="High" />
<Delete Files="Properties\Version.cs" />
<WriteLinesToFile File="Properties\Version.cs" Lines="using System.Reflection%3B
[assembly: AssemblyVersion("$(VersionNumber)")]
[assembly: AssemblyFileVersion("$(VersionNumber)")]" />
The only piece I'm missing now is how to get this VersionNumberShort into the DLL file name. Unless someone has a better idea, I can take Peter's suggestion and use Move tasks:
<Target Name="AfterBuild">
<Move SourceFiles="$(OutputPath)$(AssemblyName).pdb" DestinationFiles="$(OutputPath)$(AssemblyName).v$(VersionNumberShort).pdb" />
<Move SourceFiles="$(OutputPath)$(AssemblyName).dll" DestinationFiles="$(OutputPath)$(AssemblyName).v$(VersionNumberShort).dll" />
</Target>
<Target Name="AfterClean" DependsOnTargets="Common">
<Delete Files="$(OutputPath)$(AssemblyName).v$(VersionNumberShort).pdb" ContinueOnError="true" />
<Delete Files="$(OutputPath)$(AssemblyName).v$(VersionNumberShort).dll" ContinueOnError="true" />
</Target>
Since I needed the same property definitions as before, I moved the snippet above into a "Common" target and referenced it in both the build and clean tasks shown here.
Peter - If you want to move your comment as an answer, I'll accept it.
Thanks!
EDIT: Following jdlugosz's answer, I tried setting the AssemblyName inside my task. Unfortunately, this still didn't seem to have any effect based on the original example listed at the top:
<Target Name="BeforeBuild">
...
<WriteLinesToFile ... />
<PropertyGroup>
<AssemblyName>dllfile.v$(VersionNumber)</AssemblyName>
</PropertyGroup>
</Target>
I tried running this with MSBuild from a Visual Studio Developer Command Prompt:
msbuild /target:clean projfile.csproj
msbuild /verbosity:diag projfile.csproj > out.txt
Prior to this, I renamed the at the top of my csproj file and in the "redefinition" to something unique to make it easy to search (e.g. "dllfileoriginal" vs. "dllfilemodified").
Looking through the output log, I can't find any reference to the modified text; it's still dllfileoriginal everywhere in the output.
Following the WriteLinesToFile task, it looks like the following targets were built:
IncrementalClean (finished)
PostBuildEvent
CoreBuild
AfterBuild
Build
There's no reference to either DLL name inside these.
It looks like the is currently my best bet still.
The Target Name is is shown on the General page under the Configuration Properties tab in the IDE Property Page editor. I don't have one handy myself to look up the name for you, but you can do it by changing the blank in the IDE to something like XXXX and save. Then view the diff in the version control commit reviewer and see what the name of the Property is. In this case, then edit the line to change XXXX to $(OutputPath)$(AssemblyName).v$(VersionNumberShort)
Oh, check out the FormatVersion task, which might help. I think there are some premade tasks that manipulate a version assembly similar to what you show, too.
What I'm doing for versions is passing the pieces in via #defines as /D command line arguments. I guess you don't have that in C# though, IIRC.
This works for me, and it solves the seemingly simple problem of appending the version info string to a filename at build.
First, the post-build event:
(Right-Click Project -> Properties -> Build Events -> Edit Post-build...)
$(TargetPath) "version" > $(TargetDir)text.txt
set /p version= <$(TargetDir)text.txt
copy $(TargetPath) $(TargetDir)$(TargetName)_%version%.exe
del $(TargetDir)text.txt
Now, the trick:
Overload sub main to return the version info, and call it in a post-build event on the exe that was just built.
here is an example in F#:
[<EntryPoint>]
let main argv =
let version = argv.Length = 1 && argv.[0] = "version"
if version then
let version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString()
do stdout.WriteLine(version)
do stdout.Flush()
else
try
//...
The post-build event above
1) calls the newly built exe with a "version" arg, and writes the output to a txt file
2) reads the text file contents into a local variable
3) renames the newly built exe by adding the version info
3) copies the newly built exe adding the version info to the name
4) cleans up the temp file
*changed "move" to "copy" so that Visual Studio can still F5 the project
Related
I've done a lot of MSBuild customization for my C++ projects in the past. The Input and Output attributes of a MSBuild target are used to determine whether the target has to be executed or not. Additionally Visual Studio uses the .tlog files (located in the intermediate directory) to determine whether MSBuild has to be invoked at all.
Now I'm working on a C# project. I wrote a simple MSBuild target which copies a file to the output directory:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="CopyMyFile" BeforeTargets="AfterBuild" Inputs="$(ProjectDir)File.dat" Outputs="$(TargetDir)FileRenamed.dat">
<Copy SourceFiles="$(ProjectDir)File.dat" DestinationFiles="$(TargetDir)FileRenamed.dat" OverwriteReadOnlyFiles="true">
</Copy>
</Target>
</Project>
The target works as expected if the build is invoked through MSBuild.exe. The file is copied if the target file does not exist or the source file has been modified.
If I invoke the build inside Visual Studio it does not work as expected. Visual Studio does not invoke MSBuild if I delete the file from the output directory. On the other hand MSBuild is invoked every time I build the project after modifiying the source file even if there are no other changes made.
It seems that Visual Studio just compares every file from a project to the output files (.exe, .dll or .pdb). If any file in the project is newer than the output files, MSBuild is invoked. In my case MSBuild does not update the .exe file, so MSBuild is invoked again and again.
In a C++ project this behaviour is controlled by the .tlog files. Is there anything similar in a C# project?
Thanks a lot!
The answer might be no, nothing similar to the tlog mechanism. I am not 100% sure though, also because it's strange you cannot do something quite basic as this as that would mean MS basically ditched the tracker stuff for C# (and similar) projects but didn't replace it with something which can be hooked into by users.
Using procmon you can see VS getting timestamps of output and input files, but nowhere I found a way to interfere with what it treats as input and output files. It looks like VS gets a list of everything included directly in the project file (i.e. Reference/Content/Compile/.. item groups of what is shown in VS), not what is listed in Taget's Inputs/Outputs, and at the start of a build compares timstamps for just those items. If everything (well, everything as far as VS is considered) is up to date no msbuild process is launched for the build.
There is a workaround, though not super nice: if you add a 'dummy' Content item (e.g. Right-click project->Add New Item->Text File) and set it to always be copied (Right-clik text file just added->Properties->Copy to Output Directory->Copy always) then VS will always start a build and hence check your target's Inputs vs the Outputs and run if if you deleted FileRenamed.dat.
It looks like this is just poorly documented. This site shows you can easily hook up a command line tool, while lifting on the incremental features of tlog files.
To make sure the information doesn't get lost, I'll just copy over their use case, but looking at that, I think it's easy to transform into your needs. Every occurrence of dcx can be replaced by e.g. data
1. Create a definition .xml file
Define an ItemType
Link a ContentType to the ItemType
Hook up a FileExtension
<?xml version="1.0" encoding="utf-8"?>
<ProjectSchemaDefinitions xmlns="http://schemas.microsoft.com/build/2009/properties">
<!-- Associate DXCShader item type with .hlsl files -->
<ItemType Name="DXCShader" DisplayName="DXC Shader" />
<ContentType Name="DXCShader" ItemType="DXCShader" DisplayName="DXC Shader" />
<FileExtension Name=".hlsl" ContentType="DXCShader" />
</ProjectSchemaDefinitions>
2. Create a .targets file
Include the .xml definitions file
Create a Target that depends on one of your build hooks (here: ClCompile)
Create an ItemGroup in your Target that will serve as the argument to your CustomBuild. Message, Command, AdditionalInputs and Output are meta-attributes that are relevant.
Invoke CustomBuild with MinimalRebuildFromTracking="true" and a TrackerLogDirectory to contain the tlog files. This part is the magic ingredient that makes MSBuild skip the build if your dependencies are up-to-date.
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<!-- Include definitions from dxc.xml, which defines the DXCShader item. -->
<PropertyPageSchema Include="$(MSBuildThisFileDirectory)dxc.xml" />
<!-- Hook up DXCShader items to be built by the DXC target. -->
<AvailableItemName Include="DXCShader">
<Targets>DXC</Targets>
</AvailableItemName>
</ItemGroup>
<Target
Name="DXC"
Condition="'#(DXCShader)' != ''"
BeforeTargets="ClCompile">
<Message Importance="High" Text="Building shaders!!!" />
<!-- Find all shader headers (.hlsli files) -->
<ItemGroup>
<ShaderHeader Include="*.hlsli" />
</ItemGroup>
<PropertyGroup>
<ShaderHeaders>#(ShaderHeader)</ShaderHeaders>
</PropertyGroup>
<!-- Setup metadata for custom build tool -->
<ItemGroup>
<DXCShader>
<Message>%(Filename)%(Extension)</Message>
<Command>
"$(WDKBinRoot)\x86\dxc.exe" -T vs_6_0 -E vs_main %(Identity) -Fh %(Filename).vs.h -Vn %(Filename)_vs
"$(WDKBinRoot)\x86\dxc.exe" -T ps_6_0 -E ps_main %(Identity) -Fh %(Filename).ps.h -Vn %(Filename)_ps
</Command>
<AdditionalInputs>$(ShaderHeaders)</AdditionalInputs>
<Outputs>%(Filename).vs.h;%(Filename).ps.h</Outputs>
</DXCShader>
</ItemGroup>
<!-- Compile by forwarding to the Custom Build Tool infrastructure,
so it will take care of .tlogs and error/warning parsing -->
<CustomBuild
Sources="#(DXCShader)"
MinimalRebuildFromTracking="true"
TrackerLogDirectory="$(TLogLocation)"
ErrorListRegex="(?'FILENAME'.+):(?'LINE'\d+):(?'COLUMN'\d+): (?'CATEGORY'error|warning): (?'TEXT'.*)" />
</Target>
</Project>
Once my nant build has completed I'd like to rename the generated .exe file using a post-build command which is appended to the end of the projects .csproj file (within the Project element):
<Target Name="AfterBuild">
<Copy SourceFiles="$(TargetDir)\$(TargetName).exe" DestinationFiles="$(TargetDir)\MyApplication-$(AssemblyFileVersion).exe" SkipUnchangedFiles="true" />
</Target>
As you can see above, I am trying to rename the executable to: MyApplication-$(AssemblyFileVersion).exe however, this is obviously wrong, as the resulting executable is simply named: MyApplication-.exe (so the version I am trying to add at the end of the file name is missing).
I have defined the AssemblyFileInfoVersion in the AssemblyInfo.cs file as follows:
[assembly: AssemblyFileVersion("1.5.1")]
So the question is: How can I access the AssemblyFileVersion in the csproj file of that same project?
GetAssemblyIdentity can get information about complied assemblies. The task output contain metadata entries about Version, PublicKeyToken, and Culture.
I used $(TargetDir)\$(TargetName).exe as the assembly file.
<ItemGroup>
<AssembliesPath Include="$(TargetDir)\$(TargetName).exe" />
</ItemGroup>
<Target Name="GetAssemblyInfo">
<GetAssemblyIdentity AssemblyFiles="#(AssembliesPath)">
<Output TaskParameter="Assemblies" ItemName="AssemblyInfo"/>
</GetAssemblyIdentity>
</Target>
And then:
<Target Name="AfterBuild">
<GetAssemblyInfo />
<Copy SourceFiles="$(TargetDir)\$(TargetName).exe" DestinationFiles="$(TargetDir)\MyApplication-%(AssemblyInfo.Version).exe" SkipUnchangedFiles="true" />
</Target>
The following code is from ISun's original answer and this is actually how I ended up doing it in the end, as I had problems defining a custom task (ms build references were constantly auto-kicked and the build kept failing over and again).
As you can see from the comments under ISun's answer I always got the version 0.0.0.0 - despite having changed the version for AssemblyFileVersion and AssemblyVersion to 1.0.0.0 by manually opening the AssemblyInfo.cs in a texteditor. I later read how to edit the AssemblyInfo.cs from my Visual Studio, here is how:
Right-click on your project (that generates the exe file)
Click on Properties
Open the Application tab (first tab on the left) on the window that'll open
You'll see fields for setting the Assembly name, Default namespace etc however to edit the
AssemblyInfo.cs, simply click on the Button called Assembly Information to the right
And for some reason - I have no clue why it suddenly worked, after I had put in all the information via the above method (using Visual Studio) ! When I first opened the AssemblyInfo.cs using the above way, all my fields were actually empty, despite them being filled in the actual file.
And now that I got the AssemblyInfo.cs to finally function correctly, I used ISun's original code to achieve my goal. By adding the following snippet just before the closing tag in my project's .csproj file:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="AfterBuild">
<GetAssemblyIdentity AssemblyFiles="$(TargetDir)\$(TargetName).exe">
<Output TaskParameter="Assemblies" ItemName="AssemblyInfo"/>
</GetAssemblyIdentity>
<Copy SourceFiles="$(TargetDir)\$(TargetName).exe" DestinationFiles="$(TargetDir)\MyApplication-%(AssemblyInfo.Version).exe" SkipUnchangedFiles="true" />
</Target>
This seems like an obvious thing to want to do but I have pulled most of my hair out trying to find any examples on the web or do it myself.
I have a c# solution with 19 projects and a Jenkins build server running a build script to drive MSBuild. MSBuild will of course determine what does and does not need to be compiled based on inputs versus outputs.
I am trying to create a custom target to conditionally update the AssemblyInfo.cs of those projects MSBuild is going to compile to increment the file versions. Of course I want to leave the projects not being compiled alone.
I know how to inject a target prior to the CoreBuild that runs every time so if there is some variable I can test to see if a compile will occur that can work. I also know how to determine if a compile ran and therefore conditionally do some post processing which is possible but not ideal.
How can I tweak my build process to achieve this?
Since it seems there's no straight answer to the question, does anyone know how to perform the same logic as MSBuild to determine what projects require a rebuild?
In the end the solution was a combination of Sayed Ibrahim Hashimi's blog entry and information from the MSDN Forum entry 'Execute target when (core)compile will execute'.
I basically took Sayed's injection method to get my target to run 'extend-corecompile.proj' on all projects without having to edit each proj file but replaced it's contents with an override for 'CoreCompileDependsOn' that points to a custom target that adopts the same inputs and outputs as the 'CoreCompile' target. The end result is a target that only runs when 'CoreCompile' will run while being centrally managed in the build script.
Thanks to all for their input and here is the skeleton code I used in 'extend-corecompile.proj':
<!--The following property group adds our custom post-target to the post compile call list -->
<PropertyGroup>
<TargetsTriggeredByCompilation>
$(TargetsTriggeredByCompilation);
CustomPostTarget
</TargetsTriggeredByCompilation>
</PropertyGroup>
<!--The following property group adds our custom pre-target to CoreCompileDependsOn to ensure it is called before CoreCompile -->
<PropertyGroup>
<CoreCompileDependsOn>
$(CoreCompileDependsOn);
CustomPreTarget
</CoreCompileDependsOn>
</PropertyGroup>
<!-- The following custom pre-target has the same inputs and outputs as CoreCompile so that it will only run when CoreCompile runs.
Because we have injected this file and Targets are resolved in sequence we know this Target will fire before CoreCompile.-->
<Target Name="CustomPreTarget"
Inputs="$(MSBuildAllProjects);
#(Compile);
#(_CoreCompileResourceInputs);
$(ApplicationIcon);
$(AssemblyOriginatorKeyFile);
#(ReferencePath);
#(CompiledLicenseFile);
#(EmbeddedDocumentation);
$(Win32Resource);
$(Win32Manifest);
#(CustomAdditionalCompileInputs)"
Outputs="#(DocFileItem);
#(IntermediateAssembly);
#(_DebugSymbolsIntermediatePath);
$(NonExistentFile);
#(CustomAdditionalCompileOutputs)">
<!--Do pre-compilation processing here-->
</Target>
<!--This target will be called by CoreCompile-->
<Target Name="CustomPostTarget" >
<!--Do post-compilation processing here-->
</Target>
Not sure what will happen if CoreCompile fails, does it still call our target? I guess in time we'll find out :)
I just blogged the answer to this at http://sedodream.com/2012/07/28/MSBuildHowToExecuteATargetAfterCoreCompilePart2.aspx but I've pasted the solution for you below.
A couple of months ago I wrote a blog post MSBuild how to execute a target after CoreCompile in which I describe how you can execute a target if the CoreCompile target is executed, if CoreCompile is skipped then so will your other target. The draw back of the approach that I outlined in my previous post was that it required you to edit your .csproj/.vbproj/etc file itself. So if you had a scenario where you were building multiple projects then you would have to edit all of the project files. In this post I’ll describe how you can perform the same customization without having to edit the project file itself.
Before we get to the solution for this particular case let me describe an extensibility hook that the C# and VB projects have. Most of the logic for building C# and VB projects is captured in the MSBuild targets file at C:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets. If you take a look in that file you will notice at the top an import looking like the one below.
<Import Project="$(CustomBeforeMicrosoftCommonTargets)" Condition="'$(CustomBeforeMicrosoftCommonTargets)' != '' and Exists('$(CustomBeforeMicrosoftCommonTargets)')"/>
This statement will import a file (located at the value for CustomBeforeMicrosoftCommonTargets) if the property is not empty and the file exists. The default value for CustomBeforeMicrosoftCommonTargets is C:\Program Files (x86)\MSBuild\v4.0\Custom.Before.Microsoft.Common.targets. So if you drop an MSBuild file at that location it will modify the build process for every C#/VB project built on that machine. Alternatively if you do not want (or cannot due to ACLs) then you can drop the file somewhere else and then specify its location by overriding the CustomBeforeMicrosoftCommonTargets property. This is the approach that I will take here. I have created a sample solution which consists of two projects ProjA and ProjB. I also have a build script, build.proj, to automate the build for this. Below is the entire contents of build.proj.
build.proj
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<FileToInject Condition=" '$(FileToInject)'=='' ">$(MSBuildThisFileDirectory)extend-corecompile.proj</FileToInject>
</PropertyGroup>
<ItemGroup>
<ProjectsToBuild Include="ProjA\ProjA.csproj"/>
<ProjectsToBuild Include="ProjB\ProjB.csproj"/>
</ItemGroup>
<Target Name="Build">
<MSBuild Projects="#(ProjectsToBuild)"
Properties="CustomBeforeMicrosoftCommonTargets=$(FileToInject)" />
</Target>
<Target Name="Clean">
<MSBuild Projects="#(ProjectsToBuild)" Targets="Clean"/>
</Target>
<Target Name="Rebuild" DependsOnTargets="Clean;Build"/>
</Project>
In the Build target above I use the MSBuild task to build both ProjA and ProjB. As you can see I am passing the property CustomBeforeMicrosoftCommonTargets=$(FileToInject) which points to extend-corecompile.proj. By passing this property when ProjA, and ProjB, is built it will automatically import the extend-corecompile.proj file for the build process. You can see the contents of extend-corecompile.proj below.
extend-corecompile.proj
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<TargetsTriggeredByCompilation>
$(TargetsTriggeredByCompilation);
MyCustomTarget
</TargetsTriggeredByCompilation>
</PropertyGroup>
<Target Name="MyCustomTarget">
<Message Text="MyCustomTarget called" Importance ="high"/>
</Target>
</Project>
This project file uses the technique outlined in my previous blog post to execute the MyCustomTarget only if CoreCompile is executed.
Note: You can get the latest version of this sample at https://github.com/sayedihashimi/sayed-samples/tree/master/ExtBuildMultiple.
Alternately, you can use a single auto-generated VersionInfo.cs file that is referenced by all of the projects. To use this technique, strip out the version, company info, etc. attributes from your projects' AssemblyInfo.cs file (yes, this is a pain, but you only have to do this once), and have a batch command spit out a VersionInfo.cs file based on a template. To reference the common file in Visual Studio, you choose Add Existing Item from the project context menu, and after you've navigated to the VersionInfo.cs file in the file browser, click the drop-down arrow next to Add and select Add as Link.
Below is an example of one I use. This script is checked into our SCC system and is executed at the beginning of the build, supplying %BUILD_NUMBER% to the script.
SET BUILD=%1
#echo using System.Reflection; > "%~p0Version.cs"
#echo [assembly: AssemblyCompany("MyCompany, Inc.")] >> "%~p0Version.cs"
#echo [assembly: AssemblyProduct("MyProduct")] >> "%~p0Version.cs"
#echo [assembly: AssemblyCopyright("Copyright © 2012 MyCompany, Inc.")] >> "%~p0Version.cs"
#echo [assembly: AssemblyTrademark("")]#echo [assembly: AssemblyVersion("1.0.%BUILD%.0")] >> "%~p0Version.cs"
#echo [assembly: AssemblyFileVersion("1.0.%BUILD%.0")] >> "%~p0Version.cs"
#echo ^<Include xmlns="http://schemas.microsoft.com/wix/2006/wi"^> > "%~p0Version.wxi"
#echo ^<?define VersionBuild="%BUILD%"?^> >> "%~p0Version.wxi"
#echo ^</Include^> >> "%~p0\Version.wxi"
Even if you got the list of projects needing compilation, if you update the assemblyinfo.cs of one of them, it may induce a change that triggers a compilation of another project.
So, simpliest way is to generate all AssemblyInfo.cs files according to source control revision number. You could even get latest revision number for each project directory, effectively knowing when was the "last" modification on this project.
See this question : How can I change AssemblyProduct, AssemblyTitle using MSBuild?
According to your comment, have you looked into the BeforeBuild and AfterBuild targets (at the end of your csproj file) :
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
Background:
We're using a 3rd party tool in our .NET C# solution. This tool has it's own syntax and integrates with Visual Studio. When we use this tool we write its markup within Visual Studio and then when we build the solution the custom tool runs and generates a .cs file based on the markup we have written.
This generated source file contains a version number which is causing problems when we check these in to version control (Endless conflicts). Our understanding is that it's considered best practice not to check in generated source files.
So we excluded the generated .cs files from SVN and then the next issue we ran in to was that the Visual Studio solution referenced these files, so when TeamCity (Our continuous build/integration software) went to build the solution it would fail straight away as it couldn't find these files.
We then removed these from the solution as well as excluding them from SVN, this fixed the original issue, we're no longer checking in generated code and it builds fine in TeamCity (As the files are re-generated with every build).
We now have a new problem - As the generated files are no longer included in the solution, intellisense and code inspection fails as the generated classes cannot be found. The solution builds just fine (As again the code is re-generated during the build).
Question
Is there a way to tell ReSharper to include generated .cs files in its code inspection? These files are external to the solution but they are in the obj directory.
Cheers,
Tyler
We had a similar problem and couldn't come up with a good solution so I wrote a ReSharper extension to include external code:
https://resharper-plugins.jetbrains.com/packages/ReSharper.ExternalCode
As mentioned in my comment, one workaround is to keep the generated files in the solution (but not in source control), while adding a pre-build step to create empty .cs files (if the real generated file isn't present) so that the file is always available during a build.
In my projects, I use the following MSBuild targets to generate empty files by using the Touch task. You may need to make some modifications - in my case, the target files are actually defined within a project not at the solution level; and the build action for the files is set to "None" which is important to understand how these targets work.
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<!--
Creates empty 'dummy' files for any files that are specified but do not exist.
To be processed, the following must be true:
1. The file is included in an ItemGroup called CanCreateDummy, e.g.
<ItemGroup>
<CanCreateDummy Include="SomeFile.cs" />
</ItemGroup>
If you want to specify a CanCreateDummy file in the .csproj file, you would
modify the above slightly as follows to prevent it appearing twice:
<ItemGroup>
<CanCreateDummy Include="SomeFile.cs">
<Visible>false</Visible>
</CanCreateDummy>
</ItemGroup>
2. The file is included in the ItemGroup called None. This is normally performed
by adding the file to the project in the usual way through Visual Studio, and
then setting the file's Build Action property to None.
-->
<Target
Name="CreateDummyFiles"
AfterTargets="BeforeBuild"
>
<!--
This voodoo creates the intersection of 2 lists - #(CanCreateDummy) and #(None)
(this latter item is defined in the project file). We want to create a filtered
list of all items that are in both these lists, which is called _ProjectDummyFiles.
See http://blogs.msdn.com/b/msbuild/archive/2006/05/30/610494.aspx for how the
Condition voodoo works.
-->
<CreateItem Include="#(CanCreateDummy)" Condition="'%(Identity)' != '' and '#(None)' != ''" >
<Output TaskParameter="Include" ItemName="_ProjectDummyFiles"/>
</CreateItem>
<Message
Text="Creating dummy settings file #(_ProjectDummyFiles)"
Condition=" !Exists('%(_ProjectDummyFiles.FullPath)')"
/>
<Touch
AlwaysCreate="true"
Files="#(_ProjectDummyFiles)"
Condition=" !Exists('%(_ProjectDummyFiles.FullPath)')"
/>
</Target>
</Project>
Hope this helps
Rich
I just finished setting up an out-of-place build system for our existing C++ code using inherited property sheets, a feature that seems to be specific to the Visual C++ product. Building out-of-place requires that many of the project settings be changed, and the inherited property sheets allowed me to change all the necessary settings just by attaching a property sheet to the project. I am migrating our team from C++/MFC for UI to C# and WPF, but I need to provide the same out-of-place build functionality, hopefully with the same convenience. I cannot seem to find a way to do this with C# projects - I first looked to see if I could reference an MsBuild targets file, but could not find a way to do this. I know I could just use MsBuild for the whole thing, but that seems more complicated than necessary. Is there a way I can define a macro for a directory and use it in the output path, for example?
I'm not quite sure what an "out-of-place" build system is, but if you just need the ability to copy the compiled files (or other resources) to other directories you can do so by tying into the MSBuild build targets.
In our projects we move the compiled dlls into lib folders and put the files into the proper locations after a build is complete. To do this we've created a custom build .target file that creates the Target's, Property's, and ItemGroup's that we then use to populate our external output folder.
Our custom targets file looks a bit like this:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectName>TheProject</ProjectName>
<ProjectDepthPath>..\..\</ProjectDepthPath>
<ProjectsLibFolder>..\..\lib\</ProjectsLibFolder>
<LibFolder>$(ProjectsLibFolder)$(ProjectName)\$(Configuration)\</LibFolder>
</PropertyGroup>
<Target Name="DeleteLibFiles">
<Delete Files="#(LibFiles-> '$(ProjectDepthPath)$(LibFolder)%(filename)%(extension)')" TreatErrorsAsWarnings="true" />
</Target>
<Target Name="CopyLibFiles">
<Copy SourceFiles="#(LibFiles)" DestinationFolder="$(ProjectDepthPath)$(LibFolder)" SkipUnchangedFiles="True" />
</Target>
<ItemGroup>
<LibFiles Include=" ">
<Visible>false</Visible>
</LibFiles>
</ItemGroup>
</Project>
The .csproj file in Visual Studio then integrates with this custom target file:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" ... >
...
<Import Project="..\..\..\..\build\OurBuildTargets.targets" />
<ItemGroup>
<LibFiles Include="$(OutputPath)$(AssemblyName).dll">
<Visible>false</Visible>
</LibFiles>
</ItemGroup>
<Target Name="BeforeClean" DependsOnTargets="DeleteLibFiles" />
<Target Name="AfterBuild" DependsOnTargets="CopyLibFiles" />
</Project>
In a nutshell, this build script first tells MSBuild to load our custom build script, then adds the compiled file to the LibFiles ItemGroup, and lastly ties our custom build targets, DeleteLibFiles and CopyLibFiles, into the build process. We set this up for each project in our solution so only the files that are updated get deleted/copied and each project is responsible for it's own files (dlls, images, etc).
I hope this helps. I apologize if I misunderstood what you mean by out-of-place build system and this is completely useless to you!
Is there a way I can define a macro for a directory and use it in the output path
Have you looked at the pre-build and post-build events of a project?
Actually, pre-build and post-build events seem to be solely a place to add batch-file type commands. This would not help me to set up standard build directories for our projects, unfortunately. And having these events create batch files seems like a very 1980's approach for a modern language like C#, IMO.
After digging some more, and experimenting, I have found that you can add an <Import> directive into your .csproj file. When you do this, the IDE pops up a warning dialog that there is an unsafe entry point in your project - but you can ignore this, and you can make it not appear at all by editing a registry entry, evidently. So this would give me a way to get the variables containing the directory paths I need into the .csproj file.
Now to get the Output Path to refer to it - unfortunately when you add a string like "$(MySpecialPath)/Debug" to the Output Path field, and save the project, the $ and () chars are converted to hex, and your file get's put in a Debug directory under a directory named "$(MySpecialPath)". Arrgghh. If you edit the .csproj file in a text editor, you can set this correctly however, and it seems to work as long as the <Import> tag appears before the <PropertyGroup> containing the Output Path.
So I think the solution for me will be to create a standard OurTeam.targets MsBuild file in a standard location, add an installer for changing the registry so it doesn't flag warnings, and then create custom project templates that <Import> this file, and also set the Output Path to use the properties defined in the OurTeam.targets file. Sadly, this is more work and a less elegant solution than the property sheet inheritance mechanism in C++.