rebar3/_build/default/lib/relx/src/rlx_util.erl

285 lines
9.4 KiB
Erlang

%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*-
%%% Copyright 2012 Erlware, LLC. All Rights Reserved.
%%%
%%% This file is provided to you under the Apache License,
%%% Version 2.0 (the "License"); you may not use this file
%%% except in compliance with the License. You may obtain
%%% a copy of the License at
%%%
%%% http://www.apache.org/licenses/LICENSE-2.0
%%%
%%% Unless required by applicable law or agreed to in writing,
%%% software distributed under the License is distributed on an
%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
%%% KIND, either express or implied. See the License for the
%%% specific language governing permissions and limitations
%%% under the License.
%%%---------------------------------------------------------------------------
%%% @author Eric Merritt <ericbmerritt@gmail.com>
%%% @copyright (C) 2012 Erlware, LLC.
%%%
%%% @doc Trivial utility file to help handle common tasks
-module(rlx_util).
-export([is_sasl_gte/0,
parse_vsn/1,
parsed_vsn_lte/2,
get_code_paths/2,
release_output_dir/2,
list_search/2,
to_binary/1,
to_string/1,
is_error/1,
error_reason/1,
indent/1,
render/2,
load_file/3,
template_files/0,
sh/1]).
-export([os_type/1]).
-define(ONE_LEVEL_INDENT, " ").
-include("rlx_log.hrl").
is_sasl_gte() ->
%% default check is for sasl 3.5 and above
%% version 3.5 of sasl has systools with changes for relx
%% related to `make_script', `make_tar' and the extended start script
is_sasl_gte({{3, 5, 0}, {[], []}}).
is_sasl_gte(Version) ->
application:load(sasl),
case application:get_key(sasl, vsn) of
{ok, SaslVsn} ->
not(parsed_vsn_lt(parse_vsn(SaslVsn), Version));
_ ->
false
end.
%% parses a semver into a tuple {{Major, Minor, Patch}, {PreRelease, Build}}
-spec parse_vsn(string()) -> {{integer(), integer(), integer()}, {string(), string()}}.
parse_vsn(Vsn) ->
case re:run(Vsn, "^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))?"
"(-(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*)?"
"(\\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$", [{capture, [1,2,4,6,8], list}]) of
%% OTP application's leave out patch version when it is .0
%% this regex currently drops prerelease and build if the patch version is left out
%% so 3.11-0+meta would return {{3,11,0},{[], []}} intsead of {{3,1,0},{"0","meta"}}
{match, [Major, Minor, [], PreRelease, Build]} ->
{{list_to_integer(Major), list_to_integer(Minor), 0}, {PreRelease, Build}};
{match, [Major, Minor, Patch, PreRelease, Build]} ->
{{list_to_integer(Major), list_to_integer(Minor), list_to_integer(Patch)}, {PreRelease, Build}};
_ ->
0
end.
%% less than or equal to comparison for versions parsed with `parse_vsn'
parsed_vsn_lte(VsnA, VsnB) ->
parsed_vsn_lt(VsnA, VsnB) orelse VsnA =:= VsnB.
parsed_vsn_lt({MMPA, {AlphaA, PatchA}}, {MMPB, {AlphaB, PatchB}}) ->
((MMPA < MMPB)
orelse
((MMPA =:= MMPB)
andalso
((AlphaB =:= [] andalso AlphaA =/= [])
orelse
((not (AlphaA =:= [] andalso AlphaB =/= []))
andalso
(AlphaA < AlphaB))))
orelse
((MMPA =:= MMPB)
andalso
(AlphaA =:= AlphaB)
andalso
((PatchA =:= [] andalso PatchB =/= [])
orelse
PatchA < PatchB))).
%% @doc Generates the correct set of code paths for the system.
-spec get_code_paths(rlx_release:t(), file:name()) -> [filename:filename_all()].
get_code_paths(Release, OutDir) ->
LibDir = filename:join(OutDir, "lib"),
[filename:join([LibDir, [rlx_app_info:name(App), "-", rlx_app_info:vsn(App)], "ebin"]) ||
App <- rlx_release:applications(Release)].
-spec release_output_dir(rlx_state:t(), rlx_release:t()) -> string().
release_output_dir(State, Release) ->
OutputDir = rlx_state:base_output_dir(State),
filename:join([OutputDir,
rlx_release:name(Release),
"releases",
rlx_release:vsn(Release)]).
%% @doc ident to the level specified
-spec indent(non_neg_integer()) -> iolist().
indent(Amount) when erlang:is_integer(Amount) ->
[?ONE_LEVEL_INDENT || _ <- lists:seq(1, Amount)].
list_search(Pred, [Hd|Tail]) ->
case Pred(Hd) of
{true, Value} -> {value, Value};
true -> {value, Hd};
false -> list_search(Pred, Tail)
end;
list_search(Pred, []) when is_function(Pred, 1) ->
false.
-spec to_binary(iolist() | binary()) -> binary().
to_binary(String) when erlang:is_list(String) ->
erlang:iolist_to_binary(String);
to_binary(Atom) when erlang:is_atom(Atom) ->
erlang:atom_to_binary(Atom, utf8);
to_binary(Bin) when erlang:is_binary(Bin) ->
Bin.
-spec to_string(binary() | string() | atom()) -> string().
to_string(Binary) when erlang:is_binary(Binary) ->
erlang:binary_to_list(Binary);
to_string(Atom) when erlang:is_atom(Atom) ->
erlang:atom_to_list(Atom);
to_string(Else) when erlang:is_list(Else) ->
Else.
%% @doc get the reason for a particular relx error
-spec error_reason(relx:error()) -> any().
error_reason({error, {_, Reason}}) ->
Reason.
%% @doc check to see if the value is a relx error
-spec is_error(relx:error() | any()) -> boolean().
is_error({error, _}) ->
true;
is_error(_) ->
false.
-spec render(binary() | iolist(), proplists:proplist()) ->
{ok, binary()} | {error, render_failed}.
render(Template, Data) when is_list(Template) ->
render(rlx_util:to_binary(Template), Data);
render(Template, Data) when is_binary(Template) ->
case catch bbmustache:render(Template, Data,
[{key_type, atom},
{escape_fun, fun(X) -> X end}]) of
Bin when is_binary(Bin) -> {ok, Bin};
_ -> {error, render_failed}
end.
load_file(Files, escript, Name) ->
{Name, Bin} = lists:keyfind(Name, 1, Files),
Bin;
load_file(_Files, file, Name) ->
{ok, Bin} = file:read_file(Name),
Bin.
template_files() ->
find_priv_templates() ++ escript_files().
find_priv_templates() ->
Files = filelib:wildcard(filename:join([code:priv_dir(relx), "templates", "*"])),
lists:map(fun(File) ->
{ok, Bin} = file:read_file(File),
{filename:basename(File), Bin}
end, Files).
%% Scan the current escript for available files
escript_files() ->
try
{ok, Files} = escript_foldl(
fun(Name, _, GetBin, Acc) ->
[{filename:basename(Name), GetBin()} | Acc]
end, [], filename:absname(escript:script_name())),
Files
catch
_:_ ->
[]
end.
escript_foldl(Fun, Acc, File) ->
case escript:extract(File, []) of
{ok, [_Shebang, _Comment, _EmuArgs, Body]} ->
case Body of
{source, BeamCode} ->
GetInfo = fun() -> file:read_file_info(File) end,
GetBin = fun() -> BeamCode end,
{ok, Fun(".", GetInfo, GetBin, Acc)};
{beam, BeamCode} ->
GetInfo = fun() -> file:read_file_info(File) end,
GetBin = fun() -> BeamCode end,
{ok, Fun(".", GetInfo, GetBin, Acc)};
{archive, ArchiveBin} ->
zip:foldl(Fun, Acc, {File, ArchiveBin})
end;
{error, _} = Error ->
Error
end.
os_type(State) ->
case include_erts_is_win32(State) of
true -> {win32,nt};
false -> os:type()
end.
include_erts_is_win32(State) ->
case rlx_state:include_erts(State) of
true -> false;
false -> false;
Path -> is_win32_erts(Path)
end.
is_win32_erts(Path) ->
case filelib:wildcard(filename:join([Path,"bin","erl.exe"])) of
[] ->
false;
_ ->
?log_info("Including Erts is win32"),
true
end.
sh(Command0) ->
Command = lists:flatten(patch_on_windows(Command0)),
PortSettings = [exit_status, {line, 16384}, use_stdio, stderr_to_stdout, hide, eof, binary],
Port = open_port({spawn, Command}, PortSettings),
try
case sh_loop(Port, []) of
{ok, Output} ->
Output;
{error, {_Rc, _Output}=Err} ->
error(Err)
end
after
port_close(Port)
end.
sh_loop(Port, Acc) ->
receive
{Port, {data, {eol, Line}}} ->
sh_loop(Port, [unicode:characters_to_list(Line) ++ "\n" | Acc]);
{Port, {data, {noeol, Line}}} ->
sh_loop(Port, [unicode:characters_to_list(Line) | Acc]);
{Port, eof} ->
Data = lists:flatten(lists:reverse(Acc)),
receive
{Port, {exit_status, 0}} ->
{ok, Data};
{Port, {exit_status, Rc}} ->
{error, {Rc, Data}}
end
end.
%% We do the shell variable substitution ourselves on Windows and hope that the
%% command doesn't use any other shell magic.
patch_on_windows(Cmd) ->
case os:type() of
{win32,nt} ->
Cmd1 = "cmd /q /c " ++ Cmd,
%% Remove left-over vars
re:replace(Cmd1, "\\\$\\w+|\\\${\\w+}", "",
[global, {return, list}, unicode]);
_ ->
Cmd
end.