defmodule Burrito.Steps.Patch.CopyERTS do @moduledoc """ This step copies the new ERTS bins into the release, as well as replaces built-in NIFs. """ alias Burrito.Builder.Context alias Burrito.Builder.Log alias Burrito.Builder.Step @behaviour Step @impl Step def execute(%Context{} = context) do case context.target.erts_source do # nothing to do {:runtime, _} -> context {:local_unpacked, [path: location]} -> # copy unpacked bins into release working directory do_copy(location, context) end context end defp do_copy(erts_location, %Context{} = context) do Log.info(:step, "Replacing ERTS binaries...") # Clean out current bins dest_bin_path = Path.join(context.work_dir, ["erts-*/", "bin/"]) |> Path.expand() |> Path.wildcard() |> List.first() File.rm_rf!(dest_bin_path) File.mkdir!(dest_bin_path) # Copy in new bins from unpacked ERTS unpacked_path = recursively_find_dir([erts_location], ~r/^erts-.*$/) src_bin_path = Path.join(unpacked_path, ["bin/"]) File.cp_r!(src_bin_path, dest_bin_path) # The ERTS comes with some pre-built NIFs, so we need to replace those src_lib_path = recursively_find_dir([erts_location], ~r/^lib$/) dest_lib_path = Path.join(context.work_dir, ["lib/"]) |> Path.expand() # List the DLL/SO files that come from our replacement ERTS # The new ERTS is treated as the "authoritative" ERTS, some DLLs/SOs may exist in it # That do not exist in the host/source ERTS. We'll log when we replace a library file to_copy = Path.join(erts_location, "*/lib/**/*.{so,dll,exe}") |> Path.expand() |> Path.wildcard() Enum.each(to_copy, fn file_to_copy -> destination_file_path = String.replace(file_to_copy, src_lib_path, dest_lib_path) possible_file_to_replace = String.replace_suffix(destination_file_path, Path.extname(to_copy), "*.{so,dll}") |> Path.wildcard() |> List.first() full_directory = Path.dirname(destination_file_path) File.mkdir_p!(full_directory) if possible_file_to_replace != nil do File.rm!(possible_file_to_replace) Log.warning( :step, "Overwriting NIF library #{possible_file_to_replace} with #{file_to_copy}" ) end File.copy!(file_to_copy, destination_file_path) Log.success( :step, "Installed NIF library #{destination_file_path}" ) end) end defp recursively_find_dir([], _target_dir_pattern), do: nil defp recursively_find_dir([current_path | rest], target_dir_pattern) do case String.match?(Path.basename(current_path), target_dir_pattern) do true -> Path.expand(current_path) false -> dir_paths = for file_name <- File.ls!(current_path), File.dir?(Path.join(current_path, file_name)), not String.starts_with?(file_name, ".") do Path.join(current_path, file_name) end recursively_find_dir(rest ++ dir_paths, target_dir_pattern) end end end