├── .dockerignore ├── .formatter.exs ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .tool-versions ├── LICENSE.md ├── README.md ├── lib ├── mix │ └── tasks │ │ ├── android_beam.dockerfile │ │ ├── android_nif.dockerfile │ │ ├── package_android_nif.ex │ │ ├── package_android_runtime.ex │ │ ├── package_ios_nif.ex │ │ └── package_ios_runtime.ex ├── runtimes.ex └── runtimes │ ├── android.ex │ └── ios.ex ├── mix.exs ├── patch ├── openssl-ios.conf └── otp-space.patch ├── scripts ├── build_android_diode.sh ├── build_anroid_otp-25.sh ├── build_ios_diode.sh ├── build_ios_otp-25.sh ├── build_nif.sh ├── install_elixir.sh ├── install_openssl.sh └── package_nif.sh ├── src └── test_main.cpp └── stubs ├── bin ├── ar-stub.sh ├── ld-stub.sh ├── libtool-stub.sh └── ranlib-stub.sh ├── obstack.h ├── obstack_printf.c └── vasnprintf.h /.dockerignore: -------------------------------------------------------------------------------- 1 | /otp 2 | /_build/dev 3 | /_build/x86_64 4 | /_build/arm 5 | /_build/arm64 6 | /lib 7 | /.history 8 | /.elixir_ls 9 | /.github 10 | *.zip 11 | *.tmp -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | on: ["push", "pull_request"] 3 | env: 4 | OTP_TAG: OTP-26.2.5.6 5 | OTP_SOURCE: https://github.com/erlang/otp 6 | 7 | jobs: 8 | build: 9 | name: "Build Android runtimes" 10 | runs-on: "ubuntu-latest" 11 | strategy: 12 | matrix: 13 | arch: ["arm", "arm64", "x86_64"] 14 | steps: 15 | - name: Setup elixir 16 | uses: erlef/setup-beam@v1 17 | with: 18 | otp-version: 26.2.5.6 19 | elixir-version: 1.16.3 20 | 21 | - uses: actions/checkout@v4 22 | 23 | - run: | 24 | scripts/install_elixir.sh "$HOME/elixir" 25 | echo "$HOME/elixir/bin" >> $GITHUB_PATH 26 | 27 | - name: Build Android ${{ matrix.arch }} runtimes 28 | run: | 29 | mix deps.get 30 | ARCH=${{ matrix.arch }} mix package.android.runtime 31 | - name: Archive Android runtimes 32 | uses: actions/upload-artifact@v4 33 | with: 34 | name: ${{ matrix.arch }}-runtime 35 | path: _build/*.zip 36 | - name: Android ${{ matrix.arch }} release 37 | uses: softprops/action-gh-release@v1 38 | if: startsWith(github.ref, 'refs/tags/') 39 | with: 40 | files: _build/*.zip 41 | 42 | ios: 43 | name: "Build iOS runtime" 44 | runs-on: "macos-latest" 45 | steps: 46 | - run: brew install git carthage coreutils 47 | - uses: actions/checkout@v4 48 | 49 | - name: Setup elixir 50 | run: | 51 | git clone https://github.com/asdf-vm/asdf.git ~/.asdf 52 | . $HOME/.asdf/asdf.sh 53 | asdf plugin add erlang 54 | asdf plugin add elixir 55 | echo "erlang 26.2.5.6" >> .tool-versions 56 | echo "elixir 1.16.3-otp-24" >> .tool-versions 57 | asdf install 58 | 59 | - name: Build runtime 60 | run: | 61 | . $HOME/.asdf/asdf.sh 62 | mix package.ios.runtime 63 | 64 | - name: Archive runtimes 65 | uses: actions/upload-artifact@v4 66 | with: 67 | name: iOS-runtime 68 | path: _build/liberlang.xcframework 69 | 70 | - name: iOS release 71 | uses: softprops/action-gh-release@v1 72 | if: startsWith(github.ref, 'refs/tags/') 73 | with: 74 | files: _build/liberlang.xcframework 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /otp 2 | /_build 3 | /erl_crash.dump 4 | *.so 5 | *.zip 6 | *.tar.gz 7 | *.tmp 8 | .DS_Store 9 | /tmp 10 | /test_main 11 | /openssl-1.1.1k 12 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | erlang 26.2.5.6 2 | elixir 1.16.3-otp-24 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2021 Dominic Letz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Elixir-Desktop Runtimes 2 | 3 | To use elixir-desktop on mobile-phones this projects packages the BEAM Virtual Machine into platform specific binaries. Currently supported are: 4 | 5 | - Android arm 64-bit 6 | - Android arm 32-bit 7 | - Android x86 64-bit (for the Android Simulator) 8 | - iOS arm 64-bit (current iPhones) 9 | - iOS arm 64-bit (MacOS M1 Simulator) 10 | - iOS x86_64 (MacOS Intel Simulator) 11 | 12 | ## Building Android Runtimes 13 | 14 | Android runtimes depends on docker and the dockercross/* docker-images created for cross-compilation. If docker is installed for your current user then building all the runtimes bundled in a zip file is as easy as: 15 | 16 | `mix package.android.runtime` 17 | 18 | After this you should have all runtimes in `_build/#{arch}-runtime.zip` these then will need to be packaged with your mobile app. 19 | 20 | ## Building iOS Runtimes 21 | 22 | For iOS builds are triggered similiary with: 23 | 24 | `mix package.ios.runtime` 25 | 26 | After the build succeeds there will be a xcframework directory in _build/liberlang.xcframework. Copy this into your Xcode project and add it to your target. 27 | 28 | ## Android Versions and API-Levels (update Apr. 2024) 29 | 30 | Just for reference from https://apilevels.com/, currently we're only supporting ABI >= 23 31 | 32 | | Culumative usage | Version | API Level | 33 | | ------------ | ------- | --------- | 34 | | 0% | Android 15 | (API level 35) | 35 | | 16.30% | Android 14 | (API level 34) | 36 | | 42.50% | Android 13 | (API level 33) | 37 | | 59.50% | Android 12 | (API level 31+32) | 38 | | 75.70% | Android 11 | (API level 30) | 39 | | 84.50% | Android 10 | (API level 29) | 40 | | 90.20% | Android 9 | (API level 28) | 41 | | 92.10% | Android 8.1 | (API level 27) | 42 | | 95.10% | Android 8.0 | (API level 26) | 43 | | 95.6% | Android 7.1 | (API level 25) | 44 | | 97.00% | Android 7.0 | (API level 24) | 45 | | 98.40% | Android 6.0 | (API level 23) | 46 | 47 | 48 | -------------------------------------------------------------------------------- /lib/mix/tasks/android_beam.dockerfile: -------------------------------------------------------------------------------- 1 | FROM dockcross/android-<%= @arch.id %> 2 | 3 | # ENV 4 | ENV NDK_ROOT $CROSS_ROOT 5 | ENV ANDROID_NDK_HOME $CROSS_ROOT 6 | ENV NDK_ABI_PLAT <%= @arch.android_name %><%= @arch.abi %> 7 | ENV PATH $NDK_ROOT/bin:$PATH 8 | ENV FC= CPP= LD= CXX=clang++ CC=clang AR=ar 9 | ENV MAKEFLAGS "-j10 -O" 10 | 11 | # Setting up openssl 12 | COPY scripts/install_openssl.sh /work/ 13 | COPY patch /work/patch 14 | 15 | # OpenSSL fails to detect this: 16 | RUN cp ${NDK_ROOT}/bin/llvm-ar ${NDK_ROOT}/bin/<%= @arch.cpu %>-linux-<%= @arch.android_name %>-ar 17 | RUN cp ${NDK_ROOT}/bin/llvm-ranlib ${NDK_ROOT}/bin/<%= @arch.cpu %>-linux-<%= @arch.android_name %>-ranlib 18 | 19 | RUN ARCH="android-<%= @arch.id %> -D__ANDROID_API__=<%= @arch.abi %>" ./install_openssl.sh 20 | 21 | # Fetching OTP 22 | COPY _build/otp otp 23 | 24 | ENV LIBS /usr/local/openssl/lib/libcrypto.a 25 | 26 | # We need -z global for liberlang.so because: 27 | # https://android-ndk.narkive.com/iNWj05IV/weak-symbol-linking-when-loading-dynamic-libraries 28 | # https://android.googlesource.com/platform/bionic/+/30b17e32f0b403a97cef7c4d1fcab471fa316340/linker/linker_namespaces.cpp#100 29 | ENV CFLAGS="-Os -fPIC" CXXFLAGS="-Os -fPIC" LDFLAGS="-z global" CXX= CC= 30 | 31 | # RUN env 32 | WORKDIR /work/otp 33 | 34 | # Build with debugger produces 35 | # dbg_wx_filedialog_win.erl:22: behaviour wx_object undefined 36 | 37 | # Build run #1, building the x86 based cross compiler which will generate the .beam files 38 | <% 39 | config = "--with-ssl=/usr/local/openssl/ --disable-dynamic-ssl-lib --without-javac --without-odbc --without-wx --without-debugger --without-observer --without-cdv --without-et --xcomp-conf=xcomp/erl-xcomp-#{@arch.id}-android.conf" 40 | %> 41 | RUN ./otp_build setup <%= config %> || bash -c 'cat erts/config.log && exit 1' 42 | RUN ./otp_build boot -a 43 | 44 | # Build run #2, now creating the arm binaries, appliying the install flags only here... 45 | ENV INSTALL_PROGRAM "/usr/bin/install -c -s --strip-program=llvm-strip" 46 | RUN ./otp_build configure <%= config %> LDFLAGS="-z global" 47 | RUN ./otp_build release -a 48 | -------------------------------------------------------------------------------- /lib/mix/tasks/android_nif.dockerfile: -------------------------------------------------------------------------------- 1 | <%= @parent %> 2 | 3 | WORKDIR /work 4 | RUN apt update && apt install -y erlang 5 | COPY scripts/install_elixir.sh /work/ 6 | RUN ./install_elixir.sh /work/elixir/ && \ 7 | ln -s /work/elixir/bin/* /usr/local/bin/ 8 | 9 | WORKDIR /work 10 | RUN mix local.hex --force && mix local.rebar 11 | 12 | ENV ERLANG_PATH /work/otp/release/<%= @arch.pc %>-linux-<%= @arch.android_name %>/erts-<%= @erts_version %>/include 13 | ENV ERTS_INCLUDE_DIR /work/otp/release/<%= @arch.pc %>-linux-<%= @arch.android_name %>/erts-<%= @erts_version %>/include 14 | ENV HOST <%= @arch.cpu %> 15 | ENV CROSSCOMPILE Android 16 | ENV CC=clang CXX=clang++ 17 | 18 | RUN git clone <%= @repo %> 19 | WORKDIR /work/<%= @basename %> 20 | <%= if @tag do %> 21 | RUN git checkout <%= @tag %> 22 | <% end %> 23 | 24 | # Three variants of building {:mix, :make, :rebar3} 25 | ENV MIX_ENV prod 26 | COPY scripts/build_nif.sh scripts/package_nif.sh /work/<%= @basename %>/ 27 | RUN ./build_nif.sh 28 | -------------------------------------------------------------------------------- /lib/mix/tasks/package_android_nif.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Package.Android.Nif do 2 | import Runtimes.Android 3 | import Runtimes 4 | use Mix.Task 5 | require EEx 6 | 7 | def run([nif]) do 8 | buildall(Map.keys(architectures()), nif) 9 | end 10 | 11 | def build(arch, nif) do 12 | nif = get_nif(nif) 13 | arch = get_arch(arch) 14 | env = nif_env(arch) 15 | 16 | # Getting an Elixir version 17 | if File.exists?(Path.join(elixir_target(arch), "bin")) do 18 | IO.puts("Elixir already exists...") 19 | else 20 | cmd(["scripts/install_elixir.sh", elixir_target(arch)]) 21 | cmd("mix do local.hex --force && mix local.rebar --force", PATH: env[:PATH]) 22 | end 23 | 24 | # Start the builds 25 | nif_dir = "_build/#{arch.name}/#{nif.basename}" 26 | 27 | if !File.exists?(nif_dir) do 28 | cmd(~w(git clone #{nif.repo} #{nif_dir}), env) 29 | end 30 | 31 | if nif.tag do 32 | cmd(~w(cd #{nif_dir} && git checkout #{nif.tag}), env) 33 | end 34 | 35 | build_nif = Path.absname("scripts/build_nif.sh") 36 | cmd(~w(cd #{nif_dir} && #{build_nif}), env) 37 | 38 | case static_lib_path(arch, nif) do 39 | nil -> raise "NIF build failed. Could not locate static lib" 40 | lib -> lib 41 | end 42 | end 43 | 44 | defp buildall(targets, nif) do 45 | for target <- targets do 46 | build(target, nif) 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/mix/tasks/package_android_runtime.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Package.Android.Runtime do 2 | import Runtimes.Android 3 | import Runtimes 4 | alias Mix.Tasks.Package.Android.Nif, as: Nif 5 | use Mix.Task 6 | require EEx 7 | 8 | def run(["env" | arch]) do 9 | archs = Map.keys(architectures()) 10 | arch = List.first(arch) 11 | 12 | if !Enum.member?(archs, arch) do 13 | raise "Architecture '#{arch}' is invalid. Possible values: #{inspect(archs)}" 14 | end 15 | 16 | File.write!("nif_env.sh", "#!/bin/bash\n") 17 | 18 | nif_env(architectures()[arch]) 19 | |> Enum.sort() 20 | |> Enum.each(fn {key, value} -> 21 | File.write!("nif_env.sh", "export #{key}=\"#{value}\"\n", [:append]) 22 | end) 23 | 24 | Mix.shell().info("Environment has been written to nif_env.sh") 25 | end 26 | 27 | def run(["with_diode_nifs"]) do 28 | nifs = [ 29 | "https://github.com/diodechain/esqlite.git", 30 | "https://github.com/diodechain/libsecp256k1.git" 31 | ] 32 | 33 | run(nifs) 34 | end 35 | 36 | def run([]) do 37 | run(["https://github.com/elixir-desktop/exqlite"]) 38 | end 39 | 40 | def run(nifs) do 41 | IO.puts("Validating nifs...") 42 | Enum.each(nifs, fn nif -> Runtimes.get_nif(nif) end) 43 | buildall(Map.keys(architectures()), nifs) 44 | end 45 | 46 | def build(archid, extra_nifs) do 47 | arch = get_arch(archid) 48 | File.mkdir_p!("_build/#{arch.name}") 49 | 50 | # Building OpenSSL 51 | if File.exists?(openssl_lib(arch)) do 52 | IO.puts("OpenSSL (#{arch.id}) already exists...") 53 | else 54 | cmd( 55 | "scripts/install_openssl.sh", 56 | [ 57 | ARCH: arch.openssl_arch, 58 | OPENSSL_PREFIX: openssl_target(arch), 59 | MAKEFLAGS: "-j10 -O" 60 | ] 61 | |> ensure_ndk_home(arch) 62 | ) 63 | end 64 | 65 | # Building OTP 66 | if File.exists?(runtime_target(arch)) do 67 | IO.puts("liberlang.a (#{arch.id}) already exists...") 68 | else 69 | if !File.exists?(otp_target(arch)) do 70 | Runtimes.ensure_otp() 71 | cmd(~w(git clone _build/otp #{otp_target(arch)})) 72 | 73 | if !File.exists?(Path.join(otp_target(arch), "patched")) do 74 | cmd("cd #{otp_target(arch)} && git apply ../../../patch/otp-space.patch") 75 | File.write!(Path.join(otp_target(arch), "patched"), "true") 76 | end 77 | end 78 | 79 | env = 80 | [ 81 | LIBS: openssl_lib(arch), 82 | INSTALL_PROGRAM: install_program(), 83 | MAKEFLAGS: "-j10 -O", 84 | RELEASE_LIBBEAM: "yes" 85 | ] 86 | |> ensure_ndk_home(arch) 87 | 88 | if System.get_env("SKIP_CLEAN_BUILD") == nil do 89 | nifs = [ 90 | "#{otp_target(arch)}/lib/asn1/priv/lib/#{arch.name}/asn1rt_nif.a", 91 | "#{otp_target(arch)}/lib/crypto/priv/lib/#{arch.name}/crypto.a" 92 | ] 93 | 94 | cmd( 95 | ~w( 96 | cd #{otp_target(arch)} && 97 | git clean -xdf && 98 | ./otp_build setup 99 | --with-ssl=#{openssl_target(arch)} 100 | --disable-dynamic-ssl-lib 101 | --enable-builtin-zlib 102 | --without-javac --without-odbc --without-wx --without-debugger --without-observer --without-cdv --without-et 103 | --xcomp-conf=xcomp/erl-xcomp-#{arch.xcomp}.conf 104 | --enable-static-nifs=#{Enum.join(nifs, ",")} 105 | ) ++ ["CFLAGS=\"-Os -fPIC\""], 106 | env 107 | ) 108 | 109 | cmd(~w(cd #{otp_target(arch)} && ./otp_build boot -a), env) 110 | cmd(~w(cd #{otp_target(arch)} && ./otp_build release -a), env) 111 | end 112 | 113 | # Second round 114 | # The extra path can only be generated AFTER the nifs are compiled 115 | # so this requires two rounds... 116 | extra_nifs = 117 | Enum.map(extra_nifs, fn nif -> 118 | if static_lib_path(arch, Runtimes.get_nif(nif)) == nil do 119 | Nif.build(archid, nif) 120 | end 121 | 122 | static_lib_path(arch, Runtimes.get_nif(nif)) 123 | |> Path.absname() 124 | end) 125 | 126 | nifs = [ 127 | "#{otp_target(arch)}/lib/asn1/priv/lib/#{arch.name}/asn1rt_nif.a", 128 | "#{otp_target(arch)}/lib/crypto/priv/lib/#{arch.name}/crypto.a" 129 | | extra_nifs 130 | ] 131 | 132 | cmd( 133 | ~w( 134 | cd #{otp_target(arch)} && ./otp_build configure 135 | --with-ssl=#{openssl_target(arch)} 136 | --disable-dynamic-ssl-lib 137 | --enable-builtin-zlib 138 | --without-javac --without-odbc --without-wx --without-debugger --without-observer --without-cdv --without-et 139 | --xcomp-conf=xcomp/erl-xcomp-#{arch.xcomp}.conf 140 | --enable-static-nifs=#{Enum.join(nifs, ",")} 141 | ) ++ ["CFLAGS=\"-Os -fPIC\""], 142 | env 143 | ) 144 | 145 | cmd(~w(cd #{otp_target(arch)} && ./otp_build boot -a), env) 146 | cmd(~w(cd #{otp_target(arch)} && ./otp_build release -a), env) 147 | 148 | {build_host, 0} = System.cmd("#{otp_target(arch)}/erts/autoconf/config.guess", []) 149 | build_host = String.trim(build_host) 150 | 151 | # [erts_version] = Regex.run(~r/erts-[^ ]+/, File.read!("otp/otp_versions.table")) 152 | # Locating all built .a files for the target architecture: 153 | files = 154 | :filelib.fold_files( 155 | String.to_charlist(otp_target(arch)), 156 | ~c".+\\.a$", 157 | true, 158 | fn name, acc -> 159 | name = List.to_string(name) 160 | 161 | if String.contains?(name, arch.name) and 162 | not (String.contains?(name, build_host) or 163 | String.ends_with?(name, "_st.a") or String.ends_with?(name, "_r.a")) do 164 | Map.put(acc, Path.basename(name), name) 165 | else 166 | acc 167 | end 168 | end, 169 | %{} 170 | ) 171 | |> Map.values() 172 | 173 | files = files ++ [openssl_lib(arch) | nifs] 174 | 175 | # Creating a new archive 176 | repackage_archive(toolpath("libtool", arch), files, runtime_target(arch), env) 177 | end 178 | end 179 | 180 | defp buildall(targets, nifs) do 181 | Runtimes.ensure_otp() 182 | 183 | # targets 184 | # |> Enum.map(fn target -> Task.async(fn -> build(target, nifs) end) end) 185 | # |> Enum.map(fn task -> Task.await(task, 60_000*60*3) end) 186 | if System.get_env("PARALLEL", "") != "" do 187 | for target <- targets do 188 | {spawn_monitor(fn -> build(target, nifs) end), target} 189 | end 190 | |> Enum.each(fn {{pid, ref}, target} -> 191 | receive do 192 | {:DOWN, ^ref, :process, ^pid, :normal} -> 193 | :ok 194 | 195 | {:DOWN, ^ref, :process, ^pid, reason} -> 196 | IO.puts("Build failed for #{target}: #{inspect(reason)}") 197 | raise reason 198 | end 199 | end) 200 | else 201 | for target <- targets do 202 | build(target, nifs) 203 | end 204 | end 205 | 206 | files_for_zip = 207 | Enum.map(targets, fn target -> 208 | arch = get_arch(target) 209 | {arch.android_type, runtime_target(arch)} 210 | end) 211 | |> Enum.map(fn {android_type, path} -> 212 | {~c"#{android_type}/liberlang.a", File.read!(path)} 213 | end) 214 | 215 | {:ok, _} = :zip.create(~c"_build/android-runtime.zip", files_for_zip) 216 | 217 | Mix.shell().info( 218 | "Created android-runtime.zip with libraries for targets: #{inspect(targets)}" 219 | ) 220 | end 221 | end 222 | -------------------------------------------------------------------------------- /lib/mix/tasks/package_ios_nif.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Package.Ios.Nif do 2 | import Runtimes.Ios 3 | import Runtimes 4 | use Mix.Task 5 | require EEx 6 | 7 | def run([nif]) do 8 | buildall(Map.keys(architectures()), nif) 9 | end 10 | 11 | def build(arch, nif) do 12 | nif = get_nif(nif) 13 | arch = get_arch(arch) 14 | 15 | # Todo: How to sync cross-compiled erlang version and local erlang version? 16 | path = 17 | [ 18 | Path.join(elixir_target(arch), "bin"), 19 | Path.join(System.get_env("HOME"), ".mix"), 20 | # Path.join(otp_target(arch), "bootstrap/bin"), 21 | System.get_env("PATH") 22 | ] 23 | |> Enum.join(":") 24 | 25 | # Getting an Elixir version 26 | if File.exists?(Path.join(elixir_target(arch), "bin")) do 27 | IO.puts("Elixir already exists...") 28 | else 29 | cmd(["scripts/install_elixir.sh", elixir_target(arch)]) 30 | cmd("mix do local.hex --force && mix local.rebar --force", PATH: path) 31 | end 32 | 33 | {sdkroot, 0} = System.cmd("xcrun", ["-sdk", arch.sdk, "--show-sdk-path"]) 34 | sdkroot = String.trim(sdkroot) 35 | cflags = arch.cflags <> " -isysroot #{sdkroot} -I#{Path.absname("stubs")}" 36 | # got: ld: only one -syslibroot is accepted for bitcode bundle for architecture armv7 37 | # lflags = "-Wl,-syslibroot,#{sdkroot} -lc++" 38 | lflags = "-lc++" 39 | erts_version = Runtimes.erts_version() 40 | 41 | env = [ 42 | PATH: path, 43 | ERLANG_PATH: 44 | Path.join(otp_target(arch), "release/#{arch.name}/erts-#{erts_version}/include"), 45 | ERTS_INCLUDE_DIR: 46 | Path.join(otp_target(arch), "release/#{arch.name}/erts-#{erts_version}/include"), 47 | HOST: arch.name, 48 | CROSSCOMPILE: "iOS", 49 | STATIC_ERLANG_NIF: "yes", 50 | CC: "xcrun -sdk #{arch.sdk} cc -arch #{arch.arch}", 51 | CFLAGS: cflags, 52 | CXX: "xcrun -sdk #{arch.sdk} c++ -arch #{arch.arch}", 53 | CXXFLAGS: cflags, 54 | LD: "xcrun -sdk #{arch.sdk} ld -arch #{arch.arch}", 55 | LDFLAGS: lflags, 56 | RANLIB: "xcrun -sdk #{arch.sdk} ranlib", 57 | LIBTOOL: "xcrun -sdk #{arch.sdk} libtool", 58 | AR: "xcrun -sdk #{arch.sdk} ar", 59 | MIX_ENV: "prod", 60 | MIX_TARGET: "ios" 61 | ] 62 | 63 | # Start the builds 64 | nif_dir = "_build/#{arch.name}/#{nif.basename}" 65 | 66 | if !File.exists?(nif_dir) do 67 | cmd(~w(git clone #{nif.repo} #{nif_dir}), env) 68 | end 69 | 70 | if nif.tag do 71 | cmd(~w(cd #{nif_dir} && git checkout #{nif.tag}), env) 72 | end 73 | 74 | build_nif = Path.absname("scripts/build_nif.sh") 75 | cmd(~w(cd #{nif_dir} && #{build_nif}), env) 76 | 77 | case static_lib_path(arch, nif) do 78 | nil -> raise "NIF build failed. Could not locate static lib" 79 | lib -> lib 80 | end 81 | end 82 | 83 | defp buildall(targets, nif) do 84 | for target <- targets do 85 | build(target, nif) 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /lib/mix/tasks/package_ios_runtime.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Package.Ios.Runtime do 2 | import Runtimes.Ios 3 | import Runtimes 4 | alias Mix.Tasks.Package.Ios.Nif 5 | use Mix.Task 6 | require EEx 7 | 8 | def run(["with_diode_nifs"]) do 9 | nifs = [ 10 | "https://github.com/diodechain/esqlite.git", 11 | "https://github.com/diodechain/libsecp256k1.git" 12 | ] 13 | 14 | run(nifs) 15 | end 16 | 17 | def run([]) do 18 | run(["https://github.com/elixir-desktop/exqlite"]) 19 | end 20 | 21 | def run(nifs) do 22 | IO.puts("Validating nifs...") 23 | Enum.each(nifs, fn nif -> Runtimes.get_nif(nif) end) 24 | buildall(Map.keys(architectures()), nifs) 25 | end 26 | 27 | def build(archid, extra_nifs) do 28 | arch = get_arch(archid) 29 | File.mkdir_p!("_build/#{arch.name}") 30 | 31 | # Building OpenSSL 32 | if File.exists?(openssl_lib(arch)) do 33 | IO.puts("OpenSSL (#{arch.id}) already exists...") 34 | else 35 | cmd("scripts/install_openssl.sh", 36 | ARCH: arch.openssl_arch, 37 | OPENSSL_PREFIX: openssl_target(arch), 38 | MAKEFLAGS: "-j10 -O" 39 | ) 40 | end 41 | 42 | # Building OTP 43 | if File.exists?(runtime_target(arch)) do 44 | IO.puts("liberlang.a (#{arch.id}) already exists...") 45 | else 46 | if !File.exists?(otp_target(arch)) do 47 | Runtimes.ensure_otp() 48 | cmd(~w(git clone _build/otp #{otp_target(arch)})) 49 | end 50 | 51 | env = [ 52 | LIBS: openssl_lib(arch), 53 | INSTALL_PROGRAM: "/usr/bin/install -c", 54 | MAKEFLAGS: "-j10 -O", 55 | RELEASE_LIBBEAM: "yes" 56 | ] 57 | 58 | if System.get_env("SKIP_CLEAN_BUILD") == nil do 59 | nifs = [ 60 | "#{otp_target(arch)}/lib/asn1/priv/lib/#{arch.name}/asn1rt_nif.a", 61 | "#{otp_target(arch)}/lib/crypto/priv/lib/#{arch.name}/crypto.a" 62 | ] 63 | 64 | # First round build to generate headers and libs required to build nifs: 65 | cmd( 66 | ~w( 67 | cd #{otp_target(arch)} && 68 | git clean -xdf && 69 | ./otp_build setup 70 | --with-ssl=#{openssl_target(arch)} 71 | --disable-dynamic-ssl-lib 72 | --xcomp-conf=xcomp/erl-xcomp-#{arch.xcomp}.conf 73 | --enable-static-nifs=#{Enum.join(nifs, ",")} 74 | ), 75 | env 76 | ) 77 | 78 | cmd(~w(cd #{otp_target(arch)} && ./otp_build boot -a), env) 79 | cmd(~w(cd #{otp_target(arch)} && ./otp_build release -a), env) 80 | end 81 | 82 | # Second round 83 | # The extra path can only be generated AFTER the nifs are compiled 84 | # so this requires two rounds... 85 | extra_nifs = 86 | Enum.map(extra_nifs, fn nif -> 87 | if static_lib_path(arch, Runtimes.get_nif(nif)) == nil do 88 | Nif.build(archid, nif) 89 | end 90 | 91 | static_lib_path(arch, Runtimes.get_nif(nif)) 92 | |> Path.absname() 93 | end) 94 | 95 | nifs = [ 96 | "#{otp_target(arch)}/lib/asn1/priv/lib/#{arch.name}/asn1rt_nif.a", 97 | "#{otp_target(arch)}/lib/crypto/priv/lib/#{arch.name}/crypto.a" 98 | | extra_nifs 99 | ] 100 | 101 | cmd( 102 | ~w( 103 | cd #{otp_target(arch)} && ./otp_build configure 104 | --with-ssl=#{openssl_target(arch)} 105 | --disable-dynamic-ssl-lib 106 | --xcomp-conf=xcomp/erl-xcomp-#{arch.xcomp}.conf 107 | --enable-static-nifs=#{Enum.join(nifs, ",")} 108 | ), 109 | env 110 | ) 111 | 112 | cmd(~w(cd #{otp_target(arch)} && ./otp_build boot -a), env) 113 | cmd(~w(cd #{otp_target(arch)} && ./otp_build release -a), env) 114 | 115 | {build_host, 0} = System.cmd("#{otp_target(arch)}/erts/autoconf/config.guess", []) 116 | build_host = String.trim(build_host) 117 | 118 | # [erts_version] = Regex.run(~r/erts-[^ ]+/, File.read!("otp/otp_versions.table")) 119 | # Locating all built .a files for the target architecture: 120 | files = 121 | :filelib.fold_files( 122 | String.to_charlist(otp_target(arch)), 123 | ~c".+\\.a$", 124 | true, 125 | fn name, acc -> 126 | name = List.to_string(name) 127 | 128 | if String.contains?(name, arch.name) and 129 | not (String.contains?(name, build_host) or 130 | String.ends_with?(name, "_st.a") or String.ends_with?(name, "_r.a")) do 131 | Map.put(acc, Path.basename(name), name) 132 | else 133 | acc 134 | end 135 | end, 136 | %{} 137 | ) 138 | |> Map.values() 139 | 140 | files = files ++ [openssl_lib(arch) | nifs] 141 | 142 | # Creating a new archive 143 | repackage_archive("libtool", files, runtime_target(arch)) 144 | end 145 | end 146 | 147 | defp buildall(targets, nifs) do 148 | Runtimes.ensure_otp() 149 | 150 | # targets 151 | # |> Enum.map(fn target -> Task.async(fn -> build(target, nifs) end) end) 152 | # |> Enum.map(fn task -> Task.await(task, 60_000*60*3) end) 153 | if System.get_env("PARALLEL", "") != "" do 154 | for target <- targets do 155 | {spawn_monitor(fn -> build(target, nifs) end), target} 156 | end 157 | |> Enum.each(fn {{pid, ref}, target} -> 158 | receive do 159 | {:DOWN, ^ref, :process, ^pid, :normal} -> 160 | :ok 161 | 162 | {:DOWN, ^ref, :process, ^pid, reason} -> 163 | IO.puts("Build failed for #{target}: #{inspect(reason)}") 164 | raise reason 165 | end 166 | end) 167 | else 168 | for target <- targets do 169 | build(target, nifs) 170 | end 171 | end 172 | 173 | {sims, reals} = 174 | Enum.map(targets, fn target -> runtime_target(get_arch(target)) end) 175 | |> Enum.split_with(fn lib -> String.contains?(lib, "simulator") end) 176 | 177 | libs = 178 | (lipo(sims) ++ lipo(reals)) 179 | |> Enum.map(fn lib -> "-library #{lib}" end) 180 | 181 | framework = "./_build/liberlang.xcframework" 182 | 183 | if File.exists?(framework) do 184 | File.rm_rf!(framework) 185 | end 186 | 187 | cmd( 188 | "xcodebuild -create-xcframework -output #{framework} " <> 189 | Enum.join(libs, " ") 190 | ) 191 | end 192 | end 193 | -------------------------------------------------------------------------------- /lib/runtimes.ex: -------------------------------------------------------------------------------- 1 | defmodule Runtimes do 2 | require EEx 3 | 4 | def cmd(args, env \\ []) do 5 | args = if is_list(args), do: Enum.join(args, " "), else: args 6 | 7 | env = 8 | Enum.map(env, fn {key, value} -> 9 | case key do 10 | atom when is_atom(atom) -> {Atom.to_string(atom), value} 11 | _other -> {key, value} 12 | end 13 | end) 14 | 15 | IO.puts("RUN: #{args}") 16 | 17 | {ret, 0} = 18 | System.cmd("bash", ["-c", args], 19 | stderr_to_stdout: true, 20 | into: IO.binstream(:stdio, :line), 21 | env: env 22 | ) 23 | 24 | ret 25 | end 26 | 27 | def docker_build(image, file) do 28 | IO.puts("RUN: docker build -t #{image} -f #{file} .") 29 | 30 | ret = 31 | System.cmd("docker", ~w(build -t #{image} -f #{file} .), 32 | stderr_to_stdout: true, 33 | into: IO.binstream(:stdio, :line) 34 | ) 35 | 36 | File.rm(file) 37 | {_, 0} = ret 38 | end 39 | 40 | def default_nifs() do 41 | [ 42 | "https://github.com/diodechain/esqlite.git", 43 | "https://github.com/elixir-desktop/exqlite", 44 | "https://github.com/diodechain/libsecp256k1.git" 45 | ] 46 | end 47 | 48 | def get_nif(url) when is_binary(url) do 49 | get_nif({url, []}) 50 | end 51 | 52 | def get_nif({url, opts}) do 53 | name = Keyword.get(opts, :name, Path.basename(url, ".git")) 54 | tag = Keyword.get(opts, :tag, nil) 55 | 56 | %{ 57 | tag: tag, 58 | repo: url, 59 | name: name, 60 | basename: Path.basename(url, ".git") 61 | } 62 | end 63 | 64 | def otp_source() do 65 | System.get_env("OTP_SOURCE", "https://github.com/erlang/otp") 66 | end 67 | 68 | def otp_tag() do 69 | System.get_env("OTP_TAG", "OTP-26.2.5.6") 70 | end 71 | 72 | def ensure_otp() do 73 | if !File.exists?("_build/otp") do 74 | File.mkdir_p!("_build") 75 | 76 | cmd( 77 | "git clone #{Runtimes.otp_source()} _build/otp && cd _build/otp && git checkout #{Runtimes.otp_tag()}" 78 | ) 79 | end 80 | end 81 | 82 | def erts_version() do 83 | ensure_otp() 84 | content = File.read!("_build/otp/erts/vsn.mk") 85 | [[_, vsn]] = Regex.scan(~r/VSN *= *([0-9\.]+)/, content) 86 | vsn 87 | end 88 | 89 | def install_program() do 90 | case :os.type() do 91 | {:unix, :linux} -> "/usr/bin/install -c -s --strip-program=llvm-strip" 92 | {:unix, :darwin} -> "/usr/bin/install -c" 93 | end 94 | end 95 | 96 | def host() do 97 | case :os.type() do 98 | {:unix, :linux} -> "linux-x86_64" 99 | {:unix, :darwin} -> "darwin-x86_64" 100 | end 101 | end 102 | 103 | def elixir_target(arch) do 104 | Path.absname("_build/#{arch.name}/elixir") 105 | end 106 | 107 | def stub_target(arch) do 108 | Path.absname("_build/#{arch.name}/stubs") 109 | end 110 | 111 | def static_lib_path(arch, nif) do 112 | nif_dir = "_build/#{arch.name}/#{nif.basename}" 113 | 114 | # Finding all .a files 115 | :filelib.fold_files( 116 | String.to_charlist(nif_dir), 117 | ~c".+\\.a$", 118 | true, 119 | fn name, acc -> [List.to_string(name) | acc] end, 120 | [] 121 | ) 122 | |> Enum.filter(fn path -> String.contains?(path, "priv") end) 123 | |> List.first() 124 | end 125 | 126 | def openssl_target(arch) do 127 | Path.absname("_build/#{arch.name}/openssl") 128 | end 129 | 130 | def openssl_lib(arch) do 131 | Path.join(openssl_target(arch), "lib/libcrypto.a") 132 | end 133 | 134 | def otp_target(arch) do 135 | Path.absname("_build/#{arch.name}/otp") 136 | end 137 | 138 | def runtime_target(arch) do 139 | "_build/#{arch.name}/liberlang.a" 140 | end 141 | 142 | # Method takes multiple ".a" archive files and extracts their ".o" contents 143 | # to then reassemble all of them into a single `target` ".a" archive 144 | # Method takes multiple ".a" archive files and extracts their ".o" contents 145 | # to then reassemble all of them into a single `target` ".a" archive 146 | def repackage_archive(libtool, files, target, env \\ []) do 147 | # Removing relative prefix so changing cwd is safe. 148 | files = Enum.join(files, " ") 149 | cmd("#{libtool} -static -o #{target} #{files}", env) 150 | end 151 | end 152 | -------------------------------------------------------------------------------- /lib/runtimes/android.ex: -------------------------------------------------------------------------------- 1 | defmodule Runtimes.Android do 2 | require EEx 3 | import Runtimes 4 | @android_abi_version 26 5 | 6 | def architectures() do 7 | %{ 8 | "arm" => %{ 9 | xcomp: "arm-android", 10 | openssl_arch: "android-arm", 11 | id: "arm", 12 | abi: @android_abi_version, 13 | cpu: "arm", 14 | bin: "armv7a", 15 | pc: "arm-unknown", 16 | name: "arm-unknown-linux-androideabi", 17 | android_name: "androideabi", 18 | android_type: "armeabi-v7a", 19 | cflags: "--target=arm-linux-android#{@android_abi_version} -march=armv7-a -mfpu=neon" 20 | }, 21 | "arm64" => %{ 22 | xcomp: "arm64-android", 23 | openssl_arch: "android-arm64", 24 | id: "arm64", 25 | abi: @android_abi_version, 26 | cpu: "aarch64", 27 | bin: "aarch64", 28 | pc: "aarch64-unknown", 29 | name: "aarch64-unknown-linux-android", 30 | android_name: "android", 31 | android_type: "arm64-v8a", 32 | cflags: "--target=aarch64-linux-android#{@android_abi_version}" 33 | }, 34 | "x86_64" => %{ 35 | xcomp: "x86_64-android", 36 | openssl_arch: "android-x86_64", 37 | id: "x86_64", 38 | abi: @android_abi_version, 39 | cpu: "x86_64", 40 | bin: "x86_64", 41 | pc: "x86_64-pc", 42 | name: "x86_64-pc-linux-android", 43 | android_name: "android", 44 | android_type: "x86_64", 45 | cflags: "--target=x86_64-linux-android#{@android_abi_version}" 46 | } 47 | } 48 | |> Map.new() 49 | end 50 | 51 | def get_arch(arch) do 52 | Map.fetch!(architectures(), arch) 53 | end 54 | 55 | # lipo joins different cpu build of the same target together 56 | def lipo([]), do: [] 57 | def lipo([one]), do: [one] 58 | 59 | def lipo(more) do 60 | File.mkdir_p!("tmp") 61 | x = System.unique_integer([:positive]) 62 | tmp = "tmp/#{x}-liberlang.a" 63 | if File.exists?(tmp), do: File.rm!(tmp) 64 | cmd("lipo -create #{Enum.join(more, " ")} -output #{tmp}") 65 | [tmp] 66 | end 67 | 68 | def ndk_home() do 69 | System.get_env("ANDROID_NDK_HOME") || 70 | guess_ndk_home() || raise "ANDROID_NDK_HOME is not set" 71 | end 72 | 73 | def guess_ndk_home() do 74 | home = System.get_env("HOME") 75 | 76 | base = 77 | case :os.type() do 78 | {:unix, :linux} -> Path.join(home, "Android/Sdk/ndk") 79 | {:unix, :darwin} -> Path.join(home, "Library/Android/sdk/ndk") 80 | end 81 | 82 | case File.ls(base) do 83 | {:ok, versions} -> Path.join(base, List.last(Enum.sort(versions))) 84 | _ -> raise "No NDK found in #{base}" 85 | end 86 | end 87 | 88 | def bin_path() do 89 | Path.join(ndk_home(), "/toolchains/llvm/prebuilt/#{host()}/bin") 90 | end 91 | 92 | def ensure_ndk_home(env, arch) do 93 | env = Map.new(env) 94 | path = env[:PATH] || System.get_env("PATH") 95 | ndk_abi_plat = "#{arch.android_name}#{arch.abi}" 96 | 97 | cflags = (env[:CFLAGS] || "") <> "-Os -fPIC" 98 | cxxflags = (env[:CXXFLAGS] || "") <> "-Os -fPIC" 99 | 100 | Map.merge( 101 | env, 102 | %{ 103 | ANDROID_NDK_HOME: ndk_home(), 104 | PATH: bin_path() <> ":" <> path, 105 | NDK_ABI_PLAT: ndk_abi_plat, 106 | CXX: toolpath("clang++", arch), 107 | CXXFLAGS: cxxflags, 108 | CC: toolpath("clang", arch), 109 | CFLAGS: cflags, 110 | AR: toolpath("ar", arch), 111 | FC: "", 112 | CPP: "", 113 | LD: toolpath("ld", arch), 114 | LIBTOOL: toolpath("libtool", arch), 115 | RANLIB: toolpath("ranlib", arch), 116 | STRIP: toolpath("strip", arch) 117 | } 118 | ) 119 | |> Map.to_list() 120 | end 121 | 122 | def toolpath(tool, arch) do 123 | stub = Path.absname("./stubs/bin/#{tool}-stub.sh") 124 | real = real_toolpath(tool, arch) 125 | 126 | if File.exists?(stub) do 127 | content = File.read!(stub) 128 | stub = Path.join(stub_target(arch), tool) 129 | File.mkdir_p!(stub_target(arch)) 130 | File.write!(stub, String.replace(content, "%TOOL%", real || "")) 131 | File.chmod!(stub, 0o755) 132 | stub 133 | else 134 | real || raise "Tool not found: #{tool} in #{bin_path()}" 135 | end 136 | end 137 | 138 | def real_toolpath(tool, arch) do 139 | [ 140 | tool, 141 | "llvm-" <> tool, 142 | "#{arch.cpu}-linux-#{arch.android_name}-#{tool}", 143 | "#{arch.bin}-linux-#{arch.android_name}-#{tool}" 144 | ] 145 | |> Enum.map(fn name -> Path.absname(Path.join(bin_path(), name)) end) 146 | |> Enum.find(fn name -> File.exists?(name) end) 147 | end 148 | 149 | def nif_env(arch) do 150 | path = 151 | [ 152 | Path.join(elixir_target(arch), "bin"), 153 | stub_target(arch), 154 | Path.join(System.get_env("HOME"), ".mix"), 155 | # Path.join(otp_target(arch), "bootstrap/bin"), 156 | System.get_env("PATH") 157 | ] 158 | |> Enum.join(":") 159 | 160 | cflags = 161 | arch.cflags <> 162 | " -I#{Path.absname("stubs")}" 163 | 164 | lflags = 165 | "-v -lc++ -L#{Path.join(ndk_home(), "toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/#{arch.cpu}-linux-android/#{arch.abi}")}" 166 | 167 | erts_version = Runtimes.erts_version() 168 | 169 | [ 170 | PATH: path, 171 | ERLANG_PATH: 172 | Path.join(otp_target(arch), "release/#{arch.name}/erts-#{erts_version}/include"), 173 | ERTS_INCLUDE_DIR: 174 | Path.join(otp_target(arch), "release/#{arch.name}/erts-#{erts_version}/include"), 175 | HOST: arch.name, 176 | CROSSCOMPILE: "Android", 177 | STATIC_ERLANG_NIF: "yes", 178 | CFLAGS: cflags, 179 | CXXFLAGS: cflags, 180 | LDFLAGS: lflags, 181 | MIX_ENV: "prod", 182 | MIX_TARGET: "Android" 183 | ] 184 | |> ensure_ndk_home(arch) 185 | end 186 | end 187 | -------------------------------------------------------------------------------- /lib/runtimes/ios.ex: -------------------------------------------------------------------------------- 1 | defmodule Runtimes.Ios do 2 | import Runtimes 3 | 4 | def architectures() do 5 | # Not sure if we still need arm-32 at all https://blakespot.com/ios_device_specifications_grid.html 6 | %{ 7 | "ios" => %{ 8 | arch: "armv7", 9 | id: "ios", 10 | sdk: "iphoneos", 11 | openssl_arch: "ios-xcrun", 12 | xcomp: "arm-ios", 13 | name: "arm-apple-ios", 14 | cflags: "-mios-version-min=7.0.0 -fno-common -Os -D__IOS__=yes" 15 | }, 16 | "ios-arm64" => %{ 17 | arch: "arm64", 18 | id: "ios64", 19 | sdk: "iphoneos", 20 | openssl_arch: "ios64-xcrun", 21 | xcomp: "arm64-ios", 22 | name: "aarch64-apple-ios", 23 | cflags: "-mios-version-min=7.0.0 -fno-common -Os -D__IOS__=yes" 24 | }, 25 | "iossimulator-x86_64" => %{ 26 | arch: "x86_64", 27 | id: "iossimulator", 28 | sdk: "iphonesimulator", 29 | openssl_arch: "iossimulator-x86_64-xcrun", 30 | xcomp: "x86_64-iossimulator", 31 | name: "x86_64-apple-iossimulator", 32 | cflags: "-mios-simulator-version-min=7.0.0 -fno-common -Os -D__IOS__=yes" 33 | }, 34 | "iossimulator-arm64" => %{ 35 | arch: "arm64", 36 | id: "iossimulator", 37 | sdk: "iphonesimulator", 38 | openssl_arch: "iossimulator-arm64-xcrun", 39 | xcomp: "arm64-iossimulator", 40 | name: "aarch64-apple-iossimulator", 41 | cflags: "-mios-simulator-version-min=7.0.0 -fno-common -Os -D__IOS__=yes" 42 | } 43 | } 44 | end 45 | 46 | def get_arch(arch) do 47 | Map.fetch!(architectures(), arch) 48 | end 49 | 50 | # lipo joins different cpu build of the same target together 51 | def lipo([]), do: [] 52 | def lipo([one]), do: [one] 53 | 54 | def lipo(more) do 55 | File.mkdir_p!("tmp") 56 | x = System.unique_integer([:positive]) 57 | tmp = "tmp/#{x}-liberlang.a" 58 | if File.exists?(tmp), do: File.rm!(tmp) 59 | cmd("lipo -create #{Enum.join(more, " ")} -output #{tmp}") 60 | [tmp] 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Runtimes.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :runtimes, 7 | version: "0.1.0", 8 | elixir: "~> 1.11", 9 | start_permanent: Mix.env() == :prod, 10 | deps: deps() 11 | ] 12 | end 13 | 14 | # Run "mix help compile.app" to learn about applications. 15 | def application do 16 | [ 17 | extra_applications: [:logger, :eex] 18 | ] 19 | end 20 | 21 | # Run "mix help deps" to learn about dependencies. 22 | defp deps do 23 | [ 24 | # {:dep_from_hexpm, "~> 0.3.0"}, 25 | # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} 26 | ] 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /patch/openssl-ios.conf: -------------------------------------------------------------------------------- 1 | #### iPhoneOS/iOS 2 | # 3 | # It takes recent enough Xcode to use following two targets. It shouldn't 4 | # be a problem by now, but if they don't work, original targets below 5 | # that depend on manual definition of environment variables should still 6 | # work... 7 | # 8 | my %targets = ( 9 | "ios-common" => { 10 | template => 1, 11 | inherit_from => [ "darwin-common" ], 12 | sys_id => "iOS", 13 | disable => [ "shared", "async" ], 14 | }, 15 | "ios-xcrun" => { 16 | inherit_from => [ "ios-common", asm("armv4_asm") ], 17 | CC => "xcrun -sdk iphoneos cc", 18 | cflags => add("-arch armv7 -mios-version-min=7.0.0 -fno-common"), 19 | perlasm_scheme => "ios32", 20 | }, 21 | "ios64-xcrun" => { 22 | inherit_from => [ "ios-common", asm("aarch64_asm") ], 23 | CC => "xcrun -sdk iphoneos cc", 24 | cflags => add("-arch arm64 -mios-version-min=7.0.0 -fno-common"), 25 | bn_ops => "SIXTY_FOUR_BIT_LONG RC4_CHAR", 26 | perlasm_scheme => "ios64", 27 | }, 28 | "iossimulator-xcrun" => { 29 | inherit_from => [ "ios-common" ], 30 | CC => "xcrun -sdk iphonesimulator cc", 31 | }, 32 | "iossimulator-x86_64-xcrun" => { 33 | inherit_from => [ "ios-common" ], 34 | CC => "xcrun -sdk iphonesimulator cc", 35 | cflags => add("-arch x86_64 -mios-simulator-version-min=7.0.0 -fno-common"), 36 | }, 37 | "iossimulator-arm64-xcrun" => { 38 | inherit_from => [ "ios-common" ], 39 | CC => "xcrun -sdk iphonesimulator cc", 40 | cflags => add("-arch arm64 -mios-simulator-version-min=7.0.0 -fno-common"), 41 | }, 42 | ); 43 | -------------------------------------------------------------------------------- /patch/otp-space.patch: -------------------------------------------------------------------------------- 1 | diff --git a/erts/emulator/Makefile.in b/erts/emulator/Makefile.in 2 | index 5def44d8cd..ed68fd2086 100644 3 | --- a/erts/emulator/Makefile.in 4 | +++ b/erts/emulator/Makefile.in 5 | @@ -197,8 +197,8 @@ LIBS += $(TYPE_LIBS) 6 | ORIG_LIBS:= $(LIBS) 7 | 8 | comma:=, 9 | -space:= 10 | -space+= 11 | +null:= 12 | +space:=$(null) $(null) 13 | 14 | STATIC_NIFS=@STATIC_NIFS@ 15 | ifneq ($(STATIC_NIFS),no) 16 | -------------------------------------------------------------------------------- /scripts/build_android_diode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export OTP_TAG=OTP-26.2.5.12 3 | export OTP_SOURCE=https://github.com/erlang/otp 4 | export OTP_SOURCE=$HOME/projects/otp 5 | mix package.android.runtime with_diode_nifs 6 | -------------------------------------------------------------------------------- /scripts/build_anroid_otp-25.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export OTP_TAG=OTP-26.2.5.6 3 | export OTP_SOURCE=https://github.com/erlang/otp 4 | mix package.android.runtime 5 | mix package.android.nif 6 | -------------------------------------------------------------------------------- /scripts/build_ios_diode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export OTP_TAG=OTP-26.2.5.6 3 | export OTP_SOURCE=https://github.com/erlang/otp 4 | mix package.ios.runtime with_diode_nifs 5 | -------------------------------------------------------------------------------- /scripts/build_ios_otp-25.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export OTP_TAG=OTP-26.2.5.6 3 | export OTP_SOURCE=https://github.com/erlang/otp 4 | mix package.ios.runtime 5 | -------------------------------------------------------------------------------- /scripts/build_nif.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # ensure there is rebar3 in path 4 | export PATH=$PATH:~/.mix 5 | 6 | # mix has priority over `make` for projects like exqlite 7 | if [ -f "mix.exs" ]; then 8 | exec mix do deps.get, release --overwrite 9 | fi 10 | 11 | if [ -f "Makefile" ]; then 12 | exec make 13 | fi 14 | 15 | if [ -f "rebar.config" ]; then 16 | exec /root/.mix/rebar3 compile 17 | fi 18 | 19 | echo "Could not identify how to build this nif" 20 | exit 1 -------------------------------------------------------------------------------- /scripts/install_elixir.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | VERSION=1.16.3 3 | set -e 4 | 5 | mkdir $1 6 | cd $1 7 | wget https://github.com/elixir-lang/elixir/archive/v${VERSION}.zip 8 | unzip v${VERSION}.zip 9 | cd elixir-${VERSION} 10 | make 11 | mv * .. 12 | -------------------------------------------------------------------------------- /scripts/install_openssl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export VSN=1.1.1v 3 | export VSN_HASH=d6697e2871e77238460402e9362d47d18382b15ef9f246aba6c7bd780d38a6b0 4 | 5 | if [ -z "$OPENSSL_PREFIX" ]; then 6 | export PREFIX=/usr/local/openssl 7 | else 8 | export PREFIX=$OPENSSL_PREFIX 9 | fi 10 | 11 | if [ -z "$ARCH" ]; then 12 | export BUILD_DIR=_build 13 | export BASE_DIR=.. 14 | else 15 | export BUILD_DIR=_build/$ARCH 16 | export BASE_DIR=../.. 17 | fi 18 | 19 | # install openssl 20 | echo "Build and install openssl......" 21 | mkdir -p $PREFIX/ssl && \ 22 | mkdir -p $BUILD_DIR && \ 23 | cd $BUILD_DIR && \ 24 | wget -nc https://www.openssl.org/source/openssl-$VSN.tar.gz && \ 25 | [ "$VSN_HASH" = "$(sha256sum openssl-$VSN.tar.gz | cut -d ' ' -f1)" ] && \ 26 | tar xzf openssl-$VSN.tar.gz && \ 27 | cp $BASE_DIR/patch/openssl-ios.conf openssl-$VSN/Configurations/15-ios.conf && \ 28 | cd openssl-$VSN && \ 29 | ./Configure $ARCH --prefix=$PREFIX "$@" && \ 30 | make clean && make depend && make && make install_sw install_ssldirs 31 | 32 | -------------------------------------------------------------------------------- /scripts/package_nif.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -d "_build/prod/lib/$1" ]; then 4 | exec zip -rjx "*.empty" - "_build/prod/lib/$1/priv/" 5 | fi 6 | 7 | exec zip -rjx "*.empty" - priv/ 8 | -------------------------------------------------------------------------------- /src/test_main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | 5 | extern "C" { 6 | #include "config.h" 7 | #include "sys.h" 8 | #include "erl_vm.h" 9 | #include "global.h" 10 | } 11 | 12 | void run_erlang() 13 | { 14 | const char *args[] = { 15 | "test_main", 16 | "-sbwt", 17 | "none", 18 | "--", 19 | "-root", 20 | "/home/dominicletz/dDrive", 21 | "-progname", 22 | "erl", 23 | "--", 24 | "-home", 25 | "/home/dominicletz", 26 | "--", 27 | "-kernel", 28 | "shell_history", 29 | "enabled", 30 | "--", 31 | "-heart", 32 | "-pa", 33 | "/home/dominicletz/.config/ddrive/update-1.2.1", 34 | "-kernel", 35 | "inet_dist_use_interface", 36 | "{127,0,0,1}", 37 | "-elixir", 38 | "ansi_enabled", 39 | "true", 40 | "-noshell", 41 | "-s", 42 | "elixir", 43 | "start_cli", 44 | "-mode", 45 | "embedded", 46 | "-setcookie", 47 | "EFIXW6GFCGFWOQCYTPRFXNR2JRYN6BJVX7FTOOLWONYFUQF46PVQ====", 48 | "-name", 49 | "ddrive_7598@127.0.0.1", 50 | "-config", 51 | "/home/dominicletz/dDrive/releases/1.2.1/sys", 52 | "-boot", 53 | "/home/dominicletz/dDrive/releases/1.2.1/start", 54 | "-boot_var", 55 | "RELEASE_LIB", 56 | "/home/dominicletz/dDrive/lib", 57 | "--", 58 | "--", 59 | "-extra", 60 | "--no-halt", 61 | }; 62 | 63 | erl_start(sizeof(args) / sizeof(args[0]), (char **)args); 64 | } 65 | 66 | int main(int argc, char *argv[]) 67 | { 68 | char *path = getenv("PATH"); 69 | 70 | auto app = std::string("dDrive"); 71 | auto home_dir = std::string("/home/dominicletz/"); 72 | auto root_dir = home_dir.append("dDrive/"); 73 | auto bin_dir = root_dir.append("erts-12.0/bin/"); 74 | 75 | auto env_bin_dir = std::string("BINDIR=").append(bin_dir); 76 | auto env_path = std::string("PATH=").append(path).append(":").append(bin_dir); 77 | auto app_icon = std::string("WX_APP_ICON=").append(root_dir).append("lib/ddrive-1.2.1/priv/diode.png"); 78 | 79 | chdir("/home/dominicletz/dDrive"); 80 | putenv((char *)env_bin_dir.c_str()); 81 | putenv((char *)env_path.c_str()); 82 | putenv((char *)"WX_APP_TITLE=dDrive"); 83 | putenv((char *)app_icon.c_str()); 84 | 85 | std::thread erlang(run_erlang); 86 | erlang.join(); 87 | // run_erlang(); 88 | exit(0); 89 | } -------------------------------------------------------------------------------- /stubs/bin/ar-stub.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | echo AR "$@" 3 | exec %TOOL% "$@" -------------------------------------------------------------------------------- /stubs/bin/ld-stub.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo LD "$@" 3 | exec %TOOL% "$@" -------------------------------------------------------------------------------- /stubs/bin/libtool-stub.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # called with `libtool -static -o ...` 3 | 4 | # remove the -static flag and handle -o flag properly 5 | output="" 6 | inputs=() 7 | skip_next=false 8 | 9 | # Create a temporary directory for extracting .a files 10 | temp_dir=$(mktemp -d) 11 | trap 'rm -rf "$temp_dir"' EXIT 12 | 13 | for arg in "$@"; do 14 | if $skip_next; then 15 | output="$arg" 16 | skip_next=false 17 | elif [ "$arg" = "-static" ]; then 18 | # Skip the -static flag 19 | continue 20 | elif [ "$arg" = "-o" ]; then 21 | skip_next=true 22 | else 23 | if [[ "$arg" == *.a ]]; then 24 | # Convert to absolute path if needed 25 | abs_path=$(readlink -f "$arg") 26 | # Extract .a file to temp directory 27 | mkdir -p "$temp_dir/$(basename "$arg")" 28 | cd "$temp_dir/$(basename "$arg")" 29 | ${AR:-ar} x "$abs_path" 30 | # Add all .o files from the extracted archive 31 | for obj in *.o; do 32 | if [ -f "$obj" ]; then 33 | inputs+=("$temp_dir/$(basename "$arg")/$obj") 34 | fi 35 | done 36 | cd - > /dev/null 37 | else 38 | inputs+=("$arg") 39 | fi 40 | fi 41 | done 42 | 43 | # Now use the output file as the first argument to ar 44 | echo ${AR:-ar} rcs "$output" "${inputs[@]}" 45 | exec ${AR:-ar} rcs "$output" "${inputs[@]}" 46 | -------------------------------------------------------------------------------- /stubs/bin/ranlib-stub.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | echo RANLIB "$@" 3 | exec %TOOL% "$@" -------------------------------------------------------------------------------- /stubs/obstack.h: -------------------------------------------------------------------------------- 1 | /* obstack.h - object stack macros 2 | Copyright (C) 1988-2021 Free Software Foundation, Inc. 3 | This file is part of the GNU C Library. 4 | 5 | This file is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as 7 | published by the Free Software Foundation; either version 3 of the 8 | License, or (at your option) any later version. 9 | 10 | This file is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public License 16 | along with this program. If not, see . */ 17 | 18 | /* Summary: 19 | 20 | All the apparent functions defined here are macros. The idea 21 | is that you would use these pre-tested macros to solve a 22 | very specific set of problems, and they would run fast. 23 | Caution: no side-effects in arguments please!! They may be 24 | evaluated MANY times!! 25 | 26 | These macros operate a stack of objects. Each object starts life 27 | small, and may grow to maturity. (Consider building a word syllable 28 | by syllable.) An object can move while it is growing. Once it has 29 | been "finished" it never changes address again. So the "top of the 30 | stack" is typically an immature growing object, while the rest of the 31 | stack is of mature, fixed size and fixed address objects. 32 | 33 | These routines grab large chunks of memory, using a function you 34 | supply, called 'obstack_chunk_alloc'. On occasion, they free chunks, 35 | by calling 'obstack_chunk_free'. You must define them and declare 36 | them before using any obstack macros. 37 | 38 | Each independent stack is represented by a 'struct obstack'. 39 | Each of the obstack macros expects a pointer to such a structure 40 | as the first argument. 41 | 42 | One motivation for this package is the problem of growing char strings 43 | in symbol tables. Unless you are "fascist pig with a read-only mind" 44 | --Gosper's immortal quote from HAKMEM item 154, out of context--you 45 | would not like to put any arbitrary upper limit on the length of your 46 | symbols. 47 | 48 | In practice this often means you will build many short symbols and a 49 | few long symbols. At the time you are reading a symbol you don't know 50 | how long it is. One traditional method is to read a symbol into a 51 | buffer, realloc()ating the buffer every time you try to read a symbol 52 | that is longer than the buffer. This is beaut, but you still will 53 | want to copy the symbol from the buffer to a more permanent 54 | symbol-table entry say about half the time. 55 | 56 | With obstacks, you can work differently. Use one obstack for all symbol 57 | names. As you read a symbol, grow the name in the obstack gradually. 58 | When the name is complete, finalize it. Then, if the symbol exists already, 59 | free the newly read name. 60 | 61 | The way we do this is to take a large chunk, allocating memory from 62 | low addresses. When you want to build a symbol in the chunk you just 63 | add chars above the current "high water mark" in the chunk. When you 64 | have finished adding chars, because you got to the end of the symbol, 65 | you know how long the chars are, and you can create a new object. 66 | Mostly the chars will not burst over the highest address of the chunk, 67 | because you would typically expect a chunk to be (say) 100 times as 68 | long as an average object. 69 | 70 | In case that isn't clear, when we have enough chars to make up 71 | the object, THEY ARE ALREADY CONTIGUOUS IN THE CHUNK (guaranteed) 72 | so we just point to it where it lies. No moving of chars is 73 | needed and this is the second win: potentially long strings need 74 | never be explicitly shuffled. Once an object is formed, it does not 75 | change its address during its lifetime. 76 | 77 | When the chars burst over a chunk boundary, we allocate a larger 78 | chunk, and then copy the partly formed object from the end of the old 79 | chunk to the beginning of the new larger chunk. We then carry on 80 | accreting characters to the end of the object as we normally would. 81 | 82 | A special macro is provided to add a single char at a time to a 83 | growing object. This allows the use of register variables, which 84 | break the ordinary 'growth' macro. 85 | 86 | Summary: 87 | We allocate large chunks. 88 | We carve out one object at a time from the current chunk. 89 | Once carved, an object never moves. 90 | We are free to append data of any size to the currently 91 | growing object. 92 | Exactly one object is growing in an obstack at any one time. 93 | You can run one obstack per control block. 94 | You may have as many control blocks as you dare. 95 | Because of the way we do it, you can "unwind" an obstack 96 | back to a previous state. (You may remove objects much 97 | as you would with a stack.) 98 | */ 99 | 100 | 101 | /* Don't do the contents of this file more than once. */ 102 | 103 | #ifndef _OBSTACK_H 104 | #define _OBSTACK_H 1 105 | 106 | #ifndef _OBSTACK_INTERFACE_VERSION 107 | # define _OBSTACK_INTERFACE_VERSION 2 108 | #endif 109 | 110 | #include /* For size_t and ptrdiff_t. */ 111 | #include /* For __GNU_LIBRARY__, and memcpy. */ 112 | 113 | #if __STDC_VERSION__ < 199901L || defined __HP_cc 114 | # define __FLEXIBLE_ARRAY_MEMBER 1 115 | #else 116 | # define __FLEXIBLE_ARRAY_MEMBER 117 | #endif 118 | 119 | #if _OBSTACK_INTERFACE_VERSION == 1 120 | /* For binary compatibility with obstack version 1, which used "int" 121 | and "long" for these two types. */ 122 | # define _OBSTACK_SIZE_T unsigned int 123 | # define _CHUNK_SIZE_T unsigned long 124 | # define _OBSTACK_CAST(type, expr) ((type) (expr)) 125 | #else 126 | /* Version 2 with sane types, especially for 64-bit hosts. */ 127 | # define _OBSTACK_SIZE_T size_t 128 | # define _CHUNK_SIZE_T size_t 129 | # define _OBSTACK_CAST(type, expr) (expr) 130 | #endif 131 | 132 | /* If B is the base of an object addressed by P, return the result of 133 | aligning P to the next multiple of A + 1. B and P must be of type 134 | char *. A + 1 must be a power of 2. */ 135 | 136 | #define __BPTR_ALIGN(B, P, A) ((B) + (((P) - (B) + (A)) & ~(A))) 137 | 138 | /* Similar to __BPTR_ALIGN (B, P, A), except optimize the common case 139 | where pointers can be converted to integers, aligned as integers, 140 | and converted back again. If ptrdiff_t is narrower than a 141 | pointer (e.g., the AS/400), play it safe and compute the alignment 142 | relative to B. Otherwise, use the faster strategy of computing the 143 | alignment relative to 0. */ 144 | 145 | #define __PTR_ALIGN(B, P, A) \ 146 | __BPTR_ALIGN (sizeof (ptrdiff_t) < sizeof (void *) ? (B) : (char *) 0, \ 147 | P, A) 148 | 149 | #ifndef __attribute_pure__ 150 | # define __attribute_pure__ _GL_ATTRIBUTE_PURE 151 | #endif 152 | 153 | /* Not the same as _Noreturn, since it also works with function pointers. */ 154 | #ifndef __attribute_noreturn__ 155 | # if 2 < __GNUC__ + (8 <= __GNUC_MINOR__) || defined __clang__ || 0x5110 <= __SUNPRO_C 156 | # define __attribute_noreturn__ __attribute__ ((__noreturn__)) 157 | # else 158 | # define __attribute_noreturn__ 159 | # endif 160 | #endif 161 | 162 | #ifdef __cplusplus 163 | extern "C" { 164 | #endif 165 | 166 | struct _obstack_chunk /* Lives at front of each chunk. */ 167 | { 168 | char *limit; /* 1 past end of this chunk */ 169 | struct _obstack_chunk *prev; /* address of prior chunk or NULL */ 170 | char contents[__FLEXIBLE_ARRAY_MEMBER]; /* objects begin here */ 171 | }; 172 | 173 | struct obstack /* control current object in current chunk */ 174 | { 175 | _CHUNK_SIZE_T chunk_size; /* preferred size to allocate chunks in */ 176 | struct _obstack_chunk *chunk; /* address of current struct obstack_chunk */ 177 | char *object_base; /* address of object we are building */ 178 | char *next_free; /* where to add next char to current object */ 179 | char *chunk_limit; /* address of char after current chunk */ 180 | union 181 | { 182 | _OBSTACK_SIZE_T i; 183 | void *p; 184 | } temp; /* Temporary for some macros. */ 185 | _OBSTACK_SIZE_T alignment_mask; /* Mask of alignment for each object. */ 186 | 187 | /* These prototypes vary based on 'use_extra_arg'. */ 188 | union 189 | { 190 | void *(*plain) (size_t); 191 | void *(*extra) (void *, size_t); 192 | } chunkfun; 193 | union 194 | { 195 | void (*plain) (void *); 196 | void (*extra) (void *, void *); 197 | } freefun; 198 | 199 | void *extra_arg; /* first arg for chunk alloc/dealloc funcs */ 200 | unsigned use_extra_arg : 1; /* chunk alloc/dealloc funcs take extra arg */ 201 | unsigned maybe_empty_object : 1; /* There is a possibility that the current 202 | chunk contains a zero-length object. This 203 | prevents freeing the chunk if we allocate 204 | a bigger chunk to replace it. */ 205 | unsigned alloc_failed : 1; /* No longer used, as we now call the failed 206 | handler on error, but retained for binary 207 | compatibility. */ 208 | }; 209 | 210 | /* Declare the external functions we use; they are in obstack.c. */ 211 | 212 | extern void _obstack_newchunk (struct obstack *, _OBSTACK_SIZE_T); 213 | extern void _obstack_free (struct obstack *, void *); 214 | extern int _obstack_begin (struct obstack *, 215 | _OBSTACK_SIZE_T, _OBSTACK_SIZE_T, 216 | void *(*) (size_t), void (*) (void *)); 217 | extern int _obstack_begin_1 (struct obstack *, 218 | _OBSTACK_SIZE_T, _OBSTACK_SIZE_T, 219 | void *(*) (void *, size_t), 220 | void (*) (void *, void *), void *); 221 | extern _OBSTACK_SIZE_T _obstack_memory_used (struct obstack *); 222 | 223 | 224 | /* Error handler called when 'obstack_chunk_alloc' failed to allocate 225 | more memory. This can be set to a user defined function which 226 | should either abort gracefully or use longjump - but shouldn't 227 | return. The default action is to print a message and abort. */ 228 | extern __attribute_noreturn__ void (*obstack_alloc_failed_handler) (void); 229 | 230 | /* Exit value used when 'print_and_abort' is used. */ 231 | extern int obstack_exit_failure; 232 | 233 | /* Pointer to beginning of object being allocated or to be allocated next. 234 | Note that this might not be the final address of the object 235 | because a new chunk might be needed to hold the final size. */ 236 | 237 | #define obstack_base(h) ((void *) (h)->object_base) 238 | 239 | /* Size for allocating ordinary chunks. */ 240 | 241 | #define obstack_chunk_size(h) ((h)->chunk_size) 242 | 243 | /* Pointer to next byte not yet allocated in current chunk. */ 244 | 245 | #define obstack_next_free(h) ((void *) (h)->next_free) 246 | 247 | /* Mask specifying low bits that should be clear in address of an object. */ 248 | 249 | #define obstack_alignment_mask(h) ((h)->alignment_mask) 250 | 251 | /* To prevent prototype warnings provide complete argument list. */ 252 | #define obstack_init(h) \ 253 | _obstack_begin ((h), 0, 0, \ 254 | _OBSTACK_CAST (void *(*) (size_t), obstack_chunk_alloc), \ 255 | _OBSTACK_CAST (void (*) (void *), obstack_chunk_free)) 256 | 257 | #define obstack_begin(h, size) \ 258 | _obstack_begin ((h), (size), 0, \ 259 | _OBSTACK_CAST (void *(*) (size_t), obstack_chunk_alloc), \ 260 | _OBSTACK_CAST (void (*) (void *), obstack_chunk_free)) 261 | 262 | #define obstack_specify_allocation(h, size, alignment, chunkfun, freefun) \ 263 | _obstack_begin ((h), (size), (alignment), \ 264 | _OBSTACK_CAST (void *(*) (size_t), chunkfun), \ 265 | _OBSTACK_CAST (void (*) (void *), freefun)) 266 | 267 | #define obstack_specify_allocation_with_arg(h, size, alignment, chunkfun, freefun, arg) \ 268 | _obstack_begin_1 ((h), (size), (alignment), \ 269 | _OBSTACK_CAST (void *(*) (void *, size_t), chunkfun), \ 270 | _OBSTACK_CAST (void (*) (void *, void *), freefun), arg) 271 | 272 | #define obstack_chunkfun(h, newchunkfun) \ 273 | ((void) ((h)->chunkfun.extra = (void *(*) (void *, size_t)) (newchunkfun))) 274 | 275 | #define obstack_freefun(h, newfreefun) \ 276 | ((void) ((h)->freefun.extra = (void *(*) (void *, void *)) (newfreefun))) 277 | 278 | #define obstack_1grow_fast(h, achar) ((void) (*((h)->next_free)++ = (achar))) 279 | 280 | #define obstack_blank_fast(h, n) ((void) ((h)->next_free += (n))) 281 | 282 | #define obstack_memory_used(h) _obstack_memory_used (h) 283 | 284 | #if defined __GNUC__ || defined __clang__ 285 | # if !(defined __GNUC_MINOR__ && __GNUC__ * 1000 + __GNUC_MINOR__ >= 2008 \ 286 | || defined __clang__) 287 | # define __extension__ 288 | # endif 289 | 290 | /* For GNU C, if not -traditional, 291 | we can define these macros to compute all args only once 292 | without using a global variable. 293 | Also, we can avoid using the 'temp' slot, to make faster code. */ 294 | 295 | # define obstack_object_size(OBSTACK) \ 296 | __extension__ \ 297 | ({ struct obstack const *__o = (OBSTACK); \ 298 | (_OBSTACK_SIZE_T) (__o->next_free - __o->object_base); }) 299 | 300 | /* The local variable is named __o1 to avoid a shadowed variable 301 | warning when invoked from other obstack macros. */ 302 | # define obstack_room(OBSTACK) \ 303 | __extension__ \ 304 | ({ struct obstack const *__o1 = (OBSTACK); \ 305 | (_OBSTACK_SIZE_T) (__o1->chunk_limit - __o1->next_free); }) 306 | 307 | # define obstack_make_room(OBSTACK, length) \ 308 | __extension__ \ 309 | ({ struct obstack *__o = (OBSTACK); \ 310 | _OBSTACK_SIZE_T __len = (length); \ 311 | if (obstack_room (__o) < __len) \ 312 | _obstack_newchunk (__o, __len); \ 313 | (void) 0; }) 314 | 315 | # define obstack_empty_p(OBSTACK) \ 316 | __extension__ \ 317 | ({ struct obstack const *__o = (OBSTACK); \ 318 | (__o->chunk->prev == 0 \ 319 | && __o->next_free == __PTR_ALIGN ((char *) __o->chunk, \ 320 | __o->chunk->contents, \ 321 | __o->alignment_mask)); }) 322 | 323 | # define obstack_grow(OBSTACK, where, length) \ 324 | __extension__ \ 325 | ({ struct obstack *__o = (OBSTACK); \ 326 | _OBSTACK_SIZE_T __len = (length); \ 327 | if (obstack_room (__o) < __len) \ 328 | _obstack_newchunk (__o, __len); \ 329 | memcpy (__o->next_free, where, __len); \ 330 | __o->next_free += __len; \ 331 | (void) 0; }) 332 | 333 | # define obstack_grow0(OBSTACK, where, length) \ 334 | __extension__ \ 335 | ({ struct obstack *__o = (OBSTACK); \ 336 | _OBSTACK_SIZE_T __len = (length); \ 337 | if (obstack_room (__o) < __len + 1) \ 338 | _obstack_newchunk (__o, __len + 1); \ 339 | memcpy (__o->next_free, where, __len); \ 340 | __o->next_free += __len; \ 341 | *(__o->next_free)++ = 0; \ 342 | (void) 0; }) 343 | 344 | # define obstack_1grow(OBSTACK, datum) \ 345 | __extension__ \ 346 | ({ struct obstack *__o = (OBSTACK); \ 347 | if (obstack_room (__o) < 1) \ 348 | _obstack_newchunk (__o, 1); \ 349 | obstack_1grow_fast (__o, datum); }) 350 | 351 | /* These assume that the obstack alignment is good enough for pointers 352 | or ints, and that the data added so far to the current object 353 | shares that much alignment. */ 354 | 355 | # define obstack_ptr_grow(OBSTACK, datum) \ 356 | __extension__ \ 357 | ({ struct obstack *__o = (OBSTACK); \ 358 | if (obstack_room (__o) < sizeof (void *)) \ 359 | _obstack_newchunk (__o, sizeof (void *)); \ 360 | obstack_ptr_grow_fast (__o, datum); }) 361 | 362 | # define obstack_int_grow(OBSTACK, datum) \ 363 | __extension__ \ 364 | ({ struct obstack *__o = (OBSTACK); \ 365 | if (obstack_room (__o) < sizeof (int)) \ 366 | _obstack_newchunk (__o, sizeof (int)); \ 367 | obstack_int_grow_fast (__o, datum); }) 368 | 369 | # define obstack_ptr_grow_fast(OBSTACK, aptr) \ 370 | __extension__ \ 371 | ({ struct obstack *__o1 = (OBSTACK); \ 372 | void *__p1 = __o1->next_free; \ 373 | *(const void **) __p1 = (aptr); \ 374 | __o1->next_free += sizeof (const void *); \ 375 | (void) 0; }) 376 | 377 | # define obstack_int_grow_fast(OBSTACK, aint) \ 378 | __extension__ \ 379 | ({ struct obstack *__o1 = (OBSTACK); \ 380 | void *__p1 = __o1->next_free; \ 381 | *(int *) __p1 = (aint); \ 382 | __o1->next_free += sizeof (int); \ 383 | (void) 0; }) 384 | 385 | # define obstack_blank(OBSTACK, length) \ 386 | __extension__ \ 387 | ({ struct obstack *__o = (OBSTACK); \ 388 | _OBSTACK_SIZE_T __len = (length); \ 389 | if (obstack_room (__o) < __len) \ 390 | _obstack_newchunk (__o, __len); \ 391 | obstack_blank_fast (__o, __len); }) 392 | 393 | # define obstack_alloc(OBSTACK, length) \ 394 | __extension__ \ 395 | ({ struct obstack *__h = (OBSTACK); \ 396 | obstack_blank (__h, (length)); \ 397 | obstack_finish (__h); }) 398 | 399 | # define obstack_copy(OBSTACK, where, length) \ 400 | __extension__ \ 401 | ({ struct obstack *__h = (OBSTACK); \ 402 | obstack_grow (__h, (where), (length)); \ 403 | obstack_finish (__h); }) 404 | 405 | # define obstack_copy0(OBSTACK, where, length) \ 406 | __extension__ \ 407 | ({ struct obstack *__h = (OBSTACK); \ 408 | obstack_grow0 (__h, (where), (length)); \ 409 | obstack_finish (__h); }) 410 | 411 | /* The local variable is named __o1 to avoid a shadowed variable 412 | warning when invoked from other obstack macros, typically obstack_free. */ 413 | # define obstack_finish(OBSTACK) \ 414 | __extension__ \ 415 | ({ struct obstack *__o1 = (OBSTACK); \ 416 | void *__value = (void *) __o1->object_base; \ 417 | if (__o1->next_free == __value) \ 418 | __o1->maybe_empty_object = 1; \ 419 | __o1->next_free \ 420 | = __PTR_ALIGN (__o1->object_base, __o1->next_free, \ 421 | __o1->alignment_mask); \ 422 | if ((size_t) (__o1->next_free - (char *) __o1->chunk) \ 423 | > (size_t) (__o1->chunk_limit - (char *) __o1->chunk)) \ 424 | __o1->next_free = __o1->chunk_limit; \ 425 | __o1->object_base = __o1->next_free; \ 426 | __value; }) 427 | 428 | # define obstack_free(OBSTACK, OBJ) \ 429 | __extension__ \ 430 | ({ struct obstack *__o = (OBSTACK); \ 431 | void *__obj = (void *) (OBJ); \ 432 | if (__obj > (void *) __o->chunk && __obj < (void *) __o->chunk_limit) \ 433 | __o->next_free = __o->object_base = (char *) __obj; \ 434 | else \ 435 | _obstack_free (__o, __obj); }) 436 | 437 | #else /* not __GNUC__ */ 438 | 439 | # define obstack_object_size(h) \ 440 | ((_OBSTACK_SIZE_T) ((h)->next_free - (h)->object_base)) 441 | 442 | # define obstack_room(h) \ 443 | ((_OBSTACK_SIZE_T) ((h)->chunk_limit - (h)->next_free)) 444 | 445 | # define obstack_empty_p(h) \ 446 | ((h)->chunk->prev == 0 \ 447 | && (h)->next_free == __PTR_ALIGN ((char *) (h)->chunk, \ 448 | (h)->chunk->contents, \ 449 | (h)->alignment_mask)) 450 | 451 | /* Note that the call to _obstack_newchunk is enclosed in (..., 0) 452 | so that we can avoid having void expressions 453 | in the arms of the conditional expression. 454 | Casting the third operand to void was tried before, 455 | but some compilers won't accept it. */ 456 | 457 | # define obstack_make_room(h, length) \ 458 | ((h)->temp.i = (length), \ 459 | ((obstack_room (h) < (h)->temp.i) \ 460 | ? (_obstack_newchunk (h, (h)->temp.i), 0) : 0), \ 461 | (void) 0) 462 | 463 | # define obstack_grow(h, where, length) \ 464 | ((h)->temp.i = (length), \ 465 | ((obstack_room (h) < (h)->temp.i) \ 466 | ? (_obstack_newchunk ((h), (h)->temp.i), 0) : 0), \ 467 | memcpy ((h)->next_free, where, (h)->temp.i), \ 468 | (h)->next_free += (h)->temp.i, \ 469 | (void) 0) 470 | 471 | # define obstack_grow0(h, where, length) \ 472 | ((h)->temp.i = (length), \ 473 | ((obstack_room (h) < (h)->temp.i + 1) \ 474 | ? (_obstack_newchunk ((h), (h)->temp.i + 1), 0) : 0), \ 475 | memcpy ((h)->next_free, where, (h)->temp.i), \ 476 | (h)->next_free += (h)->temp.i, \ 477 | *((h)->next_free)++ = 0, \ 478 | (void) 0) 479 | 480 | # define obstack_1grow(h, datum) \ 481 | (((obstack_room (h) < 1) \ 482 | ? (_obstack_newchunk ((h), 1), 0) : 0), \ 483 | obstack_1grow_fast (h, datum)) 484 | 485 | # define obstack_ptr_grow(h, datum) \ 486 | (((obstack_room (h) < sizeof (char *)) \ 487 | ? (_obstack_newchunk ((h), sizeof (char *)), 0) : 0), \ 488 | obstack_ptr_grow_fast (h, datum)) 489 | 490 | # define obstack_int_grow(h, datum) \ 491 | (((obstack_room (h) < sizeof (int)) \ 492 | ? (_obstack_newchunk ((h), sizeof (int)), 0) : 0), \ 493 | obstack_int_grow_fast (h, datum)) 494 | 495 | # define obstack_ptr_grow_fast(h, aptr) \ 496 | (((const void **) ((h)->next_free += sizeof (void *)))[-1] = (aptr), \ 497 | (void) 0) 498 | 499 | # define obstack_int_grow_fast(h, aint) \ 500 | (((int *) ((h)->next_free += sizeof (int)))[-1] = (aint), \ 501 | (void) 0) 502 | 503 | # define obstack_blank(h, length) \ 504 | ((h)->temp.i = (length), \ 505 | ((obstack_room (h) < (h)->temp.i) \ 506 | ? (_obstack_newchunk ((h), (h)->temp.i), 0) : 0), \ 507 | obstack_blank_fast (h, (h)->temp.i)) 508 | 509 | # define obstack_alloc(h, length) \ 510 | (obstack_blank ((h), (length)), obstack_finish ((h))) 511 | 512 | # define obstack_copy(h, where, length) \ 513 | (obstack_grow ((h), (where), (length)), obstack_finish ((h))) 514 | 515 | # define obstack_copy0(h, where, length) \ 516 | (obstack_grow0 ((h), (where), (length)), obstack_finish ((h))) 517 | 518 | # define obstack_finish(h) \ 519 | (((h)->next_free == (h)->object_base \ 520 | ? (((h)->maybe_empty_object = 1), 0) \ 521 | : 0), \ 522 | (h)->temp.p = (h)->object_base, \ 523 | (h)->next_free \ 524 | = __PTR_ALIGN ((h)->object_base, (h)->next_free, \ 525 | (h)->alignment_mask), \ 526 | (((size_t) ((h)->next_free - (char *) (h)->chunk) \ 527 | > (size_t) ((h)->chunk_limit - (char *) (h)->chunk)) \ 528 | ? ((h)->next_free = (h)->chunk_limit) : 0), \ 529 | (h)->object_base = (h)->next_free, \ 530 | (h)->temp.p) 531 | 532 | # define obstack_free(h, obj) \ 533 | ((h)->temp.p = (void *) (obj), \ 534 | (((h)->temp.p > (void *) (h)->chunk \ 535 | && (h)->temp.p < (void *) (h)->chunk_limit) \ 536 | ? (void) ((h)->next_free = (h)->object_base = (char *) (h)->temp.p) \ 537 | : _obstack_free ((h), (h)->temp.p))) 538 | 539 | #endif /* not __GNUC__ */ 540 | 541 | #ifdef __cplusplus 542 | } /* C++ */ 543 | #endif 544 | 545 | #endif /* _OBSTACK_H */ 546 | 547 | #include "obstack_printf.c" -------------------------------------------------------------------------------- /stubs/obstack_printf.c: -------------------------------------------------------------------------------- 1 | /* Formatted output to obstacks. 2 | Copyright (C) 2008-2020 Free Software Foundation, Inc. 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2, or (at your option) 7 | any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License along 15 | with this program; if not, see . */ 16 | 17 | /* Specification. */ 18 | #include 19 | 20 | #include "vasnprintf.h" 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | int 27 | obstack_vprintf (struct obstack *obs, const char *format, va_list args); 28 | 29 | /* Grow an obstack with formatted output. Return the number of bytes 30 | added to OBS. No trailing nul byte is added, and the object should 31 | be closed with obstack_finish before use. 32 | 33 | Upon memory allocation error, call obstack_alloc_failed_handler. 34 | Upon other error, return -1. */ 35 | int 36 | obstack_printf (struct obstack *obs, const char *format, ...) 37 | { 38 | va_list args; 39 | int result; 40 | 41 | va_start (args, format); 42 | result = obstack_vprintf (obs, format, args); 43 | va_end (args); 44 | return result; 45 | } 46 | 47 | /* Grow an obstack with formatted output. Return the number of bytes 48 | added to OBS. No trailing nul byte is added, and the object should 49 | be closed with obstack_finish before use. 50 | 51 | Upon memory allocation error, call obstack_alloc_failed_handler. 52 | Upon other error, return -1. */ 53 | int 54 | obstack_vprintf (struct obstack *obs, const char *format, va_list args) 55 | { 56 | /* If we are close to the end of the current obstack chunk, use a 57 | stack-allocated buffer and copy, to reduce the likelihood of a 58 | small-size malloc. Otherwise, print directly into the 59 | obstack. */ 60 | enum { CUTOFF = 1024 }; 61 | char buf[CUTOFF]; 62 | char *base = obstack_next_free (obs); 63 | size_t len = obstack_room (obs); 64 | char *str; 65 | 66 | if (len < CUTOFF) 67 | { 68 | base = buf; 69 | len = CUTOFF; 70 | } 71 | str = vasnprintf (base, &len, format, args); 72 | if (!str) 73 | { 74 | if (errno == ENOMEM) 75 | obstack_alloc_failed_handler (); 76 | return -1; 77 | } 78 | if (str == base && str != buf) 79 | /* The output was already computed in place, but we need to 80 | account for its size. */ 81 | obstack_blank_fast (obs, len); 82 | else 83 | { 84 | /* The output exceeded available obstack space or we used buf; 85 | copy the resulting string. */ 86 | obstack_grow (obs, str, len); 87 | if (str != buf) 88 | free (str); 89 | } 90 | return len; 91 | } 92 | -------------------------------------------------------------------------------- /stubs/vasnprintf.h: -------------------------------------------------------------------------------- 1 | /* vsprintf with automatic memory allocation. 2 | Copyright (C) 2002-2004, 2007-2020 Free Software Foundation, Inc. 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2, or (at your option) 7 | any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License along 15 | with this program; if not, see . */ 16 | 17 | #ifndef _VASNPRINTF_H 18 | #define _VASNPRINTF_H 19 | 20 | /* Get va_list. */ 21 | #include 22 | 23 | /* Get size_t. */ 24 | #include 25 | 26 | #ifdef __cplusplus 27 | extern "C" { 28 | #endif 29 | 30 | /* Write formatted output to a string dynamically allocated with malloc(). 31 | You can pass a preallocated buffer for the result in RESULTBUF and its 32 | size in *LENGTHP; otherwise you pass RESULTBUF = NULL. 33 | If successful, return the address of the string (this may be = RESULTBUF 34 | if no dynamic memory allocation was necessary) and set *LENGTHP to the 35 | number of resulting bytes, excluding the trailing NUL. Upon error, set 36 | errno and return NULL. 37 | 38 | When dynamic memory allocation occurs, the preallocated buffer is left 39 | alone (with possibly modified contents). This makes it possible to use 40 | a statically allocated or stack-allocated buffer, like this: 41 | 42 | char buf[100]; 43 | size_t len = sizeof (buf); 44 | char *output = vasnprintf (buf, &len, format, args); 45 | if (output == NULL) 46 | ... error handling ...; 47 | else 48 | { 49 | ... use the output string ...; 50 | if (output != buf) 51 | free (output); 52 | } 53 | */ 54 | #if REPLACE_VASNPRINTF 55 | # define asnprintf rpl_asnprintf 56 | # define vasnprintf rpl_vasnprintf 57 | #endif 58 | extern char * asnprintf (char *restrict resultbuf, size_t *lengthp, 59 | const char *format, ...); 60 | extern char * vasnprintf (char *restrict resultbuf, size_t *lengthp, 61 | const char *format, va_list args); 62 | 63 | #ifdef __cplusplus 64 | } 65 | #endif 66 | 67 | #endif /* _VASNPRINTF_H */ 68 | --------------------------------------------------------------------------------