Packages
relx
4.2.0
4.10.0
4.9.0
4.8.0
4.7.0
4.6.0
4.5.0
4.4.0
4.3.0
4.2.0
4.1.0
4.0.2
4.0.1
4.0.0
4.0.0-rc2
3.33.0
3.32.1
3.32.0
3.31.0
3.30.0
3.29.0
3.28.0
3.27.0
3.26.0
3.25.0
3.24.4
3.24.3
3.24.2
3.24.1
3.23.1
3.23.0
3.22.4
3.22.3
retired
3.22.2
3.22.1
retired
3.22.0
3.21.1
3.21.0
3.20.0
3.19.0
3.18.0
3.17.0
3.16.0
3.15.0
3.14.0
3.13.0
3.12.0
3.11.0
3.10.0
3.9.0
3.8.0
3.7.1
3.7.0
3.6.0
3.5.0
3.4.0
3.3.2
3.3.1
3.3.0
3.2.0
3.1.0
3.0.0
2.1.1
2.1.0
Release assembler for Erlang/OTP Releases
Current section
Files
Jump to
Current section
Files
src/rlx_util.erl
%% -*- 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 reutrn {{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.