Commit aef59d88 authored by David's avatar David Committed by Raphael Megzari
Browse files

buildRebar3: use `rebar3 bare compile`

parent 0d66d837
Loading
Loading
Loading
Loading
+5 −18
Original line number Diff line number Diff line
{ stdenv, writeText, erlang, rebar3WithPlugins, openssl, libyaml,
  pc, lib }:
{ stdenv, writeText, erlang, rebar3WithPlugins, openssl, libyaml, lib }:

{ name, version
, src
, setupHook ? null
, buildInputs ? [], beamDeps ? [], buildPlugins ? []
, postPatch ? ""
, compilePorts ? false
, installPhase ? null
, buildPhase ? null
, configurePhase ? null
@@ -21,7 +19,6 @@ let

  rebar3 = rebar3WithPlugins {
    plugins = buildPlugins;
    globalPlugins = (if compilePorts then [pc] else []);
  };

  shell = drv: stdenv.mkDerivation {
@@ -52,18 +49,9 @@ let
      rm -f rebar rebar3
    '' + postPatch;

    configurePhase = ''
      runHook preConfigure
      ${erlang}/bin/escript ${rebar3.bootstrapper} ${debugInfoFlag}
      runHook postConfigure
    '';

    buildPhase = ''
      runHook preBuild
      HOME=. rebar3 compile
      ${if compilePorts then ''
        HOME=. rebar3 pc compile
      '' else ""}
      HOME=. rebar3 bare compile -path ""
      runHook postBuild
    '';

@@ -71,10 +59,9 @@ let
      runHook preInstall
      mkdir -p "$out/lib/erlang/lib/${name}-${version}"
      for reldir in src ebin priv include; do
        fd="_build/default/lib/${name}/$reldir"
        [ -d "$fd" ] || continue
        cp -Hrt "$out/lib/erlang/lib/${name}-${version}" "$fd"
        success=1
        [ -d "$reldir" ] || continue
        # $out/lib/erlang/lib is a convention used in nixpkgs for compiled BEAM packages
        cp -Hrt "$out/lib/erlang/lib/${name}-${version}" "$reldir"
      done
      runHook postInstall
    '';
+1 −3
Original line number Diff line number Diff line
@@ -19,8 +19,6 @@ let
      sha256 = "1pcy5m79g0l9l3d8lkbx6cq1w87z1g3sa6wwvgbgraj2v3wkyy5g";
    };

    bootstrapper = ./rebar3-nix-bootstrap;

    buildInputs = [ erlang ];

    postPatch = ''
@@ -106,7 +104,7 @@ let
      }));
    in stdenv.mkDerivation {
      pname = "rebar3-with-plugins";
      inherit (rebar3) version bootstrapper;
      inherit (rebar3) version;
      nativeBuildInputs = [ erlang makeWrapper ];
      unpackPhase = "true";

+0 −228
Original line number Diff line number Diff line
#!/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
              , debug_info = false
              , erl_libs
              , root
              , name}).

-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_the_bootstrap(RequiredData).

-spec do_the_bootstrap(#data{}) -> ok.
do_the_bootstrap(RequiredData) ->
    ok = bootstrap_configs(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 one entry on the
%% command line: "debug-info"
-spec parse_args([string()]) -> #data{}.
parse_args(Args0) ->
    PossibleArgs = sets:from_list(["debug-info"]),
    Args1 = sets:from_list(Args0),
    Result = sets:subtract(Args1, PossibleArgs),
    case sets:to_list(Result) of
        [] ->
            {ok, #data{debug_info = sets:is_element("debug-info", Args1)}};
        UnknownArgs ->
            io:format("Unexpected command line arguments passed in: ~p~n",
                      [UnknownArgs]),
            erlang:halt(120)
    end.


-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_debug_info_add(RequiredData).

-spec bootstrap_libs(#data{}) -> ok.
bootstrap_libs(#data{erl_libs = ErlLibs}) ->
    io:format("Bootstrapping dependent libraries~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) ->
                          ok = 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 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;
        [Name, _Version, _Tag] -> Name
    end.

-spec gather_required_data_from_the_environment(#data{}) -> {ok, #data{}}.
gather_required_data_from_the_environment(ArgData) ->
    {ok, ArgData#data{ version = guard_env("version")
                     , erl_libs = get_env("ERL_LIBS", [])
                     , root = code:root_dir()
                     , name = guard_env("name")
                     }}.

-spec nix2bool(any()) -> boolean().
nix2bool("1") ->
    true;
nix2bool("") ->
    false.

get_env(Name) ->
    os:getenv(Name).
get_env(Name, Def) ->
    case get_env(Name) of
        false -> Def;
        Val ->   Val
    end.

-spec guard_env(string()) -> string().
guard_env(Name) ->
    case get_env(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 debug info is set we need to add debug info to the list of compile options
%%
-spec if_debug_info_add(#data{}) -> ok.
if_debug_info_add(#data{debug_info = true}) ->
    ConfigTerms = add_debug_info(read_rebar_config()),
    Text = lists:map(fun(Term) -> io_lib:format("~tp.~n", [Term]) end,
                     ConfigTerms),
    file:write_file("rebar.config", Text);
if_debug_info_add(_) ->
    ok.

-spec add_debug_info([term()]) -> [term()].
add_debug_info(Config) ->
    ExistingOpts = case lists:keysearch(erl_opts, 1, Config) of
                       {value, {erl_opts, ExistingOptsList}} -> ExistingOptsList;
                       _ -> []
                   end,
    case lists:member(debug_info, ExistingOpts) of
        true ->
            Config;
        false ->
            lists:keystore(erl_opts, 1, Config,
                           {erl_opts, [debug_info | ExistingOpts]})
    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}) ->
    SrcFile = filename:join("src",
                            lists:concat([Name, ".app.src"])),

    case filelib:is_file(SrcFile) of
        true ->
            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}),
    ok = file:write_file(SrcFile, io_lib:fwrite("~p.\n", [{application, Name, NewDetails}])).

%% @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)).