|
| 1 | +defmodule Standalone do |
| 2 | + require Logger |
| 3 | + |
| 4 | + @doc """ |
| 5 | + Copies OTP into the release. |
| 6 | + """ |
| 7 | + @spec copy_otp(Mix.Release.t()) :: Mix.Release.t() |
| 8 | + def copy_otp(release) do |
| 9 | + erts_source = Path.join(:code.root_dir(), "erts-#{release.erts_version}") |
| 10 | + otp_bin_dir = Path.join(:code.root_dir(), "bin") |
| 11 | + otp_lib_dir = :code.lib_dir() |
| 12 | + vendor_otp_dir = vendor_dir(release, "otp") |
| 13 | + File.rm_rf!(vendor_otp_dir) |
| 14 | + File.mkdir_p!(vendor_otp_dir) |
| 15 | + |
| 16 | + # 1. copy erts/{bin,include} |
| 17 | + release_erts_bin_dir = Path.join([vendor_otp_dir, "erts-#{release.erts_version}", "bin"]) |
| 18 | + File.mkdir_p!(release_erts_bin_dir) |
| 19 | + cp_r!(Path.join(erts_source, "bin"), release_erts_bin_dir) |
| 20 | + |
| 21 | + File.rm(Path.join(release_erts_bin_dir, "erl")) |
| 22 | + File.rm(Path.join(release_erts_bin_dir, "erl.ini")) |
| 23 | + |
| 24 | + File.write!(Path.join(release_erts_bin_dir, "erl"), ~S""" |
| 25 | + #!/bin/sh |
| 26 | + SELF=$(readlink "$0" || true) |
| 27 | + if [ -z "$SELF" ]; then SELF="$0"; fi |
| 28 | + BINDIR="$(cd "$(dirname "$SELF")" && pwd -P)" |
| 29 | + ROOTDIR="${ERL_ROOTDIR:-"$(dirname "$(dirname "$BINDIR")")"}" |
| 30 | + EMU=beam |
| 31 | + PROGNAME=$(echo "$0" | sed 's/.*\///') |
| 32 | + export EMU |
| 33 | + export ROOTDIR |
| 34 | + export BINDIR |
| 35 | + export PROGNAME |
| 36 | + exec "$BINDIR/erlexec" ${1+"$@"} |
| 37 | + """) |
| 38 | + |
| 39 | + make_executable(Path.join(release_erts_bin_dir, "erl")) |
| 40 | + |
| 41 | + release_erts_include_dir = |
| 42 | + Path.join([vendor_otp_dir, "erts-#{release.erts_version}", "include"]) |
| 43 | + |
| 44 | + cp_r!(Path.join(erts_source, "include"), release_erts_include_dir) |
| 45 | + |
| 46 | + # 2. copy lib |
| 47 | + release_lib_dir = Path.join(vendor_otp_dir, "lib") |
| 48 | + cp_r!(otp_lib_dir, release_lib_dir) |
| 49 | + |
| 50 | + for dir <- Path.wildcard("#{release_lib_dir}/*/doc/{xml,html,pdf}") do |
| 51 | + File.rm_rf!(dir) |
| 52 | + end |
| 53 | + |
| 54 | + for dir <- Path.wildcard("#{release_lib_dir}/*/src") do |
| 55 | + File.rm_rf!(dir) |
| 56 | + end |
| 57 | + |
| 58 | + # 3. copy boot files |
| 59 | + release_bin_dir = Path.join(vendor_otp_dir, "bin") |
| 60 | + File.mkdir!(release_bin_dir) |
| 61 | + |
| 62 | + for file <- Path.wildcard(Path.join(otp_bin_dir, "*")) do |
| 63 | + File.cp!(file, Path.join(release_bin_dir, Path.basename(file))) |
| 64 | + end |
| 65 | + |
| 66 | + # 4. copy usr |
| 67 | + cp_r!(Path.join(:code.root_dir(), "usr"), Path.join(vendor_otp_dir, "usr")) |
| 68 | + |
| 69 | + release |
| 70 | + end |
| 71 | + |
| 72 | + @doc """ |
| 73 | + Copies Elixir into the release. |
| 74 | + """ |
| 75 | + @spec copy_elixir(Mix.Release.t(), elixir_version :: String.t()) :: Mix.Release.t() |
| 76 | + def copy_elixir(release, elixir_version) do |
| 77 | + standalone_destination = vendor_dir(release, "elixir") |
| 78 | + download_elixir_at_destination(standalone_destination, elixir_version) |
| 79 | + |
| 80 | + filenames = |
| 81 | + case :os.type() do |
| 82 | + {:unix, _} -> |
| 83 | + ["elixir", "elixirc", "mix", "iex"] |
| 84 | + |
| 85 | + {:win32, _} -> |
| 86 | + ["elixir.bat", "elixirc.bat", "mix.bat", "iex.bat"] |
| 87 | + end |
| 88 | + |
| 89 | + Enum.map(filenames, &make_executable(Path.join(standalone_destination, "bin/#{&1}"))) |
| 90 | + |
| 91 | + release |
| 92 | + end |
| 93 | + |
| 94 | + defp download_elixir_at_destination(destination, version) do |
| 95 | + url = "https://builds.hex.pm/builds/elixir/v#{version}-otp-#{System.otp_release()}.zip" |
| 96 | + path = Path.join(System.tmp_dir!(), "elixir_#{version}.zip") |
| 97 | + |
| 98 | + unless File.exists?(path) do |
| 99 | + binary = fetch_body!(url) |
| 100 | + File.write!(path, binary, [:binary]) |
| 101 | + end |
| 102 | + |
| 103 | + :zip.unzip(String.to_charlist(path), cwd: String.to_charlist(destination)) |
| 104 | + end |
| 105 | + |
| 106 | + @doc """ |
| 107 | + Copies Hex into the release. |
| 108 | + """ |
| 109 | + @spec copy_hex(Mix.Release.t()) :: Mix.Release.t() |
| 110 | + def copy_hex(release) do |
| 111 | + release_archives_dir = vendor_dir(release, "archives") |
| 112 | + File.mkdir_p!(release_archives_dir) |
| 113 | + |
| 114 | + hex_version = Keyword.fetch!(Application.spec(:hex), :vsn) |
| 115 | + source_hex_path = Path.join(Mix.path_for(:archives), "hex-#{hex_version}-otp-#{System.otp_release()}") |
| 116 | + release_hex_path = Path.join(release_archives_dir, "hex-#{hex_version}-otp-#{System.otp_release()}") |
| 117 | + cp_r!(source_hex_path, release_hex_path) |
| 118 | + |
| 119 | + release |
| 120 | + end |
| 121 | + |
| 122 | + @doc """ |
| 123 | + Copies Rebar3 into the release. |
| 124 | + """ |
| 125 | + @spec copy_rebar3(Mix.Release.t(), version :: String.t()) :: Mix.Release.t() |
| 126 | + def copy_rebar3(release, version) do |
| 127 | + url = "https://github.com/erlang/rebar3/releases/download/#{version}/rebar3" |
| 128 | + path = Path.join(System.tmp_dir!(), "rebar3_#{version}") |
| 129 | + |
| 130 | + unless File.exists?(path) do |
| 131 | + binary = fetch_body!(url) |
| 132 | + File.write!(path, binary, [:binary]) |
| 133 | + end |
| 134 | + |
| 135 | + destination = vendor_dir(release, "rebar3") |
| 136 | + File.cp!(path, destination) |
| 137 | + make_executable(destination) |
| 138 | + |
| 139 | + release |
| 140 | + end |
| 141 | + |
| 142 | + defp fetch_body!(url) do |
| 143 | + Logger.debug("Downloading #{url}") |
| 144 | + |
| 145 | + Application.ensure_all_started(:req) |
| 146 | + |
| 147 | + req = Req.new() |> Livebook.Utils.req_attach_defaults() |
| 148 | + |
| 149 | + case Req.get(req, url: url, receive_timeout: :infinity, decode_body: false) do |
| 150 | + {:ok, %{status: 200, body: body}} -> |
| 151 | + body |
| 152 | + |
| 153 | + {:error, exception} -> |
| 154 | + raise "couldn't fetch #{url}: #{Exception.message(exception)}}" |
| 155 | + end |
| 156 | + end |
| 157 | + |
| 158 | + defp make_executable(path), do: File.chmod!(path, 0o755) |
| 159 | + |
| 160 | + defp cp_r!(source, destination) do |
| 161 | + File.cp_r!(source, destination, on_conflict: fn _, _ -> false end) |
| 162 | + end |
| 163 | + |
| 164 | + defp vendor_dir(release, path) do |
| 165 | + Path.join([release.path, "vendor", "livebook-#{release.version}", path]) |
| 166 | + end |
| 167 | +end |
0 commit comments