Runtime: 支持单文件分发

创建于 2018-10-05  ·  225评论  ·  资料来源: dotnet/runtime

此问题跟踪 .NET Core 3.0 单文件分发功能的进度。
这是该功能的设计文档暂存计划

area-Single-File

最有用的评论

预计首次启动会慢得多 - 它将应用程序提取到磁盘上 - 大量的 IO。

这不应该发生,你让用户体验糟糕的设计,糟糕的选择,这就是你让用户讨厌开发人员为他们使用的技术的方式

正如@Safirion所提到的,我们正在对单文件进行下一个改进,它应该直接从 .exe 运行大部分托管代码(不提取到磁盘)。 虽然还不能保证发布火车。

如果它很快就会改变,为什么现在正式发布呢? 应标记为预览/实验

在我看来,这是浪费时间和资源,专注于 AOT 编译和 tree-shaking,把你所有的资源放在那里,停止黑客攻击

所有225条评论

出于兴趣,此计划与 CoreRT 相比如何? 他们看起来像类似的努力?

它是否与“可能是本机用户代码”有关,即它仍然允许代码被 JIT 编译,而不仅仅是AOT?

另外,我假设运行时组件('本机代码(框架的运行时、主机、本机部分.. ')将是来自 CoreCLR 存储库的组件?

您提出了很好的问题,但是由于这仍处于设计初期,因此我还没有很好的答案。

出于兴趣,此计划与 CoreRT 相比如何? 他们看起来像类似的努力?

可能会有一些相似的结果(单个文件),但设计可能具有不同的性能特征或起作用/不起作用的特性。 例如,一种可能的设计是本质上将 .NET Core 自包含应用程序中的所有文件连接到一个文件中。 那是 10s 的 MB 并且可能开始更慢,但另一方面,它将允许 CoreCLR 的全部功能,包括加载插件、反射发射和高级诊断。 CoreRT 可以被认为是光谱的另一端——它是个位数的 MB 并且具有非常快的启动时间,但是由于没有 JIT,它无法加载插件或使用反射发射,并且构建时间比大多数 . NET 开发人员已经习惯了。 它目前还有一些其他限制,随着时间的推移可能会变得更好,但 .NET Core 3.0 可能不会更好(可能需要注释进行反射,缺少一些互操作场景,Linux 上的诊断有限)。 两者之间也有一些想法。 如果人们有他们想做出/避免的权衡,我们会很想知道他们。

它是否与“可能是本机用户代码”有关,即它仍然允许代码被 JIT 编译,而不仅仅是 AOT?

“本机用户代码”是指您的应用程序可能有一些 C++ 本机代码(由您或第三方组件编写)。 我们可以使用该代码执行的操作可能会受到限制——如果将其编译为 .dll,则运行它的唯一方法是从磁盘上运行; 如果它是 .lib,则可以将其链接到其中,但这会带来其他复杂性。

另外,我假设运行时组件('本机代码(框架的运行时、主机、本机部分..')将来自 CoreCLR 存储库?

基于以上所有内容,我们将确定涉及哪些 repos。 “框架的原生部分”将包括 CoreFX原生文件,如 ClrCompression 和 Unix PAL。

以这种方式分发单个文件,即使启动时间稍慢,对于易于部署也是非常宝贵的。 我宁愿拥有拥有全部权力的能力,也不愿被迫放弃其中的一部分。

我们感兴趣的一些场景。 这在跨平台方面将如何工作?
我假设我们每个平台都会有一个单独的“文件”?

关于原生代码,我如何能够根据平台选择不同的原生组件?

我们感兴趣的一些场景。 这在跨平台方面将如何工作?
我假设我们每个平台都会有一个单独的“文件”?
关于原生代码,我如何能够根据平台选择不同的原生组件?

@ayende ,我引用@morganbr评论

一种可能的设计是本质上将 .NET Core 自包含应用程序中的所有文件连接到一个文件中。

当前自包含应用程序的跨平台故事是为每个您想要定位的平台创建一个部署包,因为您将应用程序与运行时一起交付,这是特定于平台的。

@morganbr感谢您花时间提供如此详细的答案

我很想看看设计的去向,这是一个非常有趣的举措

对于想使用单文件的人,我有几个问题。 您的回答将帮助我们缩小选择范围:

  1. 您可能会使用哪种应用程序? (例如,Windows 上的 WPF?Linux Docker 容器中的 ASP.NET?还有别的吗?)
  2. 您的应用程序是否包含(非 .NET)C++/本机代码?
  3. 您的应用程序会加载最初未包含在应用程序构建中的插件或其他外部 dll 吗?
  4. 您是否愿意重建和重新分发您的应用程序以包含安全修复程序?
  5. 如果您的应用程序启动速度慢 200-500 毫秒,您会使用它吗? 5秒呢?
  6. 您认为您的应用可接受的最大尺寸是多少? 5MB? 10? 20? 50? 75? 100?
  7. 你会接受更长的发布构建时间来优化大小和/或启动时间吗? 你能接受的最长的时间是多少? 15秒? 30秒? 1分钟? 5分钟?
  8. 如果它可以将你的应用程序的大小减半,你愿意做额外的工作吗?
  1. 所有平台上的控制台/UI 应用程序。
  2. 也许作为第三方组件。
  3. 可能是的。
  4. 是的,特别是如果有一个类似 ClickOnce 的简单系统。
  5. 一些初始减速是可以容忍的。 第3点可以帮助吗?
  6. 取决于资产。 你好世界的大小应该在 MB 左右。
  7. 如果只是生产也没关系。
  8. 喜欢将反射的东西列入白名单? 是的。

@morganbr ,您认为向更广泛的受众提出这些问题是否更好? 即,比知道这个 GitHub 问题的人更广泛?

例如,一种可能的设计是本质上将 .NET Core 自包含应用程序中的所有文件连接到一个文件中。

看着压缩它; 还是在文件中使用压缩文件系统?

@tpetrina ,谢谢! 第 3 点涵盖了几个设计角度:

  1. Tree Shaker 不能很好地加载 Tree Shaker 没有看到的插件,因为它可以消除插件所依赖的代码。
  2. CoreRT 目前没有加载插件的方法
    第 5 点更多是关于我们是否会针对大小或启动时间(以及多少)进行优化
    第8点,是的,我主要是在考虑反射的东西

@TheBlueSky ,我们也联系了其他人,但它有助于从 GitHub 社区的热情人士那里获得意见。

@benaadams ,压缩是在桌面上,但我目前认为它与整体设计正交。 轻量级实验表明,以几秒钟的启动时间(和构建时间)为代价,压缩可能会减少大约 50% 的大小。 对我来说,这是一个足够激进的权衡,如果我们这样做,它应该是可选的。

@morganbr使用压缩时几秒钟的启动时间? 考虑到UPX声称的解压速度为

在古老的 Pentium 133 上约为 10 MB/秒,在 Athlon XP 2000+ 上约为 200 MB/秒。

@morganbr ,对我来说答案是:

1)服务(基本上是运行 Kestrel 的控制台应用程序)。 作为 Windows 服务/Linux 守护程序或在 docker 中运行。
2) 是的
3) 是的,通常使用AssemblyContext.LoadFrom托管程序集。 这些由最终用户提供。
4) 是的,这是意料之中的。 事实上,无论如何我们已经捆绑了整个框架,所以从这个角度来看没有改变。
5) 作为一项服务,我们不太关心启动时间。 5秒是合理的。
6) 75MB 可能是极限。 很大程度上取决于实际的压缩大小,因为所有包都是压缩交付的。
7) 对于发布版本,更长(甚至更长)的构建时间是可以接受的。
8) 是的,绝对的。 大小并不重要,但越小越好。

我没有看到提到并且非常重要的是它的可调试性。
我希望这不会破坏堆栈跟踪,我们希望能够包含pdb文件或某种调试符号。

关于压缩,考虑到几乎在所有情况下,实际的传递机制已经被压缩了。
例如,nuget 包。
用户也非常精通解压缩,所以这不是什么大问题。
我认为您可以在侧面进行压缩。

谢谢,@ayende! 你说得对,我应该提到可调试性。 我认为只有少数几种可能会影响调试的小方法:

  1. 可能无法在单个文件上使用“编辑并继续”(由于需要一种方法来重建和重新加载原始程序集)
  2. 单文件构建可能会生成 PDB 或调试所需的其他文件,而不是程序集附带的文件。
  3. 如果使用 CoreRT,它可能会随着时间的推移而填充一些调试功能(尤其是在 Linux/Mac 上)。

当您说“包含 pdb 文件”时,您是想要那些 _inside_ 单个文件还是仅仅能够生成它们并挂在它们上以防您需要调试单个文件构建?

1) 对我们来说不是问题。 E&C 在这里不相关,因为这可能仅用于实际部署,而不是日常部署。
2) 理想情况下,我们对所有内容都有一个文件,包括 PDB,而不是一个文件和一组 pdb。 已经有嵌入式 PDB 选项,如果可以的话,那就太好了。
3)在谈论调试时,我更多地谈论的是生产时间,而不是实时附加调试器。 更具体地说,堆栈跟踪信息包括文件和行号,读取转储时能够解析符号等。

  1. 主要是服务,但有一些 UI
  2. 有些人会这样做,但这并不紧急
  3. 是的
  4. 是的
  5. 几秒就好了
  6. 对我们来说无所谓。 dll大小的总和很好
  7. 理想情况下不会
  8. 尺寸对我们来说不是最重要的

对我们来说,另一个问题是您是否也可以为单个组件执行此操作(甚至可能是分阶段的)? 例如,我们有使用大量依赖项的库 dll。 如果我们可以打包它们,它将节省很多版本管理等的痛苦。如果这些反过来可以打包成一个更好的exe文件?

  1. 服务和一些用户界面。
  2. 现在不行。
  3. 是的。 理想情况下,可以从文件夹加载并在运行时重新加载的插件。
  4. 是的
  5. 只要我们不推动10-15+,这不是问题。
  6. DLL 大小的总和,或类似的。
  7. 是的。 只要调试/测试构建相当快,生产构建时间就不是真正的问题。
  8. 取决于,但该选项会很方便。
  1. 服务和用户界面。
  2. 有时。
  3. 是的,通常。
  4. 是的。
  5. 最好小于 5 秒。
  6. UI 不到 5 秒,Service 无所谓。
  7. 构建时间不重要,优化效果最重要。
  8. 是的。

@tpetrina @ayende @bencyoung @Kosyne @expcat你对问题 3 的回答是肯定的(“你的应用程序会加载插件或其他你最初没有包含在你的应用程序构建中的外部 dll 吗?”) - 你能告诉我们更多关于你的使用案子?

单个文件分发的主要卖点是只有一个文件可以分发。 如果您的应用程序在单独的文件中包含插件,那么您将从包含多个文件的单个文件分发中获得什么价值? 为什么“app.exe+plugin1.dll+plugin2.dll”优于“app.exe+coreclr.dll+clrjit.dll+...+plugin1.dll+plugin2.dll”?

app.exe + 300+ dll - 这是今天的当前状态真的很尴尬。
app.exe + 1-5个通常由用户自己定义的dll更容易。

我们的场景是我们允许用户进行某些扩展,所以我们通常只部署一个exe ,并且用户可以根据需要添加额外的功能。

我们_计划_这样做并没有太多,但如果需要,我们希望_能够_这样做。

@ayende同意,我们也一样。

此外,如果我们可以在 dll 级别这样做,那么我们可以将依赖项打包到我们的程序集中,这样它们就不会与客户端程序集冲突。 即,通过选择 NewtonSoft.Json 的版本,您当前正在为同一文件夹中的所有程序、插件和第三方程序集定义它,但如果您可以嵌入它,那么第三方将具有灵活性并提高版本兼容性

同意@ayende

谢谢大家的解答! 根据将使用本机代码或需要加载插件的人数,我们认为我们可以管理的最兼容的方法是正确的起点。 为此,我们将采用“打包和提取”方法。

这将是一种将所有应用程序和 .NET 文件作为资源嵌入到提取器可执行文件中的工具。 当可执行文件运行时,它会将所有这些文件提取到一个临时目录中,然后就像应用程序作为非单文件应用程序发布一样运行。 它不会从压缩开始,但如果有必要,我们可能会在未来添加它。

该计划最棘手的细节是提取文件的位置。 我们需要考虑几种情况:

  • 首次启动——应用程序只需要解压到磁盘上的某个位置
  • 后续启动——为了避免在每次启动时支付提取成本(可能是几秒钟),最好让提取位置具有确定性,并允许第二次启动使用第一次启动提取的文件。
  • 升级——如果启动了新版本的应用程序,它不应该使用旧版本提取的文件。 (反之亦然;人们可能希望并排运行多个版本)。 这表明确定性路径应该基于应用程序的内容。
  • 卸载——如果需要,用户应该能够找到提取的目录并删除它们。
  • 容错——如果第一次启动在部分提取其内容后失败,第二次启动应该重做提取
  • 运行提升 - 以管理员身份运行的进程应仅从管理员可写位置运行,以防止低完整性进程篡改它们。
  • 运行非提升 - 没有管理员权限运行的进程应该从用户可写的位置运行

我认为我们可以通过构建包含以下内容的路径来解释所有这些:

  1. 众所周知的基本目录(例如 Windows 上的 %LOCALAPPDATA%\dotnetApps 和其他操作系统上的用户用户配置文件位置)
  2. 提升的单独子目录
  3. 应用程序标识(可能只是 exe 名称)
  4. 版本标识符。 数字版本可能有用,但还不够,因为它还需要包含精确的依赖版本。 每个构建的 guid 或哈希可能是合适的。

这可能看起来像 c:\users\username\AppData\Local\dotnetApps\elevated\MyCoolApp\1.0.0.0_abc123\MyCoolApp.dll
(应用程序名为 MyCoolApp,其版本号为 1.0.0.0,其 hash/guid 为 abc123,并已提升启动)。

将文件嵌入到提取器中也需要做一些工作。 在 Windows 上,我们可以简单地使用原生资源,但 Linux 和 Mac 可能需要自定义工作。

最后,这可能还需要在主机(以查找提取的文件)和诊断(以查找 DAC 或其他文件)中进行调整。

抄送@swaroop-sridhar @jeffschwMSFT @vitek-karas

我觉得这种治疗比疾病更糟糕。 如果我们必须处理外部目录(不同的操作系统)、更新、卸载等,这违背了我首先想要这个功能的理由(保持一切简单、便携、独立和干净)。

如果绝对必须这样,对于我的项目,我更喜欢单个主可执行文件和解压缩文件与该可执行文件一起存在于一个目录中,或者可能决定该目录的位置。

这只是我自己,我也很想听听别人的意见。

