1%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- 2%% ex: ts=4 sw=4 et 3 4-module(rebar_relx). 5 6-export([do/2, 7 opt_spec_list/0, 8 format_error/1]). 9 10-ifdef(TEST). 11-export([merge_overlays/1]). 12-endif. 13 14-include_lib("providers/include/providers.hrl"). 15-include("rebar.hrl"). 16 17%% =================================================================== 18%% Public API 19%% =================================================================== 20 21-spec do(atom(), rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. 22do(Provider, State) -> 23 {Opts, _} = rebar_state:command_parsed_args(State), 24 RelxConfig = read_relx_config(State, Opts), 25 26 ProfileString = rebar_dir:profile_dir_name(State), 27 ExtraOverlays = [{profile_string, ProfileString}], 28 29 CurrentProfiles = rebar_state:current_profiles(State), 30 RelxMode = case lists:member(prod, CurrentProfiles) of 31 true -> 32 [{mode, prod}]; 33 false -> 34 [] 35 end, 36 DefaultOutputDir = filename:join(rebar_dir:base_dir(State), ?DEFAULT_RELEASE_DIR), 37 RelxConfig1 = RelxMode ++ [output_dir(DefaultOutputDir, Opts), 38 {overlay_vars_values, ExtraOverlays}, 39 {overlay_vars, [{base_dir, rebar_dir:base_dir(State)} | overlay_vars(Opts)]} 40 | merge_overlays(RelxConfig)], 41 42 Args = [include_erts, system_libs, vm_args, sys_config], 43 RelxConfig2 = maybe_obey_command_args(RelxConfig1, Opts, Args), 44 45 {ok, RelxState_0} = rlx_config:to_state(RelxConfig2), 46 XrefIgnores = rebar_state:get(State, xref_ignores, []), 47 RelxState = rlx_state:filter_xref_warning(RelxState_0, 48 fun(Warnings) -> 49 rebar_prv_xref:filter_xref_results(undefined_function_calls, XrefIgnores, Warnings) end), 50 51 Providers = rebar_state:providers(State), 52 Cwd = rebar_state:dir(State), 53 rebar_hooks:run_project_and_app_hooks(Cwd, pre, Provider, Providers, State), 54 55 Releases = releases_to_build(Provider, Opts, RelxState), 56 57 case Provider of 58 relup -> 59 {Release, ToVsn} = 60 %% hd/1 can't fail because --all is not a valid option to relup 61 case Releases of 62 [{Rel,Vsn}|_] when is_atom(Rel) -> 63 %% This is returned if --relvsn and --relname are given 64 {Rel, Vsn}; 65 [undefined|_] -> 66 erlang:error(?PRV_ERROR(unknown_release)); 67 [Rel|_] when is_atom(Rel) -> 68 erlang:error(?PRV_ERROR(unknown_vsn)) 69 end, 70 71 UpFromVsn = proplists:get_value(upfrom, Opts, undefined), 72 73 relx:build_relup(Release, ToVsn, UpFromVsn, RelxState); 74 _ -> 75 parallel_run(Provider, Releases, all_apps(State), RelxState) 76 end, 77 78 rebar_hooks:run_project_and_app_hooks(Cwd, post, Provider, Providers, State), 79 80 {ok, State}. 81 82read_relx_config(State, Options) -> 83 ConfigFile = proplists:get_value(config, Options, []), 84 case ConfigFile of 85 "" -> 86 ConfigPath = filename:join([rebar_dir:root_dir(State), "relx.config"]), 87 case {rebar_state:get(State, relx, []), file:consult(ConfigPath)} of 88 {[], {ok, Config}} -> 89 ?DEBUG("Configuring releases with relx.config", []), 90 Config; 91 {Config, {error, enoent}} -> 92 ?DEBUG("Configuring releases the {relx, ...} entry" 93 " from rebar.config", []), 94 Config; 95 {_, {error, Reason}} -> 96 erlang:error(?PRV_ERROR({config_file, "relx.config", Reason})); 97 {RebarConfig, {ok, _RelxConfig}} -> 98 ?WARN("Found conflicting relx configs, configuring releases" 99 " with rebar.config", []), 100 RebarConfig 101 end; 102 ConfigFile -> 103 case file:consult(ConfigFile) of 104 {ok, Config} -> 105 ?DEBUG("Configuring releases with: ~ts", [ConfigFile]), 106 Config; 107 {error, Reason} -> 108 erlang:error(?PRV_ERROR({config_file, ConfigFile, Reason})) 109 end 110 end. 111 112-spec format_error(any()) -> iolist(). 113format_error(unknown_release) -> 114 "Option --relname is missing"; 115format_error(unknown_vsn) -> 116 "Option --relvsn is missing"; 117format_error(all_relup) -> 118 "Option --all can not be applied to `relup` command"; 119format_error({config_file, Filename, Error}) -> 120 io_lib:format("Failed to read config file ~ts: ~p", [Filename, Error]); 121format_error(Error) -> 122 io_lib:format("~p", [Error]). 123 124%% 125 126parallel_run(release, [Release], AllApps, RelxState) -> 127 relx:build_release(Release, AllApps, RelxState); 128parallel_run(tar, [Release], AllApps, RelxState) -> 129 relx:build_tar(Release, AllApps, RelxState); 130parallel_run(Provider, Releases, AllApps, RelxState) -> 131 rebar_parallel:queue(Releases, fun rel_worker/2, [Provider, AllApps, RelxState], fun rel_handler/2, []). 132 133rel_worker(Release, [Provider, Apps, RelxState]) -> 134 try 135 case Provider of 136 release -> 137 relx:build_release(Release, Apps, RelxState); 138 tar -> 139 relx:build_tar(Release, Apps, RelxState) 140 end 141 catch 142 error:Error -> 143 {Release, Error} 144 end. 145 146rel_handler({{Name, Vsn}, {error, {Module, Reason}}}, _Args) -> 147 ?ERROR("Error building release ~ts-~ts:~n~ts~ts", [Name, Vsn, rebar_utils:indent(1), 148 Module:format_error(Reason)]), 149 ok; 150rel_handler({{Name, Vsn}, Other}, _Args) -> 151 ?ERROR("Error building release ~ts-~ts:~nUnknown return value: ~p", [Name, Vsn, Other]), 152 ok; 153rel_handler({ok, _}, _) -> 154 ok. 155 156releases_to_build(Provider, Opts, RelxState)-> 157 case proplists:get_value(all, Opts, undefined) of 158 undefined -> 159 case proplists:get_value(relname, Opts, undefined) of 160 undefined -> 161 [undefined]; 162 R -> 163 case proplists:get_value(relvsn, Opts, undefined) of 164 undefined -> 165 [list_to_atom(R)]; 166 RelVsn -> 167 [{list_to_atom(R), RelVsn}] 168 end 169 end; 170 true when Provider =:= relup -> 171 erlang:error(?PRV_ERROR(all_relup)); 172 true -> 173 highest_unique_releases(rlx_state:configured_releases(RelxState)) 174 end. 175 176%% takes a map of relx configured releases and returns a list of the highest 177%% version for each unique release name 178-spec highest_unique_releases(rlx_state:releases()) -> [{atom(), string() | undefined}]. 179highest_unique_releases(Releases) -> 180 Unique = maps:fold(fun({Name, Vsn}, _, Acc) -> 181 update_map_if_higher(Name, Vsn, Acc) 182 end, #{}, Releases), 183 maps:to_list(Unique). 184 185update_map_if_higher(Name, Vsn, Acc) -> 186 maps:update_with(Name, fun(Vsn1) -> 187 case rlx_util:parsed_vsn_lte(rlx_util:parse_vsn(Vsn1), 188 rlx_util:parse_vsn(Vsn)) of 189 true -> 190 Vsn; 191 false -> 192 Vsn1 193 end 194 end, Vsn, Acc). 195 196%% Don't override output_dir if the user passed one on the command line 197output_dir(DefaultOutputDir, Options) -> 198 {output_dir, proplists:get_value(output_dir, Options, DefaultOutputDir)}. 199 200merge_overlays(Config) -> 201 {Overlays, Others} = 202 lists:partition(fun(C) when element(1, C) =:= overlay -> true; 203 (_) -> false 204 end, Config), 205 %% Have profile overlay entries come before others to match how profiles work elsewhere 206 NewOverlay = lists:flatmap(fun({overlay, Overlay}) -> Overlay end, lists:reverse(Overlays)), 207 [{overlay, NewOverlay} | Others]. 208 209overlay_vars(Opts) -> 210 case proplists:get_value(overlay_vars, Opts) of 211 undefined -> 212 []; 213 [] -> 214 []; 215 FileName when is_list(FileName) -> 216 [FileName] 217 end. 218 219maybe_obey_command_args(RelxConfig, Opts, Args) -> 220 lists:foldl( 221 fun(Opt, Acc) -> 222 case proplists:get_value(Opt, Opts) of 223 undefined -> 224 Acc; 225 V -> 226 lists:keystore(Opt, 1, Acc, {Opt, V}) 227 end 228 end, RelxConfig, Args). 229 230%% 231 232%% Returns a map of all apps that are part of the rebar3 project. 233%% This means the project apps and dependencies but not OTP libraries. 234-spec all_apps(rebar_state:t()) -> #{atom() => rlx_app_info:t()}. 235all_apps(State) -> 236 maps:merge(app_infos_to_relx(rebar_state:project_apps(State), project), 237 app_infos_to_relx(rebar_state:all_deps(State), dep)). 238 239%% 240 241-spec app_infos_to_relx([rlx_app_info:t()], rlx_app_info:app_type()) -> #{atom() => rlx_app_info:t()}. 242app_infos_to_relx(AppInfos, AppType) -> 243 lists:foldl(fun(AppInfo, Acc) -> 244 Acc#{binary_to_atom(rebar_app_info:name(AppInfo), utf8) 245 => app_info_to_relx(rebar_app_info:app_to_map(AppInfo), AppType)} 246 end, #{}, AppInfos). 247 248app_info_to_relx(#{name := Name, 249 vsn := Vsn, 250 applications := Applications, 251 included_applications := IncludedApplications, 252 dir := Dir, 253 link := false}, AppType) -> 254 rlx_app_info:new(Name, Vsn, Dir, Applications, IncludedApplications, AppType). 255 256-spec opt_spec_list() -> [getopt:option_spec()]. 257opt_spec_list() -> 258 [{all, undefined, "all", boolean, 259 "If true runs the command against all configured releases"}, 260 {relname, $n, "relname", string, 261 "Specify the name for the release that will be generated"}, 262 {relvsn, $v, "relvsn", string, "Specify the version for the release"}, 263 {upfrom, $u, "upfrom", string, 264 "Only valid with relup target, specify the release to upgrade from"}, 265 {output_dir, $o, "output-dir", string, 266 "The output directory for the release. This is `./` by default."}, 267 {help, $h, "help", undefined, 268 "Print usage"}, 269 {lib_dir, $l, "lib-dir", string, 270 "Additional dir that should be searched for OTP Apps"}, 271 {dev_mode, $d, "dev-mode", boolean, 272 "Symlink the applications and configuration into the release instead of copying"}, 273 {include_erts, $i, "include-erts", string, 274 "If true include a copy of erts used to build with, if a path include erts at that path. If false, do not include erts"}, 275 {override, $a, "override", string, 276 "Provide an app name and a directory to override in the form <appname>:<app directory>"}, 277 {config, $c, "config", {string, ""}, "The path to a config file"}, 278 {overlay_vars, undefined, "overlay_vars", string, "Path to a file of overlay variables"}, 279 {vm_args, undefined, "vm_args", string, "Path to a file to use for vm.args"}, 280 {sys_config, undefined, "sys_config", string, "Path to a file to use for sys.config"}, 281 {system_libs, undefined, "system_libs", string, "Boolean or path to dir of Erlang system libs"}, 282 {version, undefined, "version", undefined, "Print relx version"}, 283 {root_dir, $r, "root", string, "The project root directory"}]. 284