commit 30724de4d84ee36c204e276f000495bc97da41d8 Author: Timerix Date: Mon Jun 8 01:14:26 2026 +0500 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1c25da3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# build results +bin/ +obj/ + +# IDE files +.vs/ +.vshistory/ +.editorconfig +*.user +*.vcxproj.filters + +# other files +.old*/ +old/ +tmp/ +temp/ +*.tmp +*.temp +logs/ +log/ +*.log +/mono/ +/dotnet-runtime/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..ea1c30f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "dependencies/tlibc"] + path = dependencies/tlibc + url = https://timerix.ddns.net/git/Timerix/tlibc.git +[submodule "dependencies/tlibtoml"] + path = dependencies/tlibtoml + url = https://timerix.ddns.net/git/Timerix/tlibtoml.git diff --git a/.vscode/.gitignore b/.vscode/.gitignore new file mode 100644 index 0000000..e38da20 --- /dev/null +++ b/.vscode/.gitignore @@ -0,0 +1 @@ +settings.json diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..5fb7746 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,18 @@ +{ + "configurations": [ + { + "name": "all", + "defines": [ + ], + "includePath": [ + "dependencies/include", + "dependencies/tlibc/include", + "dependencies/tlibtoml/include", + "${default}", + ], + "cStandard": "c99", + "cppStandard": "c++11", + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..d6df5ab --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,29 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "gdb_debug", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/bin/mono-apphost", + "windows": { "program": "${workspaceFolder}/bin/mono-apphost.exe" }, + "preLaunchTask": "build_exec_dbg", + "stopAtEntry": false, + "cwd": "${workspaceFolder}/bin", + "externalConsole": false, + "internalConsoleOptions": "neverOpen", + "MIMode": "gdb", + "miDebuggerPath": "gdb", + "setupCommands": [ + { + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ] + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..9840ce9 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,32 @@ + +{ + + "version": "2.0.0", + "tasks": [ + { + "label": "build_exec_dbg", + "detail": "build project with debug symbols", + "type": "cppbuild", + "command": "bash", + "args": [ + "-c", + "cbuild build_exec_dbg" + ], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": ["$gcc"], + "group": { + "kind": "build" + }, + "presentation": { + "echo": true, + "reveal": "always", + "focus": true, + "panel": "shared", + "showReuseMessage": false, + "clear": true + } + } + ] + } \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..4f8f9dc --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# mono-apphost +You created a C# program targeting dotnet6 or newer but users don't want to download and install 200 MB runtime from microsoft website? Here is the solution - portable and small (30 MB) runtime. + +Original mono project was discontinued, but fortunately microsoft has their own [fork of mono runtime](https://github.com/dotnet/runtime/tree/main/src/mono) in dotnet runtime repository. It is still used in mobile development and wasm, so they continue to update it. + + +## Building +1. Clone the repository with submodules + ```sh + git clone --recurse-submodules --depth 1 https://timerix.ddns.net/git/Timerix/mono-apphost.git + ``` + +2. Install [cbuild v2.3](https://timerix.ddns.net/git/Timerix/cbuild/releases/) + + ```sh + ln -s SDL2_HEADERS_DIRECTORY_ABSOLUTE_PATH -T dependencies/include/SDL2 + ``` + +3. Download and extract mono runtime nuget package + - default version + ```sh + cbuild download_mono_from_nuget + ``` + - or some specific version ([list of versions](https://www.nuget.org/packages/Microsoft.NETCore.App.Runtime.Mono.linux-x64/#versions-body-tab)) + ```sh + cbuild download_mono_from_nuget=x.y.z + ``` + - or build your own .nupkg from [dotnet source code](https://github.com/dotnet/runtime/tree/main/src/mono) + ```sh + git clone --depth 1 -b v10.0.8 https://github.com/dotnet/runtime dotnet-runtime + cd dotnet-runtime + ./build.sh mono+libs+packs.product --runtimeFlavor Mono --usemonoruntime --configuration Release --verbosity normal /p:DisableCrossgen=true + ``` + - find `Microsoft.NETCore.App.Runtime.*.nupkg` in `artifacts/packages/Release/Shipping/`. + - copy `runtimes/*/native/include/mono-2.0/mono` to `./dependencies/include/` + - copy shared library files from `runtimes/*native/` to `./dependencies/precompiled/` except `System.Private.CoreLib.dll`, it must be in `./dependencies/precompiled/mono-libs` + - copy C# standard library from `runtimes/*/lib/net10.0/` to `./dependencies/precompiled/mono-libs` + +4. Compile the program + ```sh + cbuild build_exec_dbg + ``` + +5. Create program config + - copy `./mono-apphost.toml` to `./bin` + - change `main_assembly_path` + +6. Copy files from `./bin/` to your C# program build dir \ No newline at end of file diff --git a/dependencies/.gitignore b/dependencies/.gitignore new file mode 100644 index 0000000..4119546 --- /dev/null +++ b/dependencies/.gitignore @@ -0,0 +1,2 @@ +/include/ +/precompiled/ diff --git a/dependencies/precompiled.config b/dependencies/precompiled.config new file mode 100644 index 0000000..8be79fb --- /dev/null +++ b/dependencies/precompiled.config @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +DEP_WORKING_DIR="dependencies/precompiled/$OS-$ARCH" +mkdir -p "dependencies/precompiled" +mkdir -p "$DEP_WORKING_DIR" +DEP_PRE_BUILD_COMMAND='' +DEP_BUILD_COMMAND='' +DEP_POST_BUILD_COMMAND='' +DEP_CLEAN_COMMAND='' + +# won't be copied to project $OUTDIR +DEP_STATIC_OUT_FILES=$(find "$DEP_WORKING_DIR" -name '*.a' | sed "s,$DEP_WORKING_DIR/,,") + +mkdir -p "$DEP_WORKING_DIR/mono-libs" +mono_libs=$(find "$DEP_WORKING_DIR/mono-libs" -type f | sed "s,$DEP_WORKING_DIR/,,") + +# will be copied tp project $OUTDIR +PRESERVE_OUT_DIRECTORY_STRUCTURE=true +case $OS in + WINDOWS) + DEP_DYNAMIC_OUT_FILES=$(find "$DEP_WORKING_DIR" -maxdepth 1 -name '*.dll' | sed "s,$DEP_WORKING_DIR/,,") + DEP_OTHER_OUT_FILES="$mono_libs" + ;; + LINUX) + DEP_DYNAMIC_OUT_FILES=$(find "$DEP_WORKING_DIR" -name '*.so' | sed "s,$DEP_WORKING_DIR/,,") + DEP_OTHER_OUT_FILES="$mono_libs" + ;; + *) + error "operating system $OS has no configuration variants" + ;; +esac diff --git a/dependencies/tlibc b/dependencies/tlibc new file mode 160000 index 0000000..649c2c0 --- /dev/null +++ b/dependencies/tlibc @@ -0,0 +1 @@ +Subproject commit 649c2c09680dbd7de3e245e2b5fcd710201ba6ab diff --git a/dependencies/tlibc.config b/dependencies/tlibc.config new file mode 100644 index 0000000..3db058f --- /dev/null +++ b/dependencies/tlibc.config @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +# This is a dependency config. +# You can copy it to another project to add tlibc dependency. + +DEP_WORKING_DIR="$DEPENDENCIES_DIR/tlibc" +if [[ "$TASK" = *_dbg ]]; then + dep_build_target="build_static_lib_dbg" +else + dep_build_target="build_static_lib" +fi +DEP_PRE_BUILD_COMMAND="" +DEP_BUILD_COMMAND="cbuild $dep_build_target" +DEP_POST_BUILD_COMMAND="" +DEP_CLEAN_COMMAND="cbuild clean" +DEP_DYNAMIC_OUT_FILES="" +DEP_STATIC_OUT_FILES="bin/tlibc.a" +DEP_OTHER_OUT_FILES="" +PRESERVE_OUT_DIRECTORY_STRUCTURE=false diff --git a/dependencies/tlibtoml b/dependencies/tlibtoml new file mode 160000 index 0000000..22a3a4c --- /dev/null +++ b/dependencies/tlibtoml @@ -0,0 +1 @@ +Subproject commit 22a3a4cfe52edb010f17e28aa3195653818f2e70 diff --git a/dependencies/tlibtoml.config b/dependencies/tlibtoml.config new file mode 100644 index 0000000..19ea58b --- /dev/null +++ b/dependencies/tlibtoml.config @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# This is a dependency config. +# You can copy it to another project to add tlibtoml dependency. + +DEP_WORKING_DIR="$DEPENDENCIES_DIR/tlibtoml" + +user_config_path="project.config.user" +absolute_dep_dir=$(realpath "$DEPENDENCIES_DIR") + +function setup_user_config(){ + # Set variable `DEPENDENCIES_DIR`` in `tlibtoml/project.config.user` + # to the directory where `tlibc`` is installed + file_copy_default_if_not_present "$user_config_path" "$user_config_path.default" + replace_var_value_in_script "$user_config_path" "DEPENDENCIES_DIR" "$absolute_dep_dir" +} + +if [[ "$TASK" = *_dbg ]]; then + dep_build_target="build_static_lib_dbg" +else + dep_build_target="build_static_lib" +fi +DEP_PRE_BUILD_COMMAND="setup_user_config" +DEP_BUILD_COMMAND="cbuild $dep_build_target" +DEP_POST_BUILD_COMMAND="" +DEP_CLEAN_COMMAND="cbuild clean" +DEP_DYNAMIC_OUT_FILES="" +DEP_STATIC_OUT_FILES="bin/tlibtoml.a" +DEP_OTHER_OUT_FILES="" +PRESERVE_OUT_DIRECTORY_STRUCTURE=false diff --git a/mono-apphost.toml b/mono-apphost.toml new file mode 100644 index 0000000..f19d690 --- /dev/null +++ b/mono-apphost.toml @@ -0,0 +1,25 @@ +main_assembly_path="Program.dll" +mono_libs_dir="mono-libs" + +# error, critical, warning, message, info, debug (default=error) +trace_level="" + +# trace_mask values (use ',' as separator): +# all (default) +# asm, type, dll, gc, cfg, aot, security, +# threadpool +# io-threadpool +# io-selector +# io-layer +# io-layer-process +# io-layer-socket +# io-layer-file +# io-layer-console +# io-layer-pipe +# io-layer-event +# io-layer-semaphore +# io-layer-mutex +# io-layer-handle +# w32handle, tailcall, profiler, tiered +# qcall, metadata-update, diagnostics +trace_mask="" diff --git a/project.config b/project.config new file mode 100644 index 0000000..2587161 --- /dev/null +++ b/project.config @@ -0,0 +1,209 @@ +#!/usr/bin/env bash +CBUILD_VERSION=2.3.5 + +PROJECT="mono-apphost" +CMP_C="gcc" +CMP_CPP="g++" +STD_C="c99" +STD_CPP="c++11" +WARN_COMMON="-Wall -Wextra + -Wduplicated-branches + -Wduplicated-cond + -Wformat=2 + -Wmissing-include-dirs + -Wshadow + -Werror=return-type + -Werror=pointer-arith + -Werror=init-self + -Wno-unused-parameter" +WARN_C="$WARN_COMMON + -Werror=incompatible-pointer-types" +WARN_CPP="$WARN_COMMON" +SRC_C="$(find src -name '*.c')" +SRC_CPP="$(find src -name '*.cpp')" + +# Directory with dependency configs. +# See cbuild/example_dependency_configs +DEPENDENCY_CONFIGS_DIR='dependencies' +# List of dependency config files in DEPENDENCY_CONFIGS_DIR separated by space. +ENABLED_DEPENDENCIES='tlibc tlibtoml precompiled' + +# OBJDIR structure: +# ├── objects/ - Compiled object files. Cleans on each call of build task +# ├── static_libs/ - Symbolic links to static libraries used by linker. Cleans on each call of build task. +# ├── static_libs/ - Symbolic links to dynamic libraries used by linker. Cleans on each call of build task. +# └── profile/ - gcc *.gcda profiling info files +OBJDIR="obj" +OUTDIR="bin" +STATIC_LIB_FILE="$PROJECT.a" + +# header include directories +INCLUDE="-I$DEPENDENCIES_DIR/include + -I$DEPENDENCIES_DIR/tlibc/include + -I$DEPENDENCIES_DIR/tlibtoml/include" +DEFINES="" + +# OS-specific options +case "$OS" in + WINDOWS) + EXEC_FILE="$PROJECT.exe" + SHARED_LIB_FILE="$PROJECT.dll" + LINKER_LIBS="-static -luuid" + INCLUDE="$INCLUDE " + DEFINES="$DEFINES " + ;; + LINUX) + EXEC_FILE="$PROJECT" + SHARED_LIB_FILE="$PROJECT.so" + LINKER_LIBS="" + INCLUDE="$INCLUDE " + DEFINES="$DEFINES " + ;; + *) + error "operating system $OS has no configuration variants" + ;; +esac + +# TASKS +case "$TASK" in + # creates executable using profiling info if it exists + build_exec) + # -flto applies more optimizations across object files + # -flto=auto is needed to multithreaded copilation + # -fuse-linker-plugin is required to use static libs with lto + # -fprofile-use enables compiler to use profiling info files to optimize executable + # -fprofile-prefix-path sets path where profiling info about objects are be saved + # -fdata-sections -ffunction-sections -Wl,--gc-sections removes unused code + C_ARGS="-O2 -flto=auto -fuse-linker-plugin -fprofile-use + -fprofile-prefix-path=$(realpath $OBJDIR)/objects + -fdata-sections -ffunction-sections -Wl,--gc-sections $DEFINES" + CPP_ARGS="$C_ARGS" + LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" + PRE_TASK_SCRIPT="" + TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh" + POST_TASK_SCRIPT="@cbuild/default_tasks/strip_exec.sh" + ;; + # creates executable with debug info and no optimizations + build_exec_dbg) + C_ARGS="-O0 -g3 $DEFINES" + CPP_ARGS="$C_ARGS" + LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" + PRE_TASK_SCRIPT="" + TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh" + POST_TASK_SCRIPT="" + ;; + # creates shared library + build_shared_lib) + C_ARGS="-O2 -fpic -flto -shared $DEFINES" + CPP_ARGS="$C_ARGS" + LINKER_ARGS="$CPP_ARGS $LINKER_LIBS -Wl,-soname,$SHARED_LIB_FILE" + TASK_SCRIPT="@cbuild/default_tasks/build_shared_lib.sh" + ;; + # creates shared library with debug symbols and no optimizations + build_shared_lib_dbg) + C_ARGS="-O0 -g3 -fpic -shared $DEFINES" + CPP_ARGS="$C_ARGS" + LINKER_ARGS="$CPP_ARGS $LINKER_LIBS -Wl,-soname,$SHARED_LIB_FILE" + TASK_SCRIPT="@cbuild/default_tasks/build_shared_lib.sh" + ;; + # creates static library + build_static_lib) + C_ARGS="-O2 -fpic -fdata-sections -ffunction-sections $DEFINES" + CPP_ARGS="$C_ARGS" + TASK_SCRIPT="@cbuild/default_tasks/build_static_lib.sh" + ;; + # creates static library with debug symbols and no optimizations + build_static_lib_dbg) + C_ARGS="-O0 -g3 $DEFINES" + CPP_ARGS="$C_ARGS" + TASK_SCRIPT="@cbuild/default_tasks/build_static_lib.sh" + ;; + # executes $EXEC_FILE + exec) + TASK_SCRIPT="@cbuild/default_tasks/exec.sh" + ;; + # executes $EXEC_FILE with valgrind memory checker + valgrind) + VALGRIND_ARGS="-s --read-var-info=yes --track-origins=yes + --fullpath-after=$(pwd)/ --leak-check=full --show-leak-kinds=all + --suppressions=../valgrind_ignore.supp" + TASK_SCRIPT="@cbuild/default_tasks/valgrind.sh" + ;; + # generates profiling info + profile) + OUTDIR="$OUTDIR/profile" + # -flto applies more optimizations across object files + # -flto=auto is needed to multithreaded copilation + # -fuse-linker-plugin is required to use static libs with lto + # -pg adds code to executable, that generates file containing function call info (gmon.out) + # -fprofile-generate generates executable with profiling code + # -fprofile-prefix-path sets path where profiling info about objects will be saved + C_ARGS="-O2 -flto=auto -fuse-linker-plugin -fprofile-generate + -fprofile-prefix-path=$(realpath $OBJDIR)/objects $DEFINES" + CPP_ARGS="$C_ARGS" + LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" + PRE_TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh" + TASK_SCRIPT="@cbuild/default_tasks/profile.sh" + ;; + # compiles program with -pg and runs it with gprof + # uses gprof2dot python script to generate function call tree (pip install gprof2dot) + # requires graphviz (https://www.graphviz.org/download/source/) + gprof) + OUTDIR="$OUTDIR/gprof" + # arguments that emit some call counter code and disable optimizations to see function names + # https://github.com/msys2/MINGW-packages/issues/8503#issuecomment-1365475205 + C_ARGS="-O0 -g -pg -no-pie -fno-omit-frame-pointer + -fno-inline-functions -fno-inline-functions-called-once + -fno-optimize-sibling-calls -fopenmp $DEFINES" + CPP_ARGS="$C_ARGS" + LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" + PRE_TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh" + TASK_SCRIPT="@cbuild/default_tasks/gprof.sh" + ;; + # compiles program and runs it with callgrind (part of valgrind) + # uses gprof2dot python script to generate function call tree (pip install gprof2dot) + # requires graphviz (https://www.graphviz.org/download/source/) + # P.S. detailed results can be viewed in KCacheGrind + callgrind) + OUTDIR="$OUTDIR/callgrind" + # -pg adds code to executable, that generates file containing function call info (gmon.out) + C_ARGS="-O2 -flto=auto -fuse-linker-plugin $DEFINES" + CPP_ARGS="$C_ARGS" + LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" + PRE_TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh" + TASK_SCRIPT="@cbuild/default_tasks/callgrind.sh" + ;; + # compiles executable with sanitizers and executes it to find errors and warnings + sanitize) + OUTDIR="$OUTDIR/sanitize" + C_ARGS="-O0 -g3 -fsanitize=undefined,address $DEFINES" + CPP_ARGS="$C_ARGS" + LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" + PRE_TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh" + TASK_SCRIPT="@cbuild/default_tasks/exec.sh" + ;; + # rebuilds specified dependencies + # EXAMPLE: `cbuild rebuild_dependencies=libexample1,fonts` + # 'all' can be specified to rebuild all dependencies + rebuild_dependencies) + TASK_SCRIPT="@cbuild/default_tasks/rebuild_dependencies.sh" + ;; + # Downloads mono runtime package from nuget. + # Extracts libs to dependencies/precompiled/$OS-$ARCH + # Extracts headers to dependencies/include/mono + # download_mono_from_nuget=VERSION + download_mono_from_nuget) + TASK_SCRIPT=tasks/download_mono_from_nuget.sh + ;; + # deletes generated files + clean) + TASK_SCRIPT="@cbuild/default_tasks/clean.sh" + ;; + # nothing to do + "" | no_task) + ;; + # unknown task + *) + error "task <$PROJECT/$TASK> not found" + ;; +esac diff --git a/project.config.user.default b/project.config.user.default new file mode 100644 index 0000000..f77d4b2 --- /dev/null +++ b/project.config.user.default @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# Project user config is ignored by git. +# Here you can add variables that users might want to change +# on their local machine, without commiting to the repository. + +# Directory where you install dependencies. +# Do not confuse with DEPENDENCY_CONFIGS_DIR +# Example: +# libexample source code is at `../libexample`, and dependency config +# that specifies how to build this lib is at `dependencies/libexample.config` +DEPENDENCIES_DIR="dependencies" diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..85b3ef4 --- /dev/null +++ b/src/main.c @@ -0,0 +1,198 @@ +#include "tlibc/tlibc.h" +#include "tlibc/filesystem.h" +#include "tlibtoml.h" +#include +#include +#include +#include +#include +#include +#include + +static const cstr APPHOST_CONFIG_PATH = "mono-apphost.toml"; +static const cstr DOMAIN_NAME = "mono-apphost"; + +typedef struct ApphostConfig { + char* main_assembly_path; + char* mono_libs_dir; + + // error, critical, warning, message, info, debug (default=error) + NULLABLE(char*) trace_level; + + // see dotnet-runtime/src/mono/mono/utils/mono-logger.c + NULLABLE(char*) trace_mask; + + //TODO: bool use_llvm +} ApphostConfig; + +void ApphostConfig_free(ApphostConfig* conf){ + if(!conf) + return; + free(conf->main_assembly_path); + free(conf->mono_libs_dir); + free(conf->trace_level); + free(conf->trace_mask); + free(conf); +} + +Result(ApphostConfig*) ApphostConfig_load_filename(cstr _conf_path_raw){ + Deferral(32); + + ApphostConfig* conf = (ApphostConfig*)malloc(sizeof(ApphostConfig)); + zeroStruct(conf); + bool success = false; + Defer(if(!success) ApphostConfig_free(conf)); + + str conf_path = str_copy(str_from_cstr(_conf_path_raw)); + Defer(str_destroy(conf_path)); + path_fix_separators(&conf_path); + + try(TomlTable* config_top, p, toml_load_filename(conf_path.data)); + Defer(TomlTable_free(config_top)); + + // main_assembly_path + try(str* main_assembly_path, p, TomlTable_get_str(config_top, STR("main_assembly_path"))); + if(main_assembly_path->len == 0){ + Return RESULT_ERROR_LITERAL("main_assembly_path: value is empty string") + } + + path_fix_separators(main_assembly_path); + if(!file_exists(main_assembly_path->data)){ + char* err = sprintf_malloc( + "main_assembly_path: file '%s' doesn't exist", + main_assembly_path->data); + return RESULT_ERROR_HEAP(err); + } + + conf->main_assembly_path = str_copy(*main_assembly_path).data; + + + // mono_libs_dir + try(str* mono_libs_dir, p, TomlTable_get_str(config_top, STR("mono_libs_dir"))); + if(mono_libs_dir->len == 0){ + Return RESULT_ERROR_LITERAL("mono_libs_dir: value is empty string"); + } + + path_fix_separators(mono_libs_dir); + if(!dir_exists(mono_libs_dir->data)){ + char* err = sprintf_malloc( + "mono_libs_dir: directory '%s' doesn't exist", + mono_libs_dir->data); + Return RESULT_ERROR_HEAP(err); + } + + char* corelib_dll_path = strcat_malloc(mono_libs_dir->data, path_seps, "System.Private.CoreLib.dll"); + Defer(free(corelib_dll_path)); + char* corelib_dll_so_path = strcat_malloc(corelib_dll_path, ".so"); + Defer(free(corelib_dll_so_path)); + if(!file_exists(corelib_dll_path) && !file_exists(corelib_dll_so_path)){ + char* err = sprintf_malloc( + "mono_libs_dir: can't find System.Private.CoreLib.dll in '%s'", + mono_libs_dir->data); + Return RESULT_ERROR_HEAP(err); + } + + conf->mono_libs_dir = str_copy(*mono_libs_dir).data; + + + // trace_level + NULLABLE(TomlValue*) trace_level = TomlTable_tryGet(config_top, STR("trace_level")); + if(trace_level){ + if(trace_level->s->len != 0) + conf->trace_level = str_copy(*trace_level->s).data; + } + + // trace_mask + NULLABLE(TomlValue*) trace_mask = TomlTable_tryGet(config_top, STR("trace_mask")); + if(trace_mask){ + if(trace_mask->s->len != 0) + conf->trace_mask = str_copy(*trace_mask->s).data; + } + + success = true; + Return RESULT_VALUE(p, conf); +} + + +void trace_print_handler(const char *message, mono_bool is_stdout){ + FILE* stream = is_stdout ? stdout : stderr; + fprintf(stream, "[MonoPrint]: %s\n", message); +} + +void trace_log_handler(const char *log_domain, const char *log_level, const char *message, + mono_bool fatal, void *user_data) +{ + FILE* stream = stderr; + fprintf(stream, "[%s/%s]: %s\n", log_domain, log_level, message); +} + + +Result(MonoDomain*) init_runtime(ApphostConfig* conf){ + Deferral(16); + + // configure logging + mono_trace_set_print_handler(trace_print_handler); + mono_trace_set_log_handler(trace_log_handler, NULL); + if(conf->trace_level) + mono_trace_set_level_string(conf->trace_level); + if(conf->trace_mask) + mono_trace_set_mask_string(conf->trace_mask); + + // set dll search path + mono_set_assemblies_path(conf->mono_libs_dir); + // code for compatibility with old mono + mono_set_dirs(conf->mono_libs_dir, conf->mono_libs_dir); + char* mono_config_xml_path = strcat_malloc(conf->mono_libs_dir, path_seps, "config.xml"); + Defer(free(mono_config_xml_path)); + mono_config_parse(mono_config_xml_path); + + // init mono runtime and create domain + MonoDomain* domain = mono_jit_init(DOMAIN_NAME); + if(domain == NULL){ + Return RESULT_ERROR_LITERAL("can't initialize mono jit domain"); + } + Return RESULT_VALUE(p, domain); +} + +Result(MonoAssembly*) load_assembly(MonoDomain* domain, cstr name){ + Deferral(1); + MonoAssembly* assembly = mono_domain_assembly_open(domain, name); + if(assembly == NULL){ + char* err = sprintf_malloc("can't load assembly '%s'", name); + Return RESULT_ERROR_HEAP(err); + } + Return RESULT_VALUE(p, assembly); +} + +Result(i32) try_main(i32 argc, cstr* argv){ + Deferral(8); + + try(ApphostConfig* conf, p, ApphostConfig_load_filename(APPHOST_CONFIG_PATH)); + Defer(ApphostConfig_free(conf)); + + try(MonoDomain* domain, p, init_runtime(conf)) + // mono_jit_cleanup sometimes leads to segfault. + // I guess nobody really tested this thing. + Defer(mono_jit_cleanup(domain)); + + try(MonoAssembly* assembly, p, load_assembly(domain, conf->main_assembly_path)) + + i32 ret = mono_jit_exec(domain, assembly, argc, (char**)(argv)); + Return RESULT_VALUE(i, ret); +} + +i32 main(i32 argc, cstr* argv){ + Deferral(4); + + try_fatal_void(tlibc_init()); + Defer(tlibc_deinit()); + try_fatal_void(tlibtoml_init()); + Defer(tlibtoml_deinit()); + + try_fatal(i32 ret, i, try_main(argc, argv)); + if(ret != 0){ + printfe("program exited with non-zero code: %i\n", ret); + } + + Return ret; +} diff --git a/tasks/download_mono_from_nuget.sh b/tasks/download_mono_from_nuget.sh new file mode 100644 index 0000000..695188f --- /dev/null +++ b/tasks/download_mono_from_nuget.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +package_version="$TASK_ARGS" +if [ -z "$package_version" ]; then + package_version="8.0.15" + myprint "${YELLOW}You can choose package version manually: cbuild get_mono_files_from=x.y.z" +fi +myprint "${BLUE}package_version: ${CYAN}$package_version" + +case "$OS" in + LINUX) + package_platform="linux-$ARCH" + ;; + WINDOWS) + package_platform="win-$ARCH" + ;; +esac + +package_name="Microsoft.NETCore.App.Runtime.Mono.$package_platform" +package_dir="$package_name.$package_version" +package_file="$package_name.$package_version.nupkg" +package_url="https://www.nuget.org/api/v2/package/$package_name/$package_version" + +mkdir -p "$OBJDIR/downloads" +myprint "${BLUE}downloading nuget package: ${WHITE}$package_name ${BLUE}to $OBJDIR/downloads" +cd "$OBJDIR/downloads" +wget -q $package_url -O $package_file +clean_dir $package_dir +cd $package_dir + +myprint "${BLUE}extracting nuget package" +unzip -oq "../$package_file" + +# copy headers +myprint "${BLUE}copying headers" +mkdir -p "../../../dependencies/include" +delete_dir "../../../dependencies/include/mono" +cp -r "runtimes/$package_platform/native/include/mono-2.0/mono" "../../../dependencies/include/" + +precompiled_dir="../../../dependencies/precompiled/$OS-$ARCH" +mkdir -p "$precompiled_dir" +clean_dir "$precompiled_dir/mono-libs" + +# copy mono native libraries +myprint "${BLUE}copying mono native libraries" +shared_libs=$(find "runtimes/$package_platform/native" -maxdepth 1 \( -not -name "System.Private.CoreLib.dll" -a \( -name '*.so*' -o -name '*.dll' \) \) -type f) +if [ -z "$shared_libs" ]; then + error "can't find mono shared libraries" +fi +for l in $shared_libs ; do + cp -v "$l" "$precompiled_dir" +done + +# copy mono c# libraries +myprint "${BLUE}copying mono managed libraries" +managed_libraries="runtimes/$package_platform/native/System.Private.CoreLib.dll" + +_subdirs=(runtimes/$package_platform/lib/*) +_managed_lib_dir="${_subdirs[0]}" +for l in $(find $_managed_lib_dir -type f); do + managed_libraries+=" $l" +done + +for l in $managed_libraries; do + cp -v "$l" "$precompiled_dir/mono-libs" +done + +myprint "${GREEN}mono files have been copied successfully!" +cd ../../.. +clean_dir "$OBJDIR/downloads" diff --git a/valgrind_ignore.supp b/valgrind_ignore.supp new file mode 100644 index 0000000..aead4a6 --- /dev/null +++ b/valgrind_ignore.supp @@ -0,0 +1,29 @@ +{ + coreclr_all_leaks + Memcheck:Leak + match-leak-kinds: all + ... + obj:*libcoreclr.so* +} +{ + coreclr_cond + Memcheck:Cond + ... + obj:*libcoreclr.so* +} +{ + libsystem_all_leaks + Memcheck:Leak + match-leak-kinds: all + ... + obj:*libSystem.*.so* +} + +{ + dlopen_leak + Memcheck:Leak + match-leak-kinds: reachable + ... + fun:_dl_open + ... +}