about summary refs log tree commit diff
path: root/nixpkgs/pkgs/development/beam-modules/mix-release.nix
blob: d48dc38a4b8d4a42901ec58082897a200ec00c0b (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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
{ stdenv, lib, elixir, erlang, findutils, hex, rebar, rebar3, fetchMixDeps, makeWrapper, git, ripgrep }@inputs:

{ pname
, version
, src
, nativeBuildInputs ? [ ]
, buildInputs ? [ ]
, meta ? { }
, enableDebugInfo ? false
, mixEnv ? "prod"
, compileFlags ? [ ]

  # mix fixed output derivation dependencies
, mixFodDeps ? null

  # mix dependencies generated by mix2nix
  # this assumes each dependency is built by buildMix or buildRebar3
  # each dependency needs to have a setup hook to add the lib path to $ERL_LIBS
  # this is how mix will find dependencies
, mixNixDeps ? { }

, elixir ? inputs.elixir
, hex ? inputs.hex.override { inherit elixir; }

# This reduces closure size, but can lead to some hard to understand runtime
# errors, so use with caution. See e.g.
# https://github.com/whitfin/cachex/issues/205
# https://framagit.org/framasoft/mobilizon/-/issues/1169
, stripDebug ? false

, ...
}@attrs:
let
  # remove non standard attributes that cannot be coerced to strings
  overridable = builtins.removeAttrs attrs [ "compileFlags" "mixNixDeps" ];
in
assert mixNixDeps != { } -> mixFodDeps == null;
assert stripDebug -> !enableDebugInfo;

stdenv.mkDerivation (overridable // {
  # rg is used as a better grep to search for erlang references in the final release
  nativeBuildInputs = nativeBuildInputs ++ [ erlang hex elixir makeWrapper git ripgrep ];
  buildInputs = buildInputs ++ builtins.attrValues mixNixDeps;

  MIX_ENV = mixEnv;
  MIX_DEBUG = if enableDebugInfo then 1 else 0;
  HEX_OFFLINE = 1;
  DEBUG = if enableDebugInfo then 1 else 0; # for Rebar3 compilation
  # the api with `mix local.rebar rebar path` makes a copy of the binary
  # some older dependencies still use rebar
  MIX_REBAR = "${rebar}/bin/rebar";
  MIX_REBAR3 = "${rebar3}/bin/rebar3";
  LC_ALL = "C.UTF-8";

  postUnpack = ''
    export HEX_HOME="$TEMPDIR/hex"
    export MIX_HOME="$TEMPDIR/mix"

    # Rebar
    export REBAR_GLOBAL_CONFIG_DIR="$TEMPDIR/rebar3"
    export REBAR_CACHE_DIR="$TEMPDIR/rebar3.cache"

    ${lib.optionalString (mixFodDeps != null) ''
      # compilation of the dependencies will require
      # that the dependency path is writable
      # thus a copy to the TEMPDIR is inevitable here
      export MIX_DEPS_PATH="$TEMPDIR/deps"
      cp --no-preserve=mode -R "${mixFodDeps}" "$MIX_DEPS_PATH"
    ''
    }

  '' + (attrs.postUnpack or "");

  configurePhase = attrs.configurePhase or ''
    runHook preConfigure

    ${./mix-configure-hook.sh}
    # this is needed for projects that have a specific compile step
    # the dependency needs to be compiled in order for the task
    # to be available
    # Phoenix projects for example will need compile.phoenix
    mix deps.compile --no-deps-check --skip-umbrella-children

    # Symlink dependency sources. This is needed for projects that require
    # access to the source of their dependencies. For example, Phoenix
    # applications need javascript assets to build asset bundles.
    ${lib.optionalString (mixNixDeps != { }) ''
      mkdir -p deps

      ${lib.concatMapStringsSep "\n" (dep: ''
        dep_name=$(basename ${dep} | cut -d '-' -f2)
        dep_path="deps/$dep_name"
        if [ -d "${dep}/src" ]; then
          ln -s ${dep}/src $dep_path
        fi
      '') (builtins.attrValues mixNixDeps)}
    ''}

    # Symlink deps to build root. Similar to above, but allows for mixFodDeps
    # Phoenix projects to find javascript assets.
    ${lib.optionalString (mixFodDeps != null) ''
      ln -s ../deps ./
    ''}

    runHook postConfigure
  '';

  buildPhase = attrs.buildPhase or ''
    runHook preBuild

    mix compile --no-deps-check ${lib.concatStringsSep " " compileFlags}

    runHook postBuild
  '';


  installPhase = attrs.installPhase or ''
    runHook preInstall

    mix release --no-deps-check --path "$out"

    runHook postInstall
  '';

  # Stripping of the binary is intentional
  # even though it does not affect beam files
  # it is necessary for NIFs binaries
  postFixup = ''
    if [ -e "$out/bin/${pname}.bat" ]; then # absent in special cases, i.e. elixir-ls
      rm "$out/bin/${pname}.bat" # windows file
    fi
    # contains secrets and should not be in the nix store
    # TODO document how to handle RELEASE_COOKIE
    # secrets should not be in the nix store.
    # This is only used for connecting multiple nodes
    if [ -e $out/releases/COOKIE ]; then # absent in special cases, i.e. elixir-ls
      rm $out/releases/COOKIE
    fi
    # removing unused erlang reference from resulting derivation to reduce
    # closure size
    if [ -e $out/erts-* ]; then
      echo "ERTS found in $out - removing references to erlang to reduce closure size"
      # there is a link in $out/erts-*/bin/start always
      # TODO:
      # sometimes there are links in dependencies like bcrypt compiled binaries
      # at the moment those are not removed since substituteInPlace will
      # error on binaries
      for file in $(rg "${erlang}/lib/erlang" "$out" --files-with-matches); do
        echo "removing reference to erlang in $file"
        substituteInPlace "$file" --replace "${erlang}/lib/erlang" "$out"
      done
    fi
  '' + lib.optionalString stripDebug ''
    # strip debug symbols to avoid hardreferences to "foreign" closures actually
    # not needed at runtime, while at the same time reduce size of BEAM files.
    erl -noinput -eval 'lists:foreach(fun(F) -> io:format("Stripping ~p.~n", [F]), beam_lib:strip(F) end, filelib:wildcard("'"$out"'/**/*.beam"))' -s init stop
  '';

  # TODO investigate why the resulting closure still has
  # a reference to erlang.
  # uncommenting the following will fail the build
  # disallowedReferences = [ erlang ];
})