1-module(rlx_config). 2 3-export([to_state/1, 4 load/2, 5 format_error/1]). 6 7-include("relx.hrl"). 8-include("rlx_log.hrl"). 9 10%% TODO: list out each supported config 11-type t() :: [{atom(), term()}]. 12 13-export_type([t/0]). 14 15to_state(Config) -> 16 to_state(Config, rlx_state:new()). 17 18to_state(Config, State) -> 19 %% setup warnings_as_errors before loading the rest so we can error on 20 %% any warning during the load 21 State1 = rlx_state:warnings_as_errors(State, proplists:get_bool(warnings_as_errors, Config)), 22 lists:foldl(fun load/2, {ok, State1}, Config). 23 24-spec load(term(), {ok, rlx_state:t()} | relx:error()) -> {ok, rlx_state:t()} | relx:error(). 25load({mode, Mode}, {ok, State}) -> 26 State1 = rlx_state:mode(State, Mode), 27 Expanded = expand_mode(Mode), 28 {ok, State2} = lists:foldl(fun load/2, {ok, State1}, Expanded), 29 {ok, State2}; 30load({lib_dirs, Dirs}, {ok, State}) -> 31 LibDirs = rlx_file_utils:wildcard_paths(Dirs), 32 State1 = rlx_state:add_lib_dirs(State, LibDirs), 33 {ok, State1}; 34load({exclude_apps, ExcludeApps0}, {ok, State0}) -> 35 {ok, rlx_state:exclude_apps(State0, ExcludeApps0)}; 36load({exclude_modules, ExcludeModules0}, {ok, State0}) -> 37 {ok, rlx_state:exclude_modules(State0, ExcludeModules0)}; 38load({debug_info, DebugInfo}, {ok, State0}) -> 39 {ok, rlx_state:debug_info(State0, DebugInfo)}; 40load({overrides, Overrides0}, {ok, State0}) -> 41 {ok, rlx_state:overrides(State0, Overrides0)}; 42load({dev_mode, false}, {ok, State0}) -> 43 {ok, rlx_state:dev_mode(State0, false)}; 44load({dev_mode, true}, {ok, State0}) -> 45 {ok, State1} = load({mode, dev}, {ok, State0}), 46 {ok, rlx_state:dev_mode(State1, true)}; 47load({upfrom, UpFrom}, {ok, State0}) -> 48 {ok, rlx_state:upfrom(State0, UpFrom)}; 49load({include_src, IncludeSrc}, {ok, State0}) -> 50 {ok, rlx_state:include_src(State0, IncludeSrc)}; 51load({release, {RelName, Vsn, {extend, {RelName2, Vsn2}}}, Applications}, {ok, State0}) -> 52 NewVsn = parse_vsn(Vsn), 53 NewVsn2 = parse_vsn(Vsn2), 54 add_extended_release(RelName, NewVsn, RelName2, NewVsn2, Applications, State0); 55load({release, {RelName, Vsn, {extend, RelName2}}, Applications}, {ok, State0}) -> 56 NewVsn = parse_vsn(Vsn), 57 add_extended_release(RelName, NewVsn, RelName2, NewVsn, Applications, State0); 58load({release, {RelName, Vsn, {extend, {RelName2, Vsn2}}}, Applications, Config}, {ok, State0}) -> 59 NewVsn = parse_vsn(Vsn), 60 NewVsn2 = parse_vsn(Vsn2), 61 add_extended_release(RelName, NewVsn, RelName2, NewVsn2, Applications, Config, State0); 62load({release, {RelName, Vsn, {extend, RelName2}}, Applications, Config}, {ok, State0}) -> 63 NewVsn = parse_vsn(Vsn), 64 add_extended_release(RelName, NewVsn, RelName2, NewVsn, Applications, Config, State0); 65load({release, {RelName, Vsn}, {erts, ErtsVsn}, Applications}, {ok, State0}) -> 66 NewVsn = parse_vsn(Vsn), 67 Release0 = rlx_release:erts(rlx_release:new(RelName, NewVsn), ErtsVsn), 68 Release1 = rlx_release:goals(Release0, Applications), 69 {ok, rlx_state:add_configured_release(State0, Release1)}; 70load({release, {RelName, Vsn}, {erts, ErtsVsn}, Applications, Config}, {ok, State0}) -> 71 NewVsn = parse_vsn(Vsn), 72 Release0 = rlx_release:erts(rlx_release:new(RelName, NewVsn), ErtsVsn), 73 Release1 = rlx_release:goals(Release0, Applications), 74 Release2 = rlx_release:config(Release1, Config), 75 {ok, rlx_state:add_configured_release(State0, Release2)}; 76load({release, {RelName, Vsn}, Applications}, {ok, State0}) -> 77 NewVsn = parse_vsn(Vsn), 78 Release0 = rlx_release:new(RelName, NewVsn), 79 Release1 = rlx_release:goals(Release0, Applications), 80 {ok, rlx_state:add_configured_release(State0, Release1)}; 81load({release, {RelName, Vsn}, Applications, Config}, {ok, State0}) -> 82 NewVsn = parse_vsn(Vsn), 83 Release0 = rlx_release:new(RelName, NewVsn), 84 Release1 = rlx_release:goals(Release0, Applications), 85 Release2 = rlx_release:config(Release1, Config), 86 {ok, rlx_state:add_configured_release(State0, Release2)}; 87load({vm_args, false}, {ok, State}) -> 88 {ok, rlx_state:vm_args(State, false)}; 89load({vm_args, undefined}, {ok, State}) -> 90 {ok, rlx_state:vm_args(State, undefined)}; 91load({vm_args, VmArgs}, {ok, State}) -> 92 {ok, rlx_state:vm_args(State, filename:absname(VmArgs))}; 93load({vm_args_src, false}, {ok, State}) -> 94 {ok, rlx_state:vm_args_src(State, false)}; 95load({vm_args_src, undefined}, {ok, State}) -> 96 {ok, rlx_state:vm_args_src(State, undefined)}; 97load({vm_args_src, VmArgs}, {ok, State}) -> 98 {ok, rlx_state:vm_args_src(State, filename:absname(VmArgs))}; 99load({sys_config, false}, {ok, State}) -> 100 {ok, rlx_state:sys_config(State, false)}; 101load({sys_config, undefined}, {ok, State}) -> 102 {ok, rlx_state:sys_config(State, undefined)}; 103load({sys_config, SysConfig}, {ok, State}) -> 104 {ok, rlx_state:sys_config(State, filename:absname(SysConfig))}; 105load({sys_config_src, false}, {ok, State}) -> 106 {ok, rlx_state:sys_config_src(State, false)}; 107load({sys_config_src, undefined}, {ok, State}) -> 108 {ok, rlx_state:sys_config_src(State, undefined)}; 109load({sys_config_src, SysConfigSrc}, {ok, State}) -> 110 {ok, rlx_state:sys_config_src(State, filename:absname(SysConfigSrc))}; 111load({root_dir, Root}, {ok, State}) -> 112 {ok, rlx_state:root_dir(State, filename:absname(Root))}; 113load({output_dir, OutputDir}, {ok, State}) -> 114 {ok, rlx_state:base_output_dir(State, filename:absname(OutputDir))}; 115load({overlay_vars_values, OverlayVarsValues}, {ok, State}) -> 116 CurrentOverlayVarsValues = rlx_state:overlay_vars_values(State), 117 NewOverlayVarsValues = CurrentOverlayVarsValues ++ OverlayVarsValues, 118 {ok, rlx_state:overlay_vars_values(State, NewOverlayVarsValues)}; 119load({overlay_vars, OverlayVars}, {ok, State}) -> 120 CurrentOverlayVars = rlx_state:overlay_vars(State), 121 NewOverlayVars0 = list_of_overlay_vars_files(OverlayVars), 122 NewOverlayVars1 = CurrentOverlayVars ++ NewOverlayVars0, 123 {ok, rlx_state:overlay_vars(State, NewOverlayVars1)}; 124load({warnings_as_errors, WarningsAsErrors}, {ok, State}) -> 125 {ok, rlx_state:warnings_as_errors(State, WarningsAsErrors)}; 126load({src_tests, SrcTests}, {ok, State}) -> 127 {ok, rlx_state:src_tests(State, SrcTests)}; 128load({exref, ExRef}, {ok, State}) -> 129 {ok, rlx_state:exref(State, ExRef)}; 130load({check_for_undefined_functions, CheckForUndefinedFunctions}, {ok, State}) -> 131 {ok, rlx_state:check_for_undefined_functions(State, CheckForUndefinedFunctions)}; 132load({include_erts, IncludeErts}, {ok, State}) -> 133 {ok, rlx_state:include_erts(State, IncludeErts)}; 134load({system_libs, SystemLibs}, {ok, State}) when is_boolean(SystemLibs) -> 135 {ok, rlx_state:system_libs(State, SystemLibs)}; 136load({system_libs, SystemLibs}, {ok, State}) when is_list(SystemLibs) -> 137 case filelib:is_dir(SystemLibs) of 138 true -> 139 {ok, rlx_state:system_libs(State, SystemLibs)}; 140 false -> 141 erlang:error(?RLX_ERROR({bad_system_libs, SystemLibs})) 142 end; 143load({overlay, Overlay}, {ok, State}) -> 144 {ok, rlx_state:overlay(State, Overlay)}; 145load({extended_start_script_hooks, ExtendedStartScriptHooks}, {ok, State}) -> 146 {ok, rlx_state:extended_start_script_hooks(State, ExtendedStartScriptHooks)}; 147load({extended_start_script, ExtendedStartScript}, {ok, State}) -> 148 {ok, rlx_state:extended_start_script(State, ExtendedStartScript)}; 149load({extended_start_script_extensions, ExtendedStartScriptExtensions}, {ok, State}) -> 150 {ok, rlx_state:extended_start_script_extensions(State, ExtendedStartScriptExtensions)}; 151load({include_start_scripts_for, IncludeStartScriptsFor}, {ok, State}) when is_list(IncludeStartScriptsFor) -> 152 {ok, rlx_state:include_start_scripts_for(State, IncludeStartScriptsFor)}; 153load({generate_start_script, GenerateStartScript}, {ok, State}) -> 154 {ok, rlx_state:generate_start_script(State, GenerateStartScript)}; 155load({include_nodetool, IncludeNodetool}, {ok, State}) -> 156 {ok, rlx_state:include_nodetool(State, IncludeNodetool)}; 157load(_, Error={error, _}) -> 158 erlang:error(?RLX_ERROR(Error)); 159load(InvalidTerm, {ok, State}) -> 160 Warning = {invalid_term, InvalidTerm}, 161 case rlx_state:warnings_as_errors(State) of 162 true -> 163 erlang:error(?RLX_ERROR(Warning)); 164 false -> 165 ?log_warn(format_error(Warning)), 166 {ok, State} 167 end. 168 169format_error({bad_system_libs, SetSystemLibs}) -> 170 io_lib:format("Config value for system_libs must be a boolean or directory but found: ~p", 171 [SetSystemLibs]); 172format_error({invalid_term, InvalidTerm}) -> 173 io_lib:format("Unknown term found in relx configuration: ~p", [InvalidTerm]). 174 175%% 176 177-spec expand_mode(rlx_state:mode()) -> [term()]. 178expand_mode(dev) -> 179 [{include_src, true}, {debug_info, keep}, {include_erts, false}]; 180expand_mode(prod) -> 181 [{include_src, false}, {debug_info, strip}, {include_erts, true}, {dev_mode, false}]; 182expand_mode(minimal) -> 183 %% minimal is prod without the erts 184 [{include_src, false}, {debug_info, strip}, {include_erts, false}, {dev_mode, false}]. 185 186parse_vsn(Vsn) when Vsn =:= git ; Vsn =:= "git" -> 187 try_vsn(fun git_tag_vsn/0); 188parse_vsn({git, short}) -> 189 try_vsn(fun() -> git_ref("--short") end); 190parse_vsn({git, long}) -> 191 try_vsn(fun() -> git_ref("") end); 192parse_vsn({file, File}) -> 193 try_vsn(fun() -> 194 {ok, Vsn} = file:read_file(File), 195 rlx_util:to_string(rlx_string:trim(rlx_util:to_string(Vsn), both, "\n")) 196 end); 197parse_vsn(Vsn) when Vsn =:= semver ; Vsn =:= "semver" -> 198 try_vsn(fun git_tag_vsn/0); 199parse_vsn({semver, _}) -> 200 try_vsn(fun git_tag_vsn/0); 201parse_vsn({cmd, Command}) -> 202 try_vsn(fun() -> rlx_util:sh(Command) end); 203parse_vsn(Vsn) -> 204 Vsn. 205 206try_vsn(Fun) -> 207 try 208 Fun() 209 catch 210 error:_ -> 211 "0.0.0" 212 end. 213 214git_ref(Arg) -> 215 String = rlx_util:sh("git rev-parse " ++ Arg ++ " HEAD"), 216 Vsn = rlx_string:trim(String, both, "\n"), 217 case length(Vsn) =:= 40 orelse length(Vsn) =:= 7 of 218 true -> 219 Vsn; 220 false -> 221 %% if the result isn't exactly either 40 or 7 characters then 222 %% it must have failed 223 {ok, Dir} = file:get_cwd(), 224 ?log_warn("Getting ref of git repo failed in directory ~ts. Falling back to version 0", 225 [Dir]), 226 "0.0.0" 227 end. 228 229git_tag_vsn() -> 230 {Vsn, RawRef, RawCount} = collect_default_refcount(), 231 build_vsn_string(Vsn, RawRef, RawCount). 232 233collect_default_refcount() -> 234 %% Get the tag timestamp and minimal ref from the system. The 235 %% timestamp is really important from an ordering perspective. 236 RawRef = rlx_util:sh("git log -n 1 --pretty=format:'%h\n' "), 237 238 {Tag, Vsn} = parse_tags(), 239 RawCount = get_patch_count(Tag), 240 {Vsn, RawRef, RawCount}. 241 242get_patch_count(RawRef) -> 243 Ref = re:replace(RawRef, "\\s", "", [global]), 244 Cmd = io_lib:format("git rev-list --count ~s..HEAD", [Ref]), 245 rlx_util:sh(Cmd). 246 247build_vsn_string(Vsn, RawRef, RawCount) -> 248 %% Cleanup the tag and the Ref information. Basically leading 'v's and 249 %% whitespace needs to go away. 250 RefTag = [".ref", re:replace(RawRef, "\\s", "", [global])], 251 Count = re:replace(RawCount, "\\D", "", [global]), 252 253 %% Create the valid [semver](http://semver.org) version from the tag 254 case iolist_to_binary(Count) of 255 <<"0">> -> 256 lists:flatten(Vsn); 257 CountBin -> 258 binary_to_list(iolist_to_binary([Vsn, "+build.", CountBin, RefTag])) 259 end. 260 261parse_tags() -> 262 Tag = rlx_util:sh("git describe --abbrev=0 --tags"), 263 Vsn = rlx_string:trim(rlx_string:trim(Tag, leading, "v"), trailing, "\n"), 264 {Tag, Vsn}. 265 266 267add_extended_release(RelName, NewVsn, RelName2, NewVsn2, Applications, State0) -> 268 Release0 = rlx_release:new(RelName, NewVsn), 269 ExtendRelease = rlx_state:get_configured_release(State0, RelName2, NewVsn2), 270 Applications1 = rlx_release:goals(ExtendRelease), 271 Release1 = rlx_release:parsed_goals(Release0, merge_application_goals(Applications, Applications1)), 272 {ok, rlx_state:add_configured_release(State0, Release1)}. 273 274add_extended_release(RelName, NewVsn, RelName2, NewVsn2, Applications, Config, State0) -> 275 Release0 = rlx_release:new(RelName, NewVsn), 276 ExtendRelease = rlx_state:get_configured_release(State0, RelName2, NewVsn2), 277 Applications1 = rlx_release:goals(ExtendRelease), 278 Release1 = rlx_release:parsed_goals(Release0, merge_application_goals(Applications, Applications1)), 279 Release2 = rlx_release:config(Release1, Config), 280 {ok, rlx_state:add_configured_release(State0, Release2)}. 281 282list_of_overlay_vars_files(undefined) -> 283 []; 284list_of_overlay_vars_files([]) -> 285 []; 286list_of_overlay_vars_files([H | _]=Vars) when erlang:is_list(H) ; 287 is_tuple(H) -> 288 Vars; 289list_of_overlay_vars_files(FileName) when is_list(FileName) -> 290 [FileName]. 291 292merge_application_goals(Goals, BaseGoals) -> 293 lists:foldl(fun({Key, Goal}, Acc) -> 294 lists:keystore(Key, 1, Acc, {Key, Goal}) 295 end, BaseGoals, rlx_release:parse_goals(Goals)). 296