Disable transitive PackageReference dependency for a specific MsBuild project - msbuild

I am migrating an old style MsBuild csproj project to using PackageReference format and have run into a problem with transitive dependencies.
Consider the following
Project A reference NuGet package B and C, each containing one single assembly using PackageReference.
On Build Project A uses IL merge to incorporate B as public symbols in the A assembly and C as internalized symbols.
Project D have a project reference to A.
The transitive dependencies case D to reference A, B and C.
When building D, compile errors of the type
error CS0433: The type 'X' exists in both 'A' and 'B'
occur.
Is there any way to force D to not add an explicit reference to B or C in the scenario above?

Disable transitive PackageReference dependency for a specific MsBuild project
If I understand you correct, you can try to use property <PrivateAssets>all</PrivateAssets>or PrivateAssets="all" for the PackageReference. If you have a package that's marked with private assets it merely prevents it from flowing to parent project or getting packed.
<PackageReference Include="B" Version="1.0.0" PrivateAssets="all">
<PackageReference Include="C" Version="1.0.0" PrivateAssets="all">
You can check the document Controlling dependency assets and this thread for some details.
Hope this helps.

I ended up using a workaround to move the transitive dependency to an alias to get around the compiler error.
<Target Name="ChangeAliasesOfBNameAssemblies" BeforeTargets="FindReferenceAssembliesForReferences;ResolveReferences">
<ItemGroup>
<ReferencePath Condition="'%(FileName)' == 'B'">
<Aliases>nonmerged</Aliases>
</ReferencePath>
</ItemGroup>
</Target>
I tried to use private assets but couldn't get the compiler error to go away in that way.

I was looking for this too and this answer kept popping up in google searches, but it didn't have the solution I know off (but can never remember), so I'll add my answer for future reference.
PrivateAssets can be used to hide many things, but hiding all is a bad move imo. You want some assets to move transitively, for example, for your app to run after a dotnet publish, you'll need the assemblies you need, at runtime. Hiding all assets means that when publishing, you may miss a few critical things - similar to deleting some files from the output.
From reading MS docs on the subject, I think what I want to hide, in order to not transitively be able to compile up against an assembly (but still have it at runtime), is compile. So I'll set my PrivateAssets to contentfiles;analyzers;build;compile. This should mean that if MyLibraryB depends on NugetPackageA - then MyAppC depending on MyLibraryB will be able to run and publish correctly, without being able to use any classes or code from NugetPackageA.
Note: This of course means that MyLibraryB must not expose any public API that uses types found in NugetPackageA. The consumer won't be able to use those public API's, at least not without themselves also referencing NugetPackageA.

Related

Target not running when using BeforeTargets="Build" on Build Server

I have a custom .targets file which I import into my C# MVC web application's project file. I've added custom targets to this like so:
<Target Name="CopyFiles" BeforeTargets="Build"></Target>
This works fine when building under Visual Studio, but when I use TeamCity to build it, the target never gets run, and I can't work out why.
If I change my target to use BeforeTargets="Compile" then it runs. Alternatively, if I add an additional target with the name Build to the .targets file
<Target Name="Build" />
then it will run, but doing so overrides the existing Build target and thus my application doesn't build. I can't quite make out the logic to this - it doesn't make sense. I'm using the Compile target for now, but if someone could explain why trying to execute it before the Build task doesn't work I'd really appreciate it.
'Build' is a special built-in target, so doesn't really work the same way as most other targets. It definitely can't be safely overridden.
The most relevant documentation is here: https://msdn.microsoft.com/en-us/library/ms366724.aspx
If you want something to run before build, the standard approach (as recommend by the comments in a newly-created .csproj file) is to override the BeforeBuild target (as documented above).
However, this isn't the most robust solution. As noted in the documentation above:
Overriding predefined targets is an easy way to extend the build process, but, because MSBuild evaluates the definition of targets sequentially, there is no way to prevent another project that imports your project from overriding the targets you already have overridden.
It's better (and only slightly more complex), to override the BuildDependsOn property and extend the default value of this property to include the target you want to run (this is also documented in the link above).
Another approach would be to leave BeforeBuild empty and use BeforeTargets="BeforeBuild", which feels a bit odd but is quite simple and will still work even if the BeforeBuild target gets overridden.
As to why BeforeTargets="Build" doesn't work, I can't find a reference for this in the documentation, but I think it's to do with its special nature. It doesn't work the same as ordinary targets and it's probably better not to think of it as a target at all.

