summary refs log tree commit diff
path: root/pkgs/development/beam-modules/mix-bootstrap
blob: 7e31def71fa82df4acedab7299002757ca2272d1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#!/usr/bin/env escript
%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
%%! -smp enable
%%% ---------------------------------------------------------------------------
%%% @doc
%%% The purpose of this command is to prepare a mix project so that mix
%%% understands that the dependencies are all already installed. If you want a
%%% hygienic build on nix then you must run this command before running mix. 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 mix. 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
              , erl_libs
              , root
              , name}).
-define(LOCAL_HEX_REGISTRY, "registry.ets").

main(Args) ->
    {ok, RequiredData} = gather_required_data_from_the_environment(Args),
    ok = bootstrap_libs(RequiredData).

%% @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(file:name()) -> string().
fixup_app_name(Path) ->
    BaseName = filename:basename(Path),
    case string:split(BaseName, "-") of
        [Name, _Version] -> Name;
        Name -> Name
    end.


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

-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.

-spec bootstrap_libs(#data{}) -> ok.
bootstrap_libs(#data{erl_libs = ErlLibs}) ->
    io:format("Bootstrapping dependent libraries~n"),
    Target = "_build/prod/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_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),
    ok = 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
%% 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)).