1%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- 2%%% Copyright 2012 Erlware, LLC. All Rights Reserved. 3%%% 4%%% This file is provided to you under the Apache License, 5%%% Version 2.0 (the "License"); you may not use this file 6%%% except in compliance with the License. You may obtain 7%%% a copy of the License at 8%%% 9%%% http://www.apache.org/licenses/LICENSE-2.0 10%%% 11%%% Unless required by applicable law or agreed to in writing, 12%%% software distributed under the License is distributed on an 13%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14%%% KIND, either express or implied. See the License for the 15%%% specific language governing permissions and limitations 16%%% under the License. 17%%%--------------------------------------------------------------------------- 18%%% @author Eric Merritt <ericbmerritt@gmail.com> 19%%% @copyright (C) 2012 Erlware, LLC. 20%%% 21%%% @doc Trivial utility file to help handle common tasks 22-module(rlx_util). 23 24-export([is_sasl_gte/0, 25 parse_vsn/1, 26 parsed_vsn_lte/2, 27 get_code_paths/2, 28 release_output_dir/2, 29 list_search/2, 30 to_binary/1, 31 to_string/1, 32 is_error/1, 33 error_reason/1, 34 indent/1, 35 render/2, 36 load_file/3, 37 template_files/0, 38 sh/1]). 39 40-export([os_type/1]). 41 42-define(ONE_LEVEL_INDENT, " "). 43 44-include("rlx_log.hrl"). 45 46is_sasl_gte() -> 47 %% default check is for sasl 3.5 and above 48 %% version 3.5 of sasl has systools with changes for relx 49 %% related to `make_script', `make_tar' and the extended start script 50 is_sasl_gte({{3, 5, 0}, {[], []}}). 51 52is_sasl_gte(Version) -> 53 application:load(sasl), 54 case application:get_key(sasl, vsn) of 55 {ok, SaslVsn} -> 56 not(parsed_vsn_lt(parse_vsn(SaslVsn), Version)); 57 _ -> 58 false 59 end. 60 61%% parses a semver into a tuple {{Major, Minor, Patch}, {PreRelease, Build}} 62-spec parse_vsn(string()) -> {{integer(), integer(), integer()}, {string(), string()}}. 63parse_vsn(Vsn) -> 64 case re:run(Vsn, "^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))?" 65 "(-(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-]*))*)?" 66 "(\\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$", [{capture, [1,2,4,6,8], list}]) of 67 %% OTP application's leave out patch version when it is .0 68 %% this regex currently drops prerelease and build if the patch version is left out 69 %% so 3.11-0+meta would reutrn {{3,11,0},{[], []}} intsead of {{3,1,0},{"0","meta"}} 70 {match, [Major, Minor, [], PreRelease, Build]} -> 71 {{list_to_integer(Major), list_to_integer(Minor), 0}, {PreRelease, Build}}; 72 {match, [Major, Minor, Patch, PreRelease, Build]} -> 73 {{list_to_integer(Major), list_to_integer(Minor), list_to_integer(Patch)}, {PreRelease, Build}}; 74 _ -> 75 0 76 end. 77 78%% less than or equal to comparison for versions parsed with `parse_vsn' 79parsed_vsn_lte(VsnA, VsnB) -> 80 parsed_vsn_lt(VsnA, VsnB) orelse VsnA =:= VsnB. 81 82parsed_vsn_lt({MMPA, {AlphaA, PatchA}}, {MMPB, {AlphaB, PatchB}}) -> 83 ((MMPA < MMPB) 84 orelse 85 ((MMPA =:= MMPB) 86 andalso 87 ((AlphaB =:= [] andalso AlphaA =/= []) 88 orelse 89 ((not (AlphaA =:= [] andalso AlphaB =/= [])) 90 andalso 91 (AlphaA < AlphaB)))) 92 orelse 93 ((MMPA =:= MMPB) 94 andalso 95 (AlphaA =:= AlphaB) 96 andalso 97 ((PatchA =:= [] andalso PatchB =/= []) 98 orelse 99 PatchA < PatchB))). 100 101%% @doc Generates the correct set of code paths for the system. 102-spec get_code_paths(rlx_release:t(), file:name()) -> [filename:filename_all()]. 103get_code_paths(Release, OutDir) -> 104 LibDir = filename:join(OutDir, "lib"), 105 [filename:join([LibDir, [rlx_app_info:name(App), "-", rlx_app_info:vsn(App)], "ebin"]) || 106 App <- rlx_release:applications(Release)]. 107 108-spec release_output_dir(rlx_state:t(), rlx_release:t()) -> string(). 109release_output_dir(State, Release) -> 110 OutputDir = rlx_state:base_output_dir(State), 111 filename:join([OutputDir, 112 rlx_release:name(Release), 113 "releases", 114 rlx_release:vsn(Release)]). 115 116%% @doc ident to the level specified 117-spec indent(non_neg_integer()) -> iolist(). 118indent(Amount) when erlang:is_integer(Amount) -> 119 [?ONE_LEVEL_INDENT || _ <- lists:seq(1, Amount)]. 120 121list_search(Pred, [Hd|Tail]) -> 122 case Pred(Hd) of 123 {true, Value} -> {value, Value}; 124 true -> {value, Hd}; 125 false -> list_search(Pred, Tail) 126 end; 127list_search(Pred, []) when is_function(Pred, 1) -> 128 false. 129 130-spec to_binary(iolist() | binary()) -> binary(). 131to_binary(String) when erlang:is_list(String) -> 132 erlang:iolist_to_binary(String); 133to_binary(Atom) when erlang:is_atom(Atom) -> 134 erlang:atom_to_binary(Atom, utf8); 135to_binary(Bin) when erlang:is_binary(Bin) -> 136 Bin. 137 138-spec to_string(binary() | string() | atom()) -> string(). 139to_string(Binary) when erlang:is_binary(Binary) -> 140 erlang:binary_to_list(Binary); 141to_string(Atom) when erlang:is_atom(Atom) -> 142 erlang:atom_to_list(Atom); 143to_string(Else) when erlang:is_list(Else) -> 144 Else. 145 146%% @doc get the reason for a particular relx error 147-spec error_reason(relx:error()) -> any(). 148error_reason({error, {_, Reason}}) -> 149 Reason. 150%% @doc check to see if the value is a relx error 151-spec is_error(relx:error() | any()) -> boolean(). 152is_error({error, _}) -> 153 true; 154is_error(_) -> 155 false. 156 157-spec render(binary() | iolist(), proplists:proplist()) -> 158 {ok, binary()} | {error, render_failed}. 159render(Template, Data) when is_list(Template) -> 160 render(rlx_util:to_binary(Template), Data); 161render(Template, Data) when is_binary(Template) -> 162 case catch bbmustache:render(Template, Data, 163 [{key_type, atom}, 164 {escape_fun, fun(X) -> X end}]) of 165 Bin when is_binary(Bin) -> {ok, Bin}; 166 _ -> {error, render_failed} 167 end. 168 169load_file(Files, escript, Name) -> 170 {Name, Bin} = lists:keyfind(Name, 1, Files), 171 Bin; 172load_file(_Files, file, Name) -> 173 {ok, Bin} = file:read_file(Name), 174 Bin. 175 176template_files() -> 177 find_priv_templates() ++ escript_files(). 178 179find_priv_templates() -> 180 Files = filelib:wildcard(filename:join([code:priv_dir(relx), "templates", "*"])), 181 lists:map(fun(File) -> 182 {ok, Bin} = file:read_file(File), 183 {filename:basename(File), Bin} 184 end, Files). 185 186%% Scan the current escript for available files 187escript_files() -> 188 try 189 {ok, Files} = escript_foldl( 190 fun(Name, _, GetBin, Acc) -> 191 [{filename:basename(Name), GetBin()} | Acc] 192 end, [], filename:absname(escript:script_name())), 193 Files 194 catch 195 _:_ -> 196 [] 197 end. 198 199escript_foldl(Fun, Acc, File) -> 200 case escript:extract(File, []) of 201 {ok, [_Shebang, _Comment, _EmuArgs, Body]} -> 202 case Body of 203 {source, BeamCode} -> 204 GetInfo = fun() -> file:read_file_info(File) end, 205 GetBin = fun() -> BeamCode end, 206 {ok, Fun(".", GetInfo, GetBin, Acc)}; 207 {beam, BeamCode} -> 208 GetInfo = fun() -> file:read_file_info(File) end, 209 GetBin = fun() -> BeamCode end, 210 {ok, Fun(".", GetInfo, GetBin, Acc)}; 211 {archive, ArchiveBin} -> 212 zip:foldl(Fun, Acc, {File, ArchiveBin}) 213 end; 214 {error, _} = Error -> 215 Error 216 end. 217 218os_type(State) -> 219 case include_erts_is_win32(State) of 220 true -> {win32,nt}; 221 false -> os:type() 222 end. 223 224include_erts_is_win32(State) -> 225 case rlx_state:include_erts(State) of 226 true -> false; 227 false -> false; 228 Path -> is_win32_erts(Path) 229 end. 230 231is_win32_erts(Path) -> 232 case filelib:wildcard(filename:join([Path,"bin","erl.exe"])) of 233 [] -> 234 false; 235 _ -> 236 ?log_info("Including Erts is win32"), 237 true 238 end. 239 240sh(Command0) -> 241 Command = lists:flatten(patch_on_windows(Command0)), 242 PortSettings = [exit_status, {line, 16384}, use_stdio, stderr_to_stdout, hide, eof, binary], 243 244 Port = open_port({spawn, Command}, PortSettings), 245 try 246 case sh_loop(Port, []) of 247 {ok, Output} -> 248 Output; 249 {error, {_Rc, _Output}=Err} -> 250 error(Err) 251 end 252 after 253 port_close(Port) 254 end. 255 256sh_loop(Port, Acc) -> 257 receive 258 {Port, {data, {eol, Line}}} -> 259 sh_loop(Port, [unicode:characters_to_list(Line) ++ "\n" | Acc]); 260 {Port, {data, {noeol, Line}}} -> 261 sh_loop(Port, [unicode:characters_to_list(Line) | Acc]); 262 {Port, eof} -> 263 Data = lists:flatten(lists:reverse(Acc)), 264 receive 265 {Port, {exit_status, 0}} -> 266 {ok, Data}; 267 {Port, {exit_status, Rc}} -> 268 {error, {Rc, Data}} 269 end 270 end. 271 272%% We do the shell variable substitution ourselves on Windows and hope that the 273%% command doesn't use any other shell magic. 274patch_on_windows(Cmd) -> 275 case os:type() of 276 {win32,nt} -> 277 Cmd1 = "cmd /q /c " ++ Cmd, 278 %% Remove left-over vars 279 re:replace(Cmd1, "\\\$\\w+|\\\${\\w+}", "", 280 [global, {return, list}, unicode]); 281 _ -> 282 Cmd 283 end. 284 285