MSBuild include all references in a directory - msbuild

I have an MSBuild script that builds a class library to a DLL.
In order to build, I need to include references to several dlls (i.e. log4net.dll, Elmah.dll etc etc).
At the moment, my build file includes the path to each of the dlls like this:
<Reference Include="C:\Projects\MillinCommon\Trunk\bin\Debug\log4net.dll" />
<Reference Include="C:\Projects\MillinCommon\Trunk\bin\Debug\Elmah.dll" />
<Reference Include="C:\Projects\MillinCommon\Trunk\bin\Debug\Microsoft.Practices.EnterpriseLibrary.Common.dll" />
etc. etc. etc.
Then, in the Target, I have my CSC:
References="#(Reference)"
This is becoming quite tedious. What I would like to do is put all dependent dll's into a single directory someplace on the filesystem, and then pass a single to reference to the directory and MSBuild would use and required dll's contained in that directory.
Is this possible?

To include all items from one folder, simply use a wildcard in your reference include statement:
<Reference Include="C:\Projects\MillinCommon\Trunk\bin\Debug\*.dll" />
You can even use recursive wildcards such as C:\Projects\MillinCommon\Trunk\bin\**\*.dll to include from a folder and all its subfolders.
See MSBuild Items - Using Wildcards to Specify Items for details.

Related

Include additional files in build using MSBuild ProjectReference?

I have a class library that has several files that it depends on, and those files must be packaged up for deployment with any project that depends on this library. Right now this means that I must customize each one of those dependent projects to ensure that they copy the files, in addition to adding the library as an MSBuild PrjectReference. When more files are added, all of the projects must be updated.
I've been looking through Microsoft.Common.targets for a way to include these files with the outputs of the library's own project file, so any project that has a ProjectReference to the library will automatically get the files when doing a build. I haven't gotten anything working yet, but I'm curious more generally if this is possible. It seems like it should be, and the _CopyFilesMarkedCopyLocal target even respects an otherwise-unused %(DestinationSubDirectory) metadata item that would allow for customized placement of those files, which would be perfect.
I believe what I'm missing is, for building a project A that depends on project B, the piece that adds the project outputs of project B into the items for project A's build.
EDIT: Leo's comment, I hadn't noticed that files marked with CopyToOutputDirectory are also copied to dependent project output directories because we use ItemGroups with names other than Content, EmbeddedResource, None, etc. Digging deeper, the target that uses those is GetCopyToOutputDirectoryItems and it appears to recursively call the MSBuild task to determine the project outputs, so I should be able to define some custom target that can be imported into our projects that adds our custom ItemGroups in the GetCopyToOutputDirectoryItems target, so that we don't have to use Content/None, etc.
However, the target that does the copying though is _CopyOutOfDateSourceItemsToOutputDirectoryAlways, which doesn't respect %(DestinationSubDirectory) unfortunately, and so all of these files are copied directly to $(OutDir).
My new goal is to see if there's some way to add custom files into the ReferenceCopyLocalPaths ItemGroup of dependent projects so that they are copied instead by the _CopyFilesMarkedCopyLocal target, which does utilize %(DestinationSubDirectory).
Adds custom build action for ItemGroups in the _CopyOutOfDateSourceItemsToOutputDirectoryAlways target
The above title should be closer to bwerks's goal. As test we could to know that the custom build action for ItemGroups would not copied to the Output directory, so we need to our custom build action to the target _CopyOutOfDateSourceItemsToOutputDirectoryAlways.
To accomplish this, open the file Microsoft.Common.CurrentVersion.targets in the MSBuild 15.0 folder C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\Microsoft.Common.CurrentVersion.targets(Make sure you have sufficient permissions and back up it), find the target _CopyOutOfDateSourceItemsToOutputDirectoryAlways:
<Copy
SourceFiles = "#(_SourceItemsToCopyToOutputDirectoryAlways)"
DestinationFiles = "#(_SourceItemsToCopyToOutputDirectoryAlways->'$(OutDir)%(TargetPath)')"
...
>
<Output TaskParameter="DestinationFiles" ItemName="FileWrites"/>
</Copy>
Then we could to know the copy source file is #(_SourceItemsToCopyToOutputDirectoryAlways), search _SourceItemsToCopyToOutputDirectoryAlways this in the targets, you will find:
<ItemGroup>
<_SourceItemsToCopyToOutputDirectoryAlways KeepMetadata="$(_GCTODIKeepMetadata)" Include="#(ContentWithTargetPath->'%(FullPath)')" Condition="'%(ContentWithTargetPath.CopyToOutputDirectory)'=='Always'"/>
<_SourceItemsToCopyToOutputDirectory KeepMetadata="$(_GCTODIKeepMetadata)" Include="#(ContentWithTargetPath->'%(FullPath)')" Condition="'%(ContentWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"/>
</ItemGroup>
Could to know the source file is #(ContentWithTargetPath), keeping search the ContentWithTargetPath in the target, finally we got following:
<AssignTargetPath Files="#(Content)" RootFolder="$(MSBuildProjectDirectory)">
<Output TaskParameter="AssignedFiles" ItemName="ContentWithTargetPath" />
</AssignTargetPath>
So, we could to know how the target is to copy the default build action file to the output directory.
Now, go to our custom build action, we just need add our custom build action to the ItemName="ContentWithTargetPath", so add following in the file Microsoft.Common.CurrentVersion.targets:
<AssignTargetPath Files="#(MyBuildAction)" RootFolder="$(MSBuildProjectDirectory)">
<Output TaskParameter="AssignedFiles" ItemName="ContentWithTargetPath" />
</AssignTargetPath>
Save it.
For the file in the project file .csproj:
<ItemGroup>
<MyBuildAction Include="TextFile1.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</MyBuildAction>
</ItemGroup>
Hope this helps.