MSBuild: Generate XML documentation for main project but not dependency projects

I have a .sln file with several projects in it. To keep this simple, let's call them...
ProjectA
ProjectB
ProjectC
...where A is the main project which references B and C. My goal is to update my build script to generate an XML "Intellisense" documentation file for ProjectA, without giving build warnings about missing documentation from B and C.
Current Build Script
I have an MSBuild script which includes the following in the build step:
<PropertyGroup>
<CustomOutputPath>C:\build\output\</CustomOutputPath>
</PropertyGroup>
<ItemGroup>
<Projects Include="ProjectA\ProjectA.csproj">
<Properties>OutputPath=$(CustomOutputPath)</Properties>
</Projects>
</ItemGroup>
<MSBuild Projects="#(Projects)" />
(There are actually multiple Projects listed in the ItemGroup, but again, let's keep this simple.)
When I run the build script, it's smart enough to compile B, C, and A for me, even though I've only specified A. All output appears in the "CustomOutputPath" location.
The closest I've gotten...
If I add a 'DocumentationFile' property to my Project entry...
<ItemGroup>
<Projects Include="ProjectA\ProjectA.csproj">
<Properties>OutputPath=$(CustomOutputPath);DocumentationFile=ProjectA.xml</Properties>
</Projects>
</ItemGroup>
...then 'ProjectA.xml' appears in "CustomOutputPath". However, I also get files named 'ProjectA.xml' in the project folder for all three projects:
ProjectA/ProjectA.xml
ProjectB/ProjectA.xml
ProjectC/ProjectA.xml
These files contain the "Intellisense" documentation for their respective projects, even though they're all named "ProjectA.xml".
This creates undesired and misleadingly-named files in the project folders, and (more importantly) generates build warnings for the missing documentation comments in B and C. I don't want to add documentation comments to those projects, so I'd prefer to find a way to have MSBuild generate the documentation only for ProjectA.
Can anyone provide any insight, or an alternative solution?
Based on what I've found - DocumentationFile is a global-level property (and will be used in creation of DocFileItem - global level items list). From my understanding you won't be able to alter it in any easy way in a single logical script.
What you can do is to define special target in separate file that will be imported to every proj file (directly editing proj files or using properties like $CustomBeforeMicrosoftCommonTargets) that will overwrite DocumentationFile with project-dependent value.
As a result - you probably can generate different documentation file names for different projects.
Another solution - just clean-up all unnecessary doc files right after all projs were built.

Compile symbol not used when building via MSBUILD?

I have a project where I have added a compile symbol (MYSYMBOL) in the project properties. When building from VS it works just fine and the application acts accordingly.
Other projects has various symbols defined this way.
When building from MSBUILD, MYSYBOL is for some strange reason ignored and the application acts as if it was never set.
I tried adding /p:"DefineConstants=MYSYMBOL" to the msbuild call, but then the build fails as it seems to replace the other symbols of each project with this one alone, so one assembly e.g. requires MYSYMBOL + OTHERSYMBOL but now only gets MYSYMBOL.
This is strange since it actually does use OTHERSYMBOL for those projects that have it, when building via msbuild.
Does anyone have a clue what could be causing MYSYMBOL on the project properties not to be used when building via msbuild?
Turned out that the build script has stated a set of compiler symbols on its own, overwriting the ones in the separate project settings which in turn caused this issue.
As far as I understand, you can't add one or more compiler symbols since anything you state replaced the symbols of the projects.
Ended up having to pass every symbol every project in the entire solution uses.
Too bad :(
You will want to add a new symbol to the list of DefineConstants (as opposed to overriding the entire list of DefineConstants). This will do what you want:
<PropertyGroup>
<DefineConstants>MYSYMBOL;$(DefineConstants)</DefineConstants>
</PropertyGroup>

MSBuild enforce HintPath validation

I often see the situation when assembly references have wrong HintPath and this can cause strange random failures.
For example, you have wrong HintPath, but you have a corresponding assembly in your GAC.
It will mean that the project will compile ok on your machine but will fail on others.
In my case it is vise versa, there is a library in GAC even on CI server, so this problem will not detectable by continuous integration.
I am looking for something like custom MSBuild task which will validate all HintPaths and cause build failure if any of them are wrong.
If you want to fail the build if the hint path is invalid, why not just reference something explicitly? This is SOP at my company due to having lots of versions of the same library.
For example:
<Reference Include="C:\Path\To\Library\MyReference.dll">
<Private>False</Private>
</Reference>
Or, if you want to reference libraries based on some path dynamically, you can set some msbuild property, say, "ExternalLibs" that points to your libraries folder (if you keep libs in source control, etc). Then you can set that property via the command line (when you call msbuild) or give it some default value that each user can override in their .user files, for example.
<Reference Include="$(ExternalLibs)\MyReference.dll">
<Private>False</Private>
</Reference>
This solution is pretty flexible.

Tfs2010 Build Number and Assembly File Versions & MSBuild targets

I read John Robbins' article TFS 2010 Build Number and Assembly File Versions: Completely In Sync with Only MSBuild 4.0, and I'm wondering about the best way to go about integrating this.
The download for the article has two files, one is a targets file and one is a proj file.
The targets file has a number of tasks to scrape out a build number based on the Tfs build number (the same one used for the builds) and write that number out to some location (call it BuildNumberFile) for consumption by other proj files.
The proj file is very simple. It just imports the aforementioned targets file, and then declares a target with name "All" while also declaring DefaultTargets on the Project element to be All as well.
<Project ToolsVersion="4.0" DefaultTargets="All" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!-- The two required properties so the Wintellect.TFSBuildNumber tasks knows your major and minor values.-->
<TFSMajorBuildNumber>3</TFSMajorBuildNumber>
<TFSMinorBuildNumber>1</TFSMinorBuildNumber>
</PropertyGroup>
<Import Project="Wintellect.TFSBuildNumber.targets"/>
<!-- Just ask for the version information files you need. These are here to show all the diffent ones in
Wintellect.TFSBuildNumber.Targets. You can change the names -->
<Target Name="All"
DependsOnTargets="WriteSharedCSharpAssemblyVersionFile;
WriteSharedVBAssemblyVersionFile;
WriteSharedCPPCLIAssemblyVersionFile;
WriteSharedCPPAssemblyVersionFile;
WriteSharedWiXAssemblyVersionFile;
WriteSharedTextAssemblyVersionFile;"/>
</Project>
I have two questions about this:
I'm still learning MSBuild. If the name of the target isn't specified elsewhere in the targets, is the target executed? How do I ensure that this target is run?
Are the csproj files supposed to declare an Include item for the location where BuildNumberFile is, even though it doesn't exist until compiletime?
Do ItemGroups and Include have a DependsOnTargets or something that allows them make sure the file exists before they build?
Are the entire contents of the csproj file using this supposed to be wrapped in a target that expresses DependsOnTargets for BuildNumberFile?
Thanks!
I think I've got this figured out, but two people promoted my question so I'll answer it here:
You can ensure that a target is run by expressing a dependency on it from another target. Microsoft.Common.targets exposes two targets--BeforeBuild and AfterBuild--expressly for the purpose of being overridden for customizability. I found the easiest way to do this was <Target Name="BeforeBuild" DependsOnTargets="WriteSharedCSharpAssemblyVersionFile" /> where WriteSharedCSharpAssemblyVersionFile is the target declared in the download from the link in the original post. Also, if you're new to MSBuild, this BeforeBuild target must be declared after the Microsoft.CSharp.targets is imported, but the default csproj template guides you in doing this.
The WriteSharedCSharpAssemblyVersionFile target should indeed write the file to some central location, since when building a solution, all targets are executed only once. All projects should reference the file from that location even if it doesn't exist, since by the time compilation happens (or more importantly, by the time references are resolved), the BeforeBuild target will have run and the file will be in place.
In my structure, I have these versioning files in a folder directly underneath the branch root folder. Furthermore, since the file being built is generated, I have it build to the output directory. It seems a little strange to be referencing things from the output, but it preserves the invariant of having all build products in one place so that the output directory can be blown away as a means of performing a clean.
In MSBuild items constitute inputs into the system (usually files) so it's weird to think of them depending on targets. After some learning this question doesn't make a lot of sense. In any case, the answer is no.
The entire contents of the file should indeed not be all in one target--all that is required is to import the Wintellect.TFSBuildNumber.targets file at the beginning of your csproj file, and declare BeforeBuild's dependency on WriteSharedCSharpAssemblyVersionFile at the end.
Hope this helps!