Adding reference to another executable with ReferenceOutputAssembly=false doesn't copy dependencies - c#

I have a solution with several executables in it (say, MainApp.exe and Tool.exe).
The main goal is to ensure that the tool (Tool.exe) with its dependencies is copied to the main executable directory during build.
I used the advice from here, and it seemed to work with the older Visual Studio version (at least with some version prior to 16.8).
My project structure (simplified) looks like this:
Solution.sln
├ MainApp.csproj
├ Tool.csproj
| └ App.config
└ ToolLib.csproj
Tool project contains App.config file, and references ToolLib project.
My MainApp.csproj looks like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../Tool/Tool.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<OutputItemType>Content</OutputItemType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Targets>Build;DebugSymbolsProjectOutputGroup</Targets>
</ProjectReference>
</ItemGroup>
</Project>
After upgrading to 16.8 after the compilation the file Tool.exe was indeed copied to the output directory, but neither its dependency ToolLib.dll nor Tool.config was copied to the output directory any more.
Is this a bug or intended behaviour? What is the proper way to ensure that the whole Tool with all the needed dependencies is copied to the MainApp's output dir?
Added test project reproducing the problem here: https://github.com/vladd/ReferenceOutputAssembly

What you gave is too old and it is not suitable for VS2019. And all your projects target to net core 3.1. I have tested your project both in VS2019 16.8 , VS2019 16.7, even 16.6 which all act the same behavior as you described. Only contain the Tool.dll and Tool.exe.
So I wonder why you said before that the result of the build of ToolLib will be printed in the main project.
Actually, <ReferenceOutputAssembly>false</ReferenceOutputAssembly> will prevent the most main output files of the referenced project and its dependency project being copied into the main project.
Suggestion
You have to set it as true:
<ReferenceOutputAssembly>true</ReferenceOutputAssembly>
If you want to not copy ToolLib.pdb and Tool.pdb files into the main project, you could add these node on MainApp.csproj file:
<PropertyGroup>
<AllowedReferenceRelatedFileExtensions>*.pdb;.dll.config</AllowedReferenceRelatedFileExtensions>
</PropertyGroup>
If you also want to copy pdb files, you should add .pdb under AllowedReferenceRelatedFileExtensions.
<AllowedReferenceRelatedFileExtensions>.pdb;.dll.config</AllowedReferenceRelatedFileExtensions>
Update 1
I tried your suggestion but with it the files Tools.deps,json and
Tool.runtimeconfig.json are not copied, so running the tool fails.
Add this on MainApp.csproj file:
<PropertyGroup>
<AllowedReferenceRelatedFileExtensions>.pdb;.dll.config;.runtimeconfig.dev.json;.runtimeconfig.json</AllowedReferenceRelatedFileExtensions>
</PropertyGroup>

Related

DocumentationFile created by a referenced library does not get copied to the project's bin

I have an API project that has a few libraries as references. Each of those libraries creates a documentation XML file by including
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(MSBuildThisFileName).xml</DocumentationFile>
in the project.
When I build my API project, it pulls all the XML files from each of the referenced libraries, except one.
Here's the main property group of one that does copy over:
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(MSBuildThisFileName).xml
</DocumentationFile>
</PropertyGroup>
And here's the one for the project that doesn't copy over:
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(MSBuildThisFileName).xml
</DocumentationFile>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
I've tried removing GenerateDocumentationFile, but that makes no difference.
The XML does get generated, but remains in the library's bin. It never gets copied to the API project's bin directory.
Both of these libraries are referenced via project references.
It is possible that the library whose XML is not being copied over is being referenced inside a nuget that the API references. Could that be the cause?
I need the XML files for including example values in the Swagger.

Visual studio How to set output path dependant of assembly version

