diff --git a/lib/mix/release.exs b/lib/mix/release.exs deleted file mode 100644 index d6c032877..000000000 --- a/lib/mix/release.exs +++ /dev/null @@ -1,878 +0,0 @@ -defmodule Mix.Release do - @moduledoc """ - Defines the release structure and convenience for assembling releases. - """ - - @doc """ - The Mix.Release struct has the following read-only fields: - - * `:name` - the name of the release as an atom - * `:version` - the version of the release as a string or - `{:from_app, app_name}` - * `:path` - the path to the release root - * `:version_path` - the path to the release version inside the release - * `:applications` - a map of application with their definitions - * `:erts_source` - the ERTS source as a charlist (or nil) - * `:erts_version` - the ERTS version as a charlist - - The following fields may be modified as long as they keep their defined types: - - * `:boot_scripts` - a map of boot scripts with the boot script name - as key and a keyword list with **all** applications that are part of - it and their modes as value - * `:config_providers` - a list of `{config_provider, term}` tuples where the - first element is a module that implements the `Config.Provider` behaviour - and `term` is the value given to it on `c:Config.Provider.init/1` - * `:options` - a keyword list with all other user supplied release options - * `:overlays` - a list of extra files added to the release. If you have a custom - step adding extra files to a release, you can add these files to the `:overlays` - field so they are also considered on further commands, such as tar/zip. Each entry - in overlays is the relative path to the release root of each file - * `:steps` - a list of functions that receive the release and returns a release. - Must also contain the atom `:assemble` which is the internal assembling step. - May also contain the atom `:tar` to create a tarball of the release. - - """ - defstruct [ - :name, - :version, - :path, - :version_path, - :applications, - :boot_scripts, - :erts_source, - :erts_version, - :config_providers, - :options, - :overlays, - :steps - ] - - @type mode :: :permanent | :transient | :temporary | :load | :none - @type application :: atom() - @type t :: %__MODULE__{ - name: atom(), - version: String.t(), - path: String.t(), - version_path: String.t() | {:from_app, application()}, - applications: %{application() => keyword()}, - boot_scripts: %{atom() => [{application(), mode()}]}, - erts_version: charlist(), - erts_source: charlist() | nil, - config_providers: [{module, term}], - options: keyword(), - overlays: list(String.t()), - steps: [(t -> t) | :assemble, ...] - } - - @default_apps [kernel: :permanent, stdlib: :permanent, elixir: :permanent, sasl: :permanent] - @safe_modes [:permanent, :temporary, :transient] - @unsafe_modes [:load, :none] - @significant_chunks ~w(Atom AtU8 Attr Code StrT ImpT ExpT FunT LitT Line)c - @copy_app_dirs ["priv"] - - @doc false - @spec from_config!(atom, keyword, keyword) :: t - def from_config!(name, config, overrides) do - {name, apps, opts} = find_release(name, config) - - unless Atom.to_string(name) =~ ~r/^[a-z][a-z0-9_]*$/ do - Mix.raise( - "Invalid release name. A release name must start with a lowercase ASCII letter, " <> - "followed by lowercase ASCII letters, numbers, or underscores, got: #{inspect(name)}" - ) - end - - opts = - [overwrite: false, quiet: false, strip_beams: true] - |> Keyword.merge(opts) - |> Keyword.merge(overrides) - - {include_erts, opts} = Keyword.pop(opts, :include_erts, true) - {erts_source, erts_lib_dir, erts_version} = erts_data(include_erts) - - deps_apps = Mix.Project.deps_apps() - loaded_apps = apps |> Keyword.keys() |> load_apps(deps_apps, %{}, erts_lib_dir, [], :root) - - # Make sure IEx is either an active part of the release or add it as none. - {loaded_apps, apps} = - if Map.has_key?(loaded_apps, :iex) do - {loaded_apps, apps} - else - {load_apps([:iex], deps_apps, loaded_apps, erts_lib_dir, [], :root), apps ++ [iex: :none]} - end - - start_boot = build_start_boot(loaded_apps, apps) - start_clean_boot = build_start_clean_boot(start_boot) - - {path, opts} = - Keyword.pop_lazy(opts, :path, fn -> - Path.join([Mix.Project.build_path(config), "rel", Atom.to_string(name)]) - end) - - path = Path.absname(path) - - {version, opts} = - Keyword.pop_lazy(opts, :version, fn -> - config[:version] || - Mix.raise( - "No :version found. Please make sure a :version is set in your project definition " <> - "or inside the release the configuration" - ) - end) - - version = - case version do - {:from_app, app} -> - Application.load(app) - version = Application.spec(app, :vsn) - - if !version do - Mix.raise( - "Could not find version for #{inspect(app)}, please make sure the application exists" - ) - end - - to_string(version) - - "" -> - Mix.raise("The release :version cannot be an empty string") - - _ -> - version - end - - {config_providers, opts} = Keyword.pop(opts, :config_providers, []) - {steps, opts} = Keyword.pop(opts, :steps, [:assemble]) - validate_steps!(steps) - - %Mix.Release{ - name: name, - version: version, - path: path, - version_path: Path.join([path, "releases", version]), - erts_source: erts_source, - erts_version: erts_version, - applications: loaded_apps, - boot_scripts: %{start: start_boot, start_clean: start_clean_boot}, - config_providers: config_providers, - options: opts, - overlays: [], - steps: steps - } - end - - defp find_release(name, config) do - {name, opts_fun_or_list} = lookup_release(name, config) || infer_release(config) - opts = if is_function(opts_fun_or_list, 0), do: opts_fun_or_list.(), else: opts_fun_or_list - {apps, opts} = Keyword.pop(opts, :applications, []) - - if apps == [] and Mix.Project.umbrella?(config) do - bad_umbrella!() - end - - app = Keyword.get(config, :app) - apps = Keyword.merge(@default_apps, apps) - - if is_nil(app) or Keyword.has_key?(apps, app) do - {name, apps, opts} - else - {name, apps ++ [{app, :permanent}], opts} - end - end - - defp lookup_release(nil, config) do - case Keyword.get(config, :releases, []) do - [] -> - nil - - [{name, opts}] -> - {name, opts} - - [_ | _] -> - case Keyword.get(config, :default_release) do - nil -> - Mix.raise( - "\"mix release\" was invoked without a name but there are multiple releases. " <> - "Please call \"mix release NAME\" or set :default_release in your project configuration" - ) - - name -> - lookup_release(name, config) - end - end - end - - defp lookup_release(name, config) do - if opts = config[:releases][name] do - {name, opts} - else - found = Keyword.get(config, :releases, []) - - Mix.raise( - "Unknown release #{inspect(name)}. " <> - "The available releases are: #{inspect(Keyword.keys(found))}" - ) - end - end - - defp infer_release(config) do - if Mix.Project.umbrella?(config) do - bad_umbrella!() - else - {Keyword.fetch!(config, :app), []} - end - end - - defp bad_umbrella! do - Mix.raise(""" - Umbrella projects require releases to be explicitly defined with \ - a non-empty applications key that chooses which umbrella children \ - should be part of the releases: - - releases: [ - foo: [ - applications: [child_app_foo: :permanent] - ], - bar: [ - applications: [child_app_bar: :permanent] - ] - ] - - Alternatively you can perform the release from the children applications - """) - end - - defp erts_data(erts_data) when is_function(erts_data) do - erts_data(erts_data.()) - end - - defp erts_data(false) do - {nil, :code.lib_dir(), :erlang.system_info(:version)} - end - - defp erts_data(true) do - version = :erlang.system_info(:version) - {:filename.join(:code.root_dir(), 'erts-#{version}'), :code.lib_dir(), version} - end - - defp erts_data(erts_source) when is_binary(erts_source) do - if File.exists?(erts_source) do - [_, erts_version] = erts_source |> Path.basename() |> String.split("-") - erts_lib_dir = erts_source |> Path.dirname() |> Path.join("lib") |> to_charlist() - {to_charlist(erts_source), erts_lib_dir, to_charlist(erts_version)} - else - Mix.raise("Could not find ERTS system at #{inspect(erts_source)}") - end - end - - defp load_apps(apps, deps_apps, seen, otp_root, optional, type) do - for app <- apps, reduce: seen do - seen -> - if reentrant_seen = reentrant(seen, app, type) do - reentrant_seen - else - load_app(app, deps_apps, seen, otp_root, optional, type) - end - end - end - - defp reentrant(seen, app, type) do - properties = seen[app] - - cond do - is_nil(properties) -> - nil - - type != :root and properties[:type] != type -> - if properties[:type] == :root do - put_in(seen[app][:type], type) - else - Mix.raise( - "#{inspect(app)} is listed both as a regular application and as an included application" - ) - end - - true -> - seen - end - end - - defp load_app(app, deps_apps, seen, otp_root, optional, type) do - cond do - path = app not in deps_apps && otp_path(otp_root, app) -> - do_load_app(app, path, deps_apps, seen, otp_root, true, type) - - path = code_path(app) -> - do_load_app(app, path, deps_apps, seen, otp_root, false, type) - - app in optional -> - seen - - true -> - Mix.raise("Could not find application #{inspect(app)}") - end - end - - defp otp_path(otp_root, app) do - path = Path.join(otp_root, "#{app}-*") - - case Path.wildcard(path) do - [] -> nil - paths -> paths |> Enum.sort() |> List.last() |> to_charlist() - end - end - - defp code_path(app) do - case :code.lib_dir(app) do - {:error, :bad_name} -> nil - path -> path - end - end - - defp do_load_app(app, path, deps_apps, seen, otp_root, otp_app?, type) do - case :file.consult(Path.join(path, "ebin/#{app}.app")) do - {:ok, terms} -> - [{:application, ^app, properties}] = terms - value = [path: path, otp_app?: otp_app?, type: type] ++ properties - seen = Map.put(seen, app, value) - applications = Keyword.get(properties, :applications, []) - optional = Keyword.get(properties, :optional_applications, []) - seen = load_apps(applications, deps_apps, seen, otp_root, optional, :depended) - included_applications = Keyword.get(properties, :included_applications, []) - load_apps(included_applications, deps_apps, seen, otp_root, [], :included) - - {:error, reason} -> - Mix.raise("Could not load #{app}.app. Reason: #{inspect(reason)}") - end - end - - defp build_start_boot(all_apps, specified_apps) do - specified_apps ++ - Enum.sort( - for( - {app, props} <- all_apps, - not List.keymember?(specified_apps, app, 0), - do: {app, default_mode(props)} - ) - ) - end - - defp default_mode(props) do - if props[:type] == :included, do: :load, else: :permanent - end - - defp build_start_clean_boot(boot) do - for({app, _mode} <- boot, do: {app, :none}) - |> Keyword.put(:stdlib, :permanent) - |> Keyword.put(:kernel, :permanent) - end - - defp validate_steps!(steps) do - valid_atoms = [:assemble, :tar] - - if not is_list(steps) or Enum.any?(steps, &(&1 not in valid_atoms and not is_function(&1, 1))) do - Mix.raise(""" - The :steps option must be a list of: - - * anonymous function that receives one argument - * the atom :assemble or :tar - - Got: #{inspect(steps)} - """) - end - - if Enum.count(steps, &(&1 == :assemble)) != 1 do - Mix.raise("The :steps option must contain the atom :assemble once, got: #{inspect(steps)}") - end - - if :assemble in Enum.drop_while(steps, &(&1 != :tar)) do - Mix.raise("The :tar step must come after :assemble") - end - - if Enum.count(steps, &(&1 == :tar)) > 1 do - Mix.raise("The :steps option can only contain the atom :tar once") - end - - :ok - end - - @doc """ - Makes the `sys.config` structure. - - If there are config providers, then a value is injected into - the `:elixir` application configuration in `sys_config` to be - read during boot and trigger the providers. - - It uses the following release options to customize its behaviour: - - * `:reboot_system_after_config` - * `:start_distribution_during_config` - * `:prune_runtime_sys_config_after_boot` - - In case there are no config providers, it doesn't change `sys_config`. - """ - @spec make_sys_config(t, keyword(), Config.Provider.config_path()) :: - :ok | {:error, String.t()} - def make_sys_config(release, sys_config, config_provider_path) do - {sys_config, runtime_config?} = - merge_provider_config(release, sys_config, config_provider_path) - - path = Path.join(release.version_path, "sys.config") - - args = [runtime_config?, sys_config] - format = "%% coding: utf-8~n%% RUNTIME_CONFIG=~s~n~tw.~n" - File.mkdir_p!(Path.dirname(path)) - File.write!(path, IO.chardata_to_string(:io_lib.format(format, args))) - - case :file.consult(path) do - {:ok, _} -> - :ok - - {:error, reason} -> - invalid = - for {app, kv} <- sys_config, - {key, value} <- kv, - not valid_config?(value), - do: """ - - Application: #{inspect(app)} - Key: #{inspect(key)} - Value: #{inspect(value)} - """ - - message = - case invalid do - [] -> - "Could not read configuration file. Reason: #{inspect(reason)}" - - _ -> - "Could not read configuration file. It has invalid configuration terms " <> - "such as functions, references, and pids. Please make sure your configuration " <> - "is made of numbers, atoms, strings, maps, tuples and lists. The following entries " <> - "are wrong:\n#{Enum.join(invalid)}" - end - - {:error, message} - end - end - - defp valid_config?(m) when is_map(m), - do: Enum.all?(Map.delete(m, :__struct__), &valid_config?/1) - - defp valid_config?(l) when is_list(l), do: Enum.all?(l, &valid_config?/1) - defp valid_config?(t) when is_tuple(t), do: Enum.all?(Tuple.to_list(t), &valid_config?/1) - defp valid_config?(o), do: is_number(o) or is_atom(o) or is_binary(o) - - defp merge_provider_config(%{config_providers: []}, sys_config, _), do: {sys_config, false} - - defp merge_provider_config(release, sys_config, config_path) do - {reboot?, extra_config, initial_config} = start_distribution(release) - - prune_runtime_sys_config_after_boot = - Keyword.get(release.options, :prune_runtime_sys_config_after_boot, false) - - opts = [ - extra_config: initial_config, - prune_runtime_sys_config_after_boot: prune_runtime_sys_config_after_boot, - reboot_system_after_config: reboot?, - validate_compile_env: validate_compile_env(release) - ] - - init_config = Config.Provider.init(release.config_providers, config_path, opts) - {Config.Reader.merge(sys_config, init_config ++ extra_config), reboot?} - end - - defp validate_compile_env(release) do - with true <- Keyword.get(release.options, :validate_compile_env, true), - [_ | _] = compile_env <- compile_env(release) do - compile_env - else - _ -> false - end - end - - defp compile_env(release) do - for {_, properties} <- release.applications, - triplet <- Keyword.get(properties, :compile_env, []), - do: triplet - end - - defp start_distribution(%{options: opts}) do - reboot? = Keyword.get(opts, :reboot_system_after_config, false) - early_distribution? = Keyword.get(opts, :start_distribution_during_config, false) - - if not reboot? or early_distribution? do - {reboot?, [], []} - else - {true, [kernel: [start_distribution: false]], [kernel: [start_distribution: true]]} - end - end - - @doc """ - Copies the cookie to the given path. - - If a cookie option was given, we compare it with - the contents of the file (if any), and ask the user - if they want to override. - - If there is no option, we generate a random one - the first time. - """ - @spec make_cookie(t, Path.t()) :: :ok - def make_cookie(release, path) do - cond do - cookie = release.options[:cookie] -> - Mix.Generator.create_file(path, cookie, quiet: true) - :ok - - File.exists?(path) -> - :ok - - true -> - File.write!(path, random_cookie()) - :ok - end - end - - defp random_cookie, do: Base.encode32(:crypto.strong_rand_bytes(32)) - - @doc """ - Makes the start_erl.data file with the - ERTS version and release versions. - """ - @spec make_start_erl(t, Path.t()) :: :ok - def make_start_erl(release, path) do - File.write!(path, "#{release.erts_version} #{release.version}") - :ok - end - - @doc """ - Makes boot scripts. - - It receives a path to the boot file, without extension, such as - `releases/0.1.0/start` and this command will write `start.rel`, - `start.boot`, and `start.script` to the given path, returning - `{:ok, rel_path}` or `{:error, message}`. - - The boot script uses the RELEASE_LIB environment variable, which must - be accordingly set with `--boot-var` and point to the release lib dir. - """ - @spec make_boot_script(t, Path.t(), [{application(), mode()}], [String.t()]) :: - :ok | {:error, String.t()} - def make_boot_script(release, path, modes, prepend_paths \\ []) do - with {:ok, rel_spec} <- build_release_spec(release, modes) do - File.write!(path <> ".rel", consultable(rel_spec)) - - sys_path = String.to_charlist(path) - - sys_options = [ - :silent, - :no_dot_erlang, - :no_warn_sasl, - variables: build_variables(release), - path: build_paths(release) - ] - - case :systools.make_script(sys_path, sys_options) do - {:ok, _module, _warnings} -> - script_path = sys_path ++ '.script' - {:ok, [{:script, rel_info, instructions}]} = :file.consult(script_path) - - instructions = - instructions - |> post_stdlib_applies(release) - |> prepend_paths_to_script(prepend_paths) - - script = {:script, rel_info, instructions} - File.write!(script_path, consultable(script)) - :ok = :systools.script2boot(sys_path) - - {:error, module, info} -> - message = module.format_error(info) |> to_string() |> String.trim() - {:error, message} - end - end - end - - defp build_variables(release) do - for {_, properties} <- release.applications, - not Keyword.fetch!(properties, :otp_app?), - uniq: true, - do: {'RELEASE_LIB', properties |> Keyword.fetch!(:path) |> :filename.dirname()} - end - - defp build_paths(release) do - for {_, properties} <- release.applications, - Keyword.fetch!(properties, :otp_app?), - do: properties |> Keyword.fetch!(:path) |> Path.join("ebin") |> to_charlist() - end - - defp build_release_spec(release, modes) do - %{ - name: name, - version: version, - erts_version: erts_version, - applications: apps, - options: options - } = release - - skip_mode_validation_for = - options - |> Keyword.get(:skip_mode_validation_for, []) - |> MapSet.new() - - rel_apps = - for {app, mode} <- modes do - properties = Map.get(apps, app) || throw({:error, "Unknown application #{inspect(app)}"}) - children = Keyword.get(properties, :applications, []) - app in skip_mode_validation_for || validate_mode!(app, mode, modes, children) - build_app_for_release(app, mode, properties) - end - - {:ok, {:release, {to_charlist(name), to_charlist(version)}, {:erts, erts_version}, rel_apps}} - catch - {:error, message} -> {:error, message} - end - - defp validate_mode!(app, mode, modes, children) do - safe_mode? = mode in @safe_modes - - if not safe_mode? and mode not in @unsafe_modes do - throw( - {:error, - "Unknown mode #{inspect(mode)} for #{inspect(app)}. " <> - "Valid modes are: #{inspect(@safe_modes ++ @unsafe_modes)}"} - ) - end - - for child <- children do - child_mode = Keyword.get(modes, child) - - cond do - is_nil(child_mode) -> - throw( - {:error, - "Application #{inspect(app)} is listed in the release boot, " <> - "but it depends on #{inspect(child)}, which isn't"} - ) - - safe_mode? and child_mode in @unsafe_modes -> - throw( - {:error, - """ - Application #{inspect(app)} has mode #{inspect(mode)} but it depends on \ - #{inspect(child)} which is set to #{inspect(child_mode)}. If you really want \ - to set such mode for #{inspect(child)} make sure that all applications that depend \ - on it are also set to :load or :none, otherwise your release will fail to boot - """} - ) - - true -> - :ok - end - end - end - - defp build_app_for_release(app, mode, properties) do - vsn = Keyword.fetch!(properties, :vsn) - - case Keyword.get(properties, :included_applications, []) do - [] -> {app, vsn, mode} - included_apps -> {app, vsn, mode, included_apps} - end - end - - defp post_stdlib_applies(instructions, release) do - {pre, [stdlib | post]} = - Enum.split_while( - instructions, - &(not match?({:apply, {:application, :start_boot, [:stdlib, _]}}, &1)) - ) - - pre ++ [stdlib] ++ config_provider_apply(release) ++ post - end - - defp config_provider_apply(%{config_providers: []}), - do: [] - - defp config_provider_apply(_), - do: [{:apply, {Config.Provider, :boot, []}}] - - defp prepend_paths_to_script(instructions, []), do: instructions - - defp prepend_paths_to_script(instructions, prepend_paths) do - prepend_paths = Enum.map(prepend_paths, &String.to_charlist/1) - - Enum.map(instructions, fn - {:path, paths} -> - if Enum.any?(paths, &List.starts_with?(&1, '$RELEASE_LIB')) do - {:path, prepend_paths ++ paths} - else - {:path, paths} - end - - other -> - other - end) - end - - defp consultable(term) do - IO.chardata_to_string(:io_lib.format("%% coding: utf-8~n~tp.~n", [term])) - end - - @doc """ - Finds a template path for the release. - """ - def rel_templates_path(release, path) do - Path.join(release.options[:rel_templates_path] || "rel", path) - end - - @doc """ - Copies ERTS if the release is configured to do so. - - Returns true if the release was copied, false otherwise. - """ - @spec copy_erts(t) :: boolean() - def copy_erts(%{erts_source: nil}) do - false - end - - def copy_erts(release) do - destination = Path.join(release.path, "erts-#{release.erts_version}/bin") - File.mkdir_p!(destination) - - release.erts_source - |> Path.join("bin") - |> File.cp_r!(destination, fn _, _ -> false end) - - _ = File.rm(Path.join(destination, "erl")) - _ = File.rm(Path.join(destination, "erl.ini")) - - destination - |> Path.join("erl") - |> File.write!(~S""" - #!/bin/sh - SELF=$(readlink "$0" || true) - if [ -z "$SELF" ]; then SELF="$0"; fi - BINDIR="$(cd "$(dirname "$SELF")" && pwd -P)" - ROOTDIR="${ERL_ROOTDIR:-"$(dirname "$(dirname "$BINDIR")")"}" - EMU=beam - PROGNAME=$(echo "$0" | sed 's/.*\///') - export EMU - export ROOTDIR - export BINDIR - export PROGNAME - exec "$BINDIR/erlexec" ${1+"$@"} - """) - - File.chmod!(Path.join(destination, "erl"), 0o755) - true - end - - @doc """ - Copies the given application specification into the release. - - It assumes the application exists in the release. - """ - @spec copy_app(t, application) :: boolean() - def copy_app(release, app) do - properties = Map.fetch!(release.applications, app) - vsn = Keyword.fetch!(properties, :vsn) - - source_app = Keyword.fetch!(properties, :path) - target_app = Path.join([release.path, "lib", "#{app}-#{vsn}"]) - - if is_nil(release.erts_source) and Keyword.fetch!(properties, :otp_app?) do - false - else - File.rm_rf!(target_app) - File.mkdir_p!(target_app) - - copy_ebin(release, Path.join(source_app, "ebin"), Path.join(target_app, "ebin")) - - for dir <- @copy_app_dirs do - source_dir = Path.join(source_app, dir) - target_dir = Path.join(target_app, dir) - - source_dir = - case File.read_link(source_dir) do - {:ok, link_target} -> Path.expand(link_target, source_app) - _ -> source_dir - end - - File.exists?(source_dir) && File.cp_r!(source_dir, target_dir) - end - - true - end - end - - @doc """ - Copies the ebin directory at `source` to `target` - respecting release options such a `:strip_beams`. - """ - @spec copy_ebin(t, Path.t(), Path.t()) :: boolean() - def copy_ebin(release, source, target) do - with {:ok, [_ | _] = files} <- File.ls(source) do - File.mkdir_p!(target) - - strip_options = - release.options - |> Keyword.get(:strip_beams, true) - |> parse_strip_beams_options() - - for file <- files do - source_file = Path.join(source, file) - target_file = Path.join(target, file) - - with true <- is_list(strip_options) and String.ends_with?(file, ".beam"), - {:ok, binary} <- strip_beam(File.read!(source_file), strip_options) do - File.write!(target_file, binary) - else - _ -> - # Use File.cp!/3 to preserve file mode for any executables stored - # in the ebin directory. - File.cp!(source_file, target_file) - end - end - - true - else - _ -> false - end - end - - @doc """ - Strips a beam file for a release. - - This keeps only significant chunks necessary for the VM operation, - discarding documentation, debug info, compile information and others. - - The exact chunks that are kept are not documented and may change in - future versions. - """ - @spec strip_beam(binary(), keyword()) :: {:ok, binary()} | {:error, :beam_lib, term()} - def strip_beam(binary, options \\ []) when is_list(options) do - chunks_to_keep = options[:keep] |> List.wrap() |> Enum.map(&String.to_charlist/1) - all_chunks = Enum.uniq(@significant_chunks ++ chunks_to_keep) - - case :beam_lib.chunks(binary, all_chunks, [:allow_missing_chunks]) do - {:ok, {_, chunks}} -> - chunks = for {name, chunk} <- chunks, is_binary(chunk), do: {name, chunk} - {:ok, binary} = :beam_lib.build_module(chunks) - {:ok, :zlib.gzip(binary)} - - {:error, _, _} = error -> - error - end - end - - defp parse_strip_beams_options(options) do - case options do - options when is_list(options) -> options - true -> [] - false -> nil - end - end -end