Relative output path in MSBuild xml

I'm currently working on a WiX project. I want to use a relative output path for a .csproj inside my .wixproj file.
When including a project it automatically creates a ProjectReference like this:
<ItemGroup>
<ProjectReference Include="..\..\..\MyProject\MyProject.csproj">
<!-- Then some properties like
Name
Project (GUID)
Private, etc. -->
</ItemGroup>
The output path of my .csproj is in another folder ..\Output\MyProjectOutput\ for example but it is subject to change.
Is there any way to access the relative target directory of this ItemGroup in another ItemGroup?
ProjectReference creates a few useful variables that take advantage of conventions. (If it didn't, it would have build your project just to see where the output folders are.) Your project is not following those conventions.
To accommodate the potential for change, you'd have to create your own extractor for your .csproj. Or, have your .csproj leave a signpost in a designated place, perhaps generating a .wxi for itself.

MSBuild to include dynamically generated files as part of project dependency when the dynamic files are in the bin directory

An extension of this question, but slightly different, and the accepted answer does not quite work for this situation.
We've got a process in place on the build of our project which is generating some additional files, these files are getting (correctly) generated into the /bin folder as expected. However they are not getting copied across when this project is referenced as a dependency.
Following the above questions accepted answer (with a little bit of tweaking), I managed to get them copying across to the dependant project however they are all getting put into a /bin sub folder of the dependants /bin folder (i.e. /bin/bin), which is not what I need to have happen.
The process we're running is a 3rd party process (specifically Surviveplus.XmlCommentLocalization), so I have no control over that side of it.
I could do something additional on the dependant project to move them out of the /bin/bin into the level up, but I'd rather have the original project work as I'd expect it to.
This is the ItemGroup I'm using, derived from the other question:
<ItemGroup>
<Content Include="$(OutputPath)\**\*\*.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
I've also tried setting specifically the TargetPath value, which while un-documented (or I'm blind to it), seems to exist - as per the msbuild output log
<ItemGroup>
<Content Include="$(OutputPath)\**\*\*.xml" KeepMetadata="TargetPath">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>..\</TargetPath>
</Content>
</ItemGroup>
But it appears that when it comes to the Copy task it just ignores it, and resolves a new TargetPath. I've also tried a myriad of combinations of attributes/item types in that item group (i.e. None, EmbeddedResource) but they didn't solve it either.
Ideally I suppose MSBuild needs to mark the generated files as part of the generated assembly? But after getting lost in .target files and MSDN docs, I couldn't figure it out.
MSBuild being used is version 12.0, compiling for .net4.5.