I want to configure my main output path to something like
C:\Company\UpdaterLauncher\Worker\Version
Where version is my AssemblyInfo.Version in string.
So a new folder each time I decide to change the assembly version.
I know I can change output all time.. But it's annoying.
Is this possible to use something like "C:\Company\UpdaterLauncher\Worker\{AssemblyVersion}" for visual output path to interprete it and build where I want?
I looked a bit in documentation and didn't found anything like this...
Which way do you build the project? By msbuild command-line or within VS IDE?
First direction: Let's read the assembly version number before the build starts, then
pass it to outputpath property.
I've written a script trying to read the version before the build starts. But not completely work:(
E.g: Using a class library project as the example.
Right-click the project and choose edit the xx.csproj, add the script (From In property to FourthNum property) into the PropertyGroup:
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{DAB28A16-73AD-4EC5-9F8D-E58CE3EC84BE}</ProjectGuid>
......
<In>$([System.IO.File]::ReadAllText('$(MSBuildProjectDirectory)\properties\AssemblyInfo.cs'))</In>
<Pattern>\[assembly: AssemblyVersion\(.(\d+)\.(\d+)\.(\d+).(\d+)</Pattern>
<FirstNum>$([System.Text.RegularExpressions.Regex]::Match($(In), $(Pattern),System.Text.RegularExpressions.RegexOptions.Multiline).Groups[1].Value)</FirstNum>
<SecondNum>$([System.Text.RegularExpressions.Regex]::Match($(In), $(Pattern),System.Text.RegularExpressions.RegexOptions.Multiline).Groups[2].Value)</SecondNum>
<ThirdNum>$([System.Text.RegularExpressions.Regex]::Match($(In), $(Pattern),System.Text.RegularExpressions.RegexOptions.Multiline).Groups[3].Value)</ThirdNum>
<FourthNum>$([System.Text.RegularExpressions.Regex]::Match($(In), $(Pattern),System.Text.RegularExpressions.RegexOptions.Multiline).Groups[4].Value)</FourthNum>
</PropertyGroup>
It will read the assembly version number from AssemblyInfo.cs. If I have an assembly whose assembly version is 3.13.8.5. Then the FirstNum=3, SecondNum=13 ...
And set the outputpath as: <OutputPath>C:\Company\UpdaterLauncher\Worker\$(FirstNum).$(SecondNum).$(ThirdNum).$(FourthNum)</OutputPath>
Reload the project and build it. You can find the build output there C:\Company\UpdaterLauncher\Worker\3.13.8.5.
Note:
1.In this way, since we will build it in both debug and release mode. We need to set the outputpath value in both propertygroup for debug and release.(2 places)
2.Since we only define the output depending on version, the debug output and release will all locates in same folder. So I think the <OutputPath> would be better like:
<OutputPath>C:\Company\UpdaterLauncher\Worker\$(FirstNum).$(SecondNum).$(ThirdNum).$(FourthNum)\$(Configuration)</OutputPath>
3.This script won't work immediately after you change the version in VS IDE.
Via Command-line: It works well, every time we change the version number and build it, the output is correct.
Within VS IDE: Every time after we change the version, it needs us to unload and reload the project file by right-clicking the project, then it will work. So I say it isn't that perfect.(I would think this issue has something to do with when and how the VS loads the project file)
Second Direction: The build output actually is copy the related
assemblies to output folder. So we can copy or move the output content
to the directory after the build we want by copy or move task.
We can check this issue, using GetAssemblyIdentity to get the info after the build.
Using the way above to get version number, name it $(MyVersion). Then use a after-build target to copy the output to the specified folder.
<Target Name="CopyToSpecificFolder" AfterTargets="build">
<GetAssemblyIdentity
AssemblyFiles="$(OutputPath)$(AssemblyName).dll">
<Output
TaskParameter="Assemblies"
ItemName="MyAssemblyIdentities"/>
</GetAssemblyIdentity>
<PropertyGroup>
<MyVersion>%(MyAssemblyIdentities.Version)</MyVersion>
</PropertyGroup>
<ItemGroup>
<Out Include="$(OutputPath)*.*" />
</ItemGroup>
<Copy DestinationFolder="C:\Company\UpdaterLauncher\Worker\$(MyVersion)" SourceFiles="#(Out)"/>
</Target>
Add this script into the xx.csproj file. In the bottom of it like:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
......
<Target Name="CopyToSpecificFolder" AfterTargets="build">
......
</Target>
</Project>
It works well in whether VS IDE or by command-line. And it's for class project, if you're developing a .exe project, change the $(AssemblyName).dll to $(AssemblyName).exe.

How are preprocessors linked to different target frameworks?

I'm following along with a talk by Immo Landwerth, in a .NET standard project, I saw him switching between projects (which are not typically projects under the solution) from the top left drop-down menu, here is a gif: https://image.ibb.co/mmjoHU/pre.gif
To reproduce the same thing I created a class library in .NET Framework to see the goings, but the project failed to load (with a modified csproj file to be the same as the demo's csproj), then I created a .NET standard library and modified the .csproj file to this (the same as the demo's csproj):
<Project Sdk="MSBuild.Sdk.Extras">
<PropertyGroup>
<TargetFrameworks>netstandard1.4;net461;uap10.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.ValueTuple" Version="4.3.0" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net461' ">
<Reference Include="System.Device" />
</ItemGroup>
</Project>
I could see the three target frameworks:netstandard1.4;net461;uap10.0. but really couldn't understand how they got mapped to the preprocessors: NET461, WINDOWS_UWP. which work on the Immo's project, but didn't work with my modified .NET standard library and the three targets didn't appear.
Looks like conditional compilation symbols with linked .cs files to me.
In the Properties > Build tab, you should see a textbox labelled "Conditional compilation symbols". Any strings you put in this textbox can be used to conditionally execute code with #if and #elif. There are also symbols that the build system is already aware of (some are listed here).
I'm assuming these projects also share the same .cs files, which explains the #if/#elif/#else conditions "flipping" within the same file (when you add a file to a project, choose add it as a link to an existing file, then a copy is not made).
I have done this exact thing on multiple projects for different configurations and the behavior matches the .gif you posted.

Copy all of the required dlls to folder

I have a MonoAndroid10 project and it has a lot of dependencies(NuGet packages too). I would like to be able to copy all of the DLL dependencies to the output folder.
Normally in a .Net Standard 2.0 project the following
<PropertyGroup>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
would make that possible. But in the MonoAndroid project, nothing happens.
If what I posted above doesn't work for a MonoAndroid project, how could I copy everything that I need in some folder, preferably in a post-build action?
I'm trying to do this because after copying all of the required DLLs in one folder I can merge them together with ILRepack.
I've come across this today, solved by adding
<CopyNuGetImplementations>true</CopyNuGetImplementations>
to android project .csproj file (under the desired PropertyGroup section).
Note: there is no need to set CopyLocalLockFileAssemblies to ture neither in android project nor in any of the dependencies.

How to determine behavior of binaries in NuGet package

There is this big solution I'm working on, where I turned a lot of the projects into NuGet packages. The packages were created via a .nuproj file in a separate solution in VS.
Everything works fine, except for the following:
At bootstrap I load some catalogs for MEF to be able to import them, which worked perfectly when I worked with the original projects, but now the needed DLLs (which come from the a package) don't make it to the bin\Debug\Modules folder.
Is there a way to make NuGet copy its content to the Modules folder? (and not to the root path)
I tried using the different kinds of sub-folders inside the package with no success.
I found that the best solution for this matter is the following:
Take the files that need to be loaded and put them on the content folder. This can be done simply:
<ItemGroup>
<Content Include=" {here go the needed files} " />
</ItemGroup>
The content folder just holds the files, but it does not copy them to the output folder on the client project. In order to copy them to the desired output, a .targets file can be used, just like the following:
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="CopyToOutput" AfterTargets="Build">
<ItemGroup>
<FilesToCopy Include="$(MSBuildThisFileDirectory)..\content\**\*.*"/>
</ItemGroup>
<Copy
SourceFiles="#(FilesToCopy)"
DestinationFiles="#(FilesToCopy->'$(OutDir)/%(RecursiveDir)%(FileName)%(Extension)')"/>
</Target>
</Project>
Keep in mind that the targets file name and the ID of the NuGet have to be equal for the targets file to be added to the project.
You should be able to use a target of content/Modules. Anything in the content directory is copied in to the bin directory on build.
If you were trying to use the special "convention based" folders, like lib/net45, those are directories that cause Visual Studio to automatically create an assembly reference when the package is installed. You shouldn't use those for regular content files.
See the documentation for more details.

Categories