defmodule StripPlugin do alias Mix.Releases.Release # Distillery Behaviour @moduledoc """ ## Installation There are two ways to include the plugin. Add this repo to your mix.exs file: ```elixir def deps do [ {:strip_plugin, github: "se-apc/strip_plugin", runtime: false} ] end ``` OR Copy/Paste lib/strip_plugin.ex to your rel/plugins/strip_plugin.exs file. This Finally, add the plugin to your `rel/config.exs` file: ```elixir environment :prod do plugin StripPlugin end ``` ## ZIP Apps Erlang/Elixir supports storing all BEAM files of an app in a ZIP file. This plugin supports doing this and can be enabled by adding this to `config/config.exs` ```elixir config :strip_plugin, zip_apps: true ``` """ @do_zip Application.get_env(:strip_plugin, :zip_apps) def before_assembly(release) do release end def before_assembly(%Release{} = release, _opts) do release end def after_assembly(%Release{} = release, _opts) do path = release.profile.output_dir case strip_release(path) do {:ok, _} -> IO.puts("Release Strip Successful") :ok {:error, :beam_lib, reason} -> IO.puts("Release Strip Failed") {:error, {:archiver, {:beam_lib, reason}}} end if @do_zip do old_cwd = File.cwd!() lib_dir = Path.join([path, "lib"]) File.cd(lib_dir) for app_name <- File.ls!() do zip_app(app_name) end File.cd(old_cwd) end release end def before_package(%Release{} = release, _opts) do release end def after_package(%Release{} = release, _opts) do release end def after_cleanup(_args, _opts) do :noop end defp zip_app(app_name) do ebin_files = for ebin_file <- Path.wildcard("#{app_name}/ebin/*") do to_charlist(ebin_file) end :zip.create( '#{app_name}.ez', ebin_files, compress: :all, uncompress: ['.beam', '.app'] ) File.rm_rf(Path.join([app_name, "ebin"])) # This will only succeed if the priv folder is empy File.rmdir(app_name) end # # These are copied directly from beam_lib.erl # def strip_release(path) do path |> to_charlist() |> :filename.join('lib/*/ebin/*.beam') |> :filelib.wildcard() |> strip_files() end def strip_files(files) do result = for f <- files, do: strip_file(f) {:ok, result} end def strip_file(file) do {:ok, {mod, chunks}} = read_significant_chunks(file, significant_chunks()) {:ok, stripped0} = :beam_lib.build_module(chunks) stripped = compress(stripped0) case file do _ when is_binary(file) -> {:ok, {mod, stripped}} _ -> file_name = beam_filename(file) case :file.open(file_name, [:raw, :binary, :write]) do {:ok, fd} -> case :file.write(fd, stripped) do :ok -> :ok = :file.close(fd) {:ok, {mod, file_name}} error -> :ok = :file.close(fd) file_error(file_name, error) end error -> file_error(file_name, error) end end end def compress(binary0) do {:ok, fd} = :ram_file.open(binary0, [:write, :binary]) {:ok, _} = :ram_file.compress(fd) {:ok, binary} = :ram_file.get_file(fd) :ok = :ram_file.close(fd) binary end def beam_filename(bin) when is_binary(bin) do bin end def beam_filename(file) do :filename.rootname(file, '.beam') ++ '.beam' end def read_significant_chunks(file, chunk_list) do case :beam_lib.chunks(file, chunk_list, [:allow_missing_chunks]) do {:ok, {module, chunks0}} -> mandatory = mandatory_chunks() chunks = filter_significant_chunks(chunks0, mandatory, file, module) {:ok, {module, chunks}} end end # The following chunks must be kept when stripping a BEAM file. def significant_chunks(), do: ['Line' | md5_chunks()] # The following chunks are significant when calculating the MD5 # for a module. They are listed in the order that they should be MD5:ed. def md5_chunks(), do: ['Atom', 'AtU8', 'Attr', 'Code', 'StrT', 'ImpT', 'ExpT', 'FunT', 'LitT'] # The following chunks are mandatory in every Beam file. def mandatory_chunks(), do: ['Code', 'ExpT', 'ImpT', 'StrT'] def filter_significant_chunks([{_, data} = pair | cs], mandatory, file, mod) when is_binary(data), do: [pair | filter_significant_chunks(cs, mandatory, file, mod)] def filter_significant_chunks([{id, :missing_chunk} | cs], mandatory, file, mod) do case :lists.member(id, mandatory) do false -> filter_significant_chunks(cs, mandatory, file, mod) true -> error({:missing_chunk, file, id}) end end def filter_significant_chunks([], _, _, _), do: [] def error(reason), do: throw({:error, __MODULE__, reason}) def error(fmt, args) do :error_logger.error_msg(fmt, args) :error end def file_error(file_name, {:error, reason}), do: error({:file_error, file_name, reason}) end