MSBuild enforce HintPath validation - msbuild

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.

Related

Disable transitive PackageReference dependency for a specific MsBuild project

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.

How do I add reference in vb.net without Visual Studio?

I have found some free compilers online, but I can't figure out how to add external references to any of them. Is there a special Imports statement I can use, so that VB.NET will go looking for a third party .dll in a certain relative path, so that I don't have to add references through Visual Studio? The specific reference I am trying to add first is the DI interop for SAP B1, but this probably isn't going to be the only one I will need to add.
I know that I can add a /reference to add a reference in a command line compilation, if I am using Microsoft's command line compiler, but that seems to be against the license for my client now. I gather the old Express 2010 versions could compile and add references without revenue restrictions, but I am looking for syntax on how to tell a compiler to add a reference, not a software recommendation (since I don't think I can still get those Express editions).
The answer is as #Jaxedin and #T.S. said in comments to the question, but I wasn't aware of the existence of the MSBuild software.
I had read in various places in reference to MSBuild comments like "And keep in mind that for now, you’ll need to have Visual Studio 2015 installed in order to build the first time." (MSDN), and just took them for face value.
Being able to put the references inside an XML .vbproj is much easier than I had been imagining. Something like:
<ItemGroup>
<Reference Include="System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<Name>System</Name>
<SpecificVersion>True</SpecificVersion>
<HintPath>...\systemDir\System.dll</HintPath>
<ReferenceOutputAssembly>True</ReferenceOutputAssembly>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Reference>
</ItemGroup>

Vb.net project reference libraries locations for different configuration

Related: Conditionally use 32/64 bit reference when building in Visual Studio
Normally when you add vb.net projects as reference in the same solution there is a reference added with a hint location. now in C# as far as i remember it adds it based on architecture. Why not in Vb.net or am i just doing it wrong. Check the related question.
<Reference Include="MyComAssembly.Interop">
<HintPath>..\..\lib\x86\MyComAssembly.Interop.dll</HintPath>
</Reference>
<ItemGroup Condition=" '$(Platform)' == 'x64' ">
<Reference Include="MyComAssembly.Interop">
<HintPath>..\..\lib\x64\MyComAssembly.Interop.dll</HintPath>
</Reference>
To clarify i do get the entry but its not architecture based by default. The related question does talk about manually editing the file but I am trying to understand why its not happening automatically for VB.net compared to C#. Is there any open feature request as well.
Edit on 6/6/2017
for Vb.net it shows the references correctly as explained in first response, but why doesn't a COM reference work in the same way.
I am posting this as answer, because I need to show some code here
You said:
Normally when you add vb.net projects as reference in the same solution there is a reference added with a hint location
Answer:
No. Project reference is created as following
<ProjectReference Include="..\..\..\MyProject\MyProject.csproj">
<Project>{7316c328-7716-4b5c-b736-f5811c764158}</Project>
<Name>MyProject</Name>
</ProjectReference>
Your XML shows DLL reference. If you reference from DLL, you get the hint.
And the ItemGroupcondition is not added by default, not in C#, not in VB. This is something you do manually. I am telling you this as build master who constantly configures projects and solutions.

Setting MSBuild 'Condition' attribute via Visual Studio extension

I have a C# project which is built in a few different configurations. Some of the source files should be always included, and some only in certain configurations. So far I've been doing this with #if ... #endif around the entire files, but I was hoping to create a small extension to do this a nicer way.
I've created an extension that adds an item to files' context menus, but I can't find any way to set the Condition attribute on the item node in the project file.
I've looked at the Properties collection of the EnvDTE.ProjectItem interface, but can't see anything useful there (except BuildAction... I'll come back to that).
Then I tried getting an IVsBuildPropertyStorage on the item and calling SetItemAttribute(). This does add information to the project file, but as a child element like this:
<ItemGroup>
<Compile Include="Program.cs">
<Condition>%27%24%28Configuration%29%27==%27Debug%27</Condition>
</Compile>
</ItemGroup>
when what I was trying to achieve was:
<ItemGroup>
<Compile Include="Program.cs" Condition="'$(Configuration)'=='Debug'" />
</ItemGroup>
There's also an IVsBuildPropertyStorage.SetPropertyValue() but that adds a similar child element to a PropertyGroup section near the top, not to the item node.
I've looked at 'Project Subtypes/Flavors', but that looks like it's just going to get me another IVsBuildPropertyStorage, which doesn't seem to be useful. They do look capable of a lot of complex things, but documentation on the subject appears to be minimal and vague.
I've seen some posts describing how to use the MSBuild assemblies to directly load and manipulate the project file, but I'm not sure when is safe to do that without confusing Visual Studio and potentially losing changes, since VS prompts to reload when it detects changes to the project file.
As a last idea, I thought about manipulating the BuildAction property between Compile and None, but that sounds like it could be a lot of work for my extension to maintain correctly, keeping it in sync with every time the user switches configurations in the IDE for example.
Is there anyone with any experience with this kind of thing that has any advice to offer me, or should I give up hope and stick with manually adding #if directives everywhere?
You may like to explore the MSBuild option you mentioned.
You don't actually have to load the MSBuild project from file, because Visual Studio gives you a way of accessing the MSBuild project directly, i.e.:
string projectPath = projectItem.ContainingProject.FullName;
MsBuildProject project = ProjectCollection.GlobalProjectCollection.GetLoadedProjects(projectPath);
var compileItems = project.GetItems("Compile");
From there you can locate your specific items and potentially add the condition attribute, though I haven't tried this step myself (if this doesn't work, you might have to try modifying the project elements under the project.Xml property instead).
You can then call project.Save(), which shouldn't trigger the "Reload project?" dialog because of the way the MsBuild project instance is linked to the Visual Studio project hierarchy.
However, you may like to force Visual Studio to reload the project anyway, because if you switch build configurations (e.g. between Debug and Release), the MSBuild engine may not re-evaluate your item conditions during build. The code to do this programmatically can be found here:
How do I programmatically refresh/reload a VS project after modifying the underlying file?
Unfortunately I never got the time to persue the original goal of creating an extension for doing this, however I did achieve what I needed using the suggestion by lex-li: using separate project files per configuration.
Since the project files can all reside in the same directory, it's easy to simply use the 'Include/Exclude from project' context menu item in the solution explorer to choose which files are included. There's also no need for file linking this way, which I'd tried before and found very time-consuming to manage.
Partial Methods are also worth looking at, if you have similar needs. They allow you to define the signature of a method in one place, but optionally implement it elsewhere. If you don't implement it, no call is generated by the compiler.
With respect to the original idea of the extension, I suspect the answer by Daniel Nolan was heading in the right direction, but unfortunately I didn't get to try it out.

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!