How are preprocessors linked to different target frameworks? - c#

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.

Related

How to make visual studio project use <ReferencePackage> or DLL reference And <ProjectReference> together to switch between Debug and DLL Release?

Please I have a problem. sorry if question title are not recognized well, I can't upload full-image during my reputations.
Edit:
I need something like that maybe
https://github.com/RicoSuter/NuGetReferenceSwitcher
or
https://github.com/0UserName/NuGetSwitcher
but above repos are not updated to VS 2022? please any help?
Edit: Looks like other people asks about that issue: https://github.com/dotnet/sdk/issues/1151
Shortly, I need to make a Class Library project can modify/edit in my side only. Other people only use DLL reference or Nuget package.
What I need to do?
I need to create a main project that used across me as public source and other people as DLL only not debuggable.
In my side the class library project must seen if I choose MSBuild configuration (Debug mode) not Nuget DLL. but I need to modify whole core source. Then republish it again as Nuget to allow other people to use it privately.
I guess the whole gape in main project .csproj file. I need to modify it to allow Configuration to switch between Nuget build that visible to other developers and Debug that only visible at me. When choose it the <ProjectReference> should load and should become visible at my solution.
You can ignore Github things I mentioned. A repo can be private/public wihtout problem.
Problem Short Description:
I have main project in solution A. (Must be GIT public for other people)
I have class library in solution B (Which maybe used in 2 main projects) (Must be GIT private repo for me only)
I need the class library source only visible for me not other people. they just see Package or DLL.
The main project are public Git repo, while class library are private Git repo.
In my case I need to setup a 2 types of MSBuild configuration. (Debug/) and (Nuget/ )
the other people only allowed to use which are private nuget package and they must not debug the class library.
I need only me to use (Debug/<ProjectReference>) -> So I can change class library directly and build Nuget package for other people without PDB file,etc included.
What I try to do? What topics I read?
I Following topics I follow: Use local source code of NuGet package to debug and edit code (#Mr Qian comment)
and https://sitecore.stackexchange.com/questions/15293/what-are-packagereferences-and-how-will-they-help-optimise-the-way-i-deal-with-n/15294
I create two solutions. First (FooProject Executable), Second(FooClassLibrary)
I modify the FooProject.csproj and add following lines
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<ProjectReference
Include="..\..\FooClassLibrary\FooClassLibrary\FooClassLibrary.csproj">
<Project>{a2329af5-316e-4339-8b56-d78aba72e919}</Project>
<Name>FooClassLibrary</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Release'">
<Reference Include="FooClassLibrary">
<HintPath>..\..\FooClassLibrary\FooClassLibrary\bin\Release\FooClassLibrary.dll>
</HintPath>
</Reference>
</ItemGroup>
Edit:
I fix issues in above .csproj code. Now class library referenced with two MSBuild configuration (Debug and Release). But I don't add Class Libray in FooProject solution in Debug MSBuild.
Should now I create two solutions *.sln files? one for FooProject and reference with FooClassLibrary as DLL for non-modifying debugging?
And second FooProject solution (FooClassDebuggingAndEdit.sln) that reference project to FooProject?
Here's a two solutions *.rar link
https://anonfiles.com/rdm8A4I2x9/TwoSolutions_rar
I don't know why a solution should also reference the ClassLibrary project or it can't modified when do <ReferenceProject> in FooProject. I got following error message when debug:
Do I need change something?
Currently there's no official solution for that.
So if anyone need what approach I collected and I modify some tags to prevent conflict PackageName with ProjectName, Here's final solution, Which original copied from https://github.com/dotnet/sdk/issues/1151#issuecomment-459275750
Many thanks to script author:
Here's enhancement version of it:
<PropertyGroup>
<ReplacePackageReferences Condition="'$(ReplacePackageReferences)' == ''">true</ReplacePackageReferences>
<ReplaceProjectReferences Condition="'$(ReplaceProjectReferences)' == ''">true</ReplaceProjectReferences>
</PropertyGroup>
<Choose>
<When Condition="'$(SolutionPath)' != '' AND '$(SolutionPath)' != '*undefined*' AND Exists('$(SolutionPath)')">
<PropertyGroup>
<SolutionFileContent>$([System.IO.File]::ReadAllText($(SolutionPath)))</SolutionFileContent>
<SmartSolutionDir>$([System.IO.Path]::GetDirectoryName( $(SolutionPath) ))</SmartSolutionDir>
<RegexPattern>(?<="[PackageName]", ")(.*)(?=", ")</RegexPattern>
<HasSolution>true</HasSolution>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<HasSolution>false</HasSolution>
</PropertyGroup>
</Otherwise>
</Choose>
<Choose>
<When Condition="$(ReplacePackageReferences) AND $(HasSolution)">
<ItemGroup>
<!-- Keep the identity of the packagereference -->
<SmartPackageReference Include="#(PackageReference)">
<InProject>false</InProject>
<PackageName>%(Identity)</PackageName>
<InSolution>$(SolutionFileContent.Contains('\%(Identity).csproj'))</InSolution>
</SmartPackageReference>
<!-- Filter them by mapping them to another itemGroup using the WithMetadataValue item function -->
<PackageInSolution Include="#(SmartPackageReference -> WithMetadataValue('InSolution', True) )">
<Pattern>$(RegexPattern.Replace('[PackageName]','%(PackageName)') )</Pattern>
<SmartPath>$([System.Text.RegularExpressions.Regex]::Match( '$(SolutionFileContent)', '%(Pattern)' ))</SmartPath>
<ProjName>'%(PackageName)'</ProjName>
</PackageInSolution>
<ProjectReference Include="#(PackageInSolution -> '$(SmartSolutionDir)\%(SmartPath)' )">
<Name>#(PackageInSolution -> %(ProjName))</Name>
</ProjectReference>
<!-- Remove the package references that are now referenced as projects -->
<PackageReference Remove="#(PackageInSolution -> '%(PackageName)' )" />
</ItemGroup>
</When>
<When Condition="$(ReplaceProjectReferences) AND '$(_RestoreSolutionFileUsed)' == ''">
<ItemGroup>
<!-- Keep the identity of the project reference (relative path), determine the project name and whether the project is contained in the current solution -->
<SmartProjectReference Include="#(ProjectReference)">
<OriginalIdentity>%(Identity)</OriginalIdentity>
<ProjectName>$([System.IO.Path]::GetFileNameWithoutExtension( $([System.IO.Path]::GetFullPath( '%(OriginalIdentity)' )) ))</ProjectName>
<InSolution>$(SolutionFileContent.Contains('\%(ProjectName).csproj'))</InSolution>
</SmartProjectReference>
<!-- Filter them by mapping them to another itemGroup using the WithMetadataValue item function -->
<ProjectNotInSolution Include="#(SmartProjectReference -> WithMetadataValue('InSolution', False) )">
</ProjectNotInSolution>
<!--Reference the latest version of the package (why not * ? > see https://github.com/NuGet/Home/issues/7328-->
<PackageReference Include="#(ProjectNotInSolution -> '%(ProjectName)' )" Version="*" />
<!-- Remove the project references that are now referenced as packages -->
<ProjectReference Remove="#(ProjectNotInSolution -> '%(OriginalIdentity)' )" />
</ItemGroup>
</When>
</Choose>
Note if you need to auto update nuget package then you must specify the package above the script provided above:
<ItemGroup>
<PackageReferen``ce Include="FooClassLibrary" Version="*"/>
</ItemGroup>
Feel free to modify or edit above script or edit the answer. If you found better answer please post it (Ex You create new extension for that or something).

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

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>

Is it possible to reference a project that exists in the solution and use NuGet package reference as fall-back if not in .NET Core?

I have an .NET Standard project where I implemented a module for an ASP.NET Core CMS framework. Currently, it uses the CMS framework libraries coming from NuGet packages. If I grab the source code of the CMS framework from GitHub and add my module to its solution and replace the package references to the actual project references it will work fine.
My goal is to make it work without updating the references in the csproj file so if the project is added to the full source code solution then use the project references, otherwise use the NuGet package references.
So let's say the .NET Standard project is called 'ModuleA'. It has a package reference to 'ModuleB':
<ItemGroup>
<PackageReference Include="ModuleB" Version="1.0.0" />
</ItemGroup>
When I want to use ModuleA in a solution where ModuleB is accessible then I use project reference to it:
<ItemGroup>
<ProjectReference Include="..\..\ModuleB\ModuleB.csproj" />
</ItemGroup>
I'd like to include both of them in the .csproj file somehow and make it to use the proper references on build (e.g. based on some conditions like project exists?).
If both are added to the csproj then the build will fail (e.g. 'Unable to find project ...ModuleB.csproj. Check that the project reference is valid and that project file exists.').
You could do that probably dynamically, but I think it's not really transparant how that would work.
I would recommend to add a configuration, e.g. in my example "Local-Debug" and use conditions in your csproj.
Example
Creating the configuration:
And in your csproj you could do this:
<ItemGroup>
<ProjectReference Condition="'$(Configuration)' == 'Local-Debug'" Include="otherProject.csproj" />
<PackageReference Condition="'$(Configuration)' != 'Local-Debug'" Include="otherProjectPackage" Version="2.4.1" />
</ItemGroup>
Your dependencies must be statically known and resolve-able at build-time. See #Julian's answer for a good config-driven build-time solution.
As a run-time solution: You can dynamically load your references at run-time. This way, you can search for the DLL you need in the working directory of your app and if you don't find it there, then download it (from Nuget or elsewhere), either as a binary that you can directly load, or as a source that you can build; and then load that library dynamically.
Here's how you can load an assembly dynamically (at run-time): https://learn.microsoft.com/en-us/dotnet/api/system.reflection.assembly.loadfrom?view=netframework-4.8
And this question: Loading library dynamically
Coding against a dynamically loaded assembly has its own quirks; you will need clear interface definitions for the referenced library, or else, you'll find yourself dealing with a lot of reflection and dynamics.

exclude certain projects from using Directory.Build.props

i have a new visual studio solution that has around 350 projects. it takes visual studio a lot of time to compile the .sln file so i implemented Directory.Build.props to avoid copying of references that are not needed to copy to the local directory so the build can be made quicker. below is the code that im using inside the Directory.Build.props file under the root folder.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemDefinitionGroup>
<Reference>
<Private>False</Private>
</Reference>
<ProjectReference>
<Private>False</Private>
</ProjectReference>
</ItemDefinitionGroup>
</Project>
since i placed Directory.Build.props under root folder it is being applied for all projects.
Question::
how can i exclude few projects from applying Directory.Build.props so that the references can be copied to the local.
in short i want the Directory.Build.props to be applied to only 300 projects under the solution file remaining 50 projects need to be excluded from this
how/where can i write a condition in the above code that will exclude certain projects being affected by this code
For others dealing with the same problem, there is another trick that can be used to exclude certain project from using the Directory.Build.props file found at root level.
If you add a dummy Directory.Build.props file in the project you want to exclude, then the Directory.Build.props from the root will not be used. This is because MSBuild walks the directory structure upwards from the location of your project, until it locates the first Directory.Build.props. That will be used. This behavior is documented on the Customize your build page under Search scope at the Microsoft docs.
Sample of the dummy Directory.Build.props:
<Project>
<!-- Only here so that the default Directory.Build.props will not be used. -->
</Project>
I found this to be a convenient way to solve this issue. Especially when dealing with only a few projects that need to be excluded.
I had to work around this in a bit of a hacky way.
In my example, there was a custom analyzer project I wrote that I did not want included in another set of projects. I ended up writing something like this in my Directory.Build.props:
<Project>
...
<Choose>
<When Condition="$(MSBuildProjectName)!='Analyzer' AND ...">
<ItemGroup>
<ProjectReference Include="..\Analyzer\Analyzer.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<OutputItemType>Analyzer</OutputItemType>
</ProjectReference>
</ItemGroup>
</When>
<Otherwise>
...
</Otherwise>
</Choose>
...
</Project>
Where I filled in ... with the projects I wanted it to skip.
I understand this may not be the exact answer you were looking for, but I did a ton of research and was also unable to find any way to do it the way you described. The stuff I have posted was the only way I was able to achieve the ability to exclude certain things from being applied to specific projects by filtering via name. I know that this is hacky and sucks, but it's the only thing that was able to work for me.
Also note that <Otherwise></Otherwise> may be turned into <Otherwise /> possibly, and may even be optional altogether. I left it there so that you could place stuff inside of it if needed.

.NET Core - Nuget Pack of UniTest Project shows warning "The assembly is not inside the 'lib' folder"

I'm trying to pack a UnitTest project as a Nuget package and I always get the following warning(s) if I build my project:
The assembly
'content\SpecFlow.MSDependencyInjection.SpecFlowPlugin.dll' is not
inside the 'lib' folder and hence it won't be added as a reference
when the package is installed into a project. Move it into the 'lib'
folder if it needs to be referenced.
My csproj file looks like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>0.1.0</Version>
<IsPackable>true</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http" Version="2.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
<PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />
<PackageReference Include="MSTest.TestFramework" Version="1.4.0" />
<PackageReference Include="SpecFlow" Version="3.0.188" />
<PackageReference Include="SpecFlow.MSDependencyInjection.SpecFlowPlugin" Version="1.0.2" />
<PackageReference Include="SpecFlow.MsTest" Version="3.0.188" />
<PackageReference Include="SpecFlow.Tools.MsBuild.Generation" Version="3.0.188" />
</ItemGroup>
</Project>
The error still appears if I copy the files into the lib folder of the Nuget package. I'm at a loss what I need to change for this warning to disappear. To be frank I'm not even sure why it appears in the first place because I have a different project that works fine without this error.
Update 1:
After the detailed answer from #zivkan I changed my project structure so it is not a UnitTest project anymore.
Sadly the errors still appear if my project is a class library...
Screenshot with all Nuget-Packages that I need for my project to work
If I only add my own Nuget-Package that consists of two dependencies (Microsoft.Extensions.DependencyInjection and SpecFlow) it still produces this error but the two dependencies in this Nuget-Package don't. To me this seems to be a problem with the Nuget-Packages...
I'm not 100% sure, but my guess is that since with SDK style csproj files, when you build, only your assembly's dll is normally written to the output directory. When you run a non-test netcoreapp, the dotnet cli looks at what project references and nuget references you have, and configures the assembly loader to load from their "original" locations, rather than having all the assemblies copied to your app's bin folder. Perhaps the unit test framework doesn't support loading assemblies in this way and creates Content items out of each dll, which tells the build step to copy the content (in this case dlls) into the output directory (bin\$(Configuration)\$(TargetFramework)). Therefore, when you run unit tests, the unit test framework has all the required assemblies in the single directory, whereas that's normally not true for non-test projects.
Next you need to understand that when NuGet packs a project, it looks for MSBuild items of the type Content, and puts copies of them in the nupkg's content and contentFiles directories. Due to how NuGet works, only dlls in the lib\ or ref\ directories within the nupkg are given to the compiler, therefore any dlls you have a content directory will not be passed to the compiler, so your project that references this nupkg cannot use classes in those dlls. This is not how people usually intend to use NuGet packages and therefore NuGet generates a warning.
So, I believe the reason you're getting this warning is because you're packing a project type that is not intended to be packable. The project type does some uncommon things in order to work, which triggers NuGet warnings because typically when this uncommon thing is done, it's a mistake.
I believe this to be a case of a XY problem. I assume you're packing a unit test project because you want to share some utility code useful for tests, maybe some mocks or object initialisation code. In this case, I recommend you create a new classlib project, put your shared code in there, leaving all your test cases in your netcoreapp test project, even if it's nothing more than a single method call into the classlib. This way you can pack and share the classlib without warnings. Packing a unit test seems unusual and it would be interesting to discuss why you want to do this, what problem do you intend to solve and if packing a test is really the best way to achieve it. Unfortunately Stack Overflow isn't a good place to have discussions and is often actively discouraged.
perhaps you have missed a file, please follow this link for full details : https://learn.microsoft.com/en-us/nuget/create-packages/creating-a-package#Package_Conventions

Categories