MSBuild, include files based on a group of paths - visual-studio-2010

I'm trying to create a build script which would allow me to specify a list of paths to "module projects" that is included in a specific web site.
Hence, I have this layout:
customer folder
|_MainProject
|_ModuleProject1
|_ModuleProject2
So, basically I want to be able to specify an ItemGroup that would contain ModuleProject1 and 2, and copy relevant files from it into the MainProject.
<ItemGroup>
<CustomModule Include="ModuleProject1\*.csproj" />
<CustomModule Include="ModuleProjec2\*.csproj" />
</ItemGroup>
In my main build script I then want to find out the paths to my satellite assemblies, as well as all dlls that reside inside the obj\Release*.dll folder of the project.
I already have item groups for the project file (.csproj), so basically I just want to add some more file references. But I can't figure out how I would do that. This is what I have today:
<Satellites Include="$(ReferencesFolder)\??\*.dll" />
<Satellites Include="$(SiteRoot)\bin\??\*.dll" />
<Satellites Include="%(CustomModule.RelativeDir)obj\$(Configuration)\??\*.dll" />
How would I go about making the last line work based on the facts that I have?
When doing this:
<Message Text="%(CustomModule.RelativeDir)obj\$(Configuration)\??\*.dll" />
It outputs this:
ModuleProject1\obj\Release\??\*.dll
ModuleProject2\obj\Release\??\*.dll
And if anyone has some links to the fundamentals of MSBuild with good examples, I would really appreciate it. I always end up at msdn with some really cryptic examples that doesn't really explain a lot.
EDIT: I revised my plan and almost have it working, however the include doesn't really work as I expect it to. Nothing is included, but there are files mathing the path.
If I manually add this:
<Satellites Include="ModuleProject1\obj\Release\??\*.dll" />
It is actually included in the "Satellites" item group.

