NuGet 2.x supports the inclusion of .targets
and .props
files in NuGet packages. These files will be added as Imports in the csproj MSBuild file when the package is installed. For example, consider a package with the following files:
/
build/
net40/
MyPackage.props
MyPackage.targets
If this package is installed into an MSBuild project, the following lines are added to the MSBuild projects:
<Project ...>
<Import Project="..\packages\MyPackage\1.0.0\build\net40\MyPackage.props" Condition="Exists(...)" />
<!-- ... -->
<Import Project="..\packages\MyPackage\1.0.0\build\net40\MyPackage.targets" Condition="Exists(...)" />
</Project>
This is not going to work well in the new transitive restore world. Instead of this, we will transition to a new model where the csproj will only import a single props and a single targets file if ANY packages contain targets/props file. These files will contain imports of the specific targets/props files from all of the packages found during the restore. Each restore may potentially recreate this file, as new versions are downloaded and new packages arrive.
For example, consider now this project.json referencing MyPackage:
{
"dependencies": {
"MyPackage": "1.0.0",
"SomethingElse": "1.0.0"
}
}
Where SomethingElse
depends on SomethingBuildy
which has a .targets
file but no .props
file.
After restore, the following files would be generated:
<Project ...>
<PropertyGroup>
<!-- some code to find the Package Root based on USERPROFILE, NUGET_PACKAGES, etc. and put it in PackagesRoot -->
</PropertyGroup>
<Import Project="$(PackagesRoot)\MyPackage\1.0.0\build\net40\MyPackage.props" Condition="Exists(...)" />
</Project>
<Project ...>
<PropertyGroup>
<!-- some code to find the Package Root based on USERPROFILE, NUGET_PACKAGES, etc. and put it in PackagesRoot -->
</PropertyGroup>
<Import Project="$(PackagesRoot)\MyPackage\1.0.0\build\net40\MyPackage.targets" Condition="Exists(...)" />
<Import Project="$(PackagesRoot)\SomethingBuildy\1.0.0\build\net40\SomethingBuildy.targets" Condition="Exists(...)" />
</Project>
After this, something must update the csproj to reference these new targets/props files. The exact mechanism for this is TBD, but here are some ideas:
The UWP Project Templates should be augmented to include come with the following lines
<Project ...>
<Import Project="$(MSBuildProjectDirectory)\$(MSBuildProjectName).nuget.props" Condition="Exists("$(MSBuildProjectDirectory)\$(MSBuildProjectName).nuget.props")" />
<!-- ... -->
<Import Project="$(MSBuildProjectDirectory)\$(MSBuildProjectName).nuget.targets" Condition="Exists("$(MSBuildProjectDirectory)\$(MSBuildProjectName).nuget.targets")" />
</Project>
This would mean that only UWP applications would get this functionality, but at RTM, they are the only MSBuild project type we intend to have support project.json restore fully. Other projects types could easily add support for this by adding the necessary imports manually, which we can (and should) document. Post-RTM we could provide a gesture for "upgrading" a project to use this model, as part of an upgrade process from packages.config to project.json. This proposal minimizes the amount of risky work we have to do for RTM.
This model actually implements re-targeting in a much better way than we currently support. Now, once the target is changed in project.json, the developer need only re-restore and the new targets will be referenced.
Multi-targeting is not a supported scenario for MSBuild at this time. If a project.json defines more than one target framework, and MSBuild targets are found in a package, a Warning is emitted to the [projectname].nuget.targets file which notifies the user that this is not a supported scenario. In future releases, we can look at making groups of imports conditional on Target Framework to allow us to restore the full set correctly.
In this model, we are no longer able to produce an error if a props/targets file was expected to be present (because of a NuGet reference) but is not currently present (because 'restore' has not been run). However, many other issues will likely occur in this case, and MSBuild will likely complain because the lock file will be missing, so this does not seem like a major concern.
The project.targets/props file contains a list of Import elements which import the individual targets/props files. The paths to these targets/props are generated by the restore command. At the top of the file, a PropertyGroup is added which defines the Packages Root path by reading from the generally-supported sources. Alternatively, or additionally, the nuget restore
command could just place the exact absolute path in the file, since it knows where the packages were downloaded. However, this would make the file non-portable. This may be OK though, since we don't expect users to check this file in (it is a "build artifact" generated to prepare the project for build only).
A sample import file is as follows:
<Project ...>
<!-- Allow the user to override this value
<PropertyGroup Condition="'$(NuGetPackagesRoot)' == ''">
<NuGetPackagesRoot Condition="'$(NUGET_PACKAGES)' != ''">$(NUGET_PACKAGES)</NuGetPackagesRoot>
<!-- Should there be an MSBuild task to read from global.json?? -->
<NuGetPackagesRoot Condition="'$(NuGetPackagesRoot)' == '' And '$(USERPROFILE)' != ''">$(USERPROFILE)\.nuget\packages</NuGetPackagesRoot>
<NuGetPackagesRoot Condition="'$(NuGetPackagesRoot)' == '' And '$(HOME)' != ''">$(HOME)\.nuget\packages</NuGetPackagesRoot>
<!-- Since restore knows the absolute path that was used for dropping packages, it could also hard-code the actual path. This would make the file non-portable, but the intent is that users will not check this in anyway... -->
</PropertyGroup>
<Import Project="$(NuGetPackagesRoot)\PackageId\1.0.0\build\net40\PackageId.targets" Condition="Exists(...)" />
<!-- etc. -->
</Project>
Your *.nuget.props and *.nuget.targets files should include this boilerplate xml: