about summary refs log tree commit diff
path: root/nixpkgs/pkgs/development/beam-modules/mix-release.nix
blob: a762b8e0bc2e8036276a1f671975d83f29267f95 (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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
{ stdenv
, lib
, elixir
, erlang
, hex
, git
, rebar
, rebar3
, fetchMixDeps
, findutils
, ripgrep
, bbe
, makeWrapper
, coreutils
, gnused
, gnugrep
, gawk
}@inputs:

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

  # Options to be passed to the Erlang compiler. As documented in the reference
  # manual, these must be valid Erlang terms. They will be turned into an
  # erlang list and set as the ERL_COMPILER_OPTIONS environment variable.
  # See https://www.erlang.org/doc/man/compile
, erlangCompilerOptions ? [ ]

  # Deterministic Erlang builds remove full system paths from debug information
  # among other things to keep builds more reproducible. See their docs for more:
  # https://www.erlang.org/doc/man/compile
, erlangDeterministicBuilds ? true

  # Mix dependencies provided as a fixed output derivation
, 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 finds dependencies.
, mixNixDeps ? { }

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

  # Remove releases/COOKIE
  #
  # People have different views on the nature of cookies. Some believe that they are
  # secrets, while others believe they are just ids for clustering nodes instead of
  # secrets.
  #
  # If you think cookie is secret, you can set this attr to true, then it will be
  # removed from nix store. If not, you can set it to false.
  #
  # For backward compatibility, it is set to true by default.
  #
  # You can always specify a custom cookie by using RELEASE_COOKIE environment
  # variable, regardless of the value of this attr.
, removeCookie ? true

  # 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" "erlangCompilerOptions" "mixNixDeps" ];
in
assert mixNixDeps != { } -> mixFodDeps == null;
assert stripDebug -> !enableDebugInfo;

stdenv.mkDerivation (overridable // {
  nativeBuildInputs = nativeBuildInputs ++
    # Erlang/Elixir deps
    [ erlang elixir hex git ] ++
    # Mix deps
    (builtins.attrValues mixNixDeps) ++
    # other compile-time deps
    [ findutils ripgrep bbe makeWrapper ];

  buildInputs = buildInputs;

  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";

  ERL_COMPILER_OPTIONS =
    let
      options = erlangCompilerOptions ++ lib.optionals erlangDeterministicBuilds [ "deterministic" ];
    in
    "[${lib.concatStringsSep "," options}]";

  LC_ALL = "C.UTF-8";

  postUnpack = ''
    # Mix and Hex
    export MIX_HOME="$TEMPDIR/mix"
    export HEX_HOME="$TEMPDIR/hex"

    # 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
    # projects 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
  '';

  postFixup = ''
    echo "removing files for Microsoft Windows"
    rm -f "$out"/bin/*.bat

    echo "wrapping programs in $out/bin with their runtime deps"
    for f in $(find $out/bin/ -type f -executable); do
      wrapProgram "$f" \
        --prefix PATH : ${lib.makeBinPath [
          coreutils
          gnused
          gnugrep
          gawk
        ]}
    done
  '' + lib.optionalString removeCookie ''
    if [ -e $out/releases/COOKIE ]; then
      echo "removing $out/releases/COOKIE"
      rm $out/releases/COOKIE
    fi
  '' + ''
    if [ -e $out/erts-* ]; then
      # ERTS is included in the release, then erlang is not required as a runtime dependency.
      # But, erlang is still referenced in some places. To removed references to erlang,
      # following steps are required.

      # 1. remove references to erlang from plain text files
      for file in $(rg "${erlang}/lib/erlang" "$out" --files-with-matches); do
        echo "removing references to erlang in $file"
        substituteInPlace "$file" --replace "${erlang}/lib/erlang" "$out"
      done

      # 2. remove references to erlang from .beam files
      #
      # No need to do anything, because it has been handled by "deterministic" option specified
      # by ERL_COMPILER_OPTIONS.

      # 3. remove references to erlang from normal binary files
      for file in $(rg "${erlang}/lib/erlang" "$out" --files-with-matches --binary --iglob '!*.beam'); do
        echo "removing references to erlang in $file"
        # use bbe to substitute strings in binary files, because using substituteInPlace
        # on binaries will raise errors
        bbe -e "s|${erlang}/lib/erlang|$out|" -o "$file".tmp "$file"
        rm -f "$file"
        mv "$file".tmp "$file"
      done

      # References to erlang should be removed from output after above processing.
    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
  '';
})