MSBuild - ItemGroup of all bin directories within subdirectories - msbuild

My Solution has multiple projects (and therefore subdirectories), and there is a 'bin' folder in each project folder.
I'm trying to create an ItemGroup in my MSBuild script that includes all these directories.
I thought this would be sufficient, but it doesn't contain anything:
<ItemGroup>
<BinDirs Include="**\bin" />
</ItemGroup>
I'm not sure why this doesn't work. Can anyone point me in the right direction to achieve what I'm trying to do?
Regards,
Nick

Since this hasn't got an answer, yet comes high on the list of Google results:
The link provided by Alexey has several answers to work around this problem, but it's not obvious why the example you've given doesn't work.
MSBuild ItemGroup collections don't seem to like wildcard Transforms when targeting directories.
You can use explicit paths, e.g.
<ItemGroup>
<BinDirs Include="C:\MyProject\bin" />
</ItemGroup>
Or paths relative to where your build script is running, e.g.
<ItemGroup>
<BinDirs Include="..\MyProject\bin" />
</ItemGroup>
However it does not transform your wildcards unless you are targeting files, e.g.
<ItemGroup>
<ThisWorks Include="..\**\bin\*" />
<ThisDoesnt Include="..\**\bin" />
</ItemGroup>
That post contains several ways to select folders using wildcards, the one I tend to use is:
<ItemGroup>
<GetAllFiles Include="..\**\bin\*.*" />
<GetFolders Include="#(GetAllFiles->'%(RootDir)%(Directory)'->Distinct())" />
</ItemGroup>
As noted on the post, it's not perfect at selecting the root folders, as it has to find where there are files. Using bin*.* would only get the bin folder if files were located in it.
If your build is anything like a standard VS output, you will probably find your bin folder has no files, instead having directories based on your configuration names, e.g. bin\Debug, in which case targeting bin\**\* will result in your item group containing those folders.
E.g.
<ItemGroup>
<GetAllFiles Include="..\**\bin\**\*" />
<GetFolders Include="#(GetAllFiles->'%(RootDir)%(Directory)'->Distinct())" />
</ItemGroup>
Would get:
..\Proj1\bin\Debug
..\Proj1\bin\Release
..\Proj2\bin\Debug
..\Proj2\bin\Release
I don't know of a wildcard way to get bin folders without files in... yet. If anybody finds one, please post as it would be useful.
Hope this helps someone save some time.

In MSBuild 4.0 this is possible:
<Folders Include="$([System.IO.Directory]::GetDirectories(".","Bin", SearchOption.AllDirectories))" />

Related

csproj include (recursive) folders based on a list (batching)

I want to add some files recursively to a single project that are located in folders inside my solution root.
Right now, I found a working solution to add those files by specifying each folder manually:
<ItemGroup>
<Content Include="..\Folder1\**\*">
<Link>Folder1\%(RecursiveDir)\%(Filename)%(Extension)</Link>
</Content>
<Content Include="..\Folder2\**\*">
<Link>Folder2\%(RecursiveDir)\%(Filename)%(Extension)</Link>
</Content>
<Content Include="..\Folder3\**\*">
<Link>Folder3\%(RecursiveDir)\%(Filename)%(Extension)</Link>
</Content>
</ItemGroup>
As those can be a handfull folders and they can be different depending on my solution, I want to have an easy list to define the folder names.
Something like this:
<PropertyGroup>
<DefinedResourceFolders>Folder1;Folder2;Folder3</DefinedResourceFolders>
</PropertyGroup>
This would also allow me to input this as a property directly when calling msbuild to extend the files that are going to the build output, maybe.
I tried to look into the MSBuild Batching documentation and several other online sources, but I could not figure it out. Most batching examples I found were working with Targets, not Includes into the solution items.
Is this possible? How would I define the <ItemGroup> for the content then?
P.S.: I don't care about wildcard issues with .csproj files when new files are added, etc. This will either be a single "Resources" project only containing those displayed files, or I am using my external .props file that I am importing into each .csproj file anyway.
Suppose you want to include all files in folders root/Folder1, root/Folder2 and root/Folder3 recursively.
This is how the builds (both VS and CLI) do what you want. However, VS will not show the files as part of the project.
<Project Sdk="Microsoft.NET.Sdk" InitialTargets="__AddBatchedContent;$(InitialTargets)">
<!-- the usual properties -->
<!-- You could of course define the folder array directly in an Item,
but this is what you wanted :-) -->
<PropertyGroup>
<Lookups>Folder1;Folder2;Folder3</Lookups>
</PropertyGroup>
<ItemGroup>
<LookupDir Include="$(Lookups)" />
</ItemGroup>
<Target Name="__AddBatchedContent">
<ItemGroup>
<!-- save the original source folder of the globbed file name in custom
metadata "Folder" so we can use it later as its output base folder -->
<__BatchedFiles Include="..\%(LookupDir.Identity)\**\*" Folder="%(LookupDir.Identity)" />
<Content Include="#(__BatchedFiles)" Link="%(Folder)\%(RecursiveDir)\%(Filename)%(Extension)" CopyToOutputDirectory="Always" />
</ItemGroup>
</Target>
</Project>
Note that if you want to put this in a .targets file to use this technique in multiple projects, you should replace the ..\ with $(MSBuildThisFileDirectory)..\ so that the path is relative to the (known) location of the .targets file instead of the (unknown) location of the importing project.
Thanks to #Ilya Kozhevnikov for the basis of this answer.
You are not the only one who would like this to be simpler :-): https://github.com/dotnet/msbuild/issues/3274

