Embed a conditional XML file in a dll - msbuild

I want to create a config class for a dll that reads from an embedded resource. I've created multiple xml files with the configuration for all our different environments. I have a separate configuration for each environment DEV, QA, PROD with matches the prefix on the xml file.
.Configuration/DEV.config.xml
.Configuration/QA.config.xml
.Configuration/PROD.config.xml
etc
We've never used MSBuild before and are confused because there seems to be about 3-4 different ways of using it (pre/post build events, xml scripting, custom tasks etc). All i'm trying to do is rename the respective file to config.xml and embed it in the dll, so that the config class can read it out when it's run.
I've seen similar questions that simply embed a file but none that embed and rename at the same time. I assume that any sort of embedding would need to be done as a pre-build event.
Normally I would have tried a few bits and bobs but I have no idea where to start with this, if anyone could point me in the right direction I would be extremely grateful.
Update:
so with some help from jlew I should be able to do something like this
<ItemGroup>
<EmbeddedResource Include="Configuration\$(Configuration).config.xml" >
<LogicalName>config.xml</LogicalName>
</EmbeddedResource>
</ItemGroup>

What you probably want to do (without having seen your code) is to not rename the input file, but direct MSBuild to use a "logical name" for the resource which is different than the file.
<ItemGroup>
<EmbeddedResource Include="Dev.config.xml">
<LogicalName>MyRenamedConfig.config.xml</LogicalName>
</EmbeddedResource>
</ItemGroup>
If you are using csc.exe directly, you can do something similar with:
csc ... /resource:Dev.config.xml,MyRenamedConfig.config.xml

Related

Why is it called response file

MBuild can use response files to save and run commands. But why is it called response file? What is it responding to?
(Also in an MsBuild file the task elements are called Target. What is 'target' refering too?)
A target represents a collection of things you want to do. In an msbuild file, it is represented by an xml element that can have various child xml elements called tasks.
Conceptually it looks like this:
<Target Name="Foo">
<Task />
<AnotherTask />
</Target>
The target you want to execute can be passed in as a command line parameter to msbuild. There are other ways to execute the target of your choice, but you will need to read the docs for that:
https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild?view=vs-2019
In other build systems, a target can be called a goal.
Note:
Some build systems use a very rigid convention, where files have to be in certain places. MSBuild is not like that. It relies on configuration, where you can configure it any way you like. The only convention's really are the xml syntax and schema that you have to follow.
As for the response file name. Who knows and who cares anyways? It's just an extra place to put more command line parameters. I don't rely on it, and neither should you. If you know what you are doing you can stick all that stuff in a proper msbuild xml file and just invoke msbuild to kick off a build.

Default or specify msbuild properties in an external file

Ok, so I have a few dozen solutions all built using the exact same command line.
msbuild SolutionName.sln /p:property1=value1;property2=value2;etc etc etc.
Except the number of properties just grows and grows.
Is there a way to specify an external file some how so I don't end up with a 10 line msbuild command? (Think property 100, property 101, etc).
I'm aware of .wpp.target files. However, having to copy them into each project folder really... is my last resort.
And no, I'm not modifying any default MSBuild targets/files whatsoever.
To answer the original question, yes you can specify properties in an external file. They are called MSBuild response files.
msbuild somesolution.sln #PathToResponseFile.rsp
Inside the response file you can put your properties, one per line.
/verbosity:detailed
/target:build
/platform:AnyCPU
/configuration=Release
Some links to better understand:
http://dailytechlearnings.wordpress.com/2011/08/24/msbuild-response-file/
http://msdn.microsoft.com/en-us/library/vstudio/ms404301.aspx
However, using an msbuild file to build your solutions and projects is a better solution. You can create global targets that will do exactly as you want. You can create your own custom Clean and Build targets that will then build/clean your solutions.
First of all - I would recommend you to use msbuild scripts to build your solutions, instead of direct building sln file using command line. E.g. use something like this:
msbuild SolutionName.Build.proj
and inside this Solution1.Build.proj you can put anything as simple as
<Project ToolsVersion="4.0" DefaultTargets="BuildMe" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="BuildMe">
<MSBuild Projects="SolutionName.sln" Properties="property1=value1;property2=value2;"/>
</Target>
</Project>
After this step, which adds flexibility to your build process, you can start leverage AdditionalProperties metadata for MSBuild task.
Then you can use <Import construction to store your list of shared properties in a separate file and item metadata for passing property values:
<Project ToolsVersion="4.0" DefaultTargets="BuildMe" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="MySharedProperies.props" />
<ItemGroup>
<ProjectToBuild Include="SolutionName.sln">
<AdditionalProperties>SomeProjectSpecificProperty</AdditionalProperties>
</ProjectToBuild>
</ItemGroup>
<Target Name="BuildMe">
<MSBuild Projects="#(ProjectToBuild)" Properties="#(MySharedProperies)"/>
</Target>
</Project>
You can check this post for more details about properties and additional properties metadata or this original MSDN reference (scroll to Properties Metadata section)
This is the base idea how to do it, if you have any questions - feel free to ask.
I use an Import file for things that are common across various projects.
<Import Project="CommonBuildProperties.proj"/>
That file contains a PropertyGroup that has the things I want to have the same value across build projects. There's also a conditional statement there that sets certain folder names depending on the name of the computer it's running on. At runtime, if I need to override anything on the command line I do that.
I also have some project-specific Import files (one of our builds is a Powerbuilder application with its own toolset and pecadilloes); order of Import ensures if they require different values for the same element name I get what I want.
My command lines aren't horrible unless I'm doing something odd that needs most everything overridden. About the only things I have to pass in are version number and build type (release or debug).

