/ otp_patches / compile.escript
compile.escript
  1  #!/usr/bin/env escript
  2  %% -*- mode:erlang; erlang-indent-level: 4; indent-tabs-mode: nil -*-
  3  %%! -smp enable
  4  -mode(compile).
  5  
  6  main([]) ->
  7      Dir = filename:dirname(escript:script_name()),
  8      in_dir(fun compile_patches/0, Dir).
  9  
 10  in_dir(F, D) ->
 11      {ok, Cwd} = file:get_cwd(),
 12      try
 13          file:set_cwd(D),
 14          try F() of
 15              [] -> halt(0);
 16              Other -> fatal("~p~n", [Other])
 17          catch
 18              error:_ -> halt(1)
 19          end
 20      after
 21          file:set_cwd(Cwd)
 22      end.
 23  
 24  compile_patches() ->
 25      SubDir = determine_subdir(),
 26      Patches = filelib:wildcard(filename:join(SubDir, "*.patch")),
 27      info("Patches = ~p~n", [Patches]),
 28      [R || R <- [patch_and_compile(P) || P <- Patches], R =/= ok].
 29  
 30  -spec patch_and_compile(any()) -> ok | {error, any()}.
 31  patch_and_compile(P) ->
 32      Base = filename:basename(P, ".patch"),
 33      Mod = filename:basename(Base, ".erl"),
 34      Beam = Mod ++ ".beam",
 35      file:delete(filename:join("./ebin", filename:basename(Beam))),
 36      case code:where_is_file(Beam) of
 37          non_existing ->
 38              {error, {source_not_found, Base}};
 39          BeamFile ->
 40              case filelib:find_source(BeamFile) of
 41                  {ok, SrcFile} ->
 42                      case patch_source(P, SrcFile) of
 43                          {ok, NewF} ->
 44                              compile(NewF, SrcFile);
 45                          {error, _} = Error1 ->
 46                              info("Error patching source ~p: ~p~n", [SrcFile, Error1]),
 47                              Error1
 48                      end;
 49                  {error, _} = Error ->
 50                      info("Error finding source for ~p: ~p~n", [BeamFile, Error]),
 51                      Error
 52              end
 53      end.
 54  
 55  patch_source(Patch, Src) ->
 56      New = filename:join(cur_dir(), filename:basename(Src)),
 57      NewPath = transform_path(New),
 58      PatchPath = transform_path(filename:join(cur_dir(), Patch)),
 59      _DelRes = file:delete(New),
 60      SrcPath = transform_path(Src),
 61      Cmd = patch_cmd(PatchPath, NewPath, SrcPath),
 62      info("Executing patch cmd: ~s~n", [Cmd]),
 63      case nonl(os:cmd(Cmd)) of
 64          [] ->
 65              {ok, New};
 66          Res ->
 67              {error, {patch_error, {Res, PatchPath}}}
 68      end.
 69  
 70  nonl([$\n]) -> [];
 71  nonl([]) -> [];
 72  nonl([H|T]) -> [H|nonl(T)].
 73  
 74  compile(F, Src) ->
 75      SrcDir = filename:dirname(Src),
 76      IncDir = filename:join(filename:dirname(SrcDir), "include"),
 77      ok = filelib:ensure_dir("./ebin/foo"),
 78      case compile:file(F, [report_errors, report_warnings, debug_info,
 79                            {i, SrcDir}, {i, IncDir}, {outdir, "ebin"}]) of
 80          {ok, _Mod} ->
 81              ok;
 82          error ->
 83              {error, {compile_error, F}}
 84      end.
 85  
 86  determine_subdir() ->
 87      case filelib:wildcard("*/versions.eterm") of
 88          [] ->
 89              ".";
 90          [_|_] = Vsns ->
 91              match_versions(Vsns)
 92      end.
 93  
 94  match_versions([H|T]) ->
 95      case file:consult(H) of
 96          {ok, Apps} ->
 97              case lists:all(fun match_vsn/1, Apps) of
 98                  true ->
 99                      filename:dirname(H);
100                  false ->
101                      match_versions(T)
102              end;
103          {error, Reason} ->
104              fatal("Cannot read ~p: ~p~n", [H, Reason])
105      end;
106  match_versions([]) ->
107      ".".
108  
109  
110  match_vsn({App, Re}) ->
111      case code:lib_dir(App) of
112          {error, _} ->
113              false;
114          Dir ->
115              re_match(filename:basename(Dir), Re)
116      end.
117  
118  re_match(Str, Re) ->
119      case re:run(Str, Re) of
120          {match, _} -> true;
121          nomatch    -> false
122      end.
123  
124  
125  cur_dir() ->
126      {ok, Cwd} = file:get_cwd(),
127      Cwd.
128  
129  info(Fmt, Args) ->
130      try rebar_api:info(Fmt, Args)
131      catch
132          error:_ ->
133              io:fwrite(Fmt, Args)
134      end.
135  
136  fatal(Fmt, Args) ->
137      io:fwrite("ERROR: " ++ Fmt, Args),
138      halt(1).
139  
140  % We need to transform paths from c:/ to /c/ when running in msys on win32
141  transform_path(Path) ->
142      IsMsys = lists:member(os:getenv("MSYSTEM"), ["MSYS", "MINGW64"]),
143      HasCygpath = os:find_executable("cygpath"),
144      if
145          IsMsys and (HasCygpath =/= false) ->
146              Cmd = "cygpath \"" ++ Path ++ "\"",
147              Res = os:cmd(Cmd),
148              string:trim(Res, both, [$\r, $\n]);
149          true ->
150              Path
151      end.
152  
153  patch_cmd(PatchPath, NewPath, SrcPath) ->
154      Arch = erlang:system_info(system_architecture),
155      case string:find(Arch, "apple-darwin") of
156          nomatch ->
157              "patch -s --read-only=ignore -i \"" ++ PatchPath ++ "\" -o \"" ++ NewPath ++ "\" \"" ++ SrcPath ++ "\"";
158          _ ->
159              % The patch binary on macOS is outdated and doesn't support ignoring
160              % read-only files. However, that should not be the case on macOS
161              % anyway.
162              "patch -s -i \"" ++ PatchPath ++ "\" -o \"" ++ NewPath ++ "\" \"" ++ SrcPath ++ "\""
163      end.