我必须在这里同意,使用不同的目录可能会带来许多令人兴奋的问题——例如,您将配置文件放在 exe 旁边,而这个 exe 不会被拾取,因为“真正的”目录在其他地方。
由于访问策略等原因,磁盘空间可能是一个问题,随机文件锁定也是一个问题。
我想使用此功能,但如果它添加了许多以前无法检测到的故障模式,则不会。

同意@Kosyne - 建议的初始解决方案似乎只是自动化了各种“安装程序”。 如果这是我们试图通过单个执行官解决的问题的限制,那么我认为我们都可以自己简单地执行自动化。

单一 exec 提案的主要目标应该是能够在非托管系统上运行可执行文件。 谁知道它是否对任何选定的目标“安装”目录具有写入权限? 它当然也不应该在启动后留下自己的人工制品(默认情况下不是)。

作为对现有提案的一个小修改以满足上述要求:我们不能解压到内存中并从那里运行吗?

同意其余的评论。 解压缩到另一个位置是_已经_可用的。
我们可以有一个自解压 zip,它可以很容易地运行提取的值。 这并没有回答很多本应回答的问题,只是安装的另一个名称。

文件的位置很重要。 例如,在我们的例子中,这意味着:

  • 查找配置文件(如果没有,我们会即时生成并让用户自定义)
  • 查找/创建数据文件,通常与源 exe 相关。
  • 进程的 PID / 名称应该匹配,以确保正确的监控 / 支持。

我们的一个用户需要从 DVD 上运行我们的软件,它是如何工作的,在一个实际上可能没有_有_一个 HD 可以运行的系统上。

我同意在内存中做所有事情会更好。 而且对启动时间的担忧并不是那么大,我可以为每次重新启动支付这笔费用,或者在需要时手动执行一个步骤来缓解这种情况。

这里的另一个问题是实际尺寸。 如果这只是(有效地)一个安装程序,那意味着我们正在讨论一个合理的应用程序的文件大小在 100 MB,不是吗?

似乎构建建议的解决方案不需要(如果有的话)CLR 更改。 用户已经可以构建这样的解决方案。 将它添加到 CoreCLR 是没有意义的。 特别是,因为这个用例相当狭窄和具体。

@GSPP这似乎基本上是我今天可以用7z-Extra做的事情,我同意如果是这种情况,最好_不_拥有它。

抱歉,我参加这个聚会迟到了,我是在按照我正在跟踪的重复票中发布的链接后到达这里的。
在阅读了这里的最新评论后,很遗憾看到您正在考虑打包和提取。 这似乎有点过头了,为什么不从为基本控制台应用程序部署 SFA 的能力开始呢? 在我看来,应该可以创建一个基本的控制台应用程序,其中包含一些网络、IO 和一些位于单个文件中的外部依赖项(nuget 等)。
我想我想说的是,与其收集每个人的需求,不如收集没有人的需求,而是从对每个人都有意义并迅速产生结果的第一次迭代开始。

这似乎基本上是我今天可以用 7z-Extra 做的事情

你是对的,今天存在许多解决这个问题的编程环境不可知的解决方案。 另一个例子: https ://www.boxedapp.com/exe_bundle.html

这里的附加价值将是集成到 dotnet 工具中,这样即使非专业用户也可以轻松完成。 正如您所指出的,专家用户今天可以通过将现有工具拼接在一起来做到这一点。

就个人而言,我同意你的观点,目前尚不清楚我们在这里做出了正确的选择。 我们在核心团队内部对此进行了很多讨论。

安排一个实习生并推出一个全局工具(推荐,但不支持)也可以,而且安装起来也相当容易。

实际上,我们正在谈论dotnet publish-single-file ,这将完成幕后所需的一切。

我没有看到框架或运行时实际需要任何东西来支持这种情况,并且通过使这个东西明确地在框架之外,你将允许用户在如何修改它方面有更多的自由。
如果您想对框架进行更改,则无需获得 PR(包括所有相关的仪式、向后契约、安全性等)。
你只需派生一个共同的副业项目并使用它。

请注意,尽管我很喜欢这个功能,但我宁愿没有可以从外部完成的东西(这意味着它总是_总是会在里面)。

我想问一个更高层次的问题:客户想要单文件分发的主要动机是什么?
主要是:

  1. 包装? 如果是这样,无论解决方案是收件箱还是第三方工具,什么特性最重要?
    a) 启动时间(超过第一次运行)
    b) 能够在不可写环境中运行
    c) 运行后不留下文件
    d) 不必运行安装程序
    2)性能?
    a) 速度(本地代码的静态链接、避免多个库加载、跨模块优化等)
    b) 代码大小(只需要一张证书,摇树等)
    3)还有其他人吗?

我有点担心这个特性被一些人认为不那么重要,或者至少可能不是一个好的目标,将其包含在这里的核心特性集中。 我想重申一下,我认为对于任何充当大型应用程序的服务或子模块的应用程序来说,它都会非常强大。 你,作者,甚至可能不是开发者。 我不想传递只能通过安装(自动或其他方式)或磁盘上可能需要用户进行额外安全提升的执行后伪影来解决的依赖要求,等等。
目前,.NET 并不是解决这个利基问题的好选择。 但它可能是(并且应该是 IMO)。

编译后的可执行文件必须:

  • 打包所有相关依赖项
  • 没有执行伪影(无磁盘写入)

回复@swaroop-sridhar 我敢说每个应用程序都会有自己独特的性能需求顺序,所以我想在解决核心解决方案之后最好的方法是挑选低垂的果实并从那里开始。

@swaroop-sridhar 对我来说,这是关于最终用户的包装和易用性。
无需处理安装系统范围的东西,只需单击并运行。

这很重要,因为我们允许嵌入我们的软件,并且单个文件插件更易于管理。

@strich关于嵌入的观点很好。 我们通常被用作微服务架构中的组件,减少部署开销将使这更容易。

问题不在于它是否是一个重要特性。 问题是无论提议的解决方案(此时基本上是压缩东西)都需要_in the core_。
核心中的内容具有更高的更改标准。 将其作为外部工具会更好,因为它更易于修改和扩展。

老实说,我宁愿看到一个更好的选择。 例如,可以从内存而不是文件加载 dll(需要一些工作,但可能)。
如果发生这种情况,您可以完全从内存中运行 _entire_ 东西,对纯内存进行解包,并且没有磁盘命中。

这是应该进入核心的东西,因为它很可能需要修改运行时才能启用它。
这本身就很有价值。 而不是我们目前可以做的事情。

所以一个很好的例子就是看看像 Hashicorp 的 Consul 这样的 Go 工具的经验。 一个 exe 文件,您可以将其放到任何机器上运行。 没有安装程序,没有复制文件夹,没有在数百个文件列表中寻找配置文件,只是一个非常好的最终用户体验。

对我们来说,我不确定内存中的方法是否可行,因为我们也希望它也适用于插件 dll(因此删除插件也将是一个文件,而不是它的所有依赖项),但任何进展都会做个好人。 我们已经查看了 Fody.Costura,它适用于某些东西,但我们遇到了 .NET Core 的问题。

所以一个很好的例子就是看看像 Hashicorp 的 Consul 这样的 Go 工具的经验

嗯,对于像 consul 这样的工具,理想的解决方案是 corert,而不是这种自我提取的即兴创作。

@mikedn为什么会这样? 我看不出 AOT 或 JIT 编译与部署方法有什么关系?

我想支持@strich的话,单文件部署将为微服务架构部署以及任何控制台应用程序带来新鲜空气——或者至少开始它的生命——带有命令行的小工具开关。

为什么? 我看不出 AOT 或 JIT 编译与部署方法有什么关系?

因为它为您提供了您想要的东西 - 一个 exe 文件,您可以将它放到任何机器上(好吧,任何具有合适操作系统的机器,它毕竟是一个本机文件)并运行。 它也倾向于使用更少的资源,这对于像 consul 这样的代理来说是一件好事。 它几乎等同于 Go 给你的东西,而不是一个自我提取的解决方案。

@mikedn我猜,但是

1)它还没有真正以生产形式存在(据我所知)!
2)我们使用了很多动态特征(IL生成,任意反射)
3)我们仍然希望能够添加插件(再次理想地压缩)

看到这个问题是关于询问人们他们想要什么,我们只是给出我们的意见! 我们真的不想为了获得这种好处而不得不切换到不同的运行时模型。 对我来说,它们是正交的问题

我看不出 AOT 或 JIT 编译与部署方法有什么关系?

如果没有 JIT,就更容易获得足够好的调试故事。 JIT 部分使问题变得更加困难,这就是您在 Go 中找不到它的原因。 这是工程,因此您要么将更多工程师投入到更难的问题中并忍受新的复杂性,要​​么将其范围缩小到其他地方。 自提取器的目的是缩小范围,因为具有必要技能的工程师数量有限。

拥有更像 Go 项目(没有 JIT 要求)的项目的人可能会对 CoreRT 感到非常满意,如果他们对上面的“实验”标签感到满意的话。 这几天很容易尝试。 它使用与完整 CoreCLR 相同的垃圾收集器、代码生成器、CoreFX 和大部分 CoreLib,并生成自包含的小型可执行文件(个位数兆字节)。

它还没有真正以生产形式存在(据我所知)!

是的,我的评论主要针对 MS 人:咧嘴笑:。 他们有所有这些平行的、相关的、正在进行的项目/想法(corert、illinker),现在他们又增加了一个,这种自我提取的东西,正如许多人已经指出的那样,它有点“嗯,我们可以做到这一点我们自己”之类的。 它也有缺点,例如将文件提取到“隐藏”目录。

我们使用了很多动态特性(IL 生成、任意反射)

这是整个社区可能想要重新考虑的事情。 当然,能够做到这一点很有用,但它与单文件部署等其他需求有冲突。 您仍然可以获得单文件部署,但这往往需要付出代价——一个相当大的文件。 如果您对此感到满意,那很好。 但根据我的经验,文件越大,“单个文件”方面的用处就越小。

@MichalStrehovsky当然,有不同的选择。 但是对我们来说,我们不能使用实验性的(说服它是时候迁移到 .NET Core 已经够难了),而且我不认为提取到临时文件夹在我们的情况下也行得通。 然而,最坏的情况是我们照常进行并且不使用此功能。

这是我们想要的,但如果它按照我们想要的方式进行:)

@mikedn我同意。 多个并行解决方案更加令人困惑。 我认为我们理想的解决方案将是某种超级 ILLinker/weaver 方法,但我很高兴能够让它发挥作用,看看我们最终的结果。

我对这个功能登陆感到非常兴奋,但是对于你在@morganbr 发布的初始提案,我同样不兴奋。 我对您提出的问题列表的回答与其他人发布的类似(所以我认为有一组共同的所需功能),但恕我直言,提出的“解压到磁盘”解决方案根本不是我希望看到的实施,正如其他人所说,几乎比“疾病”还要糟糕。 @jkotas

我同意@strich和@ayende
编译后的可执行文件必须:
打包所有相关依赖项
没有执行工件(没有磁盘写入)

从内存而不是磁盘加载 .dll 可能并不容易,但这是 IMO 值得 MSFT 低级开发人员(然后在 CoreClr 中利用)的深厚专业知识与上述提议相比的那种能力,可以实现为一个外部工具(并且已经有了,请参阅 https://github.com/dgiagio/warp)。 如果实现了,我想知道第一次和后续运行之间会有多少时间差? 对于灵感/示例,我认为 Linux 3.17+ 具有 memfd_create 可供 dlopen 使用。

在另一个话题上,我想知道支持插件的要求是否过度索引设计提案? 是否值得让这个功能选择加入,这样只有需要这个功能的人才会受到潜在的惩罚(没有摇树等),而其他人(绝大多数?)会减少可部署的大小,性能优势(?)

退后一步 @swaroop-sridhar @MichalStrehovsky ,我可以看到两个广泛的用例,它们可能有足够不同的目标/愿望,很难用一种解决方案来适应每个人:

  • CLI 工具理想地希望每次都快速运行,小可分发; 也许更适合CoreRT?
  • 微服务可能可以容忍更长的首次运行(理想情况下小于 1 秒)、更大的可分发,以换取更丰富的功能,如 JIT 和动态代码特性。

我希望这个头脑风暴有点道理,我不是想成为一个混蛋,只是提供反馈,因为我对这个话题非常感兴趣。 感谢您的辛勤工作,并征求社区意见! :)

关于插件。
基本上,我唯一想要的是_不_在Assembly.LoadFromLoadLibrary调用中被阻止。
我不需要其他任何东西,剩下的可以自己做。

@ayende您能否更详细地解释一下“在LoadFrom等上被阻止”是什么意思?

例如,对此的一些建议包括 CoreRT,这意味着我们(可能)不能只加载托管 dll。
但只要我可以提供托管 dll 的路径并获取程序集或在本机 dll 上调用LoadLibrary ,我就可以将其作为插件机制。

我这样说是为了清楚地表明插件场景不应该是_considered_,而是不应该_blockced_。