MSBuild: Generate XML documentation for main project but not dependency projects

I have a .sln file with several projects in it. To keep this simple, let's call them...
ProjectA
ProjectB
ProjectC
...where A is the main project which references B and C. My goal is to update my build script to generate an XML "Intellisense" documentation file for ProjectA, without giving build warnings about missing documentation from B and C.
Current Build Script
I have an MSBuild script which includes the following in the build step:
<PropertyGroup>
<CustomOutputPath>C:\build\output\</CustomOutputPath>
</PropertyGroup>
<ItemGroup>
<Projects Include="ProjectA\ProjectA.csproj">
<Properties>OutputPath=$(CustomOutputPath)</Properties>
</Projects>
</ItemGroup>
<MSBuild Projects="#(Projects)" />
(There are actually multiple Projects listed in the ItemGroup, but again, let's keep this simple.)
When I run the build script, it's smart enough to compile B, C, and A for me, even though I've only specified A. All output appears in the "CustomOutputPath" location.
The closest I've gotten...
If I add a 'DocumentationFile' property to my Project entry...
<ItemGroup>
<Projects Include="ProjectA\ProjectA.csproj">
<Properties>OutputPath=$(CustomOutputPath);DocumentationFile=ProjectA.xml</Properties>
</Projects>
</ItemGroup>
...then 'ProjectA.xml' appears in "CustomOutputPath". However, I also get files named 'ProjectA.xml' in the project folder for all three projects:
ProjectA/ProjectA.xml
ProjectB/ProjectA.xml
ProjectC/ProjectA.xml
These files contain the "Intellisense" documentation for their respective projects, even though they're all named "ProjectA.xml".
This creates undesired and misleadingly-named files in the project folders, and (more importantly) generates build warnings for the missing documentation comments in B and C. I don't want to add documentation comments to those projects, so I'd prefer to find a way to have MSBuild generate the documentation only for ProjectA.
Can anyone provide any insight, or an alternative solution?
Based on what I've found - DocumentationFile is a global-level property (and will be used in creation of DocFileItem - global level items list). From my understanding you won't be able to alter it in any easy way in a single logical script.
What you can do is to define special target in separate file that will be imported to every proj file (directly editing proj files or using properties like $CustomBeforeMicrosoftCommonTargets) that will overwrite DocumentationFile with project-dependent value.
As a result - you probably can generate different documentation file names for different projects.
Another solution - just clean-up all unnecessary doc files right after all projs were built.

Msbuild get OutputPath from main project

Say I have a game.exe that depends on engine.dll. When I build the game.csproj I want the engine.csproj to copy some stuff to the OutputPath of the project that is referencing it.
So in this example case, I want engine.csproj to copy something to the OutputPath of game.csproj
How do I get the $(OutputPath) of game.csproj in engine.csproj?
The reason I want to do this is because I'm building a content project in game and engine like this:
<Target Name="BuildContent">
<MSBuild Projects="Content\Content.contentproj"
Properties="OutputPath=MAINPROJECTPATH" />
</Target>
So I need to specify the OutputPath to the 'main project' which is the game.
With a bit nosing around in some of the msbuild targets I found the solution.
I should pass through the Parent properties like this
<Target Name="BuildContent">
<MSBuild Projects="Content\Content.contentproj"
Properties="ParentOutputDir=$(ParentOutputDir);
ParentIntermediateDir=$(ParentIntermediateDir);
ParentProjectDir=$(ProjectDir);" />
</Target>
First off, it is not clear from the question exactly what extra files you need to be copied that will not happen automatically using inter-project references.
If the Game project includes a project reference (rather then just a DLL file reference) to the Engine project, msbuild should copy all the output from Engine into the Game output directory, doesn't it?
If you really do need to manually copy files between projects, then I do not believe there is any easy way to find out which projects reference the current project, so it would probably be simpler to create a level of indirection with Engine pushing files half way and Game (or any other projects that need to include those files) pulling the files the rest of the way:
Some post-build event code in Engine.csproj created a "References" directory under SolutionDir and copies the files it wants to share into there.
Then Game.csproj can include the files from the "References" directory - either as direct file references, or by copying with some pre-build event code in Game.csproj

Tfs2010 Build Number and Assembly File Versions & MSBuild targets

I read John Robbins' article TFS 2010 Build Number and Assembly File Versions: Completely In Sync with Only MSBuild 4.0, and I'm wondering about the best way to go about integrating this.
The download for the article has two files, one is a targets file and one is a proj file.
The targets file has a number of tasks to scrape out a build number based on the Tfs build number (the same one used for the builds) and write that number out to some location (call it BuildNumberFile) for consumption by other proj files.
The proj file is very simple. It just imports the aforementioned targets file, and then declares a target with name "All" while also declaring DefaultTargets on the Project element to be All as well.
<Project ToolsVersion="4.0" DefaultTargets="All" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!-- The two required properties so the Wintellect.TFSBuildNumber tasks knows your major and minor values.-->
<TFSMajorBuildNumber>3</TFSMajorBuildNumber>
<TFSMinorBuildNumber>1</TFSMinorBuildNumber>
</PropertyGroup>
<Import Project="Wintellect.TFSBuildNumber.targets"/>
<!-- Just ask for the version information files you need. These are here to show all the diffent ones in
Wintellect.TFSBuildNumber.Targets. You can change the names -->
<Target Name="All"
DependsOnTargets="WriteSharedCSharpAssemblyVersionFile;
WriteSharedVBAssemblyVersionFile;
WriteSharedCPPCLIAssemblyVersionFile;
WriteSharedCPPAssemblyVersionFile;
WriteSharedWiXAssemblyVersionFile;
WriteSharedTextAssemblyVersionFile;"/>
</Project>
I have two questions about this:
I'm still learning MSBuild. If the name of the target isn't specified elsewhere in the targets, is the target executed? How do I ensure that this target is run?
Are the csproj files supposed to declare an Include item for the location where BuildNumberFile is, even though it doesn't exist until compiletime?
Do ItemGroups and Include have a DependsOnTargets or something that allows them make sure the file exists before they build?
Are the entire contents of the csproj file using this supposed to be wrapped in a target that expresses DependsOnTargets for BuildNumberFile?
Thanks!
I think I've got this figured out, but two people promoted my question so I'll answer it here:
You can ensure that a target is run by expressing a dependency on it from another target. Microsoft.Common.targets exposes two targets--BeforeBuild and AfterBuild--expressly for the purpose of being overridden for customizability. I found the easiest way to do this was <Target Name="BeforeBuild" DependsOnTargets="WriteSharedCSharpAssemblyVersionFile" /> where WriteSharedCSharpAssemblyVersionFile is the target declared in the download from the link in the original post. Also, if you're new to MSBuild, this BeforeBuild target must be declared after the Microsoft.CSharp.targets is imported, but the default csproj template guides you in doing this.
The WriteSharedCSharpAssemblyVersionFile target should indeed write the file to some central location, since when building a solution, all targets are executed only once. All projects should reference the file from that location even if it doesn't exist, since by the time compilation happens (or more importantly, by the time references are resolved), the BeforeBuild target will have run and the file will be in place.
In my structure, I have these versioning files in a folder directly underneath the branch root folder. Furthermore, since the file being built is generated, I have it build to the output directory. It seems a little strange to be referencing things from the output, but it preserves the invariant of having all build products in one place so that the output directory can be blown away as a means of performing a clean.
In MSBuild items constitute inputs into the system (usually files) so it's weird to think of them depending on targets. After some learning this question doesn't make a lot of sense. In any case, the answer is no.
The entire contents of the file should indeed not be all in one target--all that is required is to import the Wintellect.TFSBuildNumber.targets file at the beginning of your csproj file, and declare BeforeBuild's dependency on WriteSharedCSharpAssemblyVersionFile at the end.
Hope this helps!