diff options
Diffstat (limited to 'pkgs/development/tools/build-managers/rebar3/rebar3-nix-bootstrap')
-rwxr-xr-x | pkgs/development/tools/build-managers/rebar3/rebar3-nix-bootstrap | 256 |
1 files changed, 256 insertions, 0 deletions
diff --git a/pkgs/development/tools/build-managers/rebar3/rebar3-nix-bootstrap b/pkgs/development/tools/build-managers/rebar3/rebar3-nix-bootstrap new file mode 100755 index 000000000000..d75d69f054d6 --- /dev/null +++ b/pkgs/development/tools/build-managers/rebar3/rebar3-nix-bootstrap @@ -0,0 +1,256 @@ +#!/usr/bin/env escript +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%%! -smp enable +%%% --------------------------------------------------------------------------- +%%% @doc +%%% The purpose of this command is to prepare a rebar3 project so that +%%% rebar3 understands that the dependencies are all already +%%% installed. If you want a hygienic build on nix then you must run +%%% this command before running rebar3. I suggest that you add a +%%% `Makefile` to your project and have the bootstrap command be a +%%% dependency of the build commands. See the nix documentation for +%%% more information. +%%% +%%% This command designed to have as few dependencies as possible so +%%% that it can be a dependency of root level packages like rebar3. To +%%% that end it does many things in a fairly simplistic way. That is +%%% by design. +%%% +%%% ### Assumptions +%%% +%%% This command makes the following assumptions: +%%% +%%% * It is run in a nix-shell or nix-build environment +%%% * that all dependencies have been added to the ERL_LIBS +%%% Environment Variable + +-record(data, {version + , registry_only = false + , compile_ports + , erl_libs + , plugins + , root + , name + , registry_snapshot}). + +-define(HEX_REGISTRY_PATH, ".cache/rebar3/hex/default/registry"). + +main(Args) -> + {ok, ArgData} = parse_args(Args), + {ok, RequiredData} = gather_required_data_from_the_environment(ArgData), + do(RequiredData). + +%% @doc +%% This actually runs the command. There are two modes 'register_only' +%% where the register is created from hex and everything else. +-spec do(#data{}) -> ok. +do(RequiredData = #data{registry_only = true}) -> + ok = bootstrap_registry(RequiredData); +do(RequiredData) -> + ok = bootstrap_registry(RequiredData), + ok = bootstrap_configs(RequiredData), + ok = bootstrap_plugins(RequiredData), + ok = bootstrap_libs(RequiredData). + +%% @doc +%% Argument parsing is super simple only because we want to keep the +%% dependencies minimal. For now there can be two entries on the +%% command line, "register-only" and "compile-ports" +-spec parse_args([string()]) -> #data{}. +parse_args(Args) -> + {ok, #data{registry_only = lists:member("registry-only", Args)}}. + +-spec bootstrap_configs(#data{}) -> ok. +bootstrap_configs(RequiredData)-> + io:format("Boostrapping app and rebar configurations~n"), + ok = if_single_app_project_update_app_src_version(RequiredData), + ok = if_compile_ports_add_pc_plugin(RequiredData). + +-spec bootstrap_plugins(#data{}) -> ok. +bootstrap_plugins(#data{plugins = Plugins}) -> + io:format("Bootstrapping rebar3 plugins~n"), + Target = "_build/default/plugins/", + Paths = string:tokens(Plugins, " "), + CopiableFiles = + lists:foldl(fun(Path, Acc) -> + gather_dependency(Path) ++ Acc + end, [], Paths), + lists:foreach(fun (Path) -> + link_app(Path, Target) + end, CopiableFiles). + +-spec bootstrap_libs(#data{}) -> ok. +bootstrap_libs(#data{erl_libs = ErlLibs}) -> + io:format("Bootstrapping dependent librariesXXXX~n"), + Target = "_build/default/lib/", + Paths = string:tokens(ErlLibs, ":"), + CopiableFiles = + lists:foldl(fun(Path, Acc) -> + gather_directory_contents(Path) ++ Acc + end, [], Paths), + lists:foreach(fun (Path) -> + link_app(Path, Target) + end, CopiableFiles). + +-spec gather_dependency(string()) -> [{string(), string()}]. +gather_dependency(Path) -> + FullLibrary = filename:join(Path, "lib/erlang/lib/"), + case filelib:is_dir(FullLibrary) of + true -> + gather_directory_contents(FullLibrary); + false -> + [raw_hex(Path)] + end. + +-spec raw_hex(string()) -> {string(), string()}. +raw_hex(Path) -> + [_, Name] = re:split(Path, "-hex-source-"), + {Path, erlang:binary_to_list(Name)}. + +-spec gather_directory_contents(string()) -> [{string(), string()}]. +gather_directory_contents(Path) -> + {ok, Names} = file:list_dir(Path), + lists:map(fun(AppName) -> + {filename:join(Path, AppName), fixup_app_name(AppName)} + end, Names). + +%% @doc +%% Makes a symlink from the directory pointed at by Path to a +%% directory of the same name in Target. So if we had a Path of +%% {`foo/bar/baz/bash`, `baz`} and a Target of `faz/foo/foos`, the symlink +%% would be `faz/foo/foos/baz`. +-spec link_app({string(), string()}, string()) -> ok. +link_app({Path, TargetFile}, TargetDir) -> + Target = filename:join(TargetDir, TargetFile), + make_symlink(Path, Target). + +-spec make_symlink(string(), string()) -> ok. +make_symlink(Path, TargetFile) -> + file:delete(TargetFile), + ok = filelib:ensure_dir(TargetFile), + io:format("Making symlink from ~s to ~s~n", [Path, TargetFile]), + ok = file:make_symlink(Path, TargetFile). + +%% @doc +%% This takes an app name in the standard OTP <name>-<version> format +%% and returns just the app name. Why? because rebar is doesn't +%% respect OTP conventions in some cases. +-spec fixup_app_name(string()) -> string(). +fixup_app_name(FileName) -> + case string:tokens(FileName, "-") of + [Name] -> Name; + [Name, _Version] -> Name + end. + +-spec bootstrap_registry(#data{}) -> ok. +bootstrap_registry(#data{registry_snapshot = RegistrySnapshot}) -> + io:format("Bootstrapping Hex Registry for Rebar~n"), + make_sure_registry_snapshot_exists(RegistrySnapshot), + filelib:ensure_dir(?HEX_REGISTRY_PATH), + ok = case filelib:is_file(?HEX_REGISTRY_PATH) of + true -> + file:delete(?HEX_REGISTRY_PATH); + false -> + ok + end, + ok = file:make_symlink(RegistrySnapshot, + ?HEX_REGISTRY_PATH). + +-spec make_sure_registry_snapshot_exists(string()) -> ok. +make_sure_registry_snapshot_exists(RegistrySnapshot) -> + case filelib:is_file(RegistrySnapshot) of + true -> + ok; + false -> + stderr("Registry snapshot (~s) does not exist!", [RegistrySnapshot]), + erlang:halt(1) + end. + +-spec gather_required_data_from_the_environment(#data{}) -> {ok, map()}. +gather_required_data_from_the_environment(ArgData) -> + {ok, ArgData#data{ version = guard_env("version") + , erl_libs = os:getenv("ERL_LIBS", []) + , plugins = os:getenv("buildPlugins", []) + , root = code:root_dir() + , name = guard_env("name") + , compile_ports = nix2bool(os:getenv("compilePorts", "")) + , registry_snapshot = guard_env("HEX_REGISTRY_SNAPSHOT")}}. + +-spec nix2bool(any()) -> boolean(). +nix2bool("1") -> + true; +nix2bool("") -> + false. + +-spec guard_env(string()) -> string(). +guard_env(Name) -> + case os:getenv(Name) of + false -> + stderr("Expected Environment variable ~s! Are you sure you are " + "running in a Nix environment? Either a nix-build, " + "nix-shell, etc?~n", [Name]), + erlang:halt(1); + Variable -> + Variable + end. + +%% @doc +%% If the compile ports flag is set, rewrite the rebar config to +%% include the 'pc' plugin. +-spec if_compile_ports_add_pc_plugin(#data{}) -> ok. +if_compile_ports_add_pc_plugin(#data{compile_ports = true}) -> + ConfigTerms = update_config(read_rebar_config()), + Text = lists:map(fun(Term) -> io_lib:format("~tp.~n", [Term]) end, + ConfigTerms), + file:write_file("rebar.config", Text); +if_compile_ports_add_pc_plugin(_) -> + ok. + +-spec update_config([term()]) -> [term()]. +update_config(Config) -> + case lists:keysearch(plugins, 1, Config) of + {ok, {plugins, PluginList}} -> + lists:keystore(plugins, 1, Config, {plugins, [Config | PluginList]}); + _ -> + [{plugins, [pc]} | Config] + end. + +-spec read_rebar_config() -> [term()]. +read_rebar_config() -> + case file:consult("rebar.config") of + {ok, Terms} -> + Terms; + _ -> + stderr("Unable to read rebar config!", []), + erlang:halt(1) + end. + + +-spec if_single_app_project_update_app_src_version(#data{}) -> ok. +if_single_app_project_update_app_src_version(#data{name = Name, + version = Version}) -> + case app_src_exists(Name) of + {true, SrcFile} -> + update_app_src_with_version(SrcFile, Version); + {false, _} -> + ok + end. + +-spec update_app_src_with_version(string(), string()) -> ok. +update_app_src_with_version(SrcFile, Version) -> + {ok, [{application, Name, Details}]} = file:consult(SrcFile), + NewDetails = lists:keyreplace(vsn, 1, Details, {vsn, Version}), + file:write_file(SrcFile, io_lib:fwrite("~p.\n", [{application, Name, NewDetails}])). + +-spec app_src_exists(string()) -> boolean(). +app_src_exists(Name) -> + FileName = filename:join("src", + lists:concat([Name, ".app.src"])), + {filelib:is_file(FileName), FileName}. + + +%% @doc +%% Write the result of the format string out to stderr. +-spec stderr(string(), [term()]) -> ok. +stderr(FormatStr, Args) -> + io:put_chars(standard_error, io_lib:format(FormatStr, Args)). |