How to generate files during build using msbuild

Does anyone know how to modify a csproj file in a way to generate code files during build without actually referencing the files?
A process like :
create file,
dynamically reference temporary file during build
compiled assembly has additional members, depending on the files created during build
The purpose of this is to create a way of generating code files using roslyn instead of using t4 templates, which are very awkward to use once you're trying to do something depending on attributes.
Hence i am planning on providing a way to use a special csharp file (for full syntax support) to generate files programatically based on the contents of that special file.
I've spent a couple of weeks looking into resources on the internet (with the topic msbuild), but until now it seems i didn't use the right keywords.
This one has been the most insightful one to me yet:
https://www.simple-talk.com/dotnet/.net-tools/extending-msbuild/
My guess is, that the correct build target for my purpose should be "BeforeCompile" in order to somehow populate the build process with custom code files.
Does anyone have experience with my issue, or is aware of any particular resources which deal with the task?
Solution i got it working with:
<UsingTask TaskName="DynamicCodeGenerator.DynamicFileGeneratorTask" AssemblyFile="..\DynamicCodeGenerator\bin\Debug\DynamicCodeGenerator.dll" />
<Target Name="DynamicCodeGeneratorTarget" BeforeTargets="BeforeBuild;BeforeRebuild">
<DynamicFileGeneratorTask>
<Output ItemName="Generated" TaskParameter="GeneratedFilePaths" />
</DynamicFileGeneratorTask>
<ItemGroup>
<Compile Include="#(Generated)" />
<FileWrites Include="#(Generated)" />
<!-- For clean to work properly -->
</ItemGroup>
</Target>
Unfortunately i did not get it to work with a propertygroup override as suggested
Update: This link is interesting too: https://github.com/firstfloorsoftware/xcc/blob/master/FirstFloor.Xcc/Targets/Xcc.targets
Generating the code file can be achieved by msbuild task or msbuild inline task. It is up to you to generate the proper code. One thing that you must care of is creating output item parameter in order to append it to the #(Compile) item. You can use $(IntDir) location to locate your newly generated file, and add them to the #(FileWrites) item group in order for Clean target work properly.
When you finish writing your task, you must use it in your project like this:
<UsingTask TaskName="TaskTypeFullName" AssemblyFile="YourAssembly.dll"/>
<PropertyGroup>
<!-- Here you need to experiment with [Build/Compile/SomeOther]DependsOn property -->
<BuildDependsOn>
MyCodeGenerator;
$(BuildDependsOn)
</BuildDependsOn>
</PropertyGroup>
<Target Name="MyCodeGenerator">
<YourTaskName>
<Output ItemName="Generated" TaskParameter="GeneratedFiles" />
</YourTaskName>
<ItemGroup>
<Compile Include="#(Generated)" />
<FileWrites Include="#(Generated)" /> <!-- For clean to work properly -->
</ItemGroup>
</Target>
I wanted to use a bash script to generate code for a project using dotnet core on Linux. Here is what worked for me. And thanks #stukselbax, I built this off of your answer.
<Target Name="GenerateProtocolBuffers" BeforeTargets="BeforeBuild;BeforeRebuild">
<Exec Command="./generatecode.sh" Outputs="proto/*.cs">
<Output ItemName="Generated" TaskParameter="Outputs" />
</Exec>
<ItemGroup>
<Compile Include="#(Generated)" />
<FileWrites Include="#(Generated)" />
</ItemGroup>
</Target>
Note that the script I'm using to generate the code is called generatecode.sh. Replace this with your own.

structure solution outputpath by project

