dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
14.91k stars 4.63k forks source link

What are the minimum required files to deploy a Mono 6 console app on macOS? #91167

Closed rochus-keller closed 1 year ago

rochus-keller commented 1 year ago

I'm the author of https://github.com/rochus-keller/Oberon/ which uses Mono to debug and run Oberon+ applications.

So far I'm using Mono 5 on all supported platforms and have just to deploy the mono-sgen executable together with mscorlib.dll which works well and is enough to run all Oberon+ applications (even GUI applications are supported via pinvoke and SDL2 or NAppGUI shared libraries).

On Mac M1 I was restricted to Mono 5 x86_64 on Rosetta. But since homebrew supports a native Mono 6 M1 version, I would like to compose a minimal set of files for my deployment in the same fashion as I did with Mono 5 (I don't need the .NET framework and don't want to impose Oberon users on Mac to install homebrew and a 400 MB Mono package). Unfortunately after experimenting for a week I'm still not successful.

Especially I'm confused that there is e.g. a mscorlib.dll.dylib; is this required to use mscorlib.dll? Isn't mscorlib.dll just an adapter to System.Core.dll which calls internal functions of the mono executable?

I'm looking for the minimum set of files from the big Mono homebrew installation which I have to copy to my local application folder.

Does anyone have experience with this?

huoyaoyuan commented 1 year ago

.NET Framework compatible Mono lives in mono/mono repo. This repo contains .NET Core compatible Mono. The same developers may also be here though.

rochus-keller commented 1 year ago

As I said I'm not interested in the framework, just in what is included in mscorlib.dll and the mono executable.

EDIT: I actually created this issue in the https://github.com/mono/mono/ repository, where I selected "Mono runtime or class libraries in .NET 5 or later" which brought me to this repository.

huoyaoyuan commented 1 year ago

where I selected "Mono runtime or class libraries in .NET 5 or later"

Note that Mono 5 is not Mono (.NET 5). There are two versions. If you do not have System.Private.CoreLib.dll or libcoreclr.so (Mono uses the same filename of coreclr to provide drop-in compatibility), you are probably using the version in mono/mono repo.

huoyaoyuan commented 1 year ago

@lambdageek for help

lambdageek commented 1 year ago

@rochus-keller I'm going to explain some things that maybe you already know, in which case apologies.

Right now .NET exists in two general families: there is .NET Framework (windows-only, closed source, currently at version 4.8.x), and there is .NET (formerly known as .NET Core, open-source, multi-platform, currently version 7.0.x). They are not compatible - user-written C# code generally targets one or the other (or both using something called .NET Standard 2.0... which I'm not going to talk about further). The .NET Framework is in some kind of a maintenance mode - generally new features are going into .NET, not .NET Framework.

Mono also exists in two versions now. There is the version of Mono in https://github.com/mono/mono - this is a separate standalone project that aims to be compatible with .NET Framework - it is currently at version 6.12.x and is also in some kind of a maintenance mode. The version numbers for this flavor of Mono are "Mono 5.8", "Mono 6.12" etc.

In this repo https://github.com/dotnet/runtime, there is a version of Mono that aims to be compatible with modern .NET (actually it's the official .NET runtime on some platforms). This version is generally keeping up with the new features that modern .NET receives. The version numbers for this version of mono are ".NET 5", ".NET 6", ".NET 7" etc.


So I think your question is about mono 6.x, not about .NET 6.

In that case what you fundamentally need just mscorlib.dll and libmonosgen-2.0.dylib. But a lot of user code depends on the other .dll files that come with Mono (this is the "BCL" - base class library) so you may need many of the System..dll files too. Additionally there are some native libraries `libmono-native-.dylib` that the runtime depends on.

Especially I'm confused that there is e.g. a mscorlib.dll.dylib; is this required to use mscorlib.dll?

The *.dll.dylib files are AOT images that are used to speed up the execution of the IL code using ahead of time compilation. You don't need them (in which case Mono will fall back to just-in-time compilation of the IL code).

Isn't mscorlib.dll just an adapter to System.Core.dll which calls internal functions of the mono executable?

In mono/mono, mscorlib.dll is the fundamental assembly that contains the basic classes needed at runtime. (in modern .NET that just is done by System.Private.CoreLib.dll)

rochus-keller commented 1 year ago

Thanks. I assume the mscorlib.dll is part of the Mono runtime, therefore - as I said - I concluded that the issue type "Mono runtime or class libraries in .NET 5 or later" was the one to go. Am I right to assume that the "Mono runtime" is the same in "Mono 6.12" and ".NET x" (with x >= 5)? Otherwhise there should be yet another issue type in https://github.com/mono/mono/issues/new/choose, e.g. "Mono runtime 6.12.x".

I already tried combinations of mscorlib.dll and libmono-native-*.dylib, but was not successful.

I also run mkbundle on my test application; the resulting, integrated executable didn't work, but it at least confirmed which assemblies are required: I18N.West.dll, I18N.dll and mscorlib.dll.

I also tried various combinations considering these three assemblies, even with both local and selected dlls and dylibs in the original paths expected by the homebrew installation.

