Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Self-Contained publish with multiple executables? #18282

Open
GunArm opened this issue Jun 16, 2021 · 12 comments
Open

Self-Contained publish with multiple executables? #18282

GunArm opened this issue Jun 16, 2021 · 12 comments
Milestone

Comments

@GunArm
Copy link

GunArm commented Jun 16, 2021

I hope I am posting this in the right place since I'm currently using dotnet 3.1 which is in the dotnet/cli repo, but that repo has removed it's issue tracker.

I maintain an auto-updating application where external runtimes are a hazard that has cost us a lot of time and expense over the last 10 years. We are moving to a self contained build to finally leave this behind us. But our application is a bundle of several executables: a windows service, a UI agent, a handful of testing tools for users, some shims for loading other things. They cannot simply be put together into one exe and have their functionality switched by arguments because it's a mixture of winforms and cli, as well as the fact that several exes need to be easily found/run by users.

I have not been able to find any supported, official mechanism to create a self contained build of multiple projects/executables together, where the union of their dependencies are included and trimmed. If there is an official, proper way to do this and I've missed it, someone please tell me. I have however found a very easy workaround to "trick" the SDK into doing this:

  • Create a dummy exe project (I call it BuildHelper)
  • Add all the other exe projects as references to the BuildHelper project, like they were dlls
  • Publish the BuildHelper project, self contained and trimmed
  • Delete the BuildHelper project, dll/exe from the output

This workaround for the most part works very well. If I were to make independent self contained builds of all the exes separately, the output would total well over a gigabyte, because of all the duplicated runtime overhead, which is not acceptable for my purposes. Building in the way described above produces a single self contained runtime with the multiple exes in it for around 90mb, which zips down to about 45mb. Two takeaways, 1. you can see this is a very useful thing to be able to do, 2. as simple as this workaround is, there shouldn't be much technical challenge to providing this feature out of the box so that this dance would not be necessary. It is my hope that something like this can be provided/supported in the future.

Since it is not (I don't think) currently supported, there are a few oddities with it. For one thing I have not gotten it to work on dotnet 5, although I have not spent much time trying, but there is some funniness with the inclusion of deps.json files (maybe the tip an iceberg). More relevantly to me at the moment, although normal 64 bit builds work fine, when I try to make a build that is 32 bit (by specifying -r win-x86 when publishing BuildHelper), it doesn't work. If I leave the BuildHelper files in the output, I can run that successfully on a 32 bit machine. But if I try to run any of the other included (built as dependencies) exes on a 32 bit machine, I get errors that look like they are built for 64 bit, even though the publish folder is full of only x86 dlls.

Running the exes on a 32 bit machine gives the output
Program 'MyApp.exe' failed to run: The specified executable is not a valid application for this OS
I wonder if for some reason it is including 64-bit exe-shims with the 32bit trimmed dlls for the sub-applications...

Also if I try to run the 32 bit exes on a 64 bit machine I get the output

The library hostfxr.dll was found, but loading it from C:\temp\32client\hostfxr.dll failed
  - Installing .NET Core prerequisites might help resolve this problem. https://go.microsoft.com/fwlink/?linkid=798306

Another oddity with the 32 bit build is that the nuget package for webview2 includes the 64bit native libraries, rather than the 32 bit ones. So it seems like the sdk is somehow not fully getting the message that the "dependency exes" (and not just BuildHelper) are being published as win-x86.

Is there a supported way to do what I'm trying to do? If not, can I request such a feature?
Also any thoughts about the 32bit build situation above are welcome.

@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged Request triage from a team member label Jun 16, 2021
@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@GunArm
Copy link
Author

GunArm commented Jun 16, 2021

To add, I had said I wondered if the problem was if for some reason it is including 64-bit exe-shims with the 32bit trimmed dlls for the sub-applications...
This indeed appears to be the case. By independantly publishing one of the sub projects as 32 bit and then taking the resulting exe-shim from it and overwriting the one in the BuildHelper published bundle, execution is successful. Additionally by the test specified here https://superuser.com/a/889267 (looking at the characters after "PE" in the binary) the 32 bit build produced 64 bit exe-shims to go with the 32 bit dlls it produced.

@dsplaisted
Copy link
Member

@agocke @LakshanF @vitek-karas for discussion of a having multiple apps be self-contained but sharing the same runtime bits.

As far as copying deps.json files, that was fixed in the 5.0.200 SDK with #14488. You can use the latest SDK and still target earlier versions of .NET.

As far as the 32 / 64-bit mismatch, the issue is probably that the RuntimeIdentifier doesn't flow across project references. If you were to set the RuntimeIdentifier in the referenced Exe projects themselves, I think it would probably work.

@dsplaisted dsplaisted added this to the Discussion milestone Aug 20, 2021
@dsplaisted dsplaisted removed the untriaged Request triage from a team member label Aug 20, 2021
@dsplaisted dsplaisted removed their assignment Aug 20, 2021
@GunArm
Copy link
Author

GunArm commented Aug 20, 2021 via email

@agocke
Copy link
Member

agocke commented Aug 20, 2021

This is working really well for me now. But figuring out those gotchas
above (and accomodating them in my build projects/scripts) was rocky and
messy, and I distinctly felt like I was doing something not supported

Yup, that's the current situation. Putting multiple apps in the same self-contained directory is unsupported, but it often works. Trimming makes this very dicey -- it is very likely that something will be trimmed away but the other app uses but the trimmer didn't see, simply because the apps weren't analyzed in the same trimmer pass, because trimming works one app at a time.

We may decide to support something like this in the future, but it's not currently scheduled.

@GunArm
Copy link
Author

GunArm commented Aug 20, 2021

To be clear, I didn't just filemerge self contained publish directories. I did try that in early experimentation and it didn't work.

The "trick", so to speak, was realizing that for trimming to work (ever) it must account for the deps of deps, and thus if all exes were part of one dep tree, it would force the trimming pass to account for all of the deps of all of the exes at once. This lead me to my hack-solution, which is adding the exes as dependancies to an extra build-target exe with some place holder references between them - so that the trimming pass would theoretically parse through all of hierarchy and come out with a union of deps to trim from.

@vitek-karas
Copy link
Member

Yes - that might make it work in the default setup where we don't trim user code. If you were to turn on full trimming even for user code this would probably break as well (as the additional exes would to even keep their Main methods, and would probably lose lot of necessary dependencies).

@GunArm
Copy link
Author

GunArm commented Aug 20, 2021

If you were to turn on full trimming even for user code this would probably break as well

Interesting, by "default" vs "full" trimming, are you referring to "link" vs "copyused"? I'd like to know what controls that so I can be careful to avoid it.

@vitek-karas
Copy link
Member

It's "link" versus "copy" (copyused is now sort of deprecated) - the property is TrimMode - if you don't set that, you're should be OK.

@GunArm
Copy link
Author

GunArm commented Aug 20, 2021

Thanks. Although, https://docs.microsoft.com/en-us/dotnet/core/deploying/trimming-options says the default is changing in .NET 6 from copy (it says "copyused") to "link". So maybe I should explicitly set it to "copy" (or "copyused"? I can't find "copy" documented anywhere yet) to be safe for when I forget this and upgrade my tools.

@vitek-karas
Copy link
Member

Sorry - my bad - the property you want to avoid is TrimmerDefaultAction - which should be set to "copy" by default. (simplifying a little: this one controls what happens to assemblies which are not marked with IsTrimmable attribute, so your application code, the TrimMode controls what happens with those which are marked as trimmable, so typically the framework)

@GunArm
Copy link
Author

GunArm commented Aug 20, 2021

Appreciate the clarification!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants