I have a solution with a few class libraries and a console project. Each one of the libraries have an Inputs dir next to the .csproj file with a bunch of files.
I'm currently doing this in each one of the library projects, so that the different files end up being copied to the console project output dir. It works as expected:
<ItemGroup>
<None Update="Inputs\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
Now I'm trying to extract that functionality to Directory.Build.props by doing something like this:
<ItemGroup Condition="$(MSBuildProjectName.StartsWith('AoC')) AND !$(MSBuildProjectName.EndsWith('Test'))" >
<None Update="$(MSBuildProjectDirectory)\Inputs\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
and I end up with no files being copied to the output directory (neither the library ones or the runner).
Interestingly enough:
- If I have an
Inputsdir in\bin\Debug\net8.0with files created from the originalItemGroupfrom a project, those files get deleted if I only keep the 'generic'ItemGroupfromDirectory.Build.propsfile. - No other files are modified (i.e. deleted).
- If there's not
Inputsdir, it's not even created.
Additionally, this target in Directory.Build.props shows the expected project paths where the Input folders live:
<Target Name="PrintDirs" BeforeTargets="Build" Condition="$(MSBuildProjectName.StartsWith('AoC')) AND !$(MSBuildProjectName.EndsWith('Test'))">
<Message Importance="High" Text="||---> $(MSBuildProjectDirectory)" />
</Target>
<..>\AoCMultiYear> dotnet build
Versión de MSBuild 17.8.3+195e7f5a3 para .NET
Determinando los proyectos que se van a restaurar...
Todos los proyectos están actualizados para la restauración.
Common -> <..>\AoCMultiYear\src\Common\bin\Debug\net8.0\Common.dll
AoC2020 -> <..>\AoCMultiYear\src\AoC2020\bin\Debug\net8.0\AoC2020.dll
AoC2021 -> <..>\AoCMultiYear\src\AoC2021\bin\Debug\net8.0\AoC2021.dll
||---> <..>\AoCMultiYear\src\AoC2020
||---> <..>\AoCMultiYear\src\AoC2021
AoC2020.Test -> <..>\AoCMultiYear\tests\AoC2020.Test\bin\Debug\net8.0\AoC2020.Test.dll
AoC2022 -> <..>\AoCMultiYear\src\AoC2022\bin\Debug\net8.0\AoC2022.dll
||---> <..>\AoCMultiYear\src\AoC2022
Runner -> <..>\AoCMultiYear\src\Runner\bin\Debug\net8.0\Runner.dll
Which piece of the msbuild puzzle am I missing here? :)
Legacy and SDK Projects
There are two types of MSBuild project files: legacy style and SDK style. Legacy projects are used for the older Windows only .NET Framework. SDK projects were developed for .NET (aka .NET Core) and can support both .NET and .NET Framework.
MSBuild project files are XML. If the
Projectelement has anSDKattribute, it is an SDK style project.e.g.
SDK style projects move a lot of content out of the actual project file and implement some new capabilities. But with this there are some behavior changes.
SDK - Default includes and excludes
Legacy projects generally explicitly specify files that are part of the project. SDK projects has a set of default includes for files. Pertinent to the question, files in the project folder and sub-folders that are not
Compileor resource items or of a few other special types are included in theNoneitem collection.Directory.Build.props and .targets
The difference between a
Directory.Build.propsfile and aDirectory.Build.targetsfile is when the file is imported. Both files can contain any valid MSBuild content. (A.propsfile is not limited to properties and a.targetsfile is not limited to targets.)The
Directory.Build.propsfile is imported very early, before the content of the project file itself and before many standard properties have been defined.The
Directory.Build.targetsfile is imported after the content of the project file.As a rule of thumb, put content into the
Directory.Build.targetsfile. When there is a need to alter the behavior of the definition of standard properties and items, then use theDirectory.Build.propsfile. As an example, there is a set of properties for altering the default item inclusion. Setting these properties should be done in theDirectory.Build.propsfile. The project file itself (and theDirectory.Build.targetsfile) are too late.Factoring Common Functionality
When factoring common functionality from across a set of project files where the functionality is currently in the project file and is working correctly, move the common code to the
Directory.Build.targetsfile. Essentially that is like moving the code to the end of the project file. You won't break the code because of the special early import of theDirectory.Build.propsfile.An Approach for the
InputsfolderAssuming an SDK style project, create a
Directory.Build.targetsfile with the following content:Notes
Directory.Build.targetsfile. The common logic runs in the context of each individual project.InputsDirectoryName) and can be overridden. (This could be done on a per project basis.)EnableCopyInputs).falsewithin a project file and/or it could be set based on the project name within theDirectory.Build.targetsfile.ItemGroupcondition requires that the copy feature is enabled and that the directory exists. If a project doesn't have an appropriate existing directory, no changes are made.Upgradeis used to modify the metadata of the existing items. They are existing items because the SDK logic will have auto-included them.Upgradecan't be used in theDirectory.Build.propsfile because the SDK's auto inclusion hasn't been performed yet.DisplayCopyInputsSettingstarget reports relevant properties and items. It can be used against a project, e.g.dotnet msbuild -t:DisplayCopyInputsSettings myproject.csproj.