I'm building a visual studio solution with msbuild
msbuild.exe my.sln
This way it outputs everything to the output paths specified in each project (bin\ by default), but in this case I need all the output artefacts to be in different folder, used for packaging. If I run
msbuild.exe my.sln /p:OutputhPath=<someFolder>
Then all the artifacts will end up in the specified folder, but the structure will be flat. What I would like it to be, is:
\package
\project1
\project2
...
But I can't think of a good way to do this, without modifying individual project files (which is almost out of question). Any ideas? (msbuild 4.0, VS2010 - if that changes anything)
There is probably a better way, but one thing you could do is build in place with msbuild.exe my.sln, and then copy the outputs to your \package dir so you keep the hierarchy. It should be pretty simple to do. You can use this as a starting point:
<Target Name="Package">
<PropertyGroup>
<SourceFolder>$(MSBuildProjectDirectory)\src</SourceFolder>
<TargetFolder>$(MSBuildProjectDirectory)\package</TargetFolder>
</PropertyGroup>
<ItemGroup>
<FilesToCopy Include="$(SourceFolder)\**\bin\Debug\**\*.*" />
</ItemGroup>
<!-- Recursive copy w/o flattening folder structure: -->
<Copy
SourceFiles="#(FilesToCopy)"
DestinationFiles="#(FilesToCopy->'$(TargetFolder)\%(RecursiveDir)%(Filename)%(Extension)')"
/>
</Target>
You can also define a property to keep track of your build configuration, and replace the hardcoded bin\Debug with bin\$(BuildConfig).

Can I use both wildcard and Link element inside the Compile element?

In the .csrpoj file, If I have
<Compile Include="c:\path\File1.cs">
<Link>Dir1\File1.cs</Link>
</Compile>
Then Visual Studio shows that file as a shortcut under Dir1 folder in the Solution Explorer.
If I have
<Compile Include="c:\path\*.cs"></Compile>
Then all .cs files show up as shortcuts in Solution Explorer at top level:
Is there a way to include all files in some folder and make then show up under a sub-folder? Omitting the filename in Link element does not work:
<Compile Include="c:\path\*.cs">
<Link>Dir1\</Link>
</Compile>
The files still show up at top level.
How do I include all files in a folder and still use the Link element? The reason I need this is, I need to include files from multiple folders and some of them have the same name. Two files at top level cannot have the same name.
Any other way to achieve this?
Others have suggested the using the Link attribute with placeholders, which indeed works. However, Microsoft has implemented a new attribute (which isn't in any of my code completion suggestions), named LinkBase, shown below.
<ItemGroup>
<Compile Include="..\SomeDirectory\*.cs" LinkBase="SomeDirectoryOfYourChoosing" />
</ItemGroup>
Sources:
https://github.com/Microsoft/msbuild/issues/2795
https://github.com/dotnet/sdk/pull/1246
Link Additional Files in Visual Studio
<Content Include="..\..\SomeDirectory\**\*.xml">
<Link>SomeLinkDirectoryOfYourChoosing\%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
For the sake of others, here's the answer plus the comment from Dean and Miserable Variable, which I found useful:
I have two projects, and I need to include *.xsd from one in the other, without copying the files or having to update the referencing csproj file every time a new XSD is added to the first.
The solution was to add the following to the csproj file
<Content Include="..\BusinessLayer\Schemas\*.xsd">
<Link>Contract\Schemas\xxx.xsd</Link>
</Content>
Note xxx.xsd, you have to give a dummy filename in the Link element. It just gets replaced.
Also, you can include all sub folders with:
<Content Include="..\BusinessLayer\Schemas\**\*.xsd">
<Link>Contract\Schemas\ThisTextDoesntMatter</Link>
</Content>
And files of all type (useful for pulling in CSS/JS/Style folders from 3rd parties) with:
<Content Include="..\PresentationLayer\CustomerStyle\**\*.*">
<Link>CustomerStyle\placeHolder</Link>
</Content>
To include subfolders:
<ItemGroup>
<Compile Include="..\SomeExternalFolder\**\*.cs" LinkBase="YourProjectFolder" />
</ItemGroup>

How do I exclude the contents of a directory but not the directory itself in MSBuild

I'm using a Web Deployment Project 2008 to build my web application. I'd like to exclude the contents of several folders from the build but keep the blank directory itself. However, if I do this
<ExcludeFromBuild Include="$(SourceWebPhysicalPath)\ImageCache\**\*.*" />
it will exclude the ImageCache directory itself. So how do I keep the directory? Thanks in advance?
I'm afraid you must do this in two lines :
<ExcludeFromBuild Include="$(SourceWebPhysicalPath)\ImageCache\**\*.*" />
<IncludeFromBuild Include="$(SourceWebPhysicalPath)\ImageCache\" />
But that could not work because The "Copy" task does not support copying directories.
Thus, what I suggest is that you exclude the files like you did and create an empty directory in the AfterMerge target :
<ItemGroup>
<ExcludeFromBuild Include="$(SourceWebPhysicalPath)\ImageCache\**\*.*" />
<ItemGroup>
<...>
<Target Name="AfterMerge">
<MakeDir Directories="$(SourceWebPhysicalPath)\ImageCache" />
</Target>