Whatever I do, I always get the same message from Mono:

System.TypeInitializationException: The type initializer for 'Input' threw an exception. ---> System.TypeInitializationException: The type initializer for 'Sys' threw an exception. ---> System.DllNotFoundException: System.Native assembly:<unknown assembly> type:<unknown type> member:(null)
  at (wrapper managed-to-native) Interop+Sys.LChflagsCanSetHiddenFlag()
  at Interop+Sys..cctor () [0x00000] in <83a10ac9d03d4b5b9cab686735823828>:0 
   --- End of inner exception stack trace ---
  at System.IO.FileSystem.FileExists (System.ReadOnlySpan`1[T] fullPath, System.Int32 fileType, Interop+ErrorInfo& errorInfo) [0x00007] in <83a10ac9d03d4b5b9cab686735823828>:0 
  at System.IO.FileSystem.FileExists (System.ReadOnlySpan`1[T] fullPath) [0x00006] in <83a10ac9d03d4b5b9cab686735823828>:0 
  at System.IO.File.Exists (System.String path) [0x00043] in <83a10ac9d03d4b5b9cab686735823828>:0 
  at System.TimeZoneInfo.TryLoadTzFile (System.String tzFilePath, System.Byte[]& rawData, System.String& id) [0x00000] in <83a10ac9d03d4b5b9cab686735823828>:0 
  at System.TimeZoneInfo.TryGetLocalTzFile (System.Byte[]& rawData, System.String& id) [0x0000f] in <83a10ac9d03d4b5b9cab686735823828>:0 
  at System.TimeZoneInfo.GetLocalTimeZoneFromTzFile () [0x00000] in <83a10ac9d03d4b5b9cab686735823828>:0 
  at System.TimeZoneInfo.GetLocalTimeZone (System.TimeZoneInfo+CachedData cachedData) [0x00000] in <83a10ac9d03d4b5b9cab686735823828>:0 
  at System.TimeZoneInfo+CachedData.CreateLocal () [0x00018] in <83a10ac9d03d4b5b9cab686735823828>:0 
  at System.TimeZoneInfo+CachedData.get_Local () [0x0000c] in <83a10ac9d03d4b5b9cab686735823828>:0 
  at System.TimeZoneInfo.get_Local () [0x00000] in <83a10ac9d03d4b5b9cab686735823828>:0 
  at System.TimeZoneInfo.GetDateTimeNowUtcOffsetFromUtc (System.DateTime time, System.Boolean& isAmbiguousLocalDst) [0x00000] in <83a10ac9d03d4b5b9cab686735823828>:0 
  at System.DateTime.get_Now () [0x00008] in <83a10ac9d03d4b5b9cab686735823828>:0 
  at Input..cctor () [0x00012] in <5b75ff3b056b458caa7a9b17c0ef528b>:0 
   --- End of inner exception stack trace ---
  at Run.beg?n () [0x00018] in <651f1f0caaff467725824d5835cbe466>:0 
  at Harness.beg?n () [0x00013] in <4c248f10230a4f82978246d9ab4f650a>:0 
  at Tester.beg?n () [0x00018] in <e2b6825127414f619c97c7796c44bee6>:0 
  at OBX.Runtime.pcall (OBX.Command cmd, System.Boolean report) [0x00000] in <ff1ddc7e08a04893984d4f2c743f28e1>:0 

Here is a compiled version of my test application: http://software.rochus-keller.ch/Are-we-fast-yet_CLI_2021-08-28.zip. It doesn't use any other class/method than the ones provided by mscorlib.dll.

lambdageek commented 1 year ago

@rochus-keller you need to copy etc/mono/config from the homebrew install of mono and set the MONO_CONFIG environment variable to the location of that file.

~Here is what I did - make a script /tmp/make-bespoke.sh:~ this script is outdated. I'm uploading a fixed one

... wrong...

Then run like this:

$ chmod a+x /tmp/make-bespoke.sh
$ /tmp/make-bespoke.sh
$ % MONO_PATH=/tmp/bespoke MONO_CONFIG=/tmp/bespoke/config ../bespoke/mono-sgen Main.exe
Starting DeltaBlue benchmark ...
DeltaBlue: iterations=12000 average: 6us total: 79184us

Starting Richards benchmark ...
Richards: iterations=100 average: 1455us total: 145532us

Starting Json benchmark ...
Json: iterations=100 average: 1636us total: 163604us

Starting Havlak benchmark ...
Havlak: iterations=10 average: 205543us total: 2055435us

Starting CD2 benchmark ...
CD2: iterations=250 average: 355us total: 88886us

Starting Bounce benchmark ...
Bounce: iterations=1500 average: 26us total: 39890us

Starting List benchmark ...
List: iterations=1500 average: 47us total: 71956us

Starting Mandelbrot benchmark ...
Mandelbrot: iterations=500 average: 0us total: 386us

Starting NBody benchmark ...
NBody: iterations=250000 average: 0us total: 187021us

Starting Permute benchmark ...
Permute: iterations=1000 average: 41us total: 41554us

