├── .gitignore ├── README.md ├── build ├── builder.erl ├── runtime.erl └── src ├── foo.gleam └── foo └── bar.gleam /.gitignore: -------------------------------------------------------------------------------- 1 | erl_crash.dump 2 | gen 3 | ebin 4 | 5 | builder.beam 6 | run 7 | see.boot -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Library Runtime 2 | 3 | A lightweight erlang runtime. 4 | No processes are started by the init process. 5 | No modules other than a small see runtime are loaded. 6 | You define a module with a `main` function which is called. 7 | Upon completion of the main funtion the process stops. 8 | 9 | Such a main function can start supervision trees and other long lived processes. 10 | 11 | The aim of this project is to write a runtime as library. 12 | I.e. you explicitly require all modules and start all processes that your program needs. 13 | All configuration is code in the main loop. 14 | 15 | i.e. in the future 16 | ```rust 17 | import gleam/logger 18 | import gleam/ssl 19 | 20 | pub fn main() { 21 | assert Ok(pid) = logger.start(config) 22 | assert Ok(ssl) = ssl.start(config) 23 | // etc 24 | } 25 | ``` 26 | 27 | ## Usage 28 | 29 | Ergonomics of this can definetly be improved. But for now the steps are as follows 30 | 31 | 1. Write a Gleam program in `/src` 32 | 2. run `./build` 33 | 3. run `./run name` (where name is the name of your module that has the main function) 34 | 35 | 36 | ## Notes 37 | 38 | #### Counting loaded modules and processes 39 | 40 | ``` 41 | code:all_loaded(). 42 | length(processes()). 43 | length(registered()). 44 | ``` 45 | 46 | #### Erlang System Principles 47 | https://erlang.org/doc/system_principles/system_principles.html#default_boot_scripts 48 | 49 | Explains boot scripts, and enumerates default boot scripts. There IS a boot script without SASL. 50 | 51 | 52 | #### Erlang Runtime options 53 | 54 | http://erlang.org/doc/man/erl.html 55 | 56 | `-s module` Tries to start with a call to `module:start()` 57 | `-s module function` Tries to start with a call to `module:function()` 58 | 59 | More than one `-s` can be specified. To start a program that stops. use. 60 | `erl -s my_module main -s init stop -noshell` 61 | 62 | 63 | `-extra` everything after extra is considered plain arguments and can be loaded using `init:get_plain_arguments()` 64 | 65 | `-r` works the same as `-s` except with this comment in the docs 66 | 67 | ``` 68 | Because of the limited length of atoms, it is recommended to use -run instead. 69 | ``` 70 | 71 | ##### Mode 72 | 73 | interactive/embedded 74 | 75 | Default mode is interactive, it loads code files on demand. `code:get_mode().` allows you to see which it is at runtime. 76 | 77 | #### Erlang preloaded source 78 | https://github.com/erlang/otp/tree/master/erts/preloaded/src 79 | -------------------------------------------------------------------------------- /build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | erlc builder.erl 4 | erl -s builder make_scripts -------------------------------------------------------------------------------- /builder.erl: -------------------------------------------------------------------------------- 1 | -module(builder). 2 | 3 | -export([make_scripts/0]). 4 | 5 | % read() -> rpc(io, read). 6 | 7 | % write(X) -> rpc(io, {write, X}). 8 | 9 | % env(Key) -> rpc(env, {lookup, Key}). 10 | 11 | % handle_env({lookup, Key}, Dict) -> 12 | % {lookup(Key, Dict), Dict}. 13 | 14 | % start_env() -> 15 | % Env = case init:get_argument(environment) of 16 | % {ok, [L]} -> L; 17 | % error -> fatal({missing, '-environment ...'}) 18 | % end, 19 | % lists:map(fun split_env/1, Env). 20 | 21 | % split_env(Str) -> split_env(Str, []). 22 | 23 | % split_env([$= | T], L) -> {reverse(L), T}; 24 | % split_env([], L) -> {reverse(L), []}; 25 | % split_env([H | T], L) -> split_env(T, [H | L]). 26 | 27 | % get_module_name() -> 28 | % case init:get_argument(load) of 29 | % {ok, [[Arg]]} -> module_name(Arg); 30 | % error -> fatal({missing, '-load Mod'}) 31 | % end. 32 | 33 | % lookup(Key, L) -> 34 | % case lists:keysearch(Key, 1, L) of 35 | % {value, T} -> {found, element(2, T)}; 36 | % false -> not_found 37 | % end. 38 | 39 | preloaded() -> 40 | [zlib, 41 | prim_file, 42 | prim_zip, 43 | prim_inet, 44 | erlang, 45 | otp_ring0, 46 | init, 47 | erl_prim_loader]. 48 | 49 | make_scripts() -> 50 | % This script is run in a normal erlang runtime. 51 | % There is no need for this code to be done in erlang, it is in erlang because it needs a call to term_to_binary, and I was following an example in erlang. 52 | {ok, Cwd} = file:get_cwd(), 53 | StdlibPath = code:lib_dir(stdlib), 54 | 55 | erlang:display(os:cmd("rm -rf ebin gen")), 56 | erlang:display(os:cmd("mkdir gen")), 57 | 58 | % The runtime module is called see and is different to the buildtime module also called see. 59 | erlang:display(os:cmd("cp runtime.erl gen/see.erl")), 60 | % compile the Gleam code, I'm pretty sure at this point the name doesn't mean anything. 61 | erlang:display(os:cmd("gleam compile-package --src src --out " 62 | "gen --name TODO")), 63 | erlang:display(os:cmd("mkdir ebin")), 64 | % The lists module is copied because it is used within the init module which starts the runtime. 65 | % This is a bit of a conflation of responsibilities for stdlib and runtime so this hack exists for now. 66 | erlang:display(os:cmd("cp " ++ 67 | StdlibPath ++ "/ebin/lists.beam ebin")), 68 | % compile all the erlang files that exist in gen, this is those compiled from Gleam and the runtime erlang file 69 | erlang:display(os:cmd("erlc -o ebin/ -pa ebin gen/*.erl")), 70 | 71 | % All files in the ebin directory are automatically loaded at boot. 72 | % By default this setup runs the vm in embed mode so modules are not loaded on demand at runtime. 73 | Files = [K 74 | || "ebin/" ++ F <- filelib:wildcard("ebin/*.beam"), 75 | K <- string:replace(F, ".beam", ""), length(K) > 0], 76 | erlang:display(Files), 77 | Mods = [list_to_atom(F) || F <- Files], 78 | erlang:display(Mods), 79 | Script = {script, 80 | {"see", "1.0"}, 81 | [{preLoaded, preloaded()}, 82 | {progress, preloaded}, 83 | {path, [Cwd ++ "/ebin"]}, 84 | {primLoad, Mods}, 85 | {kernel_load_completed}, 86 | {progress, kernel_load_completed}, 87 | {progress, started}, 88 | {apply, {see, main, []}}]}, 89 | io:format("Script:~p~n", [Script]), 90 | file:write_file("see.boot", term_to_binary(Script)), 91 | file:write_file("run", 92 | ["#!/bin/sh\nerl ", 93 | %%" -init_debug ", 94 | " -boot ", 95 | Cwd, 96 | "/see ", 97 | "-environment `printenv` -mode embedded " 98 | "-load $1\n"]), 99 | os:cmd("chmod a+x run"), 100 | init:stop(), 101 | true. 102 | -------------------------------------------------------------------------------- /runtime.erl: -------------------------------------------------------------------------------- 1 | -module(see). 2 | 3 | -export([main/0]). 4 | 5 | main() -> 6 | Mod = get_main_module(), 7 | erlang:display({"Starting", Mod}), 8 | % erlang:display(code:all_loaded()), 9 | % erlang:display(length(processes())), 10 | % length(registered())). 11 | R = Mod:main(), 12 | erlang:display({"Done", R}), 13 | erlang:halt(). 14 | 15 | module_name(Str) -> 16 | case catch list_to_atom(Str) of 17 | {'EXIT', _} -> 18 | erlang:display("Bad module name: " ++ Str), 19 | erlang:halt(1); 20 | 21 | ?MODULE -> 22 | erlang:display("Attempting to run boot module. Try another module"), 23 | erlang:halt(1); 24 | 25 | Mod -> Mod 26 | end. 27 | 28 | get_main_module() -> 29 | case init:get_argument(load) of 30 | {ok, [[Arg]]} -> module_name(Arg); 31 | _ -> 32 | erlang:display("Missing command line argument: -load Mod"), 33 | erlang:halt(1) 34 | end. 35 | -------------------------------------------------------------------------------- /src/foo.gleam: -------------------------------------------------------------------------------- 1 | pub fn main() { 2 | 2 + 3 3 | } -------------------------------------------------------------------------------- /src/foo/bar.gleam: -------------------------------------------------------------------------------- 1 | pub fn task() { 2 | 1 + 2 3 | } --------------------------------------------------------------------------------