VS 2017 new project format and external props file - msbuild

Lets say I have the following project file:
<Project>
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
<PropertyGroup>
<TargetFramework>net47</TargetFramework>
</PropertyGroup>
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
</Project>
Is it possible to move TargetFramework property to external props file? When I try doing that, Visual Studio gives me a "one-way upgrade" error.

You've hit a known issue in the way that Visual Studio interprets csproj files. On command line (msbuild.exe or dotnet.exe) you can build a project with the property TargetFramework coming in from an imported file. Visual Studio, however, does not recognize this when it is imported.
See https://github.com/dotnet/project-system/issues/1358.

Related

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

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.

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.

Inexplicably cleared msbuild properties in TeamCity build

I'm trying to create a "tools NuGet package" that provides a tool and setting that is unpacked during build and used by a later TeamCity build step.
The NuGet package contains the following content in its build\MyPackageId.props file:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MyTool1>$(MSBuildThisFileDirectory)..\tools\MyTool.exe</MyTool1>
</PropertyGroup>
<Target Name="ReportMyToolToTeamCity" BeforeTargets="PrepareToRun">
<PropertyGroup>
<MyTool2>$(MSBuildThisFileDirectory)..\tools\MyTool.exe</MyTool2>
</PropertyGroup>
<Message Text="MyTool1 = $(MyTool1)" />
<Message Text="MyTool2 = $(MyTool2)" />
</Target>
</Project>
(The messages will eventually set a TeamCity property, but this is sufficient to demonstrate the issue.)
Because it's a props file, after installing the NuGet package into a C# project it has added an import as the very first thing, above the import of Microsoft.Common.props. I want a props file rather than a targets file so that the property values are also available to other project settings and targets files.
When I compile this inside Visual Studio 2015, I see both MyTool1 and MyTool2 paths set to the same (correct) path as expected.
When I compile this from TeamCity (2017.2.2, using the Visual Studio (sln) runner), according to the output the MyTool1 property is empty and only MyTool2 shows the correct value.
Why?

MSBuild ignores changes inside *.wpp.target

I have following *.wpp.target file:
<PropertyGroup>
<CopyAllFilesToSingleFolderForMsdeployDependsOn>
ExcludeCustomFilesOrFolders;
$(CopyAllFilesToSingleFolderForPackageDependsOn);
</CopyAllFilesToSingleFolderForMsdeployDependsOn>
</PropertyGroup>
<Target Name="ExcludeCustomFilesOrFolders" BeforeTargets="ExcludeFilesFromPackage">
<ItemGroup>
<ExcludeFromPackageFolders Include="$(MSBuildProjectDirectory)\Media" />
</ItemGroup>
<Message Text="Custom Exclude From %0D Folders: #(ExcludeFromPackageFolders)%0D Files: #(ExcludeFromPackageFiles)" Importance="high"/>
</Target>
If I run it for the first time out of visual studio 17 Media folder is ignored and not published.
After when I comment the line with ExcludeFromPackageFolders node and publish again Media folder is still ignored. It seems that visual studio or msbuild does not refresh changes made inside *.wpp.target file. Do I miss here something or is VS or MSbuild just buggy?
Can you try to add /PROFILE to the linker option? Might fix the problem: https://developercommunity.visualstudio.com/content/problem/136703/wpp-trace-missing-from-pdb-files-in-vs-2017.html
You have to restart visual studio and reload solution everytime you change it. VS caches it.

Property scope using msbuild extension pack detokenise

Im trying to use the msbuild extensions pack to fix up the configuration of our app on deploy,
i want to be able to pass a property (ENV) which will load my environment specific config file to use with the detokeniser, and fix up my application configs.
Like this:
<UsingTask TaskName="MSBuild.ExtensionPack.FileSystem.Detokenise"
AssemblyFile=".\Tools\MSBuild Extension Pack 4.0.3.0\MSBuild.ExtensionPack.dll"/>
<Import Project=".\Environments\$(Env).properties"/>
<Target Name="Build" >
<ItemGroup>
<SourceTemplates Include=".\Templates\**\*.*"/>
</ItemGroup>
<RemoveDir Directories=".\Temp"/>
<MakeDir Directories=".\Temp"/>
<Message Text="#(SourceTemplates)"/>
<Copy SourceFiles="#(SourceTemplates)"
DestinationFolder=".\Temp\%(RecursiveDir)" />
<ItemGroup>
<TargetTemplates Include=".\Temp\**\*.*"/>
</ItemGroup>
<MSBuild.ExtensionPack.FileSystem.Detokenise
TaskAction="Detokenise"
TargetFiles="#(TargetTemplates)"/>
</Target>
So i call this using
msbuild Detokenise.msbuild /p:Env=Prod
Msbuild knows about my file and i have access to its properties, but when the detokeniser runs i get the error:
Detokenise Task Execution Completed [15:07:50]
C:\Source\1.2\Build\Detokenise.msbuild(27,3):
error : InvalidProjectFileException: The imported project "C:\Source\1.2\Build\Environments\.properties" was not found.
Confirm that the path in the <Import> declaration is correct, and that the file exists on disk.
C:\Source\1.2\Build\Detokenise.msbuild\r
C:\Source\1.2\Build\Detokenise.msbuild(27,3): error :
All works fine if i hard code it-
Any ideas how to solve this. I thought of doing some text replacement on the msbuild before i execute...
You could try to assign this parameter to a local property:
<PropertyGroup Condition="'$(Env)'=='Prod'">
<TargetEnv>Prod</TargetEnv>
</PropertyGroup>
<!-- add other environments as needed -->
<PropertyGroup Condition="'$(Env)'=='Test'">
<TargetEnv>Test</TargetEnv>
</PropertyGroup>
<Import Project=".\Environments\$(TargetEnv).properties"/>
You could also try to enclose your parameter value in quotes:
msbuild Detokenise.msbuild /p:"Env=Prod"
As is your problem can't be reproduced, so it may be a side effect of other parameters not shown in your sample code.
I've seen a number of other questions where a similar problems was happening:
Visual Studio Ignoring MSBuild file (csproj) Customizations