Why aren't ".targets" files imported from Directory.Buid.props not visible in Solution Explorer "Imports" folder? - msbuild

Solved
The targets WAS there, just buried under Sdk.props->Microsoft.Common.props, which makes sense as that's where it would have been loaded from. My bad.
The Problem
There is a nice feature that displays imported ".props" and ".targets" files in Solution Explorer. These do not seem to display files imported from Directory.Build.props files.
This might be a feature or a bug in Visual Studio or maybe I'm doing something wrong. It is not critical since it is correctly using the Targets2.target file, but it would be helpful to see all my targets files used.
Does anyone understand why this is happening and if there is a change I can make?
This has also been asked in Microsoft Feedback https://developercommunity.visualstudio.com/t/targets-from-directorybuildprops-missing-in-soluti/1605185
Setup C# Project
(or clone https://github.com/MafuJosh/ReproduceVS2022BugDec2021)
in Visual Studio 2022 Pro: Create New Project Class Library - C# - .NET 6
edit the .csproj file, add:
<ImportGroup>
<Import Project="Targets1.targets" />
</ImportGroup>
create Targets1.targets text file in the project folder:
<Project>
<Target Name="Test1" AfterTargets="Build">
<Message Importance="High" Text="from Targets1" />
</Target>
</Project>
create Targets2.targets text file in the project folder:
<Project>
<Target Name="Test2" AfterTargets="Build">
<Message Importance="High" Text="from Targets2" />
</Target>
</Project>
create Directory.Build.props text file in the project folder:
<Project>
<Import Project="Targets2.targets" />
</Project>
Build C# Project
build the project
in Solution Explorer, Show All Files
under now visible Imports folder, we see Targets1.target but not Targets2.target
they both work but only one shows up under the Imports folder

The targets WAS there, just buried under Sdk.props->Microsoft.Common.props, which makes sense as that's where it would have been loaded from. My bad.

Related

How are we supposed to execute package build targets in the new world where nuget packages are consumed through msbuild PackageReference?

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.

Linked files location on .Net core in Debug vs Publish

I have a shared.{Environment}.json file that is added as linked files in several .Net core 2.1 projects. When project is build or published file gets copied to output directory, in case of release its fine but on debug it doesn't work as when project run it looks up for that file in project directory not in output directory.
Whats the proper way to solve this issue for both debug and publish?
For linked files, it will not exist under the project directory.
For a workaround, you could try to copy the file with task in csproj like below:
<ItemGroup>
<Content Include="..\MVCPro\shared.{Environment}.json">
<Link>shared.{Environment}.json</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<Target Name="CopyLinkedContentFiles" BeforeTargets="Build">
<Copy SourceFiles="..\MVCPro\shared.{Environment}.json" DestinationFolder=".\" SkipUnchangedFiles="true" OverwriteReadOnlyFiles="true" />
</Target>

.NET Core 2.1 : How to trigger Copy task defined in the project file during debugging in the Visual Studio 2017?

There are some files residing in other directories that, I would like to copy to project folder automatically before build and publishing.
After some research and experimentation, I have come up with the following .csproj file.
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.4</RuntimeFrameworkVersion>
<TieredCompilation>true</TieredCompilation>
<PreserveCompilationContext>true</PreserveCompilationContext>
</PropertyGroup>
<ItemGroup>
<APIDefinition Include="D:\SomePlace\*.API.*.yaml" />
</ItemGroup>
<Target Name="CopyFiles" BeforeTargets="Compile;Build;Publish">
<Copy SourceFiles="#(APIDefinition)" DestinationFolder="wwwroot" />
<Copy SourceFiles="D:\SomePlaceElse\BaseAPISettings.json" DestinationFolder="$(MSBuildProjectDirectory)" />
</Target>
<ItemGroup>
<Compile Remove="wwwroot\**\*;node_modules;bower_components" />
<None Update="**.user;**.vspscc">
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.4" />
</ItemGroup>
</Project>
Here I have defined CopyFiles target, which should be run before the targets I've placed there. This target uses Copy task to copy YAML format API definition files and base API settings to the project directory.
This works well during build, publish etc. Also, if I delete the local file in the IDE, it instantly recopies it from the source.
Sometimes I make changes to these files between debugging sessions. Then, when I start debugging from Visual Studio, since the project files aren't changed, obviously the already built project is run.
Since the project is not built, my copy tasks are not triggered, and I end up with stale files during debuging.
Is there anything I can do to have my Copy tasks triggered, when I do "Start Debugging F5" in the IDE, regardless of the project build state ?
P.S. : I'm using Visual Studio 2017 15.8.5 and targeting .NET Core 2.1.4 runtime, if it makes any difference.
To integrate fully into the up-to-date check of the project system inside Visual Studio, I susggest the following changes:
Make the items' source and target paths known before
Register them to the up-to-date check system. (Also needs a hack to make sure the project source code is recompiled so that the output will have a newer time stamp)
Make the MSBuild target itself incremental. This also helps for command-line builds when the files don't have to be copied.
The complete changes look like this:
<ItemGroup>
<CustomCopyFile Include="..\TestFiles\*.API.*.yaml"
TargetPath="wwwroot\%(Filename)%(Extension)" />
<CustomCopyFile Include="..\TestFiles\BaseAPISettings.json"
TargetPath="%(Filename)%(Extension)" />
<UpToDateCheckInput Include="#(CustomCopyFile)" />
<UpToDateCheckBuild Include="#(CustomCopyFile->'%(TargetPath)')"
Original="#(CustomCopyFile)" />
<CustomAdditionalCompileInputs Include="#(CustomCopyFile->'%(TargetPath)')" />
</ItemGroup>
<Target Name="CopyFiles"
BeforeTargets="BeforeBuild;BeforePublish"
Inputs="#(CustomCopyFile)"
Outputs="#(CustomCopyFile->'%(TargetPath)')">
<Copy SourceFiles="#(CustomCopyFile)"
DestinationFiles="#(CustomCopyFile->'%(TargetPath)')" />
</Target>
CustomCopyFile now collects all the source files and we put the expected destination file name into the TargetPath metadata.
UpToDateCheckInput items tell Visual Studio to rebuild the project if one of these items change.
UpToDateCheckBuild items instruct Visual Studio to only check these items against special source items. This is redundant for this example project but may be helpful if the target path wasn't inside the project directory but some intermediate output (obj..) folder and no re-evaluation would see these new files. It would also be helpful if the files were also modified as part of processing (e.g. replacing variables inside the files).
CustomAdditionalCompileInputs is a hack here since the items are copied to the project folder and are considered to be "inputs to the output" automatically.. So we force the project to recompile if our source files change. If we don't do so, it would never consider the project up-to-date after a change to the source yaml files since they would be newer than the compiled app.dll file.

Want to copy / rename files via MSBuild target in .NET Core project file but I don't know how

I have a solution with one loader project and multiple plugin projects. I'd like to copy files like "State1.json" and "State2.json" from a particular plugin project to a "bin\Debug\netcoreapp1.1\Plugins" folder in the Loader project, prefixing the name of the plugin project files (i.e. "Plugin1.State1.json") but I'm unsure how to do so.
Any help in this regard would be greatly appreciated...
I figured out the answer on my own:
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<ItemGroup>
<StateFiles Include="State\*.json" />
</ItemGroup>
<Copy SourceFiles="#(StateFiles)" DestinationFiles="#(StateFiles->'$(SolutionDir)Loader\$(OutDir)Plugins\$(ProjectName)_%(Filename)%(Extension)')" />
</Target>

MSBuild and _PublishedWebsites

After MSbuild has built my solution (with an asp.net website), and the webdeployment project has built and put the website in the directory _PublishedWebsites:
c:\mybuilds\buildName\Daily_20090519.3\Release_PublishedWebsites\MyWebsite.
How do I copy this to the fixed directory where IIS points to for the test website?
I have found loads of code snippets, but I cannot seem to find one that will take into account the fact that this directory name changes.
This is pretty easy. You can edit the project and insert something similar to the following.
<PropertyGroup>
<OutputDest>$(MSBuildProjectDirectory)\..\OutputCopy\</OutputDest>
</PropertyGroup>
<Target Name="AfterBuild">
<!-- Create an item with all the output files -->
<ItemGroup>
<_OutputFiles Include="$(OutputPath)**\*" Exclude="$(OutputPath)obj\**\*" />
</ItemGroup>
<!-- You probably don't want to include the files in the obj folder so exclude them. -->
<Message Text="OutputDest : $(OutputDest)" />
<Copy SourceFiles="#(_OutputFiles)"
DestinationFiles="#(_OutputFiles->'$(OutputDest)%(RecursiveDir)%(Filename)%(Extension)')"/>
</Target>
Is this what you are looking for?
My Book: Inside the Microsoft Build Engine : Using MSBuild and Team Foundation Build
I'm using different technique.
<PropertyGroup>
<BinariesRoot>c:\BinariesForIis\</BinariesRoot>
</PropertyGroup>
The c:\BinariesForIis\ will be used for direct output compiled binaries (before copy to ...\Daily_20090519.3\Release_ ...).