我花了一些时间深入研究代码,乍一看,它似乎应该是_可能_(谈论 Windows 只是为了让事情变得简单):

  • 将整个 CoreCLR 打包到一个 exec 中(假设它是嵌入式资源或类似的东西)。
  • 枚举原生 dll,调用类似MemoryModule (https://github.com/fancycode/MemoryModule) 将它们加载到内存中
  • 调用运行时并从内存中加载程序集。

我将假设这_不_那么简单。 例如, ICLRRuntimeHost2::ExecuteAssembly没有提供任何方法给它一个缓冲区,只提供一个磁盘上的文件。
这使它成为第一个(我敢肯定会有很多)真正让它发挥作用的阻碍。

我很确定有很多代码可能会将相关的东西称为可能失败的文件,但这就是我说我想要一个文件执行程序时的意思以及为什么那种解决方案需要在 CoreCLR 中完成,而不是在外部(如 zip 示例中)。

调用类似 MemoryModule 的东西

使用 MemoryModule 加载的二进制文件是不可诊断的(例如,常规调试器、分析器等都不会对它们起作用),并且 MemoryModule 会跳过所有内置的操作系统安全措施(例如防病毒等)。 这不是我们可以作为受支持的解决方案发布的东西。

我将假设这不是那么简单。

是的,需要新的 API(托管和非托管)来支持从非扩展的单个文件包执行。 现有的程序和库不会“正常工作”。

要求反恶意软件扫描界面是一个合理的要求吗?
由 Windows 工程师在 MemoryModule 上实现,就像最近一样
所有 .net 程序集,包括从 mem 加载的程序集?

https://twitter.com/WDSecurity/status/1047380732031762432?s=19

编辑:嗯,MemoryModule 听起来像是操作系统内置的用于加载本机模块的东西,但显然不是,这可能足以让我无视我上面的建议

2018 年 12 月 5 日星期三 01:46 Jan Kotas [email protected]写道:

调用类似 MemoryModule 的东西

使用 MemoryModule 加载的二进制文件是不可诊断的(例如,没有
常规调试器、分析器等)将对它们起作用,而 MemoryModule
跳过所有内置的操作系统安全措施(例如防病毒等)。 它不是
我们可以作为受支持的解决方案发布的东西。

我将假设这不是那么简单。

是的,需要新的 API(托管和非托管)来
支持从非扩展的单个文件包执行。 现有的
程序和库不会“正常工作”。


您收到此消息是因为您订阅了此线程。
直接回复此邮件,在 GitHub 上查看
https://github.com/dotnet/coreclr/issues/20287#issuecomment-444314969
或使线程静音
https://github.com/notifications/unsubscribe-auth/AEBfudvVHxpbeKa-L_vTQjbnNZZsn6Meks5u1xdlgaJpZM4XK-X_
.

感谢所有的反馈。
我们将很快发布支持单文件分发的更新计划。

@NinoFloris见 dotnet/coreclr#21370

我意识到我迟到了这个讨论,所以如果这没有帮助,我很抱歉。

重申@miken所说的话,拥有所有这些并行解决方案可能会令人困惑,并且它引出了 MS 未来的重点在哪里的问题。 在没有官方路线图的情况下,在 CoreRT 上完成的出色工作仍然被标记为实验性的,并且在 CoreCLR(CPAOT,现在是单一文件发布)中实现了类似的功能,这使得基于该技术做出业务决策变得困难。 我不想再选错马了(Silverlight ...)。

为什么不坚持使用 CoreRT 中出色的运行时并将工作重点放在将 JIT(并扩展解释器工作直到准备好)整合到 CoreRT 中,以供那些想要完整框架功能同时仍然为他们构建本机代码的人使用? 在需要 JIT 能力的情况下,是否可以将元数据和 JIT 保存在 CoreRT 本机编译的可执行文件中?
这不是世界上最好的吗? 例如。 您将拥有最小的可执行文件、最快的启动时间、CoreRT 中 AOT 代码的限制可以通过设置 JIT 来扩展,并为那些想要保留完整框架功能的人保留元数据和 IL 代码。
IMO 的重点应该是“最好的”解决方案,而不是可能分散长期目标的短期功能。

也许我错了,但我认为将单个文件发布作为 CoreCLR 的一部分甚至会进一步分散客户对 CoreRT 的注意力,在许多情况下,这对他们来说可能是一个更好的解决方案。 例如。 如果这被标记为“现在您可以将 .NET 代码作为单个文件发布”,但它正在生成巨大的文件,将在启动性能等方面受到影响,那么我可以看到人们再次开始抱怨这个“臃肿的 .NET 运行时” . 虽然 .NET 实际上已经在 CoreRT 中有一个出色的解决方案,但缺乏官方支持/官方产品承诺。

也许我最关心的是我希望看到更少的并行产品(再次引用@mikedn),而更多地强调深入致力于、扩展和支持这里的产品(包括共享路线图)。

并行产品较少

我们只有一种产品:.NET Core。 CoreRT 不是一个单独的产品,也永远不会。 这是关于为这一产品添加选项。 以下是我们今天拥有的几个选项示例:工作站 GC 和服务器 GC; 自包含应用发布与依赖于框架的应用发布。

@jkotas我理解,我完全同意添加选项通常很棒。 如果您将我对“产品”一词的使用替换为“选项/解决方案”,但我仍然担心不完全了解我们的产品对 CoreRT 作为 .NET Core 整体产品中的选项/解决方案的承诺是否是我们的可以放心地打赌在未来的许多年里会得到支持。 想象一下,如果 API 会随着时间的推移做出不可预测的重大更改并突然被弃用,而您不知道您是否可以信任 API 留在那儿,那会是什么样子。 对于非 MS 实体,作为产品一部分的工具/选项感觉相同。 我们依赖于 CoreRT 的几个功能,我们无法(至少目前)用 CoreCLR 做(其中之一是用 PACE 保护我们的可执行文件并剥离大量元数据、IL 等的能力,简单的静态链接,更容易理解底层平台,直接在编译器中构建混淆的前景等)。 基本上,我觉得你和 Michal 以及其他人在 CoreRT 上的工作应该被优先考虑,因为 IMO 是 .NET 生态系统自最初设计以来发生的最好的事情之一。 任何时候谈论一个新的工具/选项,然后在内部可以将其视为与 CoreRT 的竞争,而不是 CoreRT 选项来完成和扩展给我感觉就像资源的错误分配。 抱歉并不是要拖延讨论,只是想确保您了解 CoreRT 工作是多么受到赞赏,并且我们中的一些人相信它应该是该平台的未来。
我将避免进一步污染这个讨论。 祝球队一切顺利,并期待你提出的任何想法,我相信它会很棒。

@christianscheuer您对这个问题和其他 CoreRT 相关问题的反馈非常有价值。 你根本没有污染这个讨论。

有一种新工具/选项的讨论,然后可以在内部将其视为与 CoreRT 的竞争

在核心团队中,我们讨论了许多不同的、雄心勃勃的或更疯狂的选择。 我不认为“解压缩到磁盘”是 CoreRT 完整 AOT 的重要竞争对手。 每个都针对不同的 .NET 用户/应用程序。

我们的产品管理团队从许多不同的渠道(包括 github 问题或面对面的客户对话)编译数据,建议最大的收益是像这里提出的解决方案。 我们仍在验证我们拥有这项权利的所有数据,并且它会产生预期的结果。

目前,我无法向您保证 CoreRT 技术何时会作为受支持的 .NET Core 产品的一部分发布。 我确实知道它具有独特的优势,我相信我们将为最终从中受益的重要 .NET 用户提供类似的支持解决方案。

在以下情况下,单个文件分发是有意义的:

  • 它不会影响启动时间(否则只有最小的应用程序才能使用它)
  • 它充当 VFS(虚拟文件系统),允许您访问程序集和资源,就好像它们来自磁盘、应用程序文件夹一样)。
  • 不影响应用程序的“可修补性”。 IE:微软应该能够通过在系统级别的其他地方提供覆盖来修补关键程序集,即使它们是嵌入的。

程序集作为嵌入式资源,提取并复制到临时文件夹不是 Microsoft 所需的解决方案。 任何需要它的客户都可以轻松实施。

需要一个“真正的”单文件解决方案,并且会提供很多价值。

@jkotas你的评论对我来说很真实。 我很难说服我公司的 .NET 开发人员(LOB/backoffice/integrations)切换到 CoreRT,因为这对他们来说是一个太大的飞跃(尤其是给定 Experimental 标签),但他们可以看到价值像这个线程正在讨论的 Core 相对简单(使用)的解决方案。 谢谢

@popcatalin81我实际上非常不同意你的观点。

启动时间对许多应用程序来说没有意义。 为了降低部署它的操作复杂性,我很乐意在任何长时间运行的服务的启动时间增加 50% - 100% 之间进行权衡。

即使我们将服务的启动时间增加 30 秒,从长远来看,这对于服务来说并没有真正的意义。
对于面向用户的应用程序,这可能是一个杀手,但在 3.0 发布之前,几乎所有 CoreCLR 应用程序都是控制台或服务。

并不是说 CoreCLR 已经有一种模式,您可以在其中将框架与您的应用程序捆绑在一起(自包含部署)。
在我们的案例 (RavenDB) 中,我们_rely_ 出于几个目的。 首先,无论我们的用户使用什么框架,都可以选择我们自己的框架。 这对我们来说是一个主要的好处,因为这意味着我们不需要使用全局安装的框架,因此不依赖于管理员决定的框架(我们曾经考虑到我们将在.NET 4.0 有时,甚至在 4.5 发布多年后,例如,这很痛苦)。
另一个重要方面是我们可以使用_我们自己的_框架的分支来运行。 如果我们遇到必须改变的事情,这非常有用。

作为其中的一部分,我们拥有补丁周期的所有权,并且不希望任何其他人弄乱它。

我不介意 VFS,但我认为这不是必需的。 而且我完全可以通过专用的 API / 跳过一些圈来获得以这种方式捆绑的程序集/本机 dll/资源。

我被@briacht指出了这个问题两天,所以我真的迟到了讨论。
只想加我的 2 美分!

首先我对@morganbr问题的回答:

  1. Windows 应用程序,混合 WPF 和 WinForms。
  2. 目前,没有
  3. 是的,在不同的变体中使用 Assembly.Load
  4. 是的
  5. 这取决于这是否是每次启动时都会受到的惩罚。 是的,如果它是几毫秒,但 5 秒:不。 如果是单次的话,5秒是没问题的。
  6. 与 <2MB 相比,50MB 已经相当大了......但如果这意味着包含 dotnet 运行时......好吧。 也许我可以提供有和没有的下载。
  7. 发布时间并不直接重要,但它不应该因为长时间使用大量 CPU 而让 CI 提供者感到不安。
  8. 是的!

我谈论的应用程序 Greenshot,当前发布的版本几乎仍然以 .NET Framework 2.0 为目标(确实如此),但在最新版本上没有问题(良好的向后兼容性)! Zhe 下载,包括安装程序但没有 .NET Framework,大约为 1.7MB。 启动的目录包含 1 个 .exe 和 4 个 .dll,子目录中还有 11 个附加组件。

我正在阅读之前的评论,其中很多听起来像是你想要一些我会使用安装程序的功能。 这确实有点道理,因为目前没有独立于平台的解决方案。

早在 8 月,我就与 Microsoft 的一些人讨论了当前的话题,我的主要关注点是讨论将所有内容链接在一起以减少部署应用程序的大小和复杂性,并减少在启动期间需要大量时间的程序集加载我的申请。

使用下一版本的 Greenshot,它同时针对 .NET Framework 4.7.1 和 dotnet core 3.0,dotnet core 3.0 的下载结果目前有大约 103 个 dll (!!!)、一个 exe 和 15 个附加组件. 总大小约为 37MB,最大的文件是 MahApps.Metro.IconPacks.dll(12MB !!),其中包含我们使用的大部分图标,大概占文件的 4% 左右。 与许多其他 dll 相同,我假设通常 50% 的代码使用量很多!

如果用户不想安装该应用程序,只需下载一个 .zip 并在大约 118 个文件之间找到可执行文件……这不是一个很好的体验! 大小大约大 35MB (17x),那么对用户有什么好处呢?

在 dotnet core 3.0 出现之前的几个月里,我使用 Fody.Costura 来完成这里所描述的工作。 在构建期间打包和在启动时解包! 它甚至制作了一个可执行文件,而无需通过破解 but 来提取文件。 但这也引起了很多问题,所以这不是我首选的解决方案。

我希望有一个或多或少的标准解决方案,我可以说:它只是有效™
也许定义一个标准的文件夹结构是有意义的,所以我们可以将可执行文件、自述文件、许可证文件等放在根目录中,并为不同的文件(dotnet 核心、依赖项等)提供不同的“已知”目录。 这可能使工具更容易使用,并提供每个人都可以决定要使用什么和不使用什么的功能。 有时即使只是在目录上运行 zip 也能解决一些问题。

有一个类似于 Fody.Costury 的解决方案肯定是我可以使用成像来简化 Greenshot 的部署,但它不会直接减少大小和加载时间。 所以我希望也能在这里度过一些时间!

@ayende请允许我不同意你的观点:)

虽然它是真正的服务,但 Web 应用程序和一般基于服务器的长期运行应用程序不受一次性启动成本的影响,同时这些应用程序并不是从单一文件分发模型中获得最大好处的应用程序。 (乌鸦是个例外,不是常态;))

在我看来,单一文件分发主要针对针对用户的应用程序和实用程序。 将下载、复制、运行此类应用程序的用户。 而在不久的将来,当 .Net Core 3.0 将提供对 UI 库的支持时,这种分发模型将变得相当普遍。 但是,我必须强调,此模型不适用于任何应用程序。 主要应用程序仍将使用安装程序。

现在的问题是,这种复制到临时文件夹的嵌入式程序集模型,如果它变得非常流行,它会运作良好吗?

在我看来,这些是当前模型的一些潜在问题:

  • 数十个应用程序创建了数千个带有重复程序集的临时文件。 这里有可能造成混乱。 (用户没有注意到,但仍然一团糟)
  • 没有压缩,没有链接器。 与 FDD 或事件压缩相比,这可能会给小型实用应用程序带来不利影响。
  • 它实际上可能会阻碍服务器部署而不是使它们更容易,原因是:使用令牌替换进行部署后配置。 (你去哪里找到临时文件夹来覆盖配置文件?你是否授予服务对其自己文件夹的写访问权限以在那里创建配置文件?这是一个安全禁忌)

我不是在这里说这种模型对于所有类型的应用程序都不是最佳的。 我已经成功地实现了它,并在过去自己将它用于 full.Net Framework,它非常适合某些应用程序。

我只是认为最佳实现是链接器将所有程序集合并到一个文件中的版本。

@popcatalin81一个单一观点的世界是一个糟糕的世界。 而且我还没有自大到认为我的部署模型是唯一的。

我想到的情况是下载和双击场景。
现在,我们必须解压缩并单击一个 shell 脚本才能运行,因为进入一个包含 100 多个文件的目录并找到正确的文件是一件苦差事。

关于临时文件中的配置。 我会非常反对。 配置文件是其他用户可见的东西_必须_位于正在使用的实际文件旁边,而不是隐藏在某个地方。
我们已经看到,当用户需要IsolatedStorage文件时,这有多糟糕。

在之前的 RavenDB 版本中,我们合并了很多东西以简化目录布局并减少可见文件的数量,这对我们软件的可用性产生了严重影响。

此功能会使用 ILMerge 吗? 如果不是,为什么不呢? 如果有陷阱,我想教育自己。

我不喜欢提出的“打包和提取”方法,尤其是如果会在磁盘上提取文件的部分。 很简单,但是问题太多了。 它应该全部从“内存”中工作,而无需提取到磁盘。

我认为它应该是 corert native compiler 或Costura.Fody之类的东西。

可选地,它还应该尝试减小二进制大小(删除未使用的引用,死 IL)。

回覆

在 dotnet core 3.0 出现之前的几个月里,我使用 Fody.Costura 来完成这里所描述的工作。 在构建期间打包和在启动时解包!

请注意,costura 不会(默认情况下)“解压到磁盘”。 它从资源中提取(在内存中)嵌入式程序集并通过字节加载它们

感谢@SimonCropp的上下文,我们正在探索解压到磁盘和从流中加载的两种选项。 在我们的第一次迭代中,为纯 IL 覆盖从流中的加载可能是可能的。

@jeffschwMSFT顺便说一句,无缝合并程序集(和可选的摇树)的功能集是我作为一等公民希望看到的。 costura 是我比较受欢迎的项目之一,但也是一个需要大量时间来支持的项目。 因此,如果/当 MS 推出替代品(即使作为测试版),请告诉我,我会花一些时间对其进行审查,并在 costura 自述文件和 nuget 描述中添加一些“有一个 MS 替代品”

虽然不太可能,但如果您需要我在 diff 许可证下发布 costura 中的任何代码,请告诉我。