Found a solution myself, seems like I needed a workaround for it to work explicitly.
Since I wasn't able to include items based on another item list, this was what I finally came up with:
<Target Name="BuildModules">
<!-- We do this because we need a property with the correct wildcards, otherwise it won't work -->
<PropertyGroup>
<CustomModuleSatellites>#(CustomModule->'%(RelativeDir)obj\$(Configuration)\??\*.dll')</CustomModuleSatellites>
</PropertyGroup>
<ItemGroup>
<Satellites Include="$(CustomModuleSatellites)" />
</ItemGroup>
</Target>
I needed to create a property within a Target (outside of it it didn't fly because it still contained the wild cards), and then use that property ti include the files using wildcards in my item list, otherwise it would be paths with wildcards in it and then the copy command didn't work either.

Related

Duplicate files in csproj file Visual Studio

Somehow I am seeing duplicate files in my solution. I dig into it and found that somehow, there are two entries for some files in .csproj file. That's why two files are shown that is actually one file. I have to find for all the files in .csproj file. Is there any easy way to remove those entries from csproj? And also can anyone let me know the cause of this?
As per this question and this other one on StackOverflow, this might happen when TFS (or other version control systems) edit a project file e.g. during a merge operation.
Anyway, you have probably opened a csproj file and seen that it's just an XML, with files being listed in elements like those below:
<ItemGroup>
<Compile Include="Controllers\AccountController.cs" />
<Compile Include="Controllers\ContactsController.cs" />
<Compile Include="Controllers\HomeController.cs" />
...
</ItemGroup>
One answer to the question I linked above also provides a reference to a deduplication Powershell script on GitHub, which I haven't tried and cannot guarantee for, although I don't see what harm it could do.
If your files are not too many, you can just look for the following string
.cs" />
with a text editor like PsPad, have it list the results and remove double entries by hand. Of course, before doing anything, backup your file.
Another solution involves selecting all duplicated files in your project, clicking "Exclude from project" and then "Show all files" and include them by hand.

How do I make my project depend on other projects via msbuild?

I only have the names/locations of my other projects but I dont want to require the developer to add project dependencies himself.
So my prebuild event needs to add "references" (=anything that makes the current project depend on the other one) via some kind of msbuild magic.
Is that possible?
Desired pseudo code:
<Task Name="MyOwnPrebuild">
<AddProjectDependencies ItemGroup="#MyProjectPaths" />
</Task>
Where I would fill the #MyProjectPaths array by iteratating over my windows folders recursively in some other task before calling this one.
In my specific case there is (luckily!) a stupidly simple, yet beautiful answer:
<ItemGroup>
<ProjectReference Include="..\**\*.csproj">
<Private>false</Private>
</ProjectReference>
</ItemGroup>
This includes all my projects from all(?) subfolders! Exactly what I want in this particular case. If I want to restrict to a specific type of project, I could easily use a more specific regex.

inject version to DLLs during build process

this is my situation:
I have VS2010 solution with X projects included.
Wix project that can create msi from all compiled artifacts.
I have build machine \ Jenkins that first compile (MSBuild .Net 4) all the solution, then compile the wix to package it to msi.
What\how can I inject to all artifacts\dlls the number of the product (e.g 11.2.0.4789) - as simple as possible?
Is there and command line arguments that can be passed while compiling the solution?
There are tools, such as several extensions for MSBuild, that do version stamping but each assumes a particular workflow. You might find one that works for you but a DIY method would help you evaluate them, even if it isn't your final solution.
You can add a property to the MSBuild command-line like this:
msbuild /p:VersionStamp=11.2.0.4789
Note: I assume you are going to parameterize the Jenkins build in some way or generate the number during a preceding build step. Here is a simulation of that:
echo 11.2.0.4789 >version.txt
set /p version=reading from pipe <version.txt
msbuild /p:VersionStamp=%version%
Now, the work is in getting each project to use it. That would depend on the project type and where you want VersionStamp to appear.
For a csproj, you might want to use it as the AssemblyVersion. The simplest way is to move the attribute to a cs file by itself and rewrite it every time. I would leave a comment in AssemblyInfo.cs as a clue to where it now comes from. You can include the cs file in your project either dynamically or permanently. I prefer dynamically since it is effectively an intermediate file for the build. So, in your .csproj add the following in a text editor (e.g. Visual Studio. Unload and Edit project):
<Target Name="BeforeBuild">
<PropertyGroup>
<AssemblyVersionPath>$(IntermediateOutputDir)AssemblyVersion.cs</AssemblyVersionPath>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(AssemblyVersionPath)" />
</ItemGroup>
<WriteLinesToFile
File='$(AssemblyVersionPath)'
Overwrite="true"
Condition="'$(ProductVersion)' != ''"
Lines='using System.Reflection%3b;
[assembly: AssemblyVersion("$(VersionStamp)")]' />
</Target>
This is sufficient but a more thorough solution would include adding the file to a list so it is cleaned with other files and only writing the file if the version changed to prevent unnecessary rebuilds, etc.
Use a similar technique for other project types.

Dynamic files include on Visual Studio build process

I got a question about the build process on VS.
I have a DLL Project with 3 .cs files inside. I would like to be able to define which files to include in the compilation process. So I can build a dll file only with file1.cs. Or file2.cs. Or file1.cs and file3.cs.
For now, the only way I know, is to create as many proj file I need and choose manually which one to build.
I would like to know if there are any other way to do it, maybe in the prebuild event you can redefine the files included or not. Maybe some addin exists ?
Thanks.
You can conditionally include files in a project file by putting a condition on the itemgroup.
Open up your csproj in a text editor:
<ItemGroup>
<Compile Include="Program.cs" Condition="$(BuildtypeA)!='True'" />
<Compile Include="Program1.cs" Condition="$(BuildtypeA)!='True'" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
Use whatever condition you like- in this case BuildtypeA. You can pass this in as aproperty on the commandline to msbuild. (or my creating.modifying a build configuration)
You can also use wild cards on a given folder and it will include in the project file what ever is at a given location.
<ItemGroup>
<Compile Include="..\dynamiccontent\*.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
No add-in required.
Not exactly sure why you couldn't just compiles the project with all 3 files all the time, but if you really need to do something like this, you can check out the MSBuild extensions. They support the ability to manipulate an Xml document (which is what a csproj file is) and you can dynamically add/remove the reference to the files as needed. Let me know if you need an example of doing something like that.

Creating a VS 2010 Project with only content files

I have some content files that I would like to share between a number of projects in Visual Studio.
I have put these files in their own project, set the build action to "Content", and the copy to output directory to "Copy if newer". I would like all these files to be copied to the bin/debug directory of the projects that reference them.
I can get it to work by including a reference to the "contents" project in each of the projects that need the files, but that requires that a minimal assembly be generated (3K). I assume there is a way, using MSBuild, to make this all work without creating the empty assembly?
Thanks to everone who took the time to make a suggestion about how to solve this problem.
It turns out that if I want my compiled content files to be treated like content files (in that they get copied to the output directory of any other project that references my project), I need to create a target which runs before GetCopyToOutputDirectoryItems, and add the full path of the compiled content files to the AllItemsFullPathWithTargetPath ItemGroup. MSBuild calls GetCopyToOutputDirectoryItems for projects on which the current project depends, and uses the resulting file list to determine the files that are copied along with the assembly.dll. Here is the XML from my .csproj, just in case someone else has a similar problem.
I have a custom task called "ZipDictionary", and I accumulate all the files that I am going to compile in an ItemGroup called DictionaryCompile. My target, "FixGetCopyToOutputDirectoryItems" is executed before "GetCopyToOutputDirectoryItems". I don't do the actual compilation there, since this target can be called multiple times by referencing projects, and it would hurt performance. The target does some transforms to get the post-compilation file names, and then returns the full paths to all the files, since relative paths will not work when copy is called from the referencing project.
<ItemGroup>
<DictionaryCompile Include="Dictionaries\it-IT.dic">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</DictionaryCompile>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<UsingTask TaskName="ZipDictionary" AssemblyFile="..\LogicTree.DictionaryCompiler\bin\Debug\LogicTree.DictionaryCompiler.dll"/>
<Target Name="BeforeCompile">
<Message Text="Files #(DictionaryCompile)" Importance="high" />
<ZipDictionary DictionaryFiles="#(DictionaryCompile)" OutputDirectory="$(OutputPath)">
<Output TaskParameter="OutputFiles" ItemName="DictionaryOutputFiles" />
</ZipDictionary>
</Target>
<Target Name="FixGetCopyToOutputDirectoryItems" BeforeTargets="GetCopyToOutputDirectoryItems">
<ItemGroup>
<_DictionaryCompile Include="#(DictionaryCompile->'$(OutputPath)Dictionaries\%(FileName).ltdic')" />
</ItemGroup>
<AssignTargetPath Files="#(_DictionaryCompile)" RootFolder="$(MSBuildProjectDirectory)\$(OutputPath)">
<Output TaskParameter="AssignedFiles" ItemName="_DictionaryCompileWithTargetPath" />
</AssignTargetPath>
<ItemGroup>
<AllItemsFullPathWithTargetPath Include="#(_DictionaryCompileWithTargetPath->'%(FullPath)')" Condition="'%(_DictionaryCompileWithTargetPath.CopyToOutputDirectory)'=='Always' or '%(_DictionaryCompileWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'" />
<_SourceItemsToCopyToOutputDirectoryAlways Include="#(_DictionaryCompileWithTargetPath->'%(FullPath)')" Condition="'%(_DictionaryCompileWithTargetPath.CopyToOutputDirectory)'=='Always'" />
<_SourceItemsToCopyToOutputDirectory Include="#(_DictionaryCompileWithTargetPath->'%(FullPath)')" Condition="'%(_DictionaryCompileWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'" />
</ItemGroup>
</Target>
A better possible solution would be to
place a common directory in the solution dir and place your common content files there.
in VS, in each project that should share this content, right-click add existing item, browse to the desired item(s), select, click the down-arrow on the add button and select add as link. In the project, you will notice the files are added with a 'shortcut' overlay.
In the project, select the newly added links and right-click->properties and select Build Action: content, Copy To Output Directory: Copy Always.
This is a simple solution to the problem given.
I use this technique for things like SQL scripts and partial config files (using configSource) with great success. This allows me to make changes to these files in a single location with the assurance that they will be propigated throughout the solution.
A more robust solution would be to create a project with embedded resources. This requires a bit more work to manage the content on the receiving end but may be worth it in the long run as having a bunch of loose artifacts flying about can become problematic.
Hope that helps.
A similar solution like the one Sky suggested can be found in my answer to "Is there a way to automatically include content files into asp.net project file?".
It allows to share your content but you must not touch the folder or its content inside VS because this breaks the recursive path.
This approach works best for auto-generated content - you don't have to bother about including new content files to your solution.
And of course you can reuse this in multiple solutions/projects.
We do something similar where we have "...ReleaseBuilds" that reference dlls and content we require for specific projects. Compiling copies everything to the bin debug folder and indeed creates the empty assembly.
Within Visual Studio we have a post-build event in the "...RealeaseBuild" (in project properties) that copies/deletes or run batch files to make sure we have all the files (configs, services etc etc) required and to delete the empty assembly.
HTH

Resources