Packages
relx
3.32.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_config.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 2011 Erlware, LLC.
%%% @doc
%%% A module that provides config parsing and support to the system
%%% @end
%%%-------------------------------------------------------------------
-module(rlx_config).
%% API
-export([do/1,
format_error/1]).
-export([load_terms/2]).
-include("relx.hrl").
%%%===================================================================
%%% API
%%%===================================================================
%% @doc parse all the configs currently specified in the state,
%% populating the state as a result.
-spec do(rlx_state:t()) ->{ok, rlx_state:t()} | relx:error().
do(State) ->
case rlx_state:config_file(State) of
[] ->
search_for_dominating_config(State);
undefined ->
search_for_dominating_config(State);
ConfigFile when erlang:is_list(ConfigFile) ->
load_config(ConfigFile, State)
end.
-spec format_error(Reason::term()) -> iolist().
format_error({consult, ConfigFile, Reason}) ->
io_lib:format("Unable to read file ~s: ~s", [ConfigFile,
file:format_error(Reason)]);
format_error({invalid_term, Term}) ->
io_lib:format("Invalid term in config file: ~p", [Term]);
format_error({failed_to_parse, Goal}) ->
io_lib:format("Unable to parse goal ~s", [Goal]);
format_error({invalid_goal, Goal}) ->
io_lib:format("Invalid goal: ~p", [Goal]).
%%%===================================================================
%%% Internal Functions
%%%===================================================================
search_for_dominating_config({ok, Cwd}) ->
ConfigFile = filename:join(Cwd, "relx.config"),
case ec_file:exists(ConfigFile) of
true ->
{ok, ConfigFile};
false ->
search_for_dominating_config(parent_dir(Cwd))
end;
search_for_dominating_config({error, _}) ->
no_config;
search_for_dominating_config(State0) ->
{ok, Cwd} = file:get_cwd(),
case search_for_dominating_config({ok, Cwd}) of
{ok, Config} ->
%% we need to set the root dir on state as well
{ok, RootDir} = parent_dir(Config),
State1 = rlx_state:root_dir(State0, RootDir),
load_config(Config, rlx_state:config_file(State1, Config));
no_config ->
{ok, State0}
end.
%% @doc Given a directory returns the name of the parent directory.
-spec parent_dir(Filename::string()) ->
{ok, DirName::string()} | {error, no_parent_dir}.
parent_dir(Filename) ->
parent_dir(filename:split(Filename), []).
%% @doc Given list of directories, splits the list and returns all dirs but the
%% last as a path.
-spec parent_dir([string()], [string()]) ->
{ok, DirName::string()} | {error, no_parent_dir}.
parent_dir([_H], []) ->
{error, no_parent_dir};
parent_dir([], []) ->
{error, no_parent_dir};
parent_dir([_H], Acc) ->
{ok, filename:join(lists:reverse(Acc))};
parent_dir([H | T], Acc) ->
parent_dir(T, [H | Acc]).
-spec config_script_file(file:filename(), rlx_state:t()) -> file:filename().
config_script_file(ConfigFile, _State) ->
ConfigFile ++ ".script".
bs(Vars) ->
lists:foldl(fun({K,V}, Bs) ->
erl_eval:add_binding(K, V, Bs)
end, erl_eval:new_bindings(), Vars).
-spec apply_config_script(proplists:proplist(), file:filename()) ->
proplists:proplist().
apply_config_script(ConfigData, ConfigScriptFile) ->
{ok, Config} = file:script(ConfigScriptFile, bs([{'CONFIG', ConfigData},
{'SCRIPT', ConfigScriptFile}])),
Config.
-spec load_config(file:filename() | proplists:proplist(), rlx_state:t()) ->
{ok, rlx_state:t()} | relx:error().
load_config(ConfigFile, State) ->
{ok, CurrentCwd} = file:get_cwd(),
CliTerms = rlx_state:cli_args(State),
Config0 = case filelib:is_regular(ConfigFile) of
true ->
ok = file:set_cwd(filename:dirname(ConfigFile)),
Result = case file:consult(ConfigFile) of
{error, Reason} ->
?RLX_ERROR({consult, ConfigFile, Reason});
{ok, Terms} -> merge_configs(CliTerms, Terms)
end,
ok = file:set_cwd(CurrentCwd),
Result;
false -> merge_configs(CliTerms, ConfigFile)
end,
% we now take the merged config and try to apply a config script to it,
% get a new config as a result
case Config0 of
{error, _} = Error -> Error;
_ ->
ConfigScriptFile = config_script_file(ConfigFile, State),
Config1 = case filelib:is_regular(ConfigScriptFile) of
false -> Config0;
true -> apply_config_script(Config0, ConfigScriptFile)
end,
lists:foldl(fun load_terms/2, {ok, State}, Config1)
end.
-spec load_terms(term(), {ok, rlx_state:t()} | relx:error()) ->
{ok, rlx_state:t()} | relx:error().
load_terms({default_release, {RelName, RelVsn}}, {ok, State}) ->
NewVsn = parse_vsn(RelVsn),
{ok, rlx_state:default_configured_release(State, RelName, NewVsn)};
load_terms({paths, Paths}, {ok, State}) ->
code:add_pathsa([filename:absname(Path) || Path <- Paths]),
{ok, State};
load_terms({default_libs, DefaultLibs}, {ok, State}) ->
State2 = rlx_state:put(State,
default_libs,
DefaultLibs),
{ok, State2};
load_terms({lib_dirs, Dirs}, {ok, State}) ->
State2 =
rlx_state:add_lib_dirs(State,
[list_to_binary(Dir) || Dir <- rlx_util:wildcard_paths(Dirs)]),
{ok, State2};
load_terms({hooks, Hooks}, {ok, State0}) ->
add_hooks(Hooks, State0);
load_terms({providers, Providers0}, {ok, State0}) ->
gen_providers(Providers0, State0);
load_terms({add_providers, Providers0}, {ok, State0}) ->
gen_providers(Providers0, State0);
load_terms({skip_apps, SkipApps0}, {ok, State0}) ->
{ok, rlx_state:skip_apps(State0, SkipApps0)};
load_terms({exclude_apps, ExcludeApps0}, {ok, State0}) ->
{ok, rlx_state:exclude_apps(State0, ExcludeApps0)};
load_terms({exclude_modules, ExcludeModules0}, {ok, State0}) ->
{ok, rlx_state:exclude_modules(State0, ExcludeModules0)};
load_terms({debug_info, DebugInfo}, {ok, State0}) ->
{ok, rlx_state:debug_info(State0, DebugInfo)};
load_terms({overrides, Overrides0}, {ok, State0}) ->
{ok, rlx_state:overrides(State0, Overrides0)};
load_terms({dev_mode, DevMode}, {ok, State0}) ->
{ok, rlx_state:dev_mode(State0, DevMode)};
load_terms({goals, Goals}, {ok, State0}) ->
parse_goals(Goals, State0);
load_terms({upfrom, UpFrom}, {ok, State0}) ->
{ok, rlx_state:upfrom(State0, UpFrom)};
load_terms({include_src, IncludeSrc}, {ok, State0}) ->
{ok, rlx_state:include_src(State0, IncludeSrc)};
load_terms({release, {RelName, Vsn, {extend, RelName2}}, Applications}, {ok, State0}) ->
NewVsn = parse_vsn(Vsn),
Release0 = rlx_release:new(RelName, NewVsn),
ExtendRelease = rlx_state:get_configured_release(State0, RelName2, NewVsn),
Applications1 = rlx_release:goals(ExtendRelease),
case rlx_release:goals(Release0,
rlx_release:merge_application_goals(Applications, Applications1)) of
E={error, _} ->
E;
{ok, Release1} ->
{ok, rlx_state:add_configured_release(State0, Release1)}
end;
load_terms({release, {RelName, Vsn, {extend, RelName2}}, Applications, Config}, {ok, State0}) ->
NewVsn = parse_vsn(Vsn),
Release0 = rlx_release:new(RelName, NewVsn),
ExtendRelease = rlx_state:get_configured_release(State0, RelName2, NewVsn),
Applications1 = rlx_release:goals(ExtendRelease),
case rlx_release:goals(Release0,
rlx_release:merge_application_goals(Applications, Applications1)) of
E={error, _} ->
E;
{ok, Release1} ->
Release2 = rlx_release:config(Release1, Config),
{ok, rlx_state:add_configured_release(State0, Release2)}
end;
load_terms({release, {RelName, Vsn}, {erts, ErtsVsn}, Applications}, {ok, State}) ->
NewVsn = parse_vsn(Vsn),
Release0 = rlx_release:erts(rlx_release:new(RelName, NewVsn), ErtsVsn),
case rlx_release:goals(Release0, Applications) of
E={error, _} ->
E;
{ok, Release1} ->
{ok, rlx_state:add_configured_release(State, Release1)}
end;
load_terms({release, {RelName, Vsn}, {erts, ErtsVsn}, Applications, Config}, {ok, State}) ->
NewVsn = parse_vsn(Vsn),
Release0 = rlx_release:erts(rlx_release:new(RelName, NewVsn), ErtsVsn),
case rlx_release:goals(Release0, Applications) of
E={error, _} ->
E;
{ok, Release1} ->
Release2 = rlx_release:config(Release1, Config),
{ok, rlx_state:add_configured_release(State, Release2)}
end;
load_terms({release, {RelName, Vsn}, Applications}, {ok, State0}) ->
NewVsn = parse_vsn(Vsn),
Release0 = rlx_release:new(RelName, NewVsn),
case rlx_release:goals(Release0, Applications) of
E={error, _} ->
E;
{ok, Release1} ->
{ok, rlx_state:add_configured_release(State0, Release1)}
end;
load_terms({release, {RelName, Vsn}, Applications, Config}, {ok, State0}) ->
NewVsn = parse_vsn(Vsn),
Release0 = rlx_release:new(RelName, NewVsn),
case rlx_release:goals(Release0, Applications) of
E={error, _} ->
E;
{ok, Release1} ->
Release2 = rlx_release:config(Release1, Config),
{ok, rlx_state:add_configured_release(State0, Release2)}
end;
load_terms({vm_args, false}, {ok, State}) ->
{ok, rlx_state:vm_args(State, false)};
load_terms({vm_args, VmArgs}, {ok, State}) ->
{ok, rlx_state:vm_args(State, filename:absname(VmArgs))};
load_terms({vm_args_src, VmArgs}, {ok, State}) ->
{ok, rlx_state:vm_args_src(State, filename:absname(VmArgs))};
load_terms({sys_config, false}, {ok, State}) ->
{ok, rlx_state:sys_config(State, false)};
load_terms({sys_config, SysConfig}, {ok, State}) ->
{ok, rlx_state:sys_config(State, filename:absname(SysConfig))};
load_terms({sys_config_src, SysConfigSrc}, {ok, State}) ->
{ok, rlx_state:sys_config_src(State, filename:absname(SysConfigSrc))};
load_terms({root_dir, Root}, {ok, State}) ->
{ok, rlx_state:root_dir(State, filename:absname(Root))};
load_terms({output_dir, OutputDir}, {ok, State}) ->
{ok, rlx_state:base_output_dir(State, filename:absname(OutputDir))};
load_terms({overlay_vars, OverlayVars}, {ok, State}) ->
CurrentOverlayVars = rlx_state:get(State, overlay_vars),
NewOverlayVars0 = list_of_overlay_vars_files(OverlayVars),
NewOverlayVars1 = CurrentOverlayVars ++ NewOverlayVars0,
{ok, rlx_state:put(State, overlay_vars, NewOverlayVars1)};
load_terms({warnings_as_errors, WarningsAsErrors}, {ok, State}) ->
{ok, rlx_state:warnings_as_errors(State, WarningsAsErrors)};
load_terms({Name, Value}, {ok, State})
when erlang:is_atom(Name) ->
{ok, rlx_state:put(State, Name, Value)};
load_terms(_, Error={error, _}) ->
Error;
load_terms(InvalidTerm, _) ->
?RLX_ERROR({invalid_term, InvalidTerm}).
-spec gen_providers([module()], rlx_state:t()) ->
{ok, rlx_state:t()} | relx:error().
gen_providers(Providers, State) ->
lists:foldl(fun(ProviderName, {ok, State1}) ->
providers:new(ProviderName, State1);
(_, E={_, {error, _}}) ->
E
end, {ok, State}, Providers).
add_hooks(Hooks, State) ->
{ok, lists:foldl(fun({pre, Target, Hook}, StateAcc) ->
rlx_state:prepend_hook(StateAcc, Target, Hook);
({post, Target, Hook}, StateAcc) ->
rlx_state:append_hook(StateAcc, Target, Hook)
end, State, Hooks)}.
parse_goals(Goals0, State) ->
{Goals, Error} = lists:mapfoldl(fun
(Goal, ok) when is_list(Goal); is_binary(Goal) ->
case rlx_goal:parse(Goal) of
{ok, Constraint} ->
{Constraint, ok};
{fail, _} ->
{[], ?RLX_ERROR({failed_to_parse, Goal})}
end;
(Goal, ok) when is_tuple(Goal); is_atom(Goal) ->
case rlx_depsolver:is_valid_raw_constraint(Goal) of
true ->
{Goal, ok};
false ->
{[], ?RLX_ERROR({invalid_goal, Goal})}
end;
(_, Err = {error, _}) ->
{[], Err};
(Goal, _) ->
{[], ?RLX_ERROR({invalid_goal, Goal})}
end, ok, Goals0),
case Error of
ok ->
{ok, rlx_state:goals(State, Goals)};
_ ->
Error
end.
list_of_overlay_vars_files(undefined) ->
[];
list_of_overlay_vars_files([]) ->
[];
list_of_overlay_vars_files([H | _]=Vars) when erlang:is_list(H) ;
is_tuple(H) ->
Vars;
list_of_overlay_vars_files(FileName) when is_list(FileName) ->
[FileName].
merge_configs([], ConfigTerms) ->
ConfigTerms;
merge_configs([{_Key, undefined} | CliTerms], ConfigTerms) ->
merge_configs(CliTerms, ConfigTerms);
merge_configs([{_Key, []} | CliTerms], ConfigTerms) ->
merge_configs(CliTerms, ConfigTerms);
merge_configs([{Key, Value} | CliTerms], ConfigTerms) ->
case Key of
X when X =:= lib_dirs
; X =:= goals
; X =:= overrides ->
case lists:keyfind(Key, 1, ConfigTerms) of
{Key, Value2} ->
MergedValue = lists:umerge([Value, Value2]),
merge_configs(CliTerms, lists:keyreplace(Key, 1, ConfigTerms, {Key, MergedValue}));
false ->
merge_configs(CliTerms, ConfigTerms++[{Key, Value}])
end;
overlay_vars ->
case lists:keyfind(overlay_vars, 1, ConfigTerms) of
{_, [H | _] = Vars} when is_list(H) ;
is_tuple(H) ->
MergedValue = Vars ++ Value,
merge_configs(CliTerms, lists:keyreplace(overlay_vars, 1, ConfigTerms, {Key, MergedValue}));
{_, Vars} when is_list(Vars) ->
MergedValue = [Vars | Value],
merge_configs(CliTerms, lists:keyreplace(overlay_vars, 1, ConfigTerms, {Key, MergedValue}));
false ->
merge_configs(CliTerms, ConfigTerms++[{Key, Value}])
end;
default_release when Value =:= {undefined, undefined} ->
%% No release specified in cli. Prevent overwriting default_release in ConfigTerms.
merge_configs(CliTerms, lists:keymerge(1, ConfigTerms, [{Key, Value}]));
_ ->
merge_configs(CliTerms, lists:reverse(lists:keystore(Key, 1, lists:reverse(ConfigTerms), {Key, Value})))
end.
parse_vsn(Vsn) when Vsn =:= git ; Vsn =:= "git" ->
{ok, V} = ec_git_vsn:vsn(ec_git_vsn:new()),
V;
parse_vsn({git, short}) ->
git_ref("--short");
parse_vsn({git, long}) ->
git_ref("");
parse_vsn({file, File}) ->
{ok, Vsn} = file:read_file(File),
binary_to_list(rlx_string:trim(Vsn, both, "\n"));
parse_vsn(Vsn) when Vsn =:= semver ; Vsn =:= "semver" ->
{ok, V} = ec_git_vsn:vsn(ec_git_vsn:new()),
V;
parse_vsn({semver, Data}) ->
{ok, V} = ec_git_vsn:vsn(Data),
V;
parse_vsn({cmd, Command}) ->
V = os:cmd(Command),
V;
parse_vsn(Vsn) ->
Vsn.
git_ref(Arg) ->
case os:cmd("git rev-parse " ++ Arg ++ " HEAD") of
String ->
Vsn = rlx_string:trim(String, both, "\n"),
case length(Vsn) =:= 40 orelse length(Vsn) =:= 7 of
true ->
Vsn;
false ->
%% if the result isn't exactly either 40 or 7 characters then
%% it must have failed
{ok, Dir} = file:get_cwd(),
ec_cmd_log:warn("Getting ref of git repo failed in ~ts. "
"Falling back to version 0", [Dir]),
{plain, "0"}
end
end.