英文:
NuGet does not resolve transitive dependencies
问题
我正在尝试从一个项目(Sub.nupkg)构建一个NuGet包。这部分工作得相当不错。
问题从这个项目本身使用包含contentFiles
的NuGet(Base.nupkg)开始,这些文件需要被“传递”,但未复制到目标目录。
问题可以通过使用IronPython.StdLib
来复制包的方式来重现。一旦包含了这个包,当在另一个项目中使用时,contentFiles
就不再被复制。
如果我们更改Sub.nupkg中的私有资产,我们可以强制将其复制到输出目录:
<PackageReference Include="pkg" Version="1.*">
<PrivateAssets>none</PrivateAssets>
</PackageReference>
如果在Sub.nupkg中配置了这个,它将强制将contentFiles
复制到输出文件夹。
问题是,我们无法创建自动配置资产的NuGet包。
我们可以在nuspec中指定developmentDependency
,如下所示:
<developmentDependency>true</developmentDependency>
但这会导致以下用法:
<PackageReference Include="pkg" Version="1.*">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
这并不会将所有的contentFiles
复制到目标目录。
我尝试使用*.props
做一些尝试,但因为无法重新配置现有的PackageReference
,所以没有成功。
我们的依赖关系类似于:
- 使用项目
- Sub.nupkg
- Base.nupkg(包含需要复制到输出目录的
contentFiles
)
- Base.nupkg(包含需要复制到输出目录的
- Sub.nupkg
我们该如何解决这个问题?
我找到了为什么依赖库中的本地dll不包含在构建输出中?,但它没有提供解决这个问题的解决方案。而且我不明白如何使用buildTransitive
来满足我们的需求。
顺便说一下:我们只使用包引用。
用IronPython.StdLib
来重现这个问题的项目:
消费者(使用引用IronPython的包):
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
</PropertyGroup>
<ItemGroup>
<PackageReference Include="IronPythonConsumer" Version="1.0.0" />
</ItemGroup>
</Project>
使用IronPython的项目:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net70</TargetFramework>
<Authors />
<Product>IronPythonConsumer</Product>
<AssemblyVersion>1.0.0</AssemblyVersion>
<FileVersion>1.0.0</FileVersion>
<Version>1.0.0</Version>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<OutputPath>$(SolutionDir)bin$(Configuration)</OutputPath>
</PropertyGroup>
<PropertyGroup>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="IronPython.StdLib" Version="2.7.12" />
</ItemGroup>
</Project>
如果你将IronPython的PackageReference更改为以下方式:
<PackageReference Include="IronPython.StdLib" Version="2.7.12">
<PrivateAssets>none</PrivateAssets>
</PackageReference>
消费者不需要更改。但之后所有的contentFiles
都会被复制到输出文件夹。
重要的是不能每次使用这些资产时都调整资产。这会导致频繁出现错误,因为编译运行没有错误。问题只在运行时出现。
英文:
I am trying to build a NuGet package from a project (Sub.nupkg). This works quite well.
The problems start as soon as this project itself uses a NuGet (Base.nupkg) which contains contentFiles
. These are needed "transitive", but are not copied to the target directory.
The problem can be reproduced with IronPython.StdLib
. Once this package is included, the contentFiles
are no longer copied when used in another project.
If we change the private assets in Sub.nupkg we're able to force the copy to output:
<PackageReference Include="pkg" Version="1.*">
<PrivateAssets>none</PrivateAssets>
</PackageReference>
If this is configured in Sub.nupkg it will foce a copy of the contentFiles
to the output folder.
The problem is that we're not able to create a NuGet package that configures that assets automatically.
We can specify developmentDependency
in nuspec like:
<developmentDependency>true</developmentDependency>
But this leads to this usage:
<PackageReference Include="pkg" Version="1.*">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
And this does not copy all the contentFiles
to the target directory.
I tried something using *.props
but didn't work because I'm not able to reconfigure an existing PackageReference
.
Our dependencies are similar to:
- Using-Project
- Sub.nupkg
- Base.nupkg (containing
contentFiles
needed to be copied to the output folder)
- Base.nupkg (containing
- Sub.nupkg
How do we solve that problem?
I found Why are native dll's from dependent libraries not included in build output? but it does not contain any solution to that problem. And I don't understand how to use buildTransitive
to achieve our needs.
Btw: We're only using package references.
Projects for reproducing that issue using IronPython.StdLib
:
Consumer (that uses a packages that references IronPython):
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
</PropertyGroup>
<ItemGroup>
<PackageReference Include="IronPythonConsumer" Version="1.0.0" />
</ItemGroup>
</Project>
Project that consumes IronPython:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net70</TargetFramework>
<Authors />
<Product>IronPythonConsumer</Product>
<AssemblyVersion>1.0.0</AssemblyVersion>
<FileVersion>1.0.0</FileVersion>
<Version>1.0.0</Version>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<OutputPath>$(SolutionDir)bin$(Configuration)</OutputPath>
</PropertyGroup>
<PropertyGroup>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="IronPython.StdLib" Version="2.7.12" />
</ItemGroup>
</Project>
You can make that work if you change the PackageReference of IronPython to:
<PackageReference Include="IronPython.StdLib" Version="2.7.12">
<PrivateAssets>none</PrivateAssets>
</PackageReference>
The Consumer does not need to be changed. But after that all the contentFiles
are copied to the output folder.
It is not an option to adjust the assets every time you use them. This leads too frequently to errors, because the compile runs without errors. The problems occur only at runtime.
答案1
得分: 0
我可以翻译以下部分:
"I was able to solve the problem by analyzing the package Stub.System.Data.SQLite.Core.NetFramework. This package manages that the ContentFiles transitively end up in the target directory. This package manages that the contentFiles
will transitively be placed in the target directory."
"通过分析包Stub.System.Data.SQLite.Core.NetFramework,我成功解决了这个问题。这个包确保ContentFiles以传递方式最终进入目标目录。这个包确保contentFiles
以传递方式放置在目标目录中。"
"这是通过在目标文件中指定必要的复制操作来实现的。这个文件由MSBuild包含,并扩展了现有的csproj
文件。"
"完整的targets
文件可能如下所示:"
"这是targets
文件的一部分,用于复制文件。在此之前,会对是否可以复制进行自我解释的测试。此Target
在后期构建事件中执行。"
"这部分收集要复制的文件。这是由Visual Studio触发的。"
"此条目注册了后期构建事件,并触发了复制文件的过程。"
"当打开项目时,将确定要复制的文件。"
"这个targets
文件确保文件被复制到目标目录。但要触发这一过程,文件的基本名称必须与NuGet id匹配,并且它必须位于build
文件夹中。"
"为了使整个过程能够以传递方式工作,必须将此文件放置在buildTransitive
目录中。"
"在我的情况下,适当的nuspec
文件如下所示:"
"使用这个nuspec
和targets
文件,contentFiles
文件夹中的文件将以传递方式复制到目标目录。"
"也许有一个更简单的解决方案,但不幸的是我没有找到。"
"在包Stub.System.Data.SQLite.Core.NetFramework中定义了清理操作。这确保在clean期间从目标目录中删除文件。"
"但是,我们使用了多目标 (net472; net7.0),它们由Visual Studio并行构建。这导致在clean期间偶尔出现错误,因为文件正在被另一个进程使用。我们决定不允许clean,并且文件保留在目标目录中。"
英文:
I was able to solve the problem by analyzing the package Stub.System.Data.SQLite.Core.NetFramework. This package manages that the ContentFiles transitively end up in the target directory. This package manages that the contentFiles
will transitively be placed in the target directory.
This is achieved by specifying the necessary copy operations in a targets file. This file is included by MSBuild and extends the existing csproj
.
The full targets
file might look like this:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Condition="'$(MSBuildThisFileFullPath)' != '' And
Exists('$(MSBuildThisFileFullPath).user')"
Project="$(MSBuildThisFileFullPath).user" />
<ItemGroup>
<CustomInteropFiles Condition="'$(MSBuildThisFileDirectory)' != '' And
HasTrailingSlash('$(MSBuildThisFileDirectory)')"
Include="$(MSBuildThisFileDirectory)..\..\contentFiles\any\any\**" />
</ItemGroup>
<ItemGroup Condition="'$(ContentCustomInteropFiles)' != '' And
'$(ContentCustomInteropFiles)' != 'false' And
'@(CustomInteropFiles)' != ''">
<Content Include="@(CustomInteropFiles)">
<Link>%(RecursiveDir)%(FileName)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<Target Name="CopyCustomInteropFiles"
Condition="'$(CopyCustomInteropFiles)' != 'false' And
'$(OutDir)' != '' And
HasTrailingSlash('$(OutDir)') And
Exists('$(OutDir)')"
Inputs="@(CustomInteropFiles)"
Outputs="@(CustomInteropFiles -> '$(OutDir)%(RecursiveDir)%(Filename)%(Extension)')">
<Copy SourceFiles="@(CustomInteropFiles)"
DestinationFiles="@(CustomInteropFiles -> '$(OutDir)%(RecursiveDir)%(Filename)%(Extension)')" />
</Target>
<Target Name="CollectCustomInteropFiles"
Condition="'$(CollectCustomInteropFiles)' != 'false'">
<ItemGroup>
<FilesForPackagingFromProject Include="@(CustomInteropFiles)">
<DestinationRelativePath>bin\%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
</FilesForPackagingFromProject>
</ItemGroup>
</Target>
<PropertyGroup>
<PostBuildEventDependsOn>
$(PostBuildEventDependsOn);
CopyCustomInteropFiles;
</PostBuildEventDependsOn>
<BuildDependsOn>
$(BuildDependsOn);
CopyCustomInteropFiles;
</BuildDependsOn>
</PropertyGroup>
<PropertyGroup Condition="'$(VisualStudioVersion)' == '' Or
'$(VisualStudioVersion)' == '10.0' Or
'$(VisualStudioVersion)' == '11.0' Or
'$(VisualStudioVersion)' == '12.0' Or
'$(VisualStudioVersion)' == '14.0' Or
'$(VisualStudioVersion)' == '15.0' Or
'$(VisualStudioVersion)' == '16.0' Or
'$(VisualStudioVersion)' == '17.0'">
<PipelineCollectFilesPhaseDependsOn>
CollectCustomInteropFiles;
$(PipelineCollectFilesPhaseDependsOn);
</PipelineCollectFilesPhaseDependsOn>
</PropertyGroup>
</Project>
Explanation
<Import Condition="'$(MSBuildThisFileFullPath)' != '' And
Exists('$(MSBuildThisFileFullPath).user')"
Project="$(MSBuildThisFileFullPath).user" />
If the per-user settings file exists, import it now. The contained settings, if any, will override the default ones provided below.
<ItemGroup>
<CustomInteropFiles Condition="'$(MSBuildThisFileDirectory)' != '' And
HasTrailingSlash('$(MSBuildThisFileDirectory)')"
Include="$(MSBuildThisFileDirectory)..\..\contentFiles\any\any\**" />
</ItemGroup>
This determines the files to be copied and stores them on the variable CustomInteropFiles
. It is important to know that all NuGet packages are unpacked in the local cache. This directory structure can be accessed. The variable MSBuildThisFileDirectory
contains the directory in the local NuGet cache where this targets
file is located.
In my example this targets
file is located in the build
folder of the NuGet package and further in the target platform folder. For this reason we need to go two directories up to access the contentFiles
. In my case all files should be copied to the destination directory.
<ItemGroup Condition="'$(ContentCustomInteropFiles)' != '' And
'$(ContentCustomInteropFiles)' != 'false' And
'@(CustomInteropFiles)' != ''">
<Content Include="@(CustomInteropFiles)">
<Link>%(RecursiveDir)%(FileName)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
Here it is checked if per-user settings prevent copying and if there are files to copy. Then these files are linked and marked for copying.
<Target Name="CopyCustomInteropFiles"
Condition="'$(CopyCustomInteropFiles)' != 'false' And
'$(OutDir)' != '' And
HasTrailingSlash('$(OutDir)') And
Exists('$(OutDir)')"
Inputs="@(CustomInteropFiles)"
Outputs="@(CustomInteropFiles -> '$(OutDir)%(RecursiveDir)%(Filename)%(Extension)')">
<Copy SourceFiles="@(CustomInteropFiles)"
DestinationFiles="@(CustomInteropFiles -> '$(OutDir)%(RecursiveDir)%(Filename)%(Extension)')" />
</Target>
This is the Target
in the csproj
file, which is used to copy the files. Before that, self-explanatory tests are made whether copying is possible or not. This Target
is executed in the post-build event.
<Target Name="CollectCustomInteropFiles"
Condition="'$(CollectCustomInteropFiles)' != 'false'">
<ItemGroup>
<FilesForPackagingFromProject Include="@(CustomInteropFiles)">
<DestinationRelativePath>bin\%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
</FilesForPackagingFromProject>
</ItemGroup>
</Target>
This part collects the files to be copied. This is triggered by Visual Studio.
<PropertyGroup>
<PostBuildEventDependsOn>
$(PostBuildEventDependsOn);
CopyCustomInteropFiles;
</PostBuildEventDependsOn>
<BuildDependsOn>
$(BuildDependsOn);
CopyCustomInteropFiles;
</BuildDependsOn>
</PropertyGroup>
This entry registers the post-build event and triggers the process of copying the files.
<PropertyGroup Condition="'$(VisualStudioVersion)' == '' Or
'$(VisualStudioVersion)' == '10.0' Or
'$(VisualStudioVersion)' == '11.0' Or
'$(VisualStudioVersion)' == '12.0' Or
'$(VisualStudioVersion)' == '14.0' Or
'$(VisualStudioVersion)' == '15.0' Or
'$(VisualStudioVersion)' == '16.0' Or
'$(VisualStudioVersion)' == '17.0'">
<PipelineCollectFilesPhaseDependsOn>
CollectCustomInteropFiles;
$(PipelineCollectFilesPhaseDependsOn);
</PipelineCollectFilesPhaseDependsOn>
</PropertyGroup>
When the project is opened, the files to be copied are determined.
This targets
file ensures that the files are copied to the target directory. But to trigger this, the base name of the file must match the NuGet id and it must be located in the build
folder.
In order to make the whole thing work transitively, this file has to be placed in the buildTransitive
directory.
In my case the appropriate nuspec
file looks something like this:
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>*****</id>
<version>*****</version>
<authors*****</authors>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="file">LICENSE.txt</license>
<projectUrl>*****</projectUrl>
<description>*****</description>
<copyright>*****</copyright>
<dependencies>
<group targetFramework=".NETFramework4.7.2" />
<group targetFramework="net7.0" />
</dependencies>
<contentFiles>
<files include="**" buildAction="None" copyToOutput="false" flatten="false" />
</contentFiles>
</metadata>
<files>
<file src=".doc\**" target=".doc" />
<file src="contentFiles\**" target="contentFiles" />
<file src="build\**" target="build\net472" />
<file src="build\**" target="build\net7.0" />
<file src="build\**" target="buildTransitive\net472" />
<file src="build\**" target="buildTransitive\net7.0" />
<file src="..\_._" target="lib\net472" />
<file src="..\_._" target="lib\net7.0" />
<file src="licenses\LICENSE.txt" target="" />
</files>
</package>
With this nuspec
and targets
file the files from the contentFiles
folder are copied transitively to the target directory.
It might be possible that there is a simpler solution for this, but unfortunately I have not found it.
In the package Stub.System.Data.SQLite.Core.NetFramework a cleanup is defined. This ensures that the files are deleted from the target directory during a clean.
However, we use multi targeting (net472; net7.0), which are built in parallel by Visual Studio. This causes a sporadic error during a clean because the files are in use by another process. We have decided not to allow a clean and the files remain in the target directory.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论