I'm currently working somewhere that have some standard settings that are applied to solutions but I'm having to repeatedly apply the settings over and over again. I wondered what the best solution would be to automate some of this.
For eg:
Assembly.cs has to be updated each time to include [assembly: AssemblyInformationalVersion("0.0.0.0")]
projects have to have this added manually
I'm thinking that I could create a custom msbuild file that could do this and then that would be one place to manually add the settings for the projects.
It is a bit tedious if there are multiple projects that have to have their csproj files edited and some msbuild settings added each time.
Any tips would be much appreciated.
A way to do this is to use a custom task provided by open source project http://msbuildtasks.tigris.org/.
There is a task called AssemblyInfoFile which writes an assemblyFile as specified.
You can create a .proj file which imports this task:
<Import Project="YourPathToCommunity.targets\MSBuild.Community.Tasks.Targets" />
and then executes it before compiling.
<Target Name="BeforeCompile">
<AssemblyInfo OutputFile="Properties\AssemblyInfo.cs"
AssemblyVersion="$(AssemblyVersion)"
AssemblyFileVersion="$(AssemblyVersion)"/>
</Target>
Then, for every project file that you want this behaviour to be applied, just import the .proj file defined in the previous step:
<Import Project="PathToYourProj\myDefinedProj.proj" />
First things first, when those projects need to be updated? Do you use any custom build process?
If you have do to this ALL the time, not only when you need a build ready to be a version, then you should do what #arthur-rizzo recommended, and I add that you should name your file with a .targets extension, since it adds a generic target to complement a project, not a standalone project. Besides, make sure you import this file after any other import that would possibly override the BeforeCompile target. A more secure way to do this IMHO is to give your target some other name like "UpdateAssemblyFile" and add the attribute BeforeTargets="Compile", so you make sure your target will not be overriden and it makes more clear for other developers what's the intend of the target.
So your file would be something like:
<Import Project="YourPathToCommunity.targets\MSBuild.Community.Tasks.Targets" />
<Target Name="UpdateAssemblyFile" BeforeTargets="Compile">
<AssemblyInfo OutputFile="Properties\AssemblyInfo.cs"
AssemblyVersion="$(AssemblyVersion)"
AssemblyFileVersion="$(AssemblyVersion)"/>
</Target>
And you will import it in the ALL the projects you want this custom behaviour like this: <Import Project="TheFileYouCreated.targets" />
Don't forget to add this file to the source control you use.
Credits for #arthur-rizzo for the original answer and adding a link to the custom tasks library.
Related
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
i am looking for a way to 'import' a .csproj file into another .csproj file.
For example the following scenario:
Main.csproj:
<Compile Include="Class1.cs" />
<Compile Include="Class2.cs" />
SubMain.csproj
<Compile Include="Class1.cs" />
<Compile Include="Class2.cs" />
<Compile Include="Class3.cs" />
Now it would be nice to somehow inherit from 'Main.csproj' in my 'SubMain.csproj'.
Maybe something like that in 'SubMain.csproj':
<Include File="Main.csproj" />
<Compile Include="Class3.cs" />
So it would automatically add the files that are in 'Main.csproj'?
Is something like that possible with Visual Studio 2010 for ASP.Net Web Projects?
And if not, are there any alternative solutions for the given scenario?
Thanks.
EDIT Why do i need that:
In my case both projects are ASP.Net Web Projects (WebForms).
The 'Main.csproj' is the Basic Application.
The 'SubMain.csproj' is a customer specific version of the Web Application. (Has the same functionality as the Basic Application + some extra features or some other behaviours for specific features)
Currently i do this by simply copy the Main.csproj and add my additonal files to it. But this method is not very nice and also causes problems when files are added to the Main.csproj and so they are not automatically in my 'SubMain.csproj'.
Likley better approach is to make "Main.csproj" a "class library" (assembly) and add it as reference to other project(s).
To achieve exactly what you asking for use Import element to add shared files.
By using the Import element, you can reuse code that is common to many project files. This makes it easier to maintain the code because any updates you make to the shared code get propagated to all the projects that import it.
By convention, shared imported project files are saved as .targets files, but they are standard MSBuild project files. MSBuild does not prevent you from importing a project that has a different file name extension, but we recommend that you use the .targets extension for consistency.
<Import Project="mySharedFiles.targets"/>
Note that Import will have copies of classes built into each assembly - if you need to share classes between assemblies class library approach is the one to use.
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 have a VS project that contains:
a pre-build action running TextTransform on a template.tt to generate generated.cs
generated.cs listed as one of the files to compile (i.e. in the list of project files)
When I build the project, the pre-build action is executed, generated.cs is re-created, but VS compiles the previous version of generated.cs (which I guess it loaded in memory when the build process started).
How to make the build use the new version of generated.cs (i.e. the one generated in the pre-build action)? How to force the build order?
Note that the text transformation input is dynamic and hence cannot be done in design time.
I don't think you need a custom pre-build action. Just add the ".tt" file to your project and set its "Custom Tool" property to "TextTemplatingFileGenerator". You might want to make sure that the *.generated.cs files are also added to the project, but I think that VS takes care of that.
There's now a solution to this problem!
Oleg Sych has a post on his blog detailing how to make transform-at-build-time work.
Here's the source: https://web.archive.org/web/20140116193428/http://www.olegsych.com/2010/04/understanding-t4-msbuild-integration/
Basically, you just include the T4 build targets in your project file and set the TransformOnBuild property to true.
Here's the relevant excerpt:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<TransformOnBuild>true</TransformOnBuild>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TextTemplating\v10.0\Microsoft.TextTemplating.targets" />
Note that the Microsoft.TextTemplating.targets file has to be included AFTER the Microsoft.CSharp.targets.
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++.