I have a test project which reference NUnit3TestAdapter. I do not this reference to be copied over to the projects that depend on this one.
I thought setting PrivateAssets = All would do it, but apparently I misunderstand how it works, because it does not have the desired effect.
Here is the code:
Rollup\Rollup.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\UITests\UITests.csproj"/>
</ItemGroup>
</Project>
UITests\UITests.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NUnit3TestAdapter" Version="3.11.2">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>
Directory.Build.rsp
.\Rollup.sln /restore /v:m
After I run msbuild all is built, but I can see NUnit3TestAdapter is in the bin folder for Rollup.
What am I missing?
(https://github.com/Microsoft/msbuild/issues/3996)
PrivateAssets works as expected but the NUnit test adapter NuGet package adds an MSBuild target to the build that adds a few dll files as content items to the project, which then flow transitively through the build - this has the same effect as if you added a text file and set its "Copy to Output Directory" property.
The NUnit3TestAdapter.props contains definitions like:
<Content Include="$(MSBuildThisFileDirectory)NUnit3.TestAdapter.dll">
<Link>NUnit3.TestAdapter.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Visible>False</Visible>
</Content>
You should see these files if you click the "Show All Files" in the Visual Studio solution explorer.
Note that test projects aren't really supposed to be packaged or referenced. They should be leaf projects. The test project templates even contain an <IsPackable>false</…> definition and XUnit's core package also adds it as an imported MSBuild file. The test frameworks expect you to use their abstraction libraries and not runtime assemblies / test adapter packages for projects that share tests or test logic.
Related
I am developing a suite of UI tests using Selenium. One of the run-time dependencies of this suite is the chromedriver.exe, which we are expected to consume through the Selenium.WebDriver.ChromeDriver NuGet package.
The old world
When this NuGet package is imported the following lines are injected into the csproj file:
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\Selenium.WebDriver.ChromeDriver.2.44.0\build\Selenium.WebDriver.ChromeDriver.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Selenium.WebDriver.ChromeDriver.2.44.0\build\Selenium.WebDriver.ChromeDriver.targets'))" />
</Target>
<Import Project="..\packages\Selenium.WebDriver.ChromeDriver.2.44.0\build\Selenium.WebDriver.ChromeDriver.targets" Condition="Exists('..\packages\Selenium.WebDriver.ChromeDriver.2.44.0\build\Selenium.WebDriver.ChromeDriver.targets')" />
And it is automatic by the Visual Studio. This covers our bases, making sure the build targets provided by the Selenium.WebDriver.ChromeDriver package are there at the time of the build and running them as needed. The logic inside the build targets file copies/publishes the chromedriver.exe to the right location.
All is green.
The new world.
I consume the same NuGet package as PackageReference in the csproj file. Cool. However, the build targets of that package are no longer executed. See https://github.com/NuGet/Home/issues/4013. Apparently, this is by design.
I could import the targets manually, but the problem is that I will have to hard code the location where the package is restored. It is no longer restored in the packages directory in the solution, but under my windows profile. But there is no property pointing to this location and hard coding it sucks.
So, here is the version that works for me and I hate it:
<PropertyGroup>
<MyPackagesPath>$(UserProfile)\.nuget\packages\</MyPackagesPath>
<SeleniumWebDriverChromeDriverTargets>$(MyPackagesPath)selenium.webdriver.chromedriver\2.44.0\build\Selenium.WebDriver.ChromeDriver.targets</SeleniumWebDriverChromeDriverTargets>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="2.44.0" />
</ItemGroup>
<Target Name="EnsureChromeDriver" AfterTargets="PrepareForRun">
<Error Text="chrome driver is missing!" Condition="!Exists('$(OutDir)chromedriver.exe')" />
</Target>
<Import Project="$(SeleniumWebDriverChromeDriverTargets)" Condition="Exists('$(SeleniumWebDriverChromeDriverTargets)') And '$(ExcludeRestorePackageImports)' == 'true'" />
Overall, the Sdk style projects are absolutely great, but this whole business of running targets from the packages is totally broken, even if it is by design.
What am I missing?
EDIT 1
So, here is the content of the generated obj\UITests.csproj.nuget.g.targets:
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
</PropertyGroup>
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)selenium.webdriver.chromedriver\2.44.0\build\Selenium.WebDriver.ChromeDriver.targets" Condition="Exists('$(NuGetPackageRoot)selenium.webdriver.chromedriver\2.44.0\build\Selenium.WebDriver.ChromeDriver.targets')" />
</ImportGroup>
</Project>
Notice the ImportGroup condition is '$(ExcludeRestorePackageImports)' != 'true'. Now, this condition is always false, because ExcludeRestorePackageImports seems to be hard coded to be true in
c:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\CommonExtensions\Microsoft\NuGet\NuGet.targets
Inspecting binary log confirms this. Plus https://github.com/NuGet/Home/issues/4013 was closed as WontFix.
Or am I still missing something?
If you are running Restore and other targets during the build, you may get unexpected results due to NuGet modifying xml files on disk or because MSBuild files imported by NuGet packages aren't imported correctly.
I have a custom project system, that uses the standard net sdk targets.
During the build, I produce an extra zip file. I'd like this extra file to be included in an output group, so that when I query my projects output groups (from vs) it shows up.
My project file looks like this:
<Project Sdk="Microsoft.NET.Sdk">
... stuff
<ItemGroup>
<PackageReference Include="DnnVsProjectSystem.BuildTools" Version="0.0.5">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>
<Import Project="$(CustomProjectExtensionsPath)DnnVsProjectSystem.targets"/>
</Project>
Notice, I am using the "sdk" attribute, which is a fairly new feature of msbuild.
The PackageReference that you see, is a nuget package that imports a .props and a .targets which augment the build with some custom build tasks. These are the ones that produce the zip file.
I have drilled into the net sdk targets and found this:
<Target Name="AllProjectOutputGroups" DependsOnTargets="
BuiltProjectOutputGroup;
DebugSymbolsProjectOutputGroup;
DocumentationProjectOutputGroup;
SatelliteDllsProjectOutputGroup;
SourceFilesProjectOutputGroup;
ContentFilesProjectOutputGroup;
SGenFilesOutputGroup" />
<!--
This is the key output for the BuiltProjectOutputGroup and is meant to be read directly from the IDE.
Reading an item is faster than invoking a target.
-->
<ItemGroup Condition=" '$(OutputType)' != 'winmdobj' ">
<BuiltProjectOutputGroupKeyOutput Include="#(IntermediateAssembly->'%(FullPath)')">
<IsKeyOutput>true</IsKeyOutput>
<FinalOutputPath>$(TargetPath)</FinalOutputPath>
<TargetPath>$(TargetFileName)</TargetPath>
<COM2REG Condition="'$(RegisterForComInterop)'=='true' and '$(OutputType)'=='library'">true</COM2REG>
</BuiltProjectOutputGroupKeyOutput>
</ItemGroup>
<ItemGroup Condition=" '$(OutputType)' == 'winmdobj' ">
<WinMDExpOutputWindowsMetadataFileItem Include="$(_IntermediateWindowsMetadataPath)" Condition="'$(_IntermediateWindowsMetadataPath)' != ''" />
<BuiltProjectOutputGroupKeyOutput Include="#(WinMDExpOutputWindowsMetadataFileItem->'%(FullPath)')">
<IsKeyOutput>true</IsKeyOutput>
<FinalOutputPath>$(TargetPath)</FinalOutputPath>
<TargetPath>$(TargetFileName)</TargetPath>
</BuiltProjectOutputGroupKeyOutput>
</ItemGroup>
This appears to be the target that is called by VS, when it wants information about output groups.
The problem is, i am not sure how I can get my item included in one of those output groups, as If i just add the item to the item group, in my own targets - my targets are irrelevent at this point, as they are not included in this dependency chain.
I also can't override any of the targets, because, as i'm using the sdk attribute, it looks like the sdk targets will always be imported last, overwriting anything that I declare.
Any guidance much appreciated.
If your only concern is to hook into the target or its dependency chain, I suggest using msbuild's BeforeTargets functionality:
<Target Name="IncludeMyCustomOutputGroup" BeforeTargets="AllProjectOutputGroups" DependsOnTargets="ResolveMyCustomPropertiesAndItems">
<ItemGroup>
<!-- Assuming #(MyCustomOutput) items are generated by your ResolveMyCustomPropertiesAndItems target, or just add anything else -->
<BuiltProjectOutputGroupKeyOutput Include="#(MyCustomOutput->'%(FullPath)')">
<IsKeyOutput>true</IsKeyOutput>
<FinalOutputPath>$(TargetPath)</FinalOutputPath>
<TargetPath>$(TargetFileName)</TargetPath>
</BuiltProjectOutputGroupKeyOutput>
</ItemGroup>
</Target>
I have a solution that contains a console application with a .csproj file like the this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
</Project>
I also have a library project that uses the console application to generate a heap of C# code that get compiled into the library, the library .csproj file looks like this.
<Project Sdk="Microsoft.NET.Sdk" InitialTargets="RunGenerator">
<PropertyGroup>
<TargetFramework>netstandard1.4</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../generator/generator.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
</ItemGroup>
<Target Name="RunGenerator">
<Exec Command="dotnet run -p "../generator/generator.csproj" input output" />
</Target>
</Project>
This fails because the dependency analysis says that a netstandard1.4 assembly cannot reference a netcoreapp1.1 assembly. That is correct except that I am not referencing the assembly.
I can work around that issue by building the generator project like this:
<Project Sdk="Microsoft.NET.Sdk" InitialTargets="RunGenerator">
<PropertyGroup>
<TargetFramework>netstandard1.4</TargetFramework>
</PropertyGroup>
<Target Name="RunGenerator">
<Exec Command="dotnet build "../generator/generator.csproj"" />
<Exec Command="dotnet run -p "../generator/generator.csproj" input output" />
</Target>
</Project>
The problem is that the generator project no longer takes part in the dependency analysis when these projects are built using the containing solution file and the explicit build of the generator project sometimes runs concurrently with another build of the same project initiated by the solution build and this results in errors because files are locked etc.
Is it possible to have a project dependency without checking the target framework?
Can anyone suggest a workaround?
Thanks.
Here are some MSBuild tips. You might need to combine a few of these ideas.
You can use your solution file to add an explicit project dependency. See https://learn.microsoft.com/en-us/visualstudio/ide/how-to-create-and-remove-project-dependencies (This question was originally asked here: Visual Studio 2010: How to enforce build order of projects in a solution?). Unfortunately, this is really hard to do if you don't have VS. The format is .sln files is kinda a nightmare.
To avoid the concurrent build issue, use the MSBuild task instead of the Exec task. See https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-task
<Target Name="CompileAnotherProject">
<MSBuild Projects="../generator/generator.csproj" Targets="Build" />
</Target>
dotnet-run invokes "dotnet build" automatically. This is actually problematic in concurrent builds. You can instead add a target to your generator.csproj that runs the app after it has been built. "dotnet filepath.dll" runs the compiled app without building it.
<Target Name="RunCodeGen" AfterTargets="Build">
<Exec Command="dotnet $(AssemblyName).dll input output"
WorkingDirectory="$(OutDir)" />
</Target>
I use the _PublishedApplications to generate the structure in the TFS Build Server. After this, I use the <HeatDirectory> in the WiX project to correctly harvest the content of _PublishedApplications folder. But my problem is the order during build.
If I use the <HeatDirectory> inside <Target Name="BeforeBuild"> it doesn't include the binaries copied to the _PublishedApplications, as the harvesting is executed before the publish (file copy).
If I change the target to BeforeCompile the compilation doesn't succeed because there is no file in first place. Here is the code for the WiX project (the relevant part of it):
<ItemGroup>
<Compile Include="Product.wxs" />
<Compile Include="Autogenerated.wxs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SimpleProject\SimpleProject.csproj">
<Name>SimpleProject</Name>
<Project>{GUID}</Project>
<Private>True</Private>
<DoNotHarvest>True</DoNotHarvest>
<RefProjectOutputGroups>Binaries</RefProjectOutputGroups>
<RefTargetDir>INSTALLFOLDER</RefTargetDir>
</ProjectReference>
</ItemGroup>
<Import Project="$(WixTargetsPath)" />
<Target Name="BeforeCompile">
<HeatDirectory OutputFile="Autogenerated.wxs" Directory="$(Sources)"
PreprocessorVariable="var.SimpleProject.TargetDir"
AutogenerateGuids="true" SuppressRegistry="true"
ToolPath="$(WixToolPath)" DirectoryRefId="INSTALLFOLDER"
ComponentGroupName="ComponentGroup_Core"
SuppressRootDirectory="true" />
</Target>
Question
How can I execute the harvesting after the binaries are copied to _PublishedApplications?
I build my solution using the following command:
msbuild SimpleInstaller.sln /p:OutDir=C:\Temp\Output\ /v:diag > C:\Temp\Log.txt
This will output all log to a text file. You can use the MSBuild logger instead.
Then I found when you build a C# project, the target Compile in file Microsoft.Common.targets is invoked. This target has an attribute DependsOnTarget which contains a reference to target BeforeCompile.
I can override this target BeforeCompile in my own C# project, just by adding the following code at the end of it (file .csproj):
...
<Target Name="BeforeCompile">
<!-- custom action. -->
</Target>
</Project>
But the problem is my WiX project cannot override the BeforeCompile target because this target isn't defined for WiX projects. You can check this in the wix2010.targets file. The target Compile only has dependence upon targets PrepareForBuild, ResolveWixExtensionReferences and GenerateCompileWithObjectPath.
My solution was to identify an alternative to BeforeCompile which is the Harvest target. My WiX project (.wixproj) has the following target now:
<Target Name="Harvest">
<HeatDirectory OutputFile="Autogenerated.wxs" Directory="$(Sources)"
PreprocessorVariable="var.SimpleProject.TargetDir"
AutogenerateGuids="true" SuppressRegistry="true"
ToolPath="$(WixToolPath)" DirectoryRefId="INSTALLFOLDER"
ComponentGroupName="ComponentGroup_Core"
SuppressRootDirectory="true" />
</Target>
All this problem occurred because my first project in the solution was the WiX project and only then I added the C# projects. For this reason the BeforeBuild was being executed before everything else.
Another solution to solve this issue is to edit the solution file (.sln) and move the WiX project declaration in the beginning of the solution file to the end of all project declarations (not the end of the solution file). Then the BeforeBuild of the WiX project will be executed after the _PublishedApplications folder is created by the C# project.
This manual edit is required because if you change the Project Build Order you are actually changing the project references (at least in the solution file), but the target BeforeBuild is called anyway before the ResolveProjectReferences which is the responsible for invoking the build of any references.
This is the project declaration that should be after all others:
Project("GUID") = "SimpleInstaller", "SimpleInstaller\SimpleInstaller.wixproj", "GUID"
EndProject
My recommendation is still to use Harvest target as it is independent of any changes in the solution file.
The first thing you need to do is add a project reference from your installer project to your application project. This will force the application to build and be available for your installer project.
You can verify the build sequence of the projects if you right click your solution under VisualStudio and click the "Project Build Order...", if you need to change the order you need to configure the Project dependencies.
Then do something like this in your installer project:
<PropertyGroup>
<RootDir>{PATH TO _PublishedApplications FOLDER}</RootDir>
<HarvestDirectoryNoLogo>true</HarvestDirectoryNoLogo>
<HarvestDirectorySuppressAllWarnings>false</HarvestDirectorySuppressAllWarnings>
<HarvestDirectoryTreatWarningsAsErrors>false</HarvestDirectoryTreatWarningsAsErrors>
<HarvestDirectoryTreatSpecificWarningsAsErrors>
</HarvestDirectoryTreatSpecificWarningsAsErrors>
<HarvestDirectoryVerboseOutput>false</HarvestDirectoryVerboseOutput>
<HarvestDirectoryAutogenerateGuids>false</HarvestDirectoryAutogenerateGuids>
<HarvestDirectoryGenerateGuidsNow>true</HarvestDirectoryGenerateGuidsNow>
<HarvestDirectorySuppressFragments>true</HarvestDirectorySuppressFragments>
<HarvestDirectorySuppressUniqueIds>false</HarvestDirectorySuppressUniqueIds>
</PropertyGroup>
<Import Project="$(WixTargetsPath)" />
<Target Name="BeforeBuild">
<ItemGroup>
<HarvestDirectory Include="$(RootDir)">
<Transforms>
</Transforms>
<ComponentGroupName>MyComponent</ComponentGroupName>
<DirectoryRefId>WebSiteRoot</DirectoryRefId>
<PreprocessorVariable>var.RootDir</PreprocessorVariable>
<SuppressCom>false</SuppressCom>
<SuppressRegistry>false</SuppressRegistry>
<SuppressRootDirectory>true</SuppressRootDirectory>
<KeepEmptyDirectories>true</KeepEmptyDirectories>
</HarvestDirectory>
</ItemGroup>
<HeatDirectory
NoLogo="$(HarvestDirectoryNoLogo)"
SuppressAllWarnings="$(HarvestDirectorySuppressAllWarnings)"
SuppressSpecificWarnings="$(HarvestDirectorySuppressSpecificWarnings)"
ToolPath="$(WixToolPath)"
TreatWarningsAsErrors="$(HarvestDirectoryTreatWarningsAsErrors)"
TreatSpecificWarningsAsErrors="$(HarvestDirectoryTreatSpecificWarningsAsErrors)"
VerboseOutput="$(HarvestDirectoryVerboseOutput)"
AutogenerateGuids="$(HarvestDirectoryAutogenerateGuids)"
GenerateGuidsNow="$(HarvestDirectoryGenerateGuidsNow)"
OutputFile="$(IntermediateOutputPath)_%(HarvestDirectory.Filename)_dir.wxs"
SuppressFragments="$(HarvestDirectorySuppressFragments)"
SuppressUniqueIds="$(HarvestDirectorySuppressUniqueIds)"
Transforms="%(HarvestDirectory.Transforms)"
Directory="#(HarvestDirectory)"
ComponentGroupName="%(HarvestDirectory.ComponentGroupName)"
DirectoryRefId="%(HarvestDirectory.DirectoryRefId)"
KeepEmptyDirectories="%(HarvestDirectory.KeepEmptyDirectories)"
PreprocessorVariable="%(HarvestDirectory.PreprocessorVariable)"
SuppressCom="%(HarvestDirectory.SuppressCom)"
SuppressRootDirectory="%(HarvestDirectory.SuppressRootDirectory)"
SuppressRegistry="%(HarvestDirectory.SuppressRegistry)" />
</Target>
</Project>
I generated this code based on the documentation of the HeatDirectory Task and I use it in real projects.
I've got the AssemblyInfo feature of the MSBuild Extension Pack working, so that my assemblies description and file version have the details I want in the code below ...
But I want to apply this effect across every project in a 50+ project solution!
So how can I work on all the projects ... without going through each project adding the code?
<PropertyGroup>
<CoreCompileDependsOn>
$(CoreCompileDependsOn);
AssemblyDefaults;
</CoreCompileDependsOn>
</PropertyGroup>
<PropertyGroup>
<ExtensionTasksPath>
$(MSBuildProjectDirectory)\..\packages\MSBuild.Extension.Pack.1.4.0\tools\net40\
</ExtensionTasksPath>
</PropertyGroup>
<Import Project="$(ExtensionTasksPath)\MSBuild.ExtensionPack.tasks" />
<Target Name="AssemblyDefaults">
<ItemGroup>
<AssemblyInfoFiles Include=".\Properties\AssemblyInfo.cs" />
</ItemGroup>
<AssemblyInfo
AssemblyInfoFiles="#(AssemblyInfoFiles)"
AssemblyProduct="Compiled on: $([System.Environment]::MachineName)"
AssemblyDescription="Compiled at: $([System.DateTime]::Now)"
AssemblyFileBuildNumberType="DateString"
AssemblyFileBuildNumberFormat="MMdd"
AssemblyFileRevisionType="DateString"
AssemblyFileRevisionFormat="HHmm" />
</Target>
You could create a common assembly file for all the projects and edit the csproj files to refer to the common assembly info file like:
<Compile Include="..\CommonAssemblyInfo.cs">
<Link>Properties\CommonAssemblyInfo.cs</Link>
</Compile>
More information can be found at Shared AssemblyInfo for uniform versioning across the solution