Open Logerfo opened 4 years ago
cc @swaroop-sridhar
@Logerfo I think what you need to do is to publish the app as a framework-dependent single-file.
That is, dotnet publish -r <runtime> --self-contained=false /p:PublishSingleFile=true
This will embed all dependencies of the app except the framework binaries.
@swaroop-sridhar I didn't know about that possibility, but it still doesn't make sense to me. Why does it require a runtime identifier to be specified if it's not self contained? The default runtime environment is supposed to be framework dependent...
@Logerfo the single-exe app includes the managed components and the native host. The result is a native binary that must be built per target OS/architecture. Therefore, PublishSingleFile
option requires that a RID
be specified.
The framework-dependent aspect just means that we don't package the framework binaries into the app -- thus making it smaller. That is, the resultant app is still a platform specific native binary containing all dependencies except for framework binaries (which need to be installed elsewhere).
The current support for framework dependent single-file apps seems to fulfill your original objective -- embedding third-party dependencies and not including the framework.
What you're probably also asking is to embed all dependencies into the (portable) managed app, which can then be launched via the dotnet
host. We don't support embedding dependencies into managed assemblies yet. This is similar to supporting single-file plugins -- which may be considered in further stages.
Adjusted the title to refer to the issues discussed in the previous comment
Another problem: I have a console application which has references to System.Windows.Forms
in order to create a notification icon in the task bar. This worked fine with .NET Framework and Mono, since it would only crash on Linux when the method was called (and of course my application only allows it if it's being run on Windows). While migrating to .NET Core, I noticed a new file called <MyApplication>.runtimeconfig.json
. This file has a reference to Microsoft.WindowsDesktop.App
, which makes the application unlaunchable on Linux. Manually changing it to Microsoft.NETCore.App
after building solved the problem, making the application behave just like Mono would (crash only when encounters unavailable API). Publishing as a single file embeds this json file inside the published file, so I cannot edit it anymore. I don't know if it's safe (and if it would work) to open the file in a text editor and change it nevertheless.
@swaroop-sridhar ,
When I tried running your command I saw the following error:
C:\XGitPrivate\DeveImageOptimizerWPF\DeveImageOptimizerWPF>dotnet publish -r win-x64 -c Release --self-contained=false /p:PublishSingleFile=true /p:PublishTrimmed=true
Microsoft (R) Build Engine version 16.3.0+0f4c62fea for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.
C:\XGitPrivate\DeveImageOptimizerWPF\DeveImageOptimizerWPF\DeveImageOptimizerWPF.csproj : warning NU1701: Package 'MvvmLightLibs 5.4.1.1' was restored using '.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8' instead of the project target framework '.NETCoreApp,Version=v3.0'. This package may not be fully compatible with your project.
Restore completed in 951.03 ms for C:\XGitPrivate\DeveImageOptimizerWPF\DeveImageOptimizerWPF\DeveImageOptimizerWPF.csproj.
C:\XGitPrivate\DeveImageOptimizerWPF\DeveImageOptimizerWPF\DeveImageOptimizerWPF.csproj : warning NU1701: Package 'MvvmLightLibs 5.4.1.1' was restored using '.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8' instead of the project target framework '.NETCoreApp,Version=v3.0'. This package may not be fully compatible with your project.
DeveImageOptimizerWPF -> C:\XGitPrivate\DeveImageOptimizerWPF\DeveImageOptimizerWPF\bin\Release\netcoreapp3.0\win-x64\DeveImageOptimizerWPF.dll
C:\Program Files\dotnet\sdk\3.0.100\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.ILLink.targets(113,5): error NETSDK1102: Optimizing assemblies for size is not supported for the selected publish configuration. Please ensure that you are publishing a self-contained app. [C:\XGitPrivate\DeveImageOptimizerWPF\DeveImageOptimizerWPF\DeveImageOptimizerWPF.csproj]
@Logerfo editing the single-file app once built is not a good idea. The single-file app contains a directory of contents, with hard-coded offsets and sizes. So, there's a good chance your edit will break the knowledge encoded in this directory.
The edit to change the framework seems like a hack to me. My advise would be:
BundlePublishDirectory
target in the SDK). @devedse You've added the /p:PublishTrimmed=true
setting.
Trimming assemblies for framework-dependent apps is not yet supported (because there's no framework assemblies to trim).
But you can still publish a framework-dependent app as a single-file.
@swaroop-sridhar ,
The startup times of DotNet Core Single File WPF apps is a lot slower then the original ILMerge-ed WPF application build on .net 4.7. Is this to be expected or will this improve in the future?
Builds come from my ImageOptimizer: https://github.com/devedse/DeveImageOptimizerWPF/releases
Type | Estimated First Startup time | Estimated second startup time | Size | Download link |
---|---|---|---|---|
.NET 4.7.0 + ILMerge | ~3 sec | ~1 sec | 39.3mb | LINK |
dotnet publish -r win-x64 -c Release --self-contained=false /p:PublishSingleFile=true | ~10 sec | ~3 sec | 49mb | |
dotnet publish -r win-x64 -c Release /p:PublishSingleFile=true | ~19 sec | ~2 sec | 201 mb | |
dotnet publish -r win-x64 -c Release /p:PublishSingleFile=true /p:PublishTrimmed=true | ~15 sec | ~3 sec | 136mb | LINK |
dotnet publish -r win-x64 -c Release | ~2.5 sec | ~1.5 sec | 223kb for exe (+400mb in dlls) |
@devedse, to make sure, is the "second startup" the average of several runs (other than the first)?
I'm curious, but lacking any explanation for why the /p:PublishSingleFile=true /p:PublishTrimmed=true
run should be slower than `/p:PublishSingleFile=true
run.
So, before investigating, I want to make sure the numbers in the "second startup" are stable numbers and that the difference in startup is reproducible,
Also, this issue is about single-file plugins, can you please move the perf discussion to a new issue, or to https://github.com/dotnet/coreclr/issues/20287? Thanks.
Tagging subscribers to this area: @swaroop-sridhar Notify danmosemsft if you want to be subscribed.
Would these DLLs be openable by double clicking them, similar to the old CIL EXEs or Java JARs, or would they still require the user to pass them to the dotnet
command manually?
Single-file platform-independent DLLs would be great, but from a UX perspective for a GUI user, they would still need some sort of shell script or LNK shortcut to launch them with the dotnet
loader, no? This is easy on the command line where you can just run dotnet Application.dll
, but it's harder when you've just unzipped an application and are opening it from the GUI file browser. That's certainly far from an insurmountable issue, since it's easy to cover nearly every platform with just those two tiny files (.lnk
and .sh
), but it means that things are back up to three files total. What could be done for a portable GUI launching solution?
Would these DLLs be openable by double clicking them, similar to the old CIL EXEs or Java JARs, or would they still require the user to pass them to the dotnet command manually?
Short answer: no.
In order to support double-click like action on anything but executables there would have to be one of two things:
Additionally for GUI applications (at least on Windows) - running app.exe
and running dotnet app.dll
is not completely identical. On Windows the .exe
contains native resources like the icon, manifest, version and so on. These are things handled by the OS itself, some of them even before the app gets to run any code (icon and to some degree manifest for example). dotnet.exe
can't do these (for example the icon), so by running the app via dotnet.exe
looses some of this functionality. Also dotnet.exe
is currently a CUI (Console UI) application, which means that when started OS will automatically open a console window for it (unlike GUI apps which don't get console window) - this is pretty distracting. It's technically possible for dotnet.exe
to somehow detect that it's running a GUI app and close the console, but it console window would still popup/close for a short time, there's no way to avoid that. (In theory dotnet.exe could be marked as GUI and open console when it needs to, but that is actually a very tricky thing to do correctly in all situations, it's highly unlikely we would go this route).
I don't know enough about GUI systems on Linux/Mac to be able to tell if there's a more elegant solution on those platforms.
I don't know enough about GUI systems on Linux/Mac to be able to tell if there's a more elegant solution on those platforms.
On Linux (and I think it's the same on Mac), every executable is fundamentally a command-line one, and just has the option of opening a GUI window. If you double click on an executable which doesn't open a GUI, it will just run and close without anything visible happening. So I don't think associating a file with dotnet
would be an issue there. The issue of the fact that Windows distinguishes between CUI and GUI executables didn't occur to me, and it's certainly a bit of a conundrum.
I think a good cross-platform solution could be to have a loader which is itself a .NET program, and which includes the native loader as usual (and is therefore platform-specific). Then, it could be associated with a file extension for single-file .NET applications, and when you open one of those applications, hopefully it would be able to call the entry point without having to create an entire new process. Such a tool could be made and distributed separately from .NET itself, but depending on it would probably be too risky at that point, since users would be very unlikely to have it, so the file would just be unrecognized. So I think for such a format to be useful it would have to be part of .NET itself.
Ultimately though, I can see why this is not as large of a priority as it used to be. Opening loose executables off a disk is becoming a lot less common in a world of package management, so having a platform-specific wrapper script to run dotnet Application.dll
isn't hard for a package maintainer to add even for a closed-source application that they don't have the source to.
Currently, single executable applications (with or without trimming) packages both framework and third party libraries. Because of the former, the result is very large sized if compared to non-packaged builds. There should be an option to only package third party libraries, still relying on the installed framework. An alternative is Costura, but: 1) It's not free; 2) It's being deprecated in favor of single executables, as for Fody/Costura#442.
I don't think it would be hard to implement this, since the current feature is more complex than the requested alternative approach.