解决方案级 --output
选项不再对生成相关命令有效
在 7.0.200 SDK 中,存在一处变更:将解决方案文件与以下命令一起使用时,不再接受 --output
/-o
选项:
build
clean
pack
publish
store
test
vstest
这是因为 OutputPath
属性的语义(由 --output
/-o
选项控制)没有针对解决方案明确定义。 以这种方式生成的项目会将其输出放置在同一目录下,这是不一致的,会导致很多用户报告的问题。
在 7.0.201 SDK 中,此变更的严重性级别已降低到警告等级,并且 pack
已从受影响的命令列表中删除。
引入的版本
.NET 7.0.200 SDK,仅在 7.0.201 SDK 中降低到警告等级。
旧行为
以前,如果在使用解决方案文件时指定了 --output
/-o
,所有生成项目的输出将按未定义且不一致的顺序放置在指定目录下。
新行为
如果将 --output
/-o
选项与解决方案文件一起使用,dotnet
CLI 将出错。 从 7.0.201 SDK 开始,将改为发出警告,如果是 dotnet pack
,则不会生成警告或错误。
中断性变更的类型
此中断性变更可能需要修改才能生成脚本和持续集成管道。 因此,它会影响二进制和源兼容性。
更改原因
进行此更改是因为 OutputPath
属性(由 --output
/-o
选项控制)的语义未针对解决方案明确定义。 以这种方式生成的项目会将其输出放置在同一目录下,这是不一致的,会导致很多用户报告的问题。
使用 --output
选项生成解决方案时,所有项目的 OutputPath
属性被设置为相同的值,这意味着所有项目都会将其输出将放置在同一目录下。 根据解决方案中项目的复杂性,可能会出现不同、不一致的结果。 让我们来看看不同解决方案形状的一些示例,以及它们是如何被共享的 OutputPath
所影响的。
单个项目,单个 TargetFramework
假设有一个解决方案包含一个面向单个 TargetFramework
、net7.0
的项目。 在这种情况下,提供 --output
选项等效于在项目文件中设置 OutputPath
属性。 在生成(或其他命令,但让我们暂时将讨论范围限定为生成)期间,项目的所有输出都将放置在指定的目录下。
单个项目、多个 TargetFramework
现在假设有一个解决方案包含一个具有多个 TargetFrameworks
、net6.0
和 net7.0
的项目。 由于是多目标,项目将生成两次,一次用于一个 TargetFramework
。 对于每个“内部”生成,OutputPath
将被设置为相同的值,因此每个内部生成的输出将放在同一目录下。 这意味着,无论哪个生成最后完成,都将覆盖另一个生成的输出,并且在默认情况下运行的 MSBuild 等并行生成系统中,“最后”是不确定的。
库 => 控制台 => 测试,单个 TargetFramework
现在,假设有一个解决方案包含一个库项目、一个引用了库项目的控制台项目,以及一个引用了控制台项目的测试项目。 所有这些项目都以单个 TargetFramework
、net7.0
为目标。 在这种情况下,将首先生成库项目,然后生成控制台项目。 测试项目将最后生成,并将引用控制台项目。 对于每个生成的项目,每个生成的输出都将复制到 OutputPath
指定的目录,因此最终目录将包含所有三个项目的资产。 这适用于测试,但对于发布来说,这可能会导致测试资产被发送到生产环境。
库 => 控制台 => 测试,多个 TargetFramework
现在,采用相同的项目链,除了 net7.0
生成之外,还向其添加 net6.0
TargetFramework
生成。 发生与单项目、多目标生成相同的问题 - 将特定于 TFM 的资产复制到指定目录时出现不一致的情况。
多个应用
到目前为止,我们一直在研究具有线性依赖项关系图的方案,但许多解决方案可能包含多个相关应用程序。 这意味着多个应用可以同时生成到同一输出文件夹。 如果应用包含同名的依赖项文件,则当多个项目同时尝试写入输出路径中的该文件时,生成可能出现间歇性失败。
如果多个应用依赖于不同版本的文件,那么即使生成成功,将哪个版本的文件复制到输出路径也可能是不确定的。 当项目(可能以可传递方式)依赖于不同版本的 NuGet 包时,可能会发生这种情况。 在单个项目中,NuGet 有助于确保其依赖项(包括通过 NuGet 包和/或项目引用的任何可传递依赖项)都统一到同一版本。 由于统一是在单个项目及其依赖项目的上下文中实现的,这意味着在生成两个单独的顶级项目时,可以解析不同版本的包。 如果依赖于较高版本的项目最后才复制依赖项,应用往往会成功运行。 但是,如果最后复制较低版本,针对较高版本编译的应用将无法在运行时加载程序集。 由于复制的版本可能是不确定的,这可能会导致零星的、不可靠的内部版本,在这种情况下很难诊断出问题。
其他示例
有关此基本错误在实践中如何呈现的更多示例,请参阅 dotnet/sdk#15607 上的讨论。
建议的操作
一般建议是执行之前在没有 --output
/-o
选项的情况下采取的操作,然后在命令完成后将输出移动到所需位置。 也可以在特定项目中执行此操作,但仍应用 --output
/-o
选项,因为它具有更加明确定义的语义。
如果要准确维护现有行为,可以使用 --property
标志将 MSBuild 属性设置为所需目录。 要使用的属性因命令而异:
命令 | 属性 | 示例 |
---|---|---|
build |
OutputPath |
dotnet build --property:OutputPath=DESIRED_PATH |
clean |
OutputPath |
dotnet clean --property:OutputPath=DESIRED_PATH |
pack |
PackageOutputPath |
dotnet pack --property:PackageOutputPath=DESIRED_PATH |
publish |
PublishDir |
dotnet publish --property:PublishDir=DESIRED_PATH |
store |
OutputPath |
dotnet store --property:OutputPath=DESIRED_PATH |
test |
TestResultsDirectory |
dotnet test --property:OutputPath=DESIRED_PATH |
注意:为了获得最佳结果,应该将 DESIRED_PATH 作为绝对路径。 相对路径将以你可能意想不到的方式“锚定”(即成为绝对路径),而且可能不会在所有的命令中起相同的作用。