Roslyn代码生成器构建:如何将所有依赖项嵌入NuGet包?
问题描述
我正在构建一个Roslyn代码生成器,这类分析器项目有硬性约束:必须将所有依赖项包含在包内,不能存在外部NuGet依赖。
当前项目结构:
- Generator.csproj(代码生成器项目):依赖Syntax项目
- Syntax.csproj:依赖
System.Text.JsonNuGet包 - 测试项目:引用Generator项目作为分析器,用于运行测试
现有项目文件内容:
Generator.csproj
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> <LangVersion>10.0</LangVersion> <IsRoslynComponent>true</IsRoslynComponent> </PropertyGroup> <ItemGroup> <ProjectReference Include="..\Syntax\Sytnax.csproj" /> </ItemGroup> </Project>
Syntax.csproj
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> <LangVersion>10.0</LangVersion> </PropertyGroup> <ItemGroup> <PackageReference Include="System.Text.Json" Version="7.0.3" /> </ItemGroup> </Project>
测试项目.csproj
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6</TargetFramework> </PropertyGroup> <ItemGroup> <ProjectReference Include="..\Generator\Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> </ItemGroup> </Project>
需求
- 将Syntax.dll、
System.Text.Json.dll及其所有依赖项,都复制到Generator项目的输出目录,并包含在最终的NuGet包中 - 测试项目引用Generator时,能自动找到所有依赖项
已尝试方案的局限
为Generator对Syntax的项目引用标记PrivateAssets="all",并添加了如下目标,但仅能处理直接项目依赖,无法包含间接的NuGet包依赖:
<Target Name="CopyProjectReferencesToPackage" DependsOnTargets="BuildOnlySettings;ResolveReferences"> <ItemGroup> <BuildOutputInPackage Include="@(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference')->WithMetadataValue('PrivateAssets', 'All'))" /> </ItemGroup> </Target>
解决方案
要同时处理项目依赖和间接NuGet包依赖,需要调整项目配置和打包目标,核心是收集所有需要本地复制的依赖项并包含到包中。
步骤1:调整项目引用的私有资产设置
修改Generator.csproj中的项目引用,确保依赖项会被本地复制:
<ItemGroup> <ProjectReference Include="..\Syntax\Syntax.csproj" PrivateAssets="all" /> </ItemGroup>
PrivateAssets="all"确保该项目引用的所有依赖不会作为NuGet包的依赖项对外暴露,而是会被本地复制。
步骤2:添加目标收集所有依赖项并包含到包中
在Generator.csproj中添加如下目标,替换之前的CopyProjectReferencesToPackage:
<Target Name="IncludeAllDependenciesInPackage" DependsOnTargets="ResolveReferences"> <!-- 收集所有需要本地复制的依赖项(包括项目引用和NuGet包引用) --> <ItemGroup> <BuildOutputInPackage Include="@(ReferenceCopyLocalPaths)" /> <!-- 确保依赖项的符号文件也被包含(可选,用于调试) --> <BuildOutputInPackage Include="@(ReferenceCopyLocalPaths->Replace('.dll', '.pdb'))" Condition="Exists('%(ReferenceCopyLocalPaths.RootDir)%(ReferenceCopyLocalPaths.Directory)%(ReferenceCopyLocalPaths.Filename).pdb')" /> </ItemGroup> </Target> <!-- 将自定义目标绑定到打包流程 --> <PropertyGroup> <TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);IncludeAllDependenciesInPackage</TargetsForTfmSpecificBuildOutput> </PropertyGroup>
ReferenceCopyLocalPaths包含了所有需要本地复制的依赖项,不管是来自项目引用还是NuGet包引用,完美解决间接依赖的问题- 额外添加符号文件的包含逻辑,方便调试(可根据需求移除)
步骤3:确保类库项目复制依赖到输出目录
默认类库项目不会复制依赖到输出,需要在Generator.csproj和Syntax.csproj中添加如下配置,让它们像控制台项目一样复制所有依赖:
<PropertyGroup> <!-- 启用依赖复制到输出目录 --> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> </PropertyGroup>
该配置会让项目将所有锁定文件中的依赖项复制到输出目录,确保Generator的输出目录包含所有需要的dll。
步骤4:验证测试项目引用
测试项目的现有配置无需修改,因为它将Generator作为Analyzer引用,会自动从Generator的输出目录加载所有包含的依赖项。
效果验证
- 构建Generator项目后,检查其输出目录,应该包含:Generator.dll、Syntax.dll、System.Text.Json.dll及其所有依赖dll
- 运行
dotnet pack生成NuGet包,解压后查看lib/netstandard2.0目录,所有依赖项都应在其中 - 运行测试项目,代码生成器能正常加载所有依赖并工作
内容的提问来源于stack exchange,提问作者Paul Turner
相关产品推荐
相关产品推荐