感谢@SimonCropp ,因为我们开始将 mono/illinker 集成到 .NET Core 工具链中,我们将接受您的审查。

请注意,costura 不会(默认情况下)“解压到磁盘”。 它从资源中提取(在内存中)嵌入式程序集并通过字节加载它们

更好的是,我相信它使用AppDomain.AssemblyResolve事件按需加载,因此启动不应该受到太大影响,因为它只会在需要时加载程序集(到内存中)。

这里的谈话迟到了。 抱歉,如果这已经被覆盖。

我们可以假设这里的人工制品是确定性的吗? 因此,相同的构建总是会产生相同的二进制文件? 逐字节。 能够分发带有规范的随附校验和的二进制文件也会很有帮助。

@morganbr这里需要大量阅读。 是否可以记录迄今为止所做的决定?

@edblackburn我们正在根据反馈汇总一个更新的设计。 @swaroop-sridhar 现在正在推动设计。

此功能会使用 ILMerge 吗? 如果不是,为什么不呢? 如果有陷阱,我想教育自己。

@simplejackcoder ILMerge将来自许多程序集的 IL 合并为一个,但在此过程中,丢失了合并程序集的原始标识。 因此,诸如基于原始程序集名称使用反射之类的事情将失败。

这项工作的目标不是合并程序集,而是在保持程序集身份的同时将它们打包在一起。 此外,我们不仅需要打包程序集,还需要打包本机二进制文件、配置文件、可能的运行时以及应用程序所需的任何其他依赖项。

合并 IL 对于可以适应它的应用程序来说是一个有用的功能——它只是与这个问题不同的一个功能。

设计评论发布在https://github.com/dotnet/designs/pull/52
让我们继续讨论 PR。

对于想使用单文件的人,我有几个问题。 您的回答将帮助我们缩小选择范围:

  1. 您可能会使用哪种应用程序? (例如,Windows 上的 WPF?Linux Docker 容器中的 ASP.NET?还有别的吗?)
  2. 您的应用程序是否包含(非 .NET)C++/本机代码?
  3. 您的应用程序会加载最初未包含在应用程序构建中的插件或其他外部 dll 吗?
  4. 您是否愿意重建和重新分发您的应用程序以包含安全修复程序?
  5. 如果您的应用程序启动速度慢 200-500 毫秒,您会使用它吗? 5秒呢?
  6. 您认为您的应用可接受的最大尺寸是多少? 5MB? 10? 20? 50? 75? 100?
  7. 你会接受更长的发布构建时间来优化大小和/或启动时间吗? 你能接受的最长的时间是多少? 15秒? 30秒? 1分钟? 5分钟?
  8. 如果它可以将你的应用程序的大小减半,你愿意做额外的工作吗?
  1. 到目前为止,我们的 dotnet/coreclr#1 用例是作为实用程序的控制台应用程序。 我们想为 windows 或 linux 发布并指定--single-file以获取一个可执行文件。 对于 Windows,我们希望它以.exe结尾,对于 linux,它将是一个可执行的二进制文件。
  2. 通常不会,但它可以通过我们不知道的 NuGet 包
  3. 最初不是,但这将是一个很棒的未来功能
  4. 是的
  5. 是的
  6. 无论什么作品
  7. 是的,如果它优化了可执行文件的结果大小。 理想情况下,可能是使用--optimizing-level=5设置优化级别的标志
  8. 是的,在某种程度上

谢谢@BlitzkriegSoftware。
我认为这里提出的设计涵盖了您的用例。 请看一下设计——我们欢迎您的反馈。

对于想使用单文件的人,我有几个问题。 您的回答将帮助我们缩小选择范围:

  1. 您可能会使用哪种应用程序? (例如,Windows 上的 WPF?Linux Docker 容器中的 ASP.NET?还有别的吗?)
  2. 您的应用程序是否包含(非 .NET)C++/本机代码?
  3. 您的应用程序会加载最初未包含在应用程序构建中的插件或其他外部 dll 吗?
  4. 您是否愿意重建和重新分发您的应用程序以包含安全修复程序?
  5. 如果您的应用程序启动速度慢 200-500 毫秒,您会使用它吗? 5秒呢?
  6. 您认为您的应用可接受的最大尺寸是多少? 5MB? 10? 20? 50? 75? 100?
  7. 你会接受更长的发布构建时间来优化大小和/或启动时间吗? 你能接受的最长的时间是多少? 15秒? 30秒? 1分钟? 5分钟?
  8. 如果它可以将你的应用程序的大小减半,你愿意做额外的工作吗?
  1. WPF .Core 3.0 应用程序
  2. 也许在一个 nuget 包中
  3. 是的
  4. 是的,最长 200-500 毫秒
  5. 不重要,只要它不是原始发布文件夹大小的 2 或 3 倍
  6. 它是用于生产的,所以并不重要
  7. 是的,但如果工作涉及第三方图书馆,可以不这样做可能会有用

你收到的反馈主要是2019年之前的开发,一般是后台启动的webservice、控制台应用等服务。 但是现在有了 .net core 3.0,我们需要 WPF 和 UI 应用程序的解决方案。 .Net Framework 4.8 将不支持 .Net Standard 2.1,我们需要一个解决方案来替换我们迄今为止用于 WPF 应用程序的 ILMerge,因为我们必须将我们的应用程序从 .net 框架(死框架)升级到 .net 核心。

正如之前针对此类程序的其他评论中所解释的那样,自解压包显然不是一个好的解决方案。

  1. 主要是 CLI 应用程序。 小型 Windows 服务和偶尔出现的 WPF 应用程序也很有趣。 对于 web 的东西,单个文件分发似乎基本上是无关紧要的。 出于这个原因,剩下的大部分都是面向 Windows 的; 我们对 Linux .NET Core 的兴趣在于 Web 内容。
  2. 有时。 当它发生时,它几乎总是用于压缩库。 如果本土的东西没有被打包进去,那也不会是世界末日。 对于需要将本机依赖项部署为单独的 dll 的纯托管内容,一个好的解决方案仍然具有价值。
  3. 不,不是在我们关心单个文件的情况下。
  4. 是的。
  5. 200ms 大约是 CLI 启动烦人之前的限制。 > 1 秒,我们几乎肯定会坚持使用 .NET Framework (ILMerge + ngen) 或查看其他编译为本机二进制文件的语言,其中包含任何必要的运行时静态链接。在很多情况下,我们谈论的是那些是在 C# 中实现的,因为一个合并的、ngen-ed C# 应用程序实际上启动得相当快。 其中一些东西在复杂性方面是合理的,用脚本语言实现是合理的,但那些往往会产生巨大的启动成本,尤其是当你需要引入一堆模块时。
  6. 要看。 超过 10MB 的 hello world 将很难卖。 巨大的可执行文件往往具有不小的启动成本。 即使操作系统理论上能够在不阅读整个内容的情况下开始执行,至少在 Windows 上,几乎总是有一些东西想要首先获得一些安全目的的哈希值。 说到这一点,这种将一堆二进制文件解包到临时文件将推动 AV(绝对包括 Defender)在资源利用率方面疯狂地扫描,如果没有别的。
  7. 当然。 如果这意味着获得合理大小的内容并快速开始执行,我会很乐意为发布构建添加一分钟或更长时间。
  8. 确实。

仔细阅读该提案,它并没有解决我们所拥有的并且我们不会使用它的任何场景。 它可能会为某人解决问题,但不是我们。

对于像我们这样正在处理的场景,似乎有很多相关的东西还没有组合成一个实际上可用/可能继续工作的工具链。 也许这只是我的无知,但我试着注意,我不清楚。 在 ILLink、Microsoft.Packaging.Tools.Trimming、CoreRT、.NET Native 以及此问题中发生的任何事情之间,似乎应该有某种方法来获得一个单一的 tree-shaking、快速启动(可能是 AoT?)、大小合理的二进制文件.NET Core(而不是 UWP)。

  1. 单个文件分发对于https://github.com/AvaloniaUI/Avalonia(Windows、Linux、MacOS )和 WPF(Windows)应用程序非常有用。 例如,拥有单个 exe 使自动更新变得容易,并且通常与数百个 dll 相比,拥有单个文件更方便。
  2. 是(从某种意义上说,它引用了预构建的原生库,例如 sqlite 和skia)
  3. 是的
  4. 200 毫秒可能没​​问题。 GUI 应用程序的启动时间为 5 秒并不好。
  5. 真的没关系
  6. 真的没关系。 我们只能为生产场景构建单个文件。
  7. 是的

请不要使用打包和提取方法。 我们已经可以通过使用存档工具来实现这一点。

请不要使用打包和提取方法。 我们已经可以通过使用存档工具来实现这一点。

我不能同意更多。 已经有几个可用的工具可以做到这一点。

感谢@Safirion、 @mattpwhite @ x2bool 、@thegreatco 的反馈。
从这些中,我看到了一系列使用场景(控制台应用程序、WPF、GUI 应用程序等),但总体要求似乎相似:

  • 关注执行/启动时间而不是构建时间。
  • 能够为进一步的版本/补丁重建应用程序
  • 能够直接从包中运行(尤其是对于纯托管组件)。

我相信这些是我们试图在设计文档中解决的要求。

请不要使用打包和提取方法

是的,正如本暂存文档中所解释的,目标是逐步减少对文件提取的依赖。

仔细阅读该提案,它并没有解决我们所拥有的并且我们不会使用它的任何场景。

@mattpwhite ,想澄清您是否认为此解决方案不适用于您的基于:

  • 本工作项目前面提出的基于自提取的方法,或
  • 文档中提到的设计
    我相信后面的文档会尝试解决您详细描述的场景。 谢谢。

对于像我们这样正在处理的场景,似乎有很多相关的东西还没有组合成一个实际上可用/可能继续工作的工具链。

@mattpwhite ,正在进行的工作是将 ILLink(tree-shaking)、crossgen(生成准备运行的本机代码)和单文件捆绑方面以流线型的方式集成到 msbuild/dotnet CLI 中,用于 .net core 3。曾经完成后,使用这些工具可以轻松完成(例如:设置某些 msbuild 属性)。

所有这些工具都需要权衡取舍。 例如,crossgen 会提前生成本机代码,从而有助于启动,但往往会增加二进制文件的大小。 使用单文件选项,可以将应用程序发布到单文件,但这可能会减慢启动速度,因为本机二进制文件会溢出到磁盘等。

@swaroop-sridhar,感谢您抽出宝贵时间回复。

想澄清您是否认为此解决方案不适用于您的基于

对于任何独立的东西,提取似乎仍在图片中,并且我不清楚 crossgen 的状态(或在发布时在 JIT 时间减少的任何其他东西)。 阅读当前设计链接到它的登台文档看起来会让我们更感兴趣的东西更远。 鉴于此,我的想法是,在未来一段时间内,我们将继续使用 .NET Framework 来满足这些需求。 我可能误会了。

所有这些工具都需要权衡取舍。 例如,crossgen 会提前生成本机代码,从而有助于启动,但往往会增加二进制文件的大小。

是的,明白其中一些是双刃剑。 凭借 JIT 传统的工作方式,ngen 一直是 CLI/GUI 应用程序的赢家。 分层编译的某种组合以及加载运行时本身的事实不一定会被在同一机器上运行的许多其他 .NET Framework 进程摊销,这可能会使 Core 上的情况不太明确。

@swaroop-sridhar 我对当前接受的提案的最大担忧是这一步

剩余的 216 个文件将在启动时提取到磁盘。

除了“Hello World”似乎需要 216 个文件才能运行之外,将它们解压到磁盘的某个位置会使删除简单的命令行应用程序变得困难。 当人们删除不是通过安装程序提供的工具时,他们不希望需要寻找其他文件来完全删除它。

恕我直言,我们的目标应该是类似于 golang 的发布体验。 虽然它需要crossgen才能完全匹配 golang 体验(有人可能会争辩说 1:1 匹配不是一件_好_的事情,因为我们在 JIT 和分层编译上输了),a没有它就可以实现很好的部分。 多年来,我使用了许多工具来获得类似的体验,最有效的是在程序集加载器上带有挂钩的嵌入式资源(ILMerge 很挑剔,Costura.Fody 还不存在)。 提案中描述的内容甚至没有完全复制该功能。 .NET Core 有一个非常强大的未来,但它在开发命令行工具方面的用途是有限的,只要我们需要使用大约 100 个依赖项或使用一些手动引导代码将它们吐出到磁盘上的某个位置。

我的 dotnet/coreclr#1 用例是命令行实用程序,一个文件,不提取是我的偏好。 但我要指出的是,作为命令切换的一部分,可以选择一体机还是一体机自解压。 在自解压的情况下,它应该包含一个undo来清理解压缩的文件。 在第一种情况下,删除实用程序意味着删除一个适合驻留在通用实用程序文件夹中的文件,而在第二种情况下,安装应该有自己的文件夹,以便更容易清理,并避免卸载删除共享文件的场景。 只是一些想法。 正如我所说,一个文件实用程序(无需安装)是我的主要用例,因为我经常为项目制作实用程序(作为工具匠)。 但我可以看到这两种情况的用例。 但它们是不同的。

对于想使用单文件的人,我有几个问题。 您的回答将帮助我们缩小选择范围:

  1. 您可能会使用哪种应用程序? (例如,Windows 上的 WPF?Linux Docker 容器中的 ASP.NET?还有别的吗?)
  2. 您的应用程序是否包含(非 .NET)C++/本机代码?
  3. 您的应用程序会加载最初未包含在应用程序构建中的插件或其他外部 dll 吗?
  4. 您是否愿意重建和重新分发您的应用程序以包含安全修复程序?
  5. 如果您的应用程序启动速度慢 200-500 毫秒,您会使用它吗? 5秒呢?
  6. 您认为您的应用可接受的最大尺寸是多少? 5MB? 10? 20? 50? 75? 100?
  7. 你会接受更长的发布构建时间来优化大小和/或启动时间吗? 你能接受的最长的时间是多少? 15秒? 30秒? 1分钟? 5分钟?
  8. 如果它可以将你的应用程序的大小减半,你愿意做额外的工作吗?
  1. CLI(Windows、Linux、MacOS)、WPF 和 WinForms。
  2. 有时。
  3. 是的,很多时候,我认为应该解决插件可以从主应用程序重用共享库的情况!
  4. 是的。
  5. 200-500ms 没问题!
  6. 对于一个没有很多依赖的简单 CLI 应用程序,10MB 就足够了(压缩)!
  7. 是的,绝对的! 构建时间(尤其是发布构建!)并不重要! 压缩大小和/或切换到发布单个文件应该是可选的!
  8. 是的,例如 CoreRT!

恕我直言,我们的目标应该是类似于 golang 的发布体验。

@thegreatco :是的,完全编译为本机代码是构建单文件应用程序的好选择。 像 GO 这样的语言是使用提前 (AOT) 编译作为它们的主要模型构建的——这对通过 JIT 编译实现的动态语言特性有一定的限制。