How can I get MIDL to search additional include directories for qualified paths

Update: Just over six months after opening a support call to Microsoft it has been rejected, they claim it is not a bug (since the documentation doesn't explicitly say that the behaviour seen isn't the correct one). They rejected the DCR saying that since they haven't heard any complaints in the last 10 years this is obviously not a common use case.
This is a call to arms, if you have run into the same issue, please open a support call with Microsoft so they understand that it should be fixed. I know that at least someone has run into the same problem becasue I found this comment in Chrome's source code:
# Building .idl files.
# This is a total mess. MIDL needs to be run from $OPEN_DIR because it's too
# stupid to apply its include paths to a relative path like "ui/ie/bla.idl"
# (it only looks in the current dir). So we have to jump through hoops to fix
# up our relative include paths and output files.
Original question:
I have the following file structure:
C:\first\Foo.idl
C:\second\Bar.idl
Where Bar.idl contains the following line:
import "first/Foo.idl";
How can I get midl to compile Bar.idl when compiling from C:\second?
If I imported Foo.idl directly (without specifying first/) then specifying first as an additional include directory would be enough (midl /I c:\first Bar.idl) and it would find Foo.idl
Alternately if I compiled from C:\ (midl second\Bar.idl) that would be OK too.
The problem is that when compiling from within C:\second with the command line midl /I C:\ Bar.idl, I get the following compilation error:
c1 : fatal error C1083: Cannot open source file: 'first\Foo.idl': No such file or directory
It looks like midl is willing to search relative paths only if they are relative to the current directory and not to one of the specified additional include directories and uses the additional include directories only for unqualified file names, this behaviour is specific to the import keyword, when using include the results are as expected.
I would like to be able to add two different additional include directories so that if I have the file on my local machine midl will take that version, otherwise it will take the file from the server (so chdiring to the root folder is not an option).
Is there a way to get around this?
Its the end of 2020 and MIDL 3.0 is out. However, the problem described by the OP still persists. But if you're using Visual Studio there is a straight forward way to deal with that issue.
If you're adding an .idl file to a project the following MSBuild code gets generated in the project file to which the .idl file is added:
<ItemGroup>
<Midl Include="Folder1\YourCustomFile.idl" />
</ItemGroup>
If you add a second file in another folder and reference the first one this will generate another entry:
<ItemGroup>
<Midl Include="Folder1\YourCustomFile.idl" />
<Midl Include="Folder2\YourSecondCustomFile.idl" />
</ItemGroup>
The problem is that if you compile that code the MIDL compiler will not be aware of any additional include directories. Hence, if you add
#include "YourCustomFile.idl"
at the beginning of YourSecondCustomFile.idl the MIDL compiler will not search Folder1 for any .idl files to be included and compilation will fail.
However, by adding AdditionalIncludeDirectories MSBuild item metadata you can influence which folders are passed to the MIDL compiler as additional include directories.
So, to instruct the MIDL compiler to search Folder1 for include files when compiling YourSecondCustomFile.idl modify the MSBuild code as follows:
<ItemGroup>
<Midl Include="Folder1\YourCustomFile.idl" />
<Midl Include="Folder2\YourSecondCustomFile.idl">
<AdditionalIncludeDirectories>$(ProjectDir);$(ProjectDir)Folder1</AdditionalIncludeDirectories>
</Midl>
</ItemGroup>
ProjectDir is an MSBuild property that points to the directory containing the current project (at least in C++ projects it does). The ; is used to separate different directories. Each of those directories will be passed to the MIDL compiler as a separate directory to search for include files.
This should resolve the compilation problem. I don't think the AdditionalIncludeDirectories item metadata can be added using Visual Studio's user interface so you should edit the Visual Studio project directly in a text editor.
Note that the item metadata is valid per item, i.e. for each individual MIDL file. So, you have to add AdditionalIncludeDirectories to every MIDL file that references other MIDL file.
If you need the same content in multiple AdditionalIncludeDirectories you could define a property somewhere else in the project file like this:
<PropertyGroup>
<AdditionalMidlIncludeDirectories>$(ProjectDir);$(ProjectDir)Folder1;$(ProjectDir)Folder2</AdditionalMidlIncludeDirectories>
</PropertyGroup>
And then you use that property everwhere. You could add the same AdditionalIncludeDirectories statement to every single MIDL which would ensure that the same include directories would be used for every MIDL compiler call:
<ItemGroup>
<Midl Include="Folder1\YourCustomFile.idl">
<AdditionalIncludeDirectories>$(AdditionalMidlIncludeDirectories)</AdditionalIncludeDirectories>
</Midl>
<Midl Include="Folder2\YourSecondCustomFile.idl">
<AdditionalIncludeDirectories>$(AdditionalMidlIncludeDirectories)</AdditionalIncludeDirectories>
</Midl>
</ItemGroup>
Edit:
As mentioned in the comment below the code can be further simplified by applying MSBuild's ItemDefinitionGroup. An ItemDefinitionGroup is used to add metadata to MSBuild items which means that the AdditionalIncludeDirectories metadata can be automatically added to each and every Midl element. The ItemDefinitionGroup is defined as follows:
<ItemDefinitionGroup>
<Midl>
<AdditionalIncludeDirectories>
$(ProjectDir);
$(ProjectDir)Folder1;
$(ProjectDir)Folder2
</AdditionalIncludeDirectories>
</Midl>
</ItemDefinitionGroup>
This simplifies the Midl ItemGroup as follows:
<ItemGroup>
<Midl Include="Folder1\YourCustomFile.idl" />
<Midl Include="Folder2\YourSecondCustomFile.idl" />
</ItemGroup>
As you note, while this is dumb, Microsoft Support have confirmed this is not a bug. The following are possible workarounds.
1. Use the /I switch. A lot.
Use the /I switch to specify both c:\first and c:\second, and specify import "Foo.idl" instead of a relative path.
If the command line becomes too long specify a response file.
2. Use symbolic links
Use symbolic links or junctions to the include directories to reference them all into a single hierarchy under a known directory. Then you can use paths relative to that directory.
A pre-build step might be used to maintain the symbolic links.
MKLINK.exe can create junctions or symbolic links.
3. Use an additional build step
Create an additional build step which copies the required files to known locations, then inport them from there.
This is a call to arms...
12 years, 6 months later, I'm here with my pitchfork, ready to go.
In the meantime, for an easier fix, you can go to Project Properties -> MIDL -> General -> Additional Include Directories. Setting that to "$(MSBuildProjectDirectory)\YourSubFolder" works just fine.

How to create a common repository of MSBuild .proj files?

Since I started using MSBuild for our projects, I've created several .proj scripts that are shared by several projects in our repository. All these shared scripts reside in a single directory.
So far I've been referring to the shared scripts by using a relative path, something like this:
<MSBuild Projects="..\..\common\build\MyScriptA.proj" Properties="ABC=XYZ"/>
However, every project also imports a common .proj script like so:
<Import Project="..\..\common\build\CommonImports.proj"/>
which <Import>s several other things and defines some properties.
This morning I thought I could replace the relative path with a variable, perhaps $(CommonDir), which would be defined by importing the CommonImports.proj mentioned above. This would enable me to call the common tasks like this:
<MSBuild Projects="$(CommonDir)\MyScriptA.proj" Properties="ABC=XYZ"/>
However, I can't figure out a way to define this $(CommonDir) variable in such a way as to make it work in all other MSBuild scripts that import CommonImports.proj, regardless of their location.
This question offers several ways of creating a property containing an absolute path from a relative path, but none of those seem to work if all I do is <Import> the script defining the property.
Question 1: I'm fairly new to MSBuild; is there a better way of creating a "library" of reusable .proj scripts I could run via the <MSBuild> task? I am aware of $(MSBuildExtensionsPath), however I would like the common tasks to reside in my checkout so that our build machine would automatically get the latest versions of the common tasks whenever it performs a checkout.
Question 2: How do I define $(CommonDir) inside CommonImports.proj so as to make it contain the absolute path to the directory containing CommonImports.proj?
I wonder if you shouldn't put it in the MSBuild path:
For example, this project is consumed via:
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
and installs itself into:
{program files}\MSBuild\MSBuildCommunityTasks
So perhaps define your own specific sub-folder, and use from there?
<Import Project="$(MSBuildExtensionsPath)\romkyns\CommonImports.proj"/>
etc. Because the $MSBuildExtensionsPath variable is defined separately you shouldn't have as much difficulty with it. Maybe.
This isn't a detailed answer, but here's some notes that are generally very useful when people solve this problem. All require MSBuild 4.0 or later.
(1) The $(MSBuildThisFile) property and similar properties. It allows imported files to refer to files relative to themselves, rather than to the project they're imported in. That uncouples them from the project they're imported into.
(2) The "GetDirectoryNameOfFileAbove" function. See here. This wonderfully useful function makes it possible for projects to import files whose location they don't know. Put the shared files (or one single stub shared file that imports the others from somewhere) at the top of your source tree. Then use this function in projects below it to find that stub and import it and thus all your shared build process.
(3) Import tag allows wildcards. This makes it possible to cause a file to be imported -- in other words, extend a build process -- by simply dropping it in a particular location, and editing no existing files.
Here's what's worked best for us.
First, check in all reusable stuff into a /common/build/ directory in the VCS.
Then, add a /common/build/CommonImports.proj, looking something like this:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
ToolsVersion="4.0">
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
<Import Project="ILMerge.proj"/>
<Import Project="Mage.proj"/>
<Import Project="InnoSetup.proj"/>
<UsingTask TaskName="RT.Tasks.AssemblyVersion" AssemblyFile="Tasks\Release\RT.Tasks.dll"/>
<UsingTask TaskName="RT.Tasks.WaitForProcessesToTerminate" AssemblyFile="Tasks\Release\RT.Tasks.dll"/>
<PropertyGroup>
<BuildSingleProj>$(Root)\common\build\BuildSingle.proj</BuildSingleProj>
</PropertyGroup>
</Project>
This imports a few projects and tasks that everything uses, and defines a property group that is shared by everything. Then, add this in every build script:
<PropertyGroup>
<Root>..\..</Root>
[... other global properties ...]
</PropertyGroup>
<Import Project="$(Root)\common\build\CommonImports.proj"/>
10+ years later, another strategy is to put a Directory.Build.props file in the root of the repository declaring a property using $(MSBuildThisFileDirectory) to get the path to the root of the repository (or other useful place). Then all projects in the repository can use paths relative to the root instead of to themselves.
I would probably only do this if I don't mind an extra file at the root of the repository, and if the repository primarily consists of MSBuildable projects.
Example:
/Directory.Build.props
/common/build/MyScriptA.proj
/common/build/CommonImports.proj
Directory.Build.props:
<Project>
<PropertyGroup>
<RepoRootDir>$(MSBuildThisFileDirectory)</RepoRootDir>
<CommonDir>$(RepoRootDir)common\build\</CommonDir>
</PropertyGroup>
</Project>
Note that if you use nested Directory.Build.props files, they would need to import upwards using something like this: <Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" /> since it stops after finding the first match.