Starting Queens benchmark ...
Queens: iterations=1000 average: 30us total: 30834us

Starting Sieve benchmark ...
Sieve: iterations=3000 average: 21us total: 63958us

Starting Storage benchmark ...
Storage: iterations=1000 average: 97us total: 97448us

Starting Towers benchmark ...
Towers: iterations=600 average: 87us total: 52761us

End Tester

you need to set MONO_PATH to the directory with all the .dll files, and MONO_CONFIG to the location of the config file that was copied from ${MONO_BASE}/etc/mono/config

lambdageek commented 1 year ago

wait this doesn't work when I remove the homebrew install. it's still looking for the dylib files from homebrew. There's a way to override that... I need to remember how that works...

rochus-keller commented 1 year ago

I've spent the whole week with testing, renaming /opt/homebrew/Cellar/mono/6.12.0.182 , trying again, only to find that it doesn't work anymore. I even tried to build Mono 6.12.x myself on macOS M1, in the hope to find out whether there is an option to avoid any dependencies besides mono-sgen and mscorlib.dll, but unfortunately the build fails.

lambdageek commented 1 year ago

Here's the updated make-bespoke.sh. Verified that it works if I uninstall homebrew Mono

#! /bin/sh

set -e
set -x

DEST=/tmp/bespoke

MONO_BASE=/opt/homebrew/Cellar/mono/6.12.0.182

mkdir -p ${DEST}/etc
mkdir -p ${DEST}/bin
mkdir -p ${DEST}/lib/mono

# copy the mono binary
cp ${MONO_BASE}/bin/mono-sgen ${DEST}/bin

# config file that tells mono how to map Windows native APIs to dylibs
# also the runtime looks for the "lib" dir relative to the location of this etc directory
cp ${MONO_BASE}/etc/mono/config ${DEST}/etc

# mono support dylib libraries
cp ${MONO_BASE}/lib/libmono-native*.dylib ${DEST}/lib
cp ${MONO_BASE}/lib/libMono*.dylib ${DEST}/lib

# note we don't need libmono-2.0.dylib or libmonosgen-2.0.dylib because Homebrew statically links their mono-sgen executable
# cp ${MONO_BASE}/lib/libmonosgen-*.dylib ${DEST}/lib

# mono base class library
cp ${MONO_BASE}/lib/mono/4.5/*.dll ${DEST}/lib/mono

You need to run Main.exe like this:

MONO_PATH=/tmp/bespoke/lib/mono MONO_CONFIG=/tmp/bespoke/etc/config /tmp/bespoke/bin/mono-sgen Main.exe
rochus-keller commented 1 year ago

Wow, let my try this.

rochus-keller commented 1 year ago

Thanks again.

With your hint I was finally able to create a working configuration without the full homebrew mono package. But the configuration is close to 70MB in size with close to 150 *.dll files (which is much more than my current solution which consists of only the mono executable and mscorlib.dll with less than 10 MB).

So I did further research and was able to reduce the setup to the following files and dir structure:

mono
├── bin
│   └── mono-sgen
├── etc
│   └── config
└── lib
    ├── libmono-native.dylib
    └── mono
        └── 4.5
            └── mscorlib.dll

I can run the app by just calling MONO_CONFIG=<path-to>/mono/etc/config <path-to>mono/bin/mono-sgen Main.exe, i.e. MONO_PATH is not required when this dir structure is used. The bin directory can be renamed with no problem, but the etc and lib directory must be there and one directory up relative to the mono-sgen executable. Also the MONO_CONFIG must be explicitly set, i.e. if I leave it away the usual exception occurs, and if I set it to another directory name and rename etc accordingly, I also get the exception.

So in summary I now have four (instead of two) files with a dedicated (and apparently fragile) directory structure, but still less than 10MB, and I have to explicitly set MONO_CONFIG (which I hadn't so far). But that's at least a configuration I can work with.

I will now do the necessary modifications to the Oberon IDE to cope with this configuration and also try to find out how I could do without MONO_CONFIG. More to come...

rochus-keller commented 1 year ago

I found a way to get rid of the explicit MONO_CONFIG.

If I use this directory structure, then the config file is found without explicitly setting its path:

mono
├── bin
│   └── mono-sgen
├── etc
│   └── mono
│      └── config
└── lib
    ├── libmono-native.dylib
    └── mono
        └── 4.5
            └── mscorlib.dll

I.e. instead of etc/config I have to use etc/mono/config.

With this trick I can now just call <path-to>/mono/bin/mono-sgen Main.exe.

More to come.

rochus-keller commented 1 year ago

Ok, the Oberon IDE works with the new native, minimal M1 Mono CLR.

I didn't even have to modify the IDE source code, but just replace the mono subdirectory in the application bundle by the bin etc lib tripple; I renamed bin to mono and moved mscorlib.dll from the lib to the mono directory because the IDE sets the MONO_PATH to the mono directory.

Also pinvoke works as before on x86_64; I tested with SDL2 and NAppGUI; and all with just the few files I mentioned in my previous commend.

The goal of my request is achieved and I can close the issue. Thank you very much for your help.