CoreRT是用于 AOT 编译应用程序的 .Net Core 解决方案。 但是,在 CoreRT 可用之前,我们将在本期中尝试通过其他方式实现单文件应用程序。

除了“Hello World”似乎需要 216 个文件才能运行之外,

有几个选项可用于减少 HelloWorld 所需的文件数量”

  • 构建一个依赖框架的app,只需要3-4个文件
  • 如果我们需要构建自包含的应用程序,请使用ILLinker减少文件依赖项的数量

将它们提取到某个位置的磁盘会使删除简单的命令行应用程序变得困难。

  • 随着单文件功能的发展进入更深的阶段,溢出到磁盘的文件数量将减少,并且对于大多数应用程序来说理想情况下为零。 但在初始阶段,必须溢出几个文件(包含本机代码的文件)。
  • 溢出到磁盘的文件的位置始终是确定性和可配置的。 因此,可以使用简单的脚本清理溢出的文件。 但我同意这并不理想。

感谢@mattpwhite 的澄清。

是的,直到功能开发的后期阶段,独立应用程序才会看到提取到磁盘的文件。 您也许可以使用我在上述评论中写的一些技术来改善您的应用程序的情况。

感谢您的回复@BlitzkriegSoftware。

但我要指出的是,作为命令切换的一部分,提供选择是一体机还是一体机自解压

作为单个文件发布的应用程序是否需要提取取决于应用程序的内容(例如:它是否包含本机文件)。 bundler 设计提供了一些配置来决定是否应该提取所有文件。 我们可以添加一个属性来说明应用程序在运行时不得对文件进行提取。

<propertygroup>
    <SingleFileExtract> Never | Always | AsNeeded </SingleFileExtract>
</propertygroup>

这里的理解是:在编译 SingleFileExtract=Never 时,如果应用程序包含只能通过提取(本机二进制文件)处理的文件,则发布到单个文件将失败。

在自解压的情况下,它应该包括一个清除解压文件的撤消。

听起来很合理。 提取的确切位置(如果有)是确定性和可配置的。 因此,我们可以有一个脚本来删除应用程序及其所有提取的依赖项。

感谢您的回复@DoCode。 您的场景很有趣,因为它使用了插件。
如果插件本身有一些依赖项(不与主应用程序共享),您是否需要/更喜欢将插件发布为单个文件(嵌入依赖项)? 谢谢。

是的@swaroop-sridhar,这就是目标。 我们认为在这种情况下插件应该嵌入它们的依赖项。 只有共享的依赖项应该留在单文件主应用程序之外。

今天, @madskristensen与 Visual Studio 和流行的 Newtonsoft.Json NuGet 包分享了一个很好的说明

我们有时在类似的情况下同样的情况!

谢谢@DoCode。 一旦单文件应用程序实现并稳定,我们计划解决单文件插件的功能。 设计文档在这里讨论插件。

感谢@swaroop-sridhar 继续参与此功能,非常感谢。 具有撤消命令对于原始阶段尤其有用。

  1. 您可能会使用哪种应用程序? (例如,Windows 上的 WPF?Linux Docker 容器中的 ASP.NET?还有别的吗?)
    -- 命令行实用程序(控制台应用程序)
  2. 您的应用程序是否包含(非 .NET)C++/本机代码?
    - 不
  3. 您的应用程序会加载最初未包含在应用程序构建中的插件或其他外部 dll 吗?
    ——通常不会
  4. 您是否愿意重建和重新分发您的应用程序以包含安全修复程序?
    - 是的
  5. 如果您的应用程序启动速度慢 200-500 毫秒,您会使用它吗? 5秒呢?
    -- < 1 秒是我的偏好
  6. 您认为您的应用可接受的最大尺寸是多少? 5MB? 10? 20? 50? 75? 100?
    -- 大小不重要
  7. 你会接受更长的发布构建时间来优化大小和/或启动时间吗? 你能接受的最长的时间是多少? 15秒? 30秒? 1分钟? 5分钟?
    - 30秒
  8. 如果它可以将你的应用程序的大小减半,你愿意做额外的工作吗?
    -- 确定是否可以编写脚本或配置

1 - 您可能会使用哪种应用程序? (例如,Windows 上的 WPF?Linux Docker 容器中的 ASP.NET?还有别的吗?)

  • 游戏
  • 命令行工具

2 - 您的应用程序是否包含(非 .NET)C++/本机代码?

是的

3 - 您的应用程序是否会加载您最初未包含在应用程序构建中的插件或其他外部 dll?

是的,但是已经有了已知的符号,我不依赖反射,因为它很慢

4 - 您是否愿意重建和重新分发您的应用程序以包含安全修复程序?

是的

5 - 如果您的应用程序启动慢 200-500 毫秒,您会使用它吗? 5秒呢?

不,已经有点慢了

6 - 您认为您的应用程序可接受的最大尺寸是多少? 5MB? 10? 20? 50? 75? 100?

取决于项目,但对于一个简单的命令行工具,我不希望它大于个位数 mbs

7 - 你会接受更长的发布构建时间来优化大小和/或启动时间吗? 你能接受的最长的时间是多少? 15秒? 30秒? 1分钟? 5分钟?

只要它不影响开发/迭代时间,我可以延长发布构建时间
但请记住,.net 核心应用程序的构建时间已经比传统的 .net 框架应用程序长

每年软件速度变慢的趋势是不可接受的,我们有更好的硬件,没有理由这么慢

8 - 如果可以将你的应用程序的大小减半,你愿意做额外的工作吗?

是的!

我真的希望有一天能够使用 CoreRT 进行 AOT 编译,这样我们就可以拥有本地编译的应用程序,如 golang 和 rust 以及其他 AOT 编译的语言。 与 Unity3D 使用 IL2CPP 的方式类似。

@morganbr这是我和与我一起工作的人的一些意见。 我使用很多语言,但主要是 C#、Go 和 Python。

您可能会使用哪种应用程序? (例如,Windows 上的 WPF?Linux Docker 容器中的 ASP.NET?还有别的吗?)

ASP.NET Core 和控制台程序,主要在 Linux 上,可能是一些 Windows 工作负载,这取决于 ML.NET 是否满足我们的需求。

您的应用程序是否包含(非 .NET)C++/本机代码?

在这一点上不太可能。

您的应用程序会加载最初未包含在应用程序构建中的插件或其他外部 dll 吗?

这取决于。 目前我们可以将所有依赖项作为包提取,但我可以看到我们为商业库付费的情况,这可能意味着使用外部 DLL。

您是否愿意重建和重新分发您的应用程序以包含安全修复程序?

绝对地。 这是我们的首要任务。

如果您的应用程序启动速度慢 200-500 毫秒,您会使用它吗? 5秒呢?

我会说任何少于 10 秒的时间对我来说都很好。 启动时间通常无关紧要,只要它是幂等的和可预测的 - 即库以相同的顺序加载,变量以相同的顺序初始化,等等。

您认为您的应用可接受的最大尺寸是多少? 5MB? 10? 20? 50? 75? 100?

越小越好,主要用于分发目的。 分发可以是用户下载的二进制文件、带有二进制文件的容器镜像等。

你会接受更长的发布构建时间来优化大小和/或启动时间吗?

当然,只要它比 C/C++ 和 Java 更快,它应该是可以接受的。

你能接受的最长的时间是多少? 15秒? 30秒? 1分钟? 5分钟?

可能 30-90 秒是理想的,但任何低于 3 分钟的时间都可能是令人满意的。 中小型项目(1k-500k LOC)超过 3 分钟会有点多。 对于非常大的项目(1M LOC)来说,5 分钟是可以接受的。

如果它可以将你的应用程序的大小减半,你愿意做额外的工作吗?

这取决于。 如果额外的工作是添加可以模板化的样板代码,那可能很好。 如果不清楚,不是简单的工作,可能不是。

感谢@ mxplusb@dark2201 @ RUSshy 、@BlitzkriegSoftware 的回复

我相信在没有纯 AOT 编译的情况下,当前的设计可以最大程度地解决您的场景。 如果您对设计有任何疑问,请随时提出。

关于具有较低的启动时间和编译时间:
依赖于框架的应用程序的启动时间将大大缩短(至少在功能启动的初始阶段)。 编译时间也更短(因为需要嵌入的文件更少)。
一旦功能开发的第一次迭代完成,我会尽快回复您启动时间、编译吞吐量和文件大小测量。

@swaroop-sridhar 的时间表是什么?

@ayende第一次迭代正在积极开发中,我想我们将在几周内得到结果。

@swaroop-sridhar

我相信在没有纯 AOT 编译的情况下,当前的设计可以最大程度地解决您的场景。 如果您对设计有任何疑问,请随时提出。

运行:HelloWorld.exe

捆绑的应用程序和配置文件直接从捆绑包中处理。
剩余的 216 个文件将在启动时提取到磁盘。

如果我需要从只读 fs 运行,这对于 IoT 来说很常见,或者如果 exe 在 ro 容器中运行,该怎么办? 为什么没有直接从exe运行它的选项?

@etherealjoy本节中显示的示例是自包含 HelloWorld 在开发此功能的第 2 阶段(如中所述)的预期输出。 在这个阶段,唯一可以直接从 EXE 运行的应用程序是依赖于框架的纯托管应用程序。

如果我们处于Stage 4Stage 5 ,则上述自包含应用程序可以直接从捆绑包中执行。

您可能会使用哪种应用程序? (例如,Windows 上的 WPF?Linux Docker 容器中的 ASP.NET?还有别的吗?)

基本上都在上面。 控制台实用程序、WPF、asp.net、游戏

您的应用程序是否包含(非 .NET)C++/本机代码?

是的。 Sqlite 就是一个很好的例子。

您的应用程序会加载最初未包含在应用程序构建中的插件或其他外部 dll 吗?

可能没有。

您是否愿意重建和重新分发您的应用程序以包含安全修复程序?

是的。

如果您的应用程序启动速度慢 200-500 毫秒,您会使用它吗? 5秒呢?

net.core 已经有点太慢了。 但是增加几秒钟是可以接受的。 5 秒——绝对不是。

您认为您的应用可接受的最大尺寸是多少? 5MB? 10? 20? 50? 75? 100?

现在无所谓

你会接受更长的发布构建时间来优化大小和/或启动时间吗? 你能接受的最长的时间是多少? 15秒? 30秒? 1分钟? 5分钟?

最多 5 分钟的感觉是可以接受的。 15-20 分钟的 UWP 发布版本让每个人都抓狂。

如果它可以将你的应用程序的大小减半,你愿意做额外的工作吗?

当然。

谢谢@mjr27

1 阶段的实施现已完成,可从3.0.100-preview5-011568 开始使用

可以通过将PublishSingleFile属性设置为true来将应用程序发布到单个文件,如此所述。

在这个版本中,所有嵌入文件在第一次运行时被提取到磁盘,并在后续运行中重复使用,如此所述。

编译吞吐量
将文件作为单个文件还是作为单个文件写入发布目录在时间上没有显着差异。

文件大小和启动
下表显示了构建为单个文件的几个应用程序的性能。

  • 控制台:一个 hello world 应用程序。 报告的时间是运行应用程序的时间,通过时钟滴答测量。
  • WPF 应用程序: msix 目录应用程序。 报告的时间是启动到初始屏幕的时间,通过秒表测量。
  • Fw:依赖于框架的构建
  • Self:独立的构建。
    所有运行均在关闭防病毒检查时进行计时,以尽量减少外部因素的影响。

测量 | 控制台固件 | 控制台自我 | WPF 固件 | WPF 自我
-- | -- | -- | -- | --
文件大小 (MB) | 0.32 | 66.5 | 20.69 | 118.9
正常运行(秒)| 0.123 | 0.127 | 3.32 | 3.24
单exe首次运行(秒)| 0.127 | 0.565 | 3.67 | 4.14
单个 exe 后续运行(秒)| 0.124 | 0.128 | 3.30 | 3.29

随着实施进入后续的开发阶段,我将不断更新这些数字。

好消息。 谢谢

暂存文档中提到的所有阶段都会在 3.0 RTM 中完成吗? 或者我们将不得不等待 3.0 后才能实施?

正如这里所提到的,.net core 3.0 预计将实现第 2 阶段,以便框架依赖的纯 msil 应用程序可以直接从包中运行。 剩余阶段将考虑进一步发布。

正如这里所提到的,.net core 3.0 预计将实现第 2 阶段,以便框架依赖的纯 msil 应用程序可以直接从包中运行。 剩余阶段将考虑进一步发布。

:(希望它能进入RTM。

@cup我以为你可以用dotnet publish -o out /p:PublishSingleFile=true /p:RuntimeIdentifier=win-x64做到这一点。 我刚刚通过创建一个香草控制台应用程序并运行该命令进行了测试。 它成功地工作了。

您可以通过使用dotnet publish -o out /p:PublishSingleFile=true /p:RuntimeIdentifier=win-x64 /p:IncludeSymbolsInSingleFile=true捆绑在.pdb文件中走得更远

感谢@seesharprun的详细说明。 是的@cup ,命令行单文件发布的推荐方法是从命令行设置PublishSingleFile属性。

@etherealjoy coreclr 运行时存储库将在一个月左右的时间内锁定 wrt 功能工作。 我认为所有阶段都没有足够的时间。 我希望后续阶段将在下一个版本中出现。

我今天得到了这个工作,但似乎改变提取基础目录不起作用。 根据本。 我应该能够在 runtimeconfig 中添加一个名为 ExtractBaseDir 的应用程序开关,以更改应用程序被提取到的位置。

我有一个看起来像这样的 runtimeconfig.template.json

{
    "configProperties" : {
        "ExtractBaseDir": "C:\\"
    },
    "ExtractBaseDir": "C:\\"   
}

生成的 app.runtimeconfig.json 如下所示:

{
  "runtimeOptions": {
    "configProperties": {
      "ExtractBaseDir": "C:\\"
    },
    "ExtractBaseDir": "C:\\"
  }
}

通过生成的 exe 运行时,尽管它仍会提取到 tmp 文件夹。

我假设此功能在 Preview5 中还没有准备好,但我想确认一下,因为不清楚“ExtractBaseDir”应该在 runtimeconfig.json 中的哪个位置并且没有示例。

@iloo15感谢您提出这个问题。

在当前预览版中,仅启用了环境变量DOTNET_BUNDLE_EXTRACT_BASE_DIR 。 这是因为在当前实施阶段,所有文件(包括runtimeconfig.json )在处理之前都被提取到磁盘中。

我正在努力在下一个预览版中添加对ExtractBaseDir的支持。

是否有一些关于如何使其与 ASP.NET Core 项目一起工作的指导? 推荐的方法是使用反射来查找我的系统类型从哪里加载并设置我的 IHostingEnvironment ContentRootPath 和 WebRootPath 相对于该目录?

@keithwill您可以使用AppContext.BaseDirectory获取将 app.dll 和所有其他文件提取到磁盘的位置。 因此,您可以将其用作ContentRootPath

@igloo15 :我很好奇您是否可以分享使用此选项的场景。

  • 您是否尝试将其用于调试或生产部署场景?
  • 在这种情况下环境变量就足够了,还是您需要runtimeconfig.json设置?

如果此设置有特定的用例,我也很想听听其他开发人员的意见。

@swaroop-sridhar

我有一个相当奇怪的情况。 我的软件存储在网络共享上,然后网络共享链接到一个随机的 windows vm 并直接从网络共享运行。 启动以从网络共享运行软件的 windows vm 不能对其进行任何修改或更改。 日志、配置文件等都必须在网络共享上创建。

奇怪的是,这种设置允许整个系统在一个文件夹中布局多个软件,然后为不同的配置/版本克隆该文件夹结构。 然后,当必须运行特定版本时,管理软件会启动 vms 并将 vms 上的驱动器映射到特定版本的网络共享并运行该软件。

1.) 它是一个生产部署场景
2.)环境变量会很困难,因为运行软件的计算机不断不同并且被破坏/重新制作

@cup我真的不同意这个话题是关于实现设计文档的,设计文档特别说ExtractBaseDir应该适用于这个功能。

如果这个问题不是关于实现这个设计文档那么这个问题是关于什么的?

感谢@iloo15 的解释。

我询问了配置选项,因为在 AppHost 代码的早期解析配置文件是有代价的。 目前 AppHost 不与解析 json 文件的代码链接(它们由 hostfxr/hostpolicy 使用)。 目前添加此功能会使 AppHost 更大/更复杂。 但是,一旦实施进入进一步的阶段(所有托管和运行时代码都链接在一起),这不再是问题。

@igloo15设计文档是其中的重要组成部分,但这个问题的核心是功能本身。 ExtractBaseDir是一个可选参数,我宁愿看到核心功能被传递给“当前”版本,而不是陷入可选参数的困境。

@cup ,我在这里问了这个问题,因为我在这个线程中引起了感兴趣的开发人员的注意。
虽然我不认为这个问题是题外话,但我理解你对这个线程长度的担忧。 下次我会创建一个链接的问题。 谢谢。

@cup @swaroop-sridhar

我首先要说的几件事是,此功能仅对阶段文档的阶段 1 很重要。 所有其他阶段 2 - 5 都是关于从包内部运行 dll 而不是提取。 因此,确定提取基本目录位置的设置对于阶段 2-5 并不是真正有用。 由于 NetCore 3.0 打算开发第 1 阶段,我希望有这个功能,因为那是它唯一有用的时候。

其次是我们如何将此方法与 dotnet-wrap 等其他方法区分开来。 对我来说,这种方法的主要优点是我们将这个过程构建到更接近编译代码时的构建链中。 此外,这是一个面向 .NET 的功能,因此可以了解 .NET Core 应用程序的细微差别,例如它们的配置、构建过程等。因此,我希望我们专注于将该工具与其他工具区分开来的功能。不是专门针对网络核心的。 这些将是诸如配置 dotnet-wrap 无法执行的提取位置之类的功能。

至于它是如何实现的,我不确定,但我认为,与其在运行时从配置中读取 ExtractBaseDir,我们不能在构建时将它嵌入到 bundle exe 中吗? 我还没有看到使这个捆绑过程工作的代码,但在我看来,它生成/构建了一个包含所有 dll 等的本机 exe。 难道我们不能在生成/构建 exe 的过程中读取捆绑的应用程序运行时配置文件,提取 ExtractBaseDir 并将其设置为本机捆绑 exe 的属性。

然后它不在运行时读取嵌入式运行时配置,而是在内部利用属性来确定提取位置。 这可能会快得多。

@iloo15 :将捆绑包的内容提取到磁盘可能对阶段 2 之后的某些应用程序有用。
例如,如果应用程序在包中有自定义的本机二进制文件,那么它们需要被提取到第 5 阶段。应用程序可能希望捆绑未知类型的内容文件,以便在运行时提取到文件中。

我同意随着我们进入进一步的开发阶段,此设置的实用性会降低。 但是,一旦我们添加了该选项,我们可能必须将其保留在所有后续版本中以保持兼容性。 因此,现在需要慎重考虑。

我同意将提取位置设置为构建时设置(例如:msbuild-property)而不是运行时配置选项更容易和更有效地实现 wrt。 谢谢。

您好,我有几个关于这个话题的问题:

我想使用单文件分发功能将 .NET Framework 4.7.2(例如)应用程序打包到单个可执行文件中。 这是此功能的目的还是仅用于支持 .NET Core 应用程序?

我的第二个问题与包装是如何完成的有关。 创建可执行文件时,目标框架运行时是否会捆绑到可执行文件中? 还是必须预先安装在将运行应用程序的目标机器上?

@gaviriar我认为它不支持 .NET Framework 应用程序。 是的,它可以将 .NET Core 运行时捆绑到可执行文件中(所谓的“自包含”发布)。

@tomrus88感谢您的回复。 对于 .NET Framework 应用程序来说,这是一个真正的耻辱。 您是否推荐任何方法可用于将 .NET Framework 运行时捆绑到可执行文件中?

我在文档的相关工作部分看到了一个工具列表。 对于我上面提出的用例,有什么特别值得推荐的吗?

@gaviriar .NET Framework 是 Windows 操作系统的一部分。 不支持将其捆绑到可执行文件中的方法。

@gaviriar需要捆绑运行时是切换到 .NET Core 的主要原因之一。

@jnm2@jkotas感谢您的澄清,这里是 .NET 世界的新手。 我同意我很想切换到 .NET Core。 但是,我面临的情况是我必须与针对 .NET Framework 的遗留库进行交互。 在这种情况下,我的理解是,如果我需要与这个库进行交互,我无法将我的应用程序切换到 .NET Core。 或者是否有其他方法可以使我的应用程序是 .NET Core 但仍可以与旧的 .NET Framework 库进行交互?

这取决于图书馆。 许多以 .NET Framework 为目标的库也可以在 .NET Core 上正常运行。 您是否尝试过在 .NET Core 中使用该库? 如果您已验证它不起作用,则在找到解决方案之前您无法切换。

我确实尝试过,但这不起作用,因为我们正在谈论特定于 Windows 的库。 我想我别无选择,只能坚持以 .NET Framework 为目标而错过您所说的 .NET Core 乐趣:/

@gaviriar这是题外话,但是您是否尝试过 Windows 兼容性 NuGet 包,它为 .NET Core 提供了 Windows api,用于您描述Microsoft.Windows.Compatibility的确切目的

@gaviriar :更多说明:

创建可执行文件时,目标框架运行时是否会捆绑到可执行文件中? 还是必须预先安装在将运行应用程序的目标机器上?

这取决于构建。 依赖于框架(运行时安装在目标上)和自包含(运行时与应用程序一起打包)应用程序都可以作为单个文件发布 - 但仅限 .net core 3.0 应用程序。

我在文档的相关工作部分看到了工具列表。 对于我上面提出的用例,有什么特别值得推荐的吗?

对于 .net 框架,我可以建议的最佳解决方案是让应用程序自行处理文件提取。 例如:将依赖项作为托管资源嵌入到应用程序二进制文件中,然后在启动时显式提取资源。 您还可以使用捆绑库进行捆绑和提取(因为该库是基于网络标准构建的),但使用托管资源是更好的解决方案。

@igloo15和 @swaroop-sridhar 我很欣赏这些意见,因为它们离题了。 虽然我希望其他人在有一天阅读时发现这个讨论很有用。

我将评估您与我分享的选项,并让您知道结果证明哪种方法最适合我的用例。

谢谢!

快速更新!

我已经测试了这个简单的Hello World应用程序。 我可以成功构建单个文件可执行文件。 但是,正如您从项目配置中看到的那样,它是独立的。 但是,当我尝试在未安装任何 .NET 核心运行时的全新 Windows 7 安装上运行此可执行文件时,我看到以下错误:

Failed to load the DLL from [C:\Users\vagrant\AppData\Local\Temp\.net\HelloWorld
\muasjfkf.kyn\hostfxr.dll], HRESULT: 0x80070057
The library hostfxr.dll was found, but loading it from C:\Users\vagrant\AppData\
Local\Temp\.net\HelloWorld\muasjfkf.kyn\hostfxr.dll failed
  - Installing .NET Core prerequisites might help resolve this problem.
     https://go.microsoft.com/fwlink/?linkid=798306

我可以看到的是,将应用程序发布为dotnet publish /p:PublishSingleFile=true/p:PublishSingleFile=false不会改变可执行文件的大小。 这是预期的行为吗?

重现步骤

  1. 发布项目生成HelloWorld.exe

  2. 将可执行文件复制到未安装任何 .NET Core 运行时的 Windows 7 安装

  3. 运行HelloWorld.exe

@gaviriar我尝试了您发布的示例,它对我来说效果很好。
也就是说,它为 HelloWorld 构建了一个运行正常的独立单 exe。

我可以看到,将应用程序构建为 dotnet publish /p:PublishSingleFile=true 或 /p:PublishSingleFile=false 不会改变可执行文件的大小。 这是预期的行为吗?

这绝对不是预期的。 单文件应用程序应约为 70MB(用于 gitlab 页面上指示的调试版本)。 否则,它是一个正常的 apphost,预计会完全按照您上面提到的那样失败。 您是从干净的目录发布吗?

关于您的项目文件的另一个注意事项 - 您的属性之一被错误地写为IncludeSymbolsInFile而不是IncludeSymbolsInSingleFile 。 但这与您报告的问题无关。

@gaviriar你的目标运行时标识符是什么。 如果您没有正确设置它,它将不会捆绑正确的 hostfxr.dll。 hostfxr.dll 可能需要特定的 vc++ 可再发行文件,而该文件在您的 Windows 7 机器上不存在。 您可以检查给定路径上的 dll 是否存在,如果它确实尝试在依赖项walker 工具中加载它,以查看它所依赖的 dll 是否存在。

合并 IL:ILMerge 之类的工具将来自许多程序集的 IL 合并为一个,但在此过程中会丢失程序集标识。 这不是单文件功能的目标。

失去装配体身份

这不是问题。
我总是反对对单个 exe 独立功能使用 zip/unzip 或其他捆绑解决方案。
因为这将导致下一个问题。
如何减小单个 exe 文件的大小!?? 或者为什么 exe 文件很大,但它只是一个你好世界!??

总是有问题,需要解决,何不做一个彻底的。
其实从一开始就应该使用类似IL-Merge的方式。

事实上,人们并没有真正对单个 EXE 如此着迷。
exe(1)+dll(1-2) 也可以,但不是 400 mount dlls 。
ppl 不希望将代码永远不会执行到服务器磁盘并运行它占用服务器内存。
人们只是想降低部署成本。(磁盘、内存、带宽等......)

我删除了 3.0 里程碑,以便此问题通过本文档中描述的阶段跟踪单文件功能的实现,这超出了 3.0 版本的范围。

@sgf ,我同意需要做一些工作来减小文件大小。 减小大小的机会有两种形式,它们与单个 exe 无关:
1)减少需要发布的内容量。 例如:
* 使用类似 ILLinker 的工具来修剪不必要的二进制文件、程序集的一部分等。
这项工作由 dotnet/coreclr#24092 和 mono/linker#607 等问题单独跟踪
* 我们可以考虑其他措施,例如在不支持 JITting 的情况下使用“缩减能力”模式来减少
2) 为单文件发布增加压缩功能。

上述功能可以协同工作以减小单文件(和非单文件)应用程序的大小:
例如,对于 HelloWorld 控制台应用程序,
正常发布大小 = 67.4 MB
修剪后的大小 = 27 MB
修剪,单个文件,压缩(通过原型)= 12MB

@swaroop-sridhar 有没有办法在应用程序中获取单个文件 exe 的路径?
使用Assembly.GetExecutingAssembly().Location将在 \AppData\Local\Temp.net 中输出提取目录。

@chris3713目前访问 exe 位置的最佳方式是通过 PInvoke 调用本机 API。 例如: GetModuleFileNameW (Null, <buffer>, <len>)

@chris3713目前访问 exe 位置的最佳方式是通过 PInvoke 调用本机 API。 例如: GetModuleFileNameW (Null, <buffer>, <len>)

谢谢,我最终使用Process.GetCurrentProcess().MainModule.FileName

与 PublishSingleFile=true 发布的 Windows 服务一起使用 asp .net core worker/web api 工作正常。

当我注册服务并通过sc create testwk binPath="[...]/MyAspNetCoreWorker.exe"然后sc start testwk运行它时,我收到了这条成功消息:

[...]\bin\publish\x64>sc start testwk

SERVICE_NAME: testwk
        TYPE               : 10  WIN32_OWN_PROCESS
        STATE              : 2  START_PENDING
                                (NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x7d0
        PID                : 5060
        FLAGS              :

[...]\bin\publish\x64>sc stop testwk

SERVICE_NAME: testwk
        TYPE               : 10  WIN32_OWN_PROCESS
        STATE              : 3  STOP_PENDING
                                (STOPPABLE, NOT_PAUSABLE, ACCEPTS_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0

我迫不及待地想看到第 2 阶段的捆绑运行😀
顺便说一句,Stage 2 是否仍计划成为 .Net Core 3.0 的一部分?

感谢您的工作,.net core 中的发布服务现在真的很简单👍

CoreRT:( 2.5mb

image

image

没有 zip hack没有隐藏成本,引擎只包含需要的内容,并且使用 csproj 配置禁用未使用的功能(反射)

这就是 dotnet 所需要的,它与 GO 竞争,甚至在大小和性能方面都超过它

对于任何有兴趣的人,请查看回购:

https://github.com/dotnet/corert

MonoGame 有一些示例:

https://github.com/dotnet/corert/tree/master/samples/MonoGame

提出的解决方案都不能达到这个结果,CoreRT 确实是最好的,MS 不专注于它而犯了一个巨大的错误

我已经用dotnet-sdk-3.0.100-preview8-013656-osx-x64.tar.gz在 macOS 上测试了这个功能。 如果我们将配置从 Debug 切换到 Release,反之亦然并再次构建,它会增加 10MB 的可执行文件大小。 除非我们删除bin目录,否则使用旧配置重新编译不会恢复大小的增加。

mkdir /tmp/dotnet-preview8
curl -s https://download.visualstudio.microsoft.com/download/pr/a974d0a6-d03a-41c1-9dfd-f5884655fd33/cf9d659401cca08c3c55374b3cb8b629/dotnet-sdk-3.0.100-preview8-013656-osx-x64.tar.gz | tar -xvz -C /tmp/dotnet-preview8
export PATH=/tmp/dotnet-preview8:$PATH

dotnet new console -o /tmp/myApp
pushd /tmp/myApp

# publish with Debug
dotnet publish /p:PublishSingleFile=true,RuntimeIdentifier=osx-x64,Configuration=Debug -o bin/a/b/c/d

bin/a/b/c/d/myApp
# outputs: Hello World!

du -sh bin/a/b/c/d
# outputs  70M  bin/a/b/c/d

# publish with Release
dotnet publish /p:PublishSingleFile=true,RuntimeIdentifier=osx-x64,Configuration=Release -o bin/a/b/c/d

du -sh bin/a/b/c/d
# outputs:  80M bin/a/b/c/d

# publish with Debug again
dotnet publish /p:PublishSingleFile=true,RuntimeIdentifier=osx-x64,Configuration=Debug -o bin/a/b/c/d
# still outputs:  80M   bin/a/b/c/d

即使不更改构建配置,如果我们在初始构建之后调用 Rebuild 目标( /t:Rebuild ),输出大小也会增加。

感谢@am11看一看。 发布保留旧二进制文件是一个已知问题 (cc @nguerrera)。

从 .Net Core 3 preview 8 开始,我无法启动我的单文件发布应用程序。
在事件查看器中我有这个错误:

image

我在 Windows Server 2019 上

编辑:将 .Net Core SDK Preview 7 更新到 .Net Core SDK Preview 8 后出现此错误。重新启动服务器后,我可以再次正确启动我的应用程序。

@Safirion我们围绕这些预览对底层内部格式进行了更改。 您可能跨越了一个预览的输出并使用另一个构建。 预览 8 和 9 之间没有中断。

在 linux 上,默认提取路径似乎是 /var/tmp/.net 。 在 AWS lambda 上,/var 路径不可写,因此创建该文件夹失败。 为了在不设置 DOTNET_BUNDLE_EXTRACT_BASE_DIR 的情况下获得开箱即用的可运行性,我建议将其更改为 /tmp/.net 。 或者也许应该在公共位置进行多次尝试(/var/tmp、/tmp、与二进制文件相同的目录等)

@stevemk14ebr感谢您报告该问题。 您可以提交一个新问题来跟踪该工作吗? https://github.com/dotnet/core-setup

@jeffschwMSFT从 .Net Core P9 到 RC1 的通道引发了同样的错误。

Description: A .NET Core application failed.
Application: Setup.exe
Path: C:\Users\Administrateur\Desktop\Setup.exe
Message: Failure processing application bundle.
Failed to determine location for extracting embedded files
A fatal error was encountered. Could not extract contents of the bundle

@Safirion感谢您报告此问题。 你可以创建一个单独的问题来重现这个吗? https://github.com/dotnet/core-setup
抄送@swaroop-sridhar

@Safirion你能发送复制说明吗? 这种失败是确定性的吗?
请按照上面的@jeffschwMSFT建议在 core-setup repo 中提出问题。

我查看了这个,只有当应用程序在无法访问临时目录的环境中运行时才会发生这种情况。 例如,如果我这样做:

set "TMP=wrong_path"
myapp.exe

它失败了

Failure processing application bundle.
Failed to determine location for extracting embedded files
A fatal error was encountered. Could not extract contents of the bundle

请注意,捆绑包使用GetTempPath win32 API 查找临时目录。 哪个使用环境。 变量TMP ,然后是TEMP等等。 如果第一个带有值的路径包含无效路径,它将像这样失败。

您可以通过确保正确设置临时路径来解决此问题,或者您可以将DOTNET_BUNDLE_EXTRACT_BASE_DIR显式设置为您希望进行提取的位置。

@Safirion你能发送复制说明吗? 这种失败是确定性的吗?
请按照上面的@jeffschwMSFT建议在 core-setup repo 中提出问题。

将 .Net Core 更新到 RC1 并重新启动服务器后,我无法重现此问题。 (我已尝试卸载 RC1 并再次安装 Preview 9 以再次升级到 RC1,但这次我的应用程序启动良好)

从 SDK 3.0 P7 到 SDK 3.0 P8 以及从 SDK 3.0 P9 到 SDK 3.0 RC1 的过程中发生了此错误。
在这两种情况下,只需重新启动服务器即可解决问题。

当我升级到 RC1 时,我使用了一个 .Net Core Windows 服务(使用C:\Windows\Temp\.net文件夹进行提取的工作人员,因为它是由系统启动的)和一个 .Net Core WPF 应用程序(由用户启动会话脚本启动)。 也许这个错误是由.Net Core SDK安装期间运行的.Net core worker引起的,我不知道......

请注意,我安装的是 SDK 而不是运行时,因为我必须同时部署 3 个 .Core 运行时(asp.net 核心、桌面 .net 核心和核心),并且https://dot.net没有给我们“完整运行时”安装程序”(我什至没有找到桌面运行时安装程序,所以别无选择)...

更新:没关系,找到文档

你好,谢谢你的辛勤工作!

应该如何分发配置文件(例如 App.config)? 我刚刚使用确切的命令序列在 RHEL 7.6 上构建了一个控制台应用程序:

dotnet restore -r win-x64 --configfile Nuget.config
dotnet build Solution.sln --no-restore -c Release -r win-x64
dotnet publish --no-build --self-contained -c Release -r win-x64 /p:PublishSingleFile=true -o ./publish ./SomeDir/SomeProj.csproj

其中 SomeProj.csproj 启用了<PublishTrimmed>true</PublishTrimmed> ,结果得到了 2 个文件: SomeProj.exeSomeProj.pdb ,但不是SomeProj.config

@catlion具有 *.config 文件的默认行为,它们应该从包中排除。 默认情况下,所有非 pdb 文件都包含在包中。
以下将从单个文件中排除配置:

<ItemGroup>
    <Content Update="*.config">
      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
    </Content>
  </ItemGroup>

https://github.com/dotnet/designs/blob/master/accepted/single-file/design.md#build -system-interface

@摩根布

您可能会使用哪种应用程序? (例如,Windows 上的 WPF?Linux Docker 容器中的 ASP.NET?还有别的吗?)

适用于 Windows 的无头和 WPF 应用程序

您的应用程序是否包含(非 .NET)C++/本机代码?

是的,我们 P/Invoke 到各种原生 dll,一些我们无法控制的第三方项目,一些来自其他团队的内部项目。

您的应用程序会加载最初未包含在应用程序构建中的插件或其他外部 dll 吗?

是的,但我们使用 CompilerServices 从源代码编译它们。

您是否愿意重建和重新分发您的应用程序以包含安全修复程序?

是的

如果您的应用程序启动速度慢 200-500 毫秒,您会使用它吗? 5秒呢?

是的,我们的应用程序通常会长时间保持打开状态。

你会接受更长的发布构建时间来优化大小和/或启动时间吗? 你能接受的最长的时间是多少? 15秒? 30秒? 1分钟? 5分钟?

是的,我们可以等待几分钟。

如果它可以将你的应用程序的大小减半,你愿意做额外的工作吗?

是的

谢谢@john-cullen

让我也为@morganbr提供一些答案:) 我不知道你是否还在阅读这些:)

  1. 您可能会使用哪种应用程序? (例如,Windows 上的 WPF?Linux Docker 容器中的 ASP.NET?还有别的吗?)

可在不同平台(Linux、MacOS、Windows)上运行的文本冒险(控制台应用程序)

  1. 您的应用程序是否包含(非 .NET)C++/本机代码?

不。

  1. 您的应用程序会加载最初未包含在应用程序构建中的插件或其他外部 dll 吗?

目前不是,但从长远来看,支持某些场景会很棒。

  1. 您是否愿意重建和重新分发您的应用程序以包含安全修复程序?

是的。

  1. 如果您的应用程序启动速度慢 200-500 毫秒,您会使用它吗? 5秒呢?

是的。
(最好提供一些文本输出或消息框来通知用户正在发生的事情)

  1. 您认为您的应用可接受的最大尺寸是多少? 5MB? 10? 20? 50? 75? 100?

10-20 MB。

  1. 你会接受更长的发布构建时间来优化大小和/或启动时间吗? 你能接受的最长的时间是多少? 15秒? 30秒? 1分钟? 5分钟?

1+分钟没问题。

  1. 如果它可以将你的应用程序的大小减半,你愿意做额外的工作吗?

可能。

感谢您的回复,@lenardg。

此问题跟踪 .NET Core 3.0 单文件分发功能的进度。
这是该功能的设计文档和登台计划。

这是一个非常有用的功能,可以简化部署并减小大小。
关于设计的改进想法很少,特别是对于通过组策略严格控制环境的企业部署。

  1. 打包单个可执行文件时是否可以有条件地进行压缩?
    即添加 Wrap like 逻辑,因此我们不需要其他工具集成。
    压缩后会产生更小的 exe。

  2. 启动 exe 时,它​​会将其内容扩展到一个临时文件夹。 对于我们的产品,在某些用户站点上,客户端计算机受到严格控制,它们不允许从临时文件夹甚至 %userprofile% 位置启动可执行文件。

是否可以在“.\extract”或此类子文件夹中指定/控制提取位置或“就地提取”?
这允许组策略合规性以及使用单个 exe 功能的能力。

  1. 在打包之前,是否可以对文件夹进行签名,然后选择要打包的文件?
    我们可以对单个 exe 进行签名,但提取文件未签名,因此在某些其他客户端位置,它不允许执行,除非二进制文件已签名。

只是想让你知道,我今天看到了与@Safirion相同的问题。 我有一个针对 ubuntu 的 dotnetcore 3.0 应用程序,并在我的 Synology 服务器上运行它。 它不会提取 - 一直失败并出现错误“遇到致命错误。无法提取捆绑包的内容”。 重修了好几次,还是一样。 重新启动主机后,它再次正常工作。

了解这里是否有文件锁或其他东西以及是否有任何时间如何调试它会很有用。

@RajeshAKumar dotnet/core-setup#7940

我不确定该链接有何帮助。
它只是谈论临时提取,我在上面列出了 3 点。

我给了你其中一个的解决方案。 我不知道其他2

我给了你其中一个的解决方案。 我不知道其他2

临时提取物对我们不起作用,因此这不是解决方案。 我的要求是能够控制它提取的位置或可以就地提取到子文件夹中。
我们许多客户端计算机的 IT 管理员不允许从临时文件夹或用户配置文件运行可执行文件。
请重新阅读帖子https://github.com/dotnet/coreclr/issues/20287#issuecomment -542497711

@RajeshAKumar :您可以设置DOTNET_BUNDLE_EXTRACT_BASE_DIR来控制主机提取捆绑内容的基本路径。
请在此处查看更多详细信息: https ://github.com/dotnet/designs/blob/master/accepted/single-file/extract.md#extraction -location

@RajeshAKumar :捆绑的单个 exe 的压缩是 .net 5 正在考虑的一项功能: https ://github.com/dotnet/designs/blob/master/accepted/single-file/design.md#compression

现在,您需要使用其他实用程序来压缩生成的单个 exe。

@RajeshAKumar ,关于签名,您可以对所有发布并捆绑到单个 exe 中的文件/二进制文件进行签名。 当主机将嵌入式组件提取到磁盘时,各个文件本身将被签名。 正如您所提到的,您还可以对构建的单个文件本身进行签名。

这符合你的要求吗?

@Webreaper如果您可以重现该问题,请在打开COREHOST_TRACE的情况下运行该应用程序(将COREHOST_TRACE环境变量设置为1 )并共享生成的日志吗? 谢谢。

@swaroop-sridhar
这是一个非常酷的功能。 您是否有关于不同阶段何时可用的 dotnet 版本的链接?

@RajeshAKumar :您可以设置DOTNET_BUNDLE_EXTRACT_BASE_DIR来控制主机提取捆绑内容的基本路径。
请在此处查看更多详细信息: https ://github.com/dotnet/designs/blob/master/accepted/single-file/extract.md#extraction -location

谢谢你 swaroop-sridhar

@RajeshAKumar ,关于签名,您可以对所有发布并捆绑到单个 exe 中的文件/二进制文件进行签名。 当主机将嵌入式组件提取到磁盘时,各个文件本身将被签名。 正如您所提到的,您还可以对构建的单个文件本身进行签名。

这符合你的要求吗?

我试图弄清楚如何打破“编译”并发布部分。
我目前使用以下命令,所以它不给我签名的能力。
设置 publishargs=-c 发布 /p:PublishSingleFile=True /p:PublishTrimmed=True /p:PublishReadyToRun=false
dotnet publish -r win-x64 -o bin\Output\Win64 %publishargs%

由于上述编译以及修剪和包,不知道如何打破它们。
理想情况下,要使用您的方法,我将不得不编译,然后签名,然后最终发布。

@Webreaper如果您可以重现该问题,请在打开COREHOST_TRACE的情况下运行该应用程序(将COREHOST_TRACE环境变量设置为1 )并共享生成的日志吗? 谢谢。

谢谢。 它现在已经消失了,因为我重新启动了,但是如果它再次发生,记录它是有用的!

@RajeshAKumar您需要将签名标记为在捆绑之前运行的目标。
由于您使用的是PublishReadyToRunPublishTrimmed ,因此您不能使用标准的AfterbuildBeforePublish目标。

您可以添加一个在BundlePublishDirectory之前运行并执行签名的目标。

@swaroop-sridhar
这是一个非常酷的功能。 您是否有关于不同阶段何时可用的 dotnet 版本的链接?

谢谢@etherealjoy。 直接从 bundle 运行的单个文件的工作正在进行中,目标是 .net 5 版本,以我最好的理解。

@RajeshAKumar您需要将签名标记为在捆绑之前运行的目标。
由于您使用的是PublishReadyToRunPublishTrimmed ,因此您不能使用标准的AfterbuildBeforePublish目标。

您可以添加一个在BundlePublishDirectory之前运行并执行签名的目标。

谢谢你。
您提供的链接看起来像 MS Build 任务。
如何为“BundlePublishDirectory”创建处理程序? 这是在工作室/项目道具/构建事件中还是我必须从头开始创建一些东西。

@RajeshAKumar在这种情况下,您需要比预构建后构建事件更细粒度的东西。
所以,我认为你应该编辑项目文件并添加如下内容:

<Target Name="Sign" BeforeTargets="BundlePublishDirectory">
    ... 
</Target>

dotnet pack命令的预期行为是什么? 假设我们将单个 exe 工件推送到本地 nuget 存储库。 如果我尝试使用 Chocolatey 安装它,它会尝试恢复项目文件中列出的所有 nuget deps。 这是以前预期的,但我怀疑这种行为是否适用于 SFD。

是否可以在提取单文件自包含 WPF 应用程序期间添加一些进度条或加载指示器,这可能需要一些时间而什么也没发生。
一个基本的独立 WPF 应用程序超过 80 个月,提取可能需要超过 5 秒。 它对用户不是很友好,我收到了最终用户的投诉。

编辑:有什么方法可以在启动时自动清理旧版本?

@Safirion我看不出这个功能的范围。 如果您需要制作自己的小应用程序来显示启动屏幕并启动真正的应用程序,然后让真正的应用程序在启动时停止启动屏幕程序,您会得到最好的服务。

@ProfessionalNihilist我想你不明白我的意思。
一个空的 WPF 应用程序自包含在编译时使用 80 个月的磁盘存储空间。 如果不将其编译为依赖于框架的应用程序,您将无法拥有比 80mo 更小的 WPF 应用程序😉
问题是在应用程序启动之前提取包含的框架的时间。 所以它必须由 .Net Core 完成,并且与此功能完全相关。

也许添加在应用程序解压缩时显示png的功能?

@Safirion @ayende在启动期间缺少“UI 反馈”在此处跟踪: https ://github.com/dotnet/core-setup/issues/7250

dotnet pack命令的预期行为是什么? 假设我们将单个 exe 工件推送到本地 nuget 存储库。 如果我尝试使用 Chocolatey 安装它,它会尝试恢复项目文件中列出的所有 nuget deps。 这是以前预期的,但我怀疑这种行为是否适用于 SFD。

@catlion PublishSingleFile属性仅受dotnet publish命令支持。 因此,它对dotnet pack没有影响。 是否有同时使用单文件和打包的动机?

编辑:有什么方法可以在启动时自动清理旧版本?

@Safirion在当前版本中,清理是手动的,主机不会尝试删除提取的文件,因为它们可能会在未来的运行中重用。

好的,我会自己做清洁工😉
感谢你的回复。

@cup对于单声道没什么特别的,这只是一个标准编译,现在添加一个 nuget 引用,该解决方案不再是单个文件。 Mono 有mkbundle虽然可以实现单个文件

@Suchiman你确定吗? 我上面的示例生成了一个 3 KB 的文件。 虽然 dotnet 最小大小似乎是 27 MB:

https://github.com/dotnet/coreclr/issues/24397#issuecomment -502217519

@cup是的

C:\Users\Robin> type .\Program.cs
using System;
class Program {
   static void Main() {
      Console.WriteLine("sunday monday");
   }
}
C:\Users\Robin> C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe .\Program.cs
Microsoft (R) Visual C# Compiler version 4.8.3752.0
for C# 5
Copyright (C) Microsoft Corporation. All rights reserved.

This compiler is provided as part of the Microsoft (R) .NET Framework, but only supports language versions up to C# 5, which is no longer the latest version. For compilers that support newer versions of the C# programming language, see http://go.microsoft.com/fwlink/?LinkID=533240

C:\Users\Robin> .\Program.exe
sunday monday
C:\Users\Robin> dir .\Program.exe


    Verzeichnis: C:\Users\Robin


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       10.11.2019     18:36           3584 Program.exe

@Suchiman你确定吗? 我上面的示例生成了一个 3 KB 的文件。 虽然 dotnet 最小大小似乎是 27 MB:

#24397(评论)

您的 3k 文件仅在已安装单声道后才有效。 dotnetcore 单文件分发的重点是您不需要安装 clr,它是一个独立的单个 exe,包括运行时。

@Webreaper实际上我相信它只使用来自 mono 的 mcs 进行编译,因为他没有编写mono sun-mon.exe它可能在 .NET Framework 上运行。

但是 .NET Core 也支持运行时被预安装的场景,也就是依赖于框架的部署。 在这种情况下,它仍然不是完全单一的文件部署,因为还有一些用于 .NET Core 的附加文件,例如.deps.json.runtimeconfig.json

@chris3713目前访问 exe 位置的最佳方式是通过 PInvoke 调用本机 API。 例如: GetModuleFileNameW (Null, <buffer>, <len>)

似乎Environment.CurrentDirectory是一个更好的解决方案,尽管我还没有在 Windows 以外的其他东西上尝试过这两种方法。

编辑:不。 该路径可能会在应用程序的不同入口点发生变化。 不好。

在一个稍微相关的说明中,我在 VS for Mac 的最新预览版中的 Blazor 应用程序的单文件发布中发现了这种回归: https ://github.com/aspnet/AspNetCore/issues/17079 - 我已经在下面报告了它AspNeCore/Blazor,但这可能与 coreclr 组更相关 - 不确定。 留给你们四处走动!

@Suchiman小心,那个编译器有问题:

https://github.com/dotnet/roslyn/issues/39856

@cup除了使用我命名的文件路径外,您使用的是用 C++ 编写的旧 C# 5 编译器,这不是 roslyn,因此他们可能会关闭该问题。 但是roslyn可以做同样的事情,只是不同的路径......

在一个稍微相关的说明中,我在 VS for Mac 的最新预览版的 Blazor 应用程序的单文件发布中发现了这种回归: aspnet/AspNetCore#17079 - 我已经在 AspNeCore/Blazor 下报告了它,但可能是这个与 coreclr 组更相关 - 不确定。 留给你们四处走动!

@Webreaper感谢您报告该问题,这看起来像是关于静态资产的 ASP.net 问题。 所以,这是归档它的正确地方。

* 将帖子从其他问题移至此处。

@swaroop-sridhar ,

DotNet Core Single File WPF 应用程序的启动时间比在 .net 4.7 上构建的原始 ILMerge-ed WPF 应用程序慢很多。 这是可以预期的还是将来会有所改善?

构建来自我的 ImageOptimizer:https://github.com/devedse/DeveImageOptimizerWPF/releases

| 类型 | 预计首次启动时间 | 预计第二次启动时间 | 尺寸 | 下载链接 |
| -- | -- | -- | -- | -- |
| .NET 4.7.0 + ILMerge | ~3 秒 | ~1 秒 | 39.3mb | 链接|
| dotnet publish -r win-x64 -c Release --self-contained=false /p:PublishSingleFile=true | ~10 秒 | ~3 秒 | 49mb | |
| dotnet 发布 -r win-x64 -c 发布 /p:PublishSingleFile=true | ~19 秒 | ~2 秒 | 201 MB | |
| dotnet 发布 -r win-x64 -c 发布 /p:PublishSingleFile=true /p:PublishTrimmed=true | ~15 秒 | ~3 秒 | 136MB | 链接|
| dotnet 发布 -r win-x64 -c 发布 | ~2.5 秒 | ~1.5 秒 | 223kb 用于 exe(+400mb 在 dll 中)| |

@devedse确保“第二次启动”是几次运行的平均值(第一次除外)?
我很好奇,但没有解释为什么/p:PublishSingleFile=true /p:PublishTrimmed=true运行应该比 ` /p:PublishSingleFile=true运行慢。

所以,在调查之前,我想确保“第二次启动”中的数字是稳定的数字,并且启动的差异是可重现的,

另外,这个问题是关于单文件插件的,你能把性能讨论移到一个新问题,或者移到 dotnet/coreclr#20287 吗? 谢谢。

@swaroop-sridhar ,回答您关于它是平均值的问题:
我很难非常准确地计时,所以计时是通过在应用程序启动时计数来完成的,然后尝试几次以查看启动时间是否存在显着差异。 如果您知道更好的方法,您可以通过构建我的解决方案轻松重现它:https://github.com/devedse/DeveImageOptimizerWPF

我的主要问题与为什么与未捆绑的 .exe 文件相比,捆绑的(单个文件)应用程序需要更长的时间才能启动。

我在这里可能错了,但这对我来说很有意义,因为单个文件存在开销。 本质上,您有一个正在启动另一个应用程序的应用程序。 当 ILMerge 直接启动时。 ILMerge 仅将引用的 dll 合并到 exe 中,它没有将整个内容包装在另一层中,而这正是 PublishSingleFile 目前正在做的。

@devedse单个文件本质上是在开始 dotnet 运行之前提取、检查校验和等。
猜猜这就是为什么花了这么长时间。
提取被“缓存”,所以下次运行时,没有 IO 开销。

@RajeshAKumar ,嗯,在这种情况下真的要走吗? 采用 ILMerge 方式并实际将 DLL 合并到一个包中不是更好吗?

尤其是对于较大的 .exe 文件,您还会产生两次存储所有文件的磁盘空间成本。

@devedse我们都在等待此功能的下一阶段(从捆绑运行),但目前,这是唯一的解决方案。 😉

https://github.com/dotnet/designs/blob/master/accepted/single-file/staging.md

这就是你在桌面上使用 JIT 得到的结果,启动缓慢,看起来只有 Apple 明白这一点

(主要是重复已经说过的内容):
预计首次启动会慢得多 - 它将应用程序提取到磁盘上 - 大量的 IO。 第二次和后续启动应该与应用程序的非单文件版本几乎相同。 在我们的内部测量中,我们没有看到差异。

如何测量:我们使用了跟踪(Windows 上的 ETW)——进程启动时有事件,然后有可用于此的运行时事件——但这并不容易。

正如@Safirion所提到的,我们正在对单文件进行下一个改进,它应该直接从 .exe 运行大部分托管代码(不提取到磁盘)。 虽然还不能保证发布火车。

JIT:所有的框架都应该使用 Ready2Run(CoreFX,WPF)进行预编译,所以在启动时只有应用程序代码应该是 JIT 的。 它并不完美,但应该会产生很大的不同。 考虑到 ~1-2 秒的启动时间,我认为它已经在所有测试中使用它。

谢谢大家,我不知道计划的下一步。 这澄清了它。

预计首次启动会慢得多 - 它将应用程序提取到磁盘上 - 大量的 IO。

这不应该发生,你让用户体验糟糕的设计,糟糕的选择,这就是你让用户讨厌开发人员为他们使用的技术的方式

正如@Safirion所提到的,我们正在对单文件进行下一个改进,它应该直接从 .exe 运行大部分托管代码(不提取到磁盘)。 虽然还不能保证发布火车。

如果它很快就会改变,为什么现在正式发布呢? 应标记为预览/实验

在我看来,这是浪费时间和资源,专注于 AOT 编译和 tree-shaking,把你所有的资源放在那里,停止黑客攻击

@RUSshy为什么这么讨厌? 如果您不希望首次启动时出现启动延迟,请不要使用单文件部署。

我发现启动时间明显少于10s,而且因为它只是你第一次运行,所以完全没有问题。 我正在部署一个服务器端 web 应用程序,这意味着在大多数情况下它将启动一次,然后运行数天/数周,因此初始提取在方案中可以忽略不计 - 所以我更喜欢这个作为权宜之计,直到有一个单一编译的图像,因为它只是使部署比在该地方复制数百个 DLL 容易得多。

+1 在我的情况下,我们有一个生成单个 exe 的构建 docker 和一个单独的 docker 来运行应用程序(使用没有 dotnet 的常规 Alpine docker 映像)。 在 bulid 步骤之后,我们热加载运行时容器一次并docker-commit层。 随后,与依赖于框架的部署相比,我们没有观察到任何性能下降。 一旦实现并交付了从捆绑文件加载机制,我们将删除中间热加载步骤。

@vitek-karas,跟踪“从包中加载运行时资产”功能是否存在问题? 有兴趣了解存在什么样的障碍。 :)

@am11我们目前正在制定详细计划。 您可以查看已在https://github.com/dotnet/coreclr/tree/single-exe 中完成的原型。 真正的实现可能不会有太大的不同(显然更好的因式分解等等,但核心思想似乎是合理的)。

@Webreaper对于网络应用程序,这根本不是问题,但可能是因为现在推荐使用 .Net Core 3 进行 WPF/WinForm 开发,并且共享一个丢失在数百个 .dll 中的桌面应用程序 .exe 不是一个选项,那么我完全了解与此功能的第一阶段相关的挫败感。
;)

并且今天没有用户在重新单击 exe 之前等待 10 秒(或超过 3 秒)。 没有加载指示器的事实是此功能的第二个大问题。 不幸的是,加载指示器似乎不会成为 .Net Core 3.1 的一部分,因此用户必须耐心等待......

桌面开发人员真的在等待第 2 阶段,我希望这将成为 .Net 5 的一部分,因为实际上,.Net Core 中的桌面开发对于最终用户来说是一种非常糟糕的体验。

@RUSshy为什么这么讨厌? 如果您不希望首次启动时出现启动延迟,请不要使用单文件部署。

这不是仇恨,这是建设性和诚实的反馈,我关心 C# 和 .net,我每天都在使用,我不希望它被 GO 或其他东西取代

刚刚:
https://old.reddit.com/r/golang/comments/e1xri3/choosing_go_at_american_express/
https://old.reddit.com/r/golang/comments/ds2z51/the_stripe_cli_is_now_available_and_its_written/

负面反馈和正面反馈一样有用,但如果你把它当作“讨厌”,那我就帮不了你了

.net 社区是沉默、被动和有偏见的方式,破坏是唯一的出路

.NET 客观上是当今大多数应用程序的最佳平台。 我希望更多的人意识到这一点。

我从 Java、Go、Rust、Node 等其他平台听到的战争故事……坦率地说,令人不安。 这些平台是生产力杀手。

在我看来,这是浪费时间和资源,专注于 AOT 编译和 tree-shaking,把你所有的资源放在那里,停止黑客攻击

我同意。 .Net 有一个很棒的类型系统。 太多次它被使用反射规避。 工具需要专注于 AOT,但也要尽量减少反射。 https://github.com/dotnet/corert/issues/7835#issuecomment -545087715 将是一个很好的开始。 现在正在减轻数十亿美元的空值错误; 使用无反射代码(或链接器或 corert 兼容代码)的设置或标记的反射也应该这样做。

消除反射会很棒。 如果禁止反思,就可以避免如此多的痛苦。 我最近的恐怖故事是发现我无法移动代码,因为使用的框架(服务结构 SDK)发现将序列化字节绑定到序列化程序实现的程序集名称是谨慎的做法,并且不可能覆盖。

在阻止反思方面取得任何进展都是进步。

顺便说一句,正在寻找合并程序集的方法,以减少包大小和加载时间,允许整个程序优化。 我认为这个问题并不是真正针对那个。

编辑:因为这篇文章收集了一些反应只是为了澄清。 我相信元编程应该发生在设计时,代码就是数据,它在我的控制之下。

我喜欢用来强制执行我可以信任的不变量的类型。 运行时反射打破了这种信任。

因此,我希望将运行时反射替换为设计时元编程。 它还可以更强大地与分析器、快速修复和重构等用例重叠。

标记该区域的订阅者:@swaroop-sridhar
如果您想订阅,请通知 danmosemsft。

单文件功能设计在本文档中: https ://github.com/dotnet/designs/blob/master/accepted/2020/single-file/design.md
本期单文件应用进度跟踪: https://github.com/dotnet/runtime/issues/36590。
感谢大家对此问题的反馈和建议。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

omariom picture omariom  ·  3评论

nalywa picture nalywa  ·  3评论

GitAntoinee picture GitAntoinee  ·  3评论

noahfalk picture noahfalk  ·  3评论

matty-hall picture matty-hall  ·  3评论