aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/relx.app.src18
-rw-r--r--src/relx.erl3
-rw-r--r--src/rlx_cmd_args.erl13
-rw-r--r--src/rlx_config.erl80
-rw-r--r--src/rlx_depsolver.erl85
-rw-r--r--src/rlx_depsolver_culprit.erl2
-rw-r--r--src/rlx_prv_archive.erl3
-rw-r--r--src/rlx_prv_assembler.erl4
-rw-r--r--src/rlx_prv_overlay.erl21
-rw-r--r--src/rlx_prv_release.erl8
-rw-r--r--src/rlx_release.erl87
-rw-r--r--src/rlx_state.erl6
-rw-r--r--src/rlx_string.erl10
-rw-r--r--src/rlx_util.erl120
14 files changed, 290 insertions, 170 deletions
diff --git a/src/relx.app.src b/src/relx.app.src
index c8915d5..c845982 100644
--- a/src/relx.app.src
+++ b/src/relx.app.src
@@ -1,11 +1,9 @@
{application,relx,
- [{description,"Release assembler for Erlang/OTP Releases"},
- {vsn,"git"},
- {modules,[]},
- {registered,[]},
- {applications,[kernel,stdlib,getopt,erlware_commons,bbmustache,
- providers]},
- {maintainers,["Eric Merritt","Tristan Sloughter",
- "Jordan Wilberding"]},
- {licenses,["Apache"]},
- {links,[{"Github","https://github.com/erlware/relx"}]}]}.
+ [{description,"Release assembler for Erlang/OTP Releases"},
+ {vsn,"git"},
+ {modules,[]},
+ {registered,[]},
+ {applications,[kernel,stdlib,getopt,erlware_commons,bbmustache,
+ providers]},
+ {licenses,["Apache"]},
+ {links,[{"Github","https://github.com/erlware/relx"}]}]}.
diff --git a/src/relx.erl b/src/relx.erl
index 8027fd4..b5c3ec5 100644
--- a/src/relx.erl
+++ b/src/relx.erl
@@ -214,7 +214,8 @@ opt_spec_list() ->
{sys_config, undefined, "sys_config", string, "Path to a file to use for sys.config"},
{system_libs, undefined, "system_libs", string, "Path to dir of Erlang system libs"},
{version, undefined, "version", undefined, "Print relx version"},
- {root_dir, $r, "root", string, "The project root directory"}].
+ {root_dir, $r, "root", string, "The project root directory"},
+ {provider, undefined, "provider", atom, "Specify an additional relx provider"}].
-spec format_error(Reason::term()) -> string().
format_error({invalid_return_value, Provider, Value}) ->
diff --git a/src/rlx_cmd_args.erl b/src/rlx_cmd_args.erl
index b20344c..4f5e9da 100644
--- a/src/rlx_cmd_args.erl
+++ b/src/rlx_cmd_args.erl
@@ -285,6 +285,19 @@ create(include_erts, Opts) ->
create(warnings_as_errors, Opts) ->
WarningsAsErrors = proplists:get_value(warnings_as_errors, Opts, false),
{warnings_as_errors, WarningsAsErrors};
+create(provider, Opts) ->
+ case proplists:get_all_values(provider, Opts) of
+ [] ->
+ [];
+ Providers ->
+ {add_providers, Providers}
+ end;
+create(add_providers, Opts) ->
+ Providers = proplists:get_value(add_providers, Opts, []),
+ {add_providers, Providers};
+create(providers, Opts) ->
+ Providers = proplists:get_value(providers, Opts, []),
+ {providers, Providers};
create(_, _) ->
[].
diff --git a/src/rlx_config.erl b/src/rlx_config.erl
index ee58db5..90cfe7c 100644
--- a/src/rlx_config.erl
+++ b/src/rlx_config.erl
@@ -53,7 +53,11 @@ 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]).
+ 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
@@ -182,7 +186,7 @@ load_terms({overrides, Overrides0}, {ok, State0}) ->
load_terms({dev_mode, DevMode}, {ok, State0}) ->
{ok, rlx_state:dev_mode(State0, DevMode)};
load_terms({goals, Goals}, {ok, State0}) ->
- {ok, rlx_state:goals(State0, Goals)};
+ parse_goals(Goals, State0);
load_terms({upfrom, UpFrom}, {ok, State0}) ->
{ok, rlx_state:upfrom(State0, UpFrom)};
load_terms({include_src, IncludeSrc}, {ok, State0}) ->
@@ -193,8 +197,7 @@ load_terms({release, {RelName, Vsn, {extend, RelName2}}, Applications}, {ok, Sta
ExtendRelease = rlx_state:get_configured_release(State0, RelName2, NewVsn),
Applications1 = rlx_release:goals(ExtendRelease),
case rlx_release:goals(Release0,
- lists:umerge(lists:usort(Applications),
- lists:usort(Applications1))) of
+ rlx_release:merge_application_goals(Applications, Applications1)) of
E={error, _} ->
E;
{ok, Release1} ->
@@ -206,8 +209,7 @@ load_terms({release, {RelName, Vsn, {extend, RelName2}}, Applications, Config},
ExtendRelease = rlx_state:get_configured_release(State0, RelName2, NewVsn),
Applications1 = rlx_release:goals(ExtendRelease),
case rlx_release:goals(Release0,
- lists:umerge(lists:usort(Applications),
- lists:usort(Applications1))) of
+ rlx_release:merge_application_goals(Applications, Applications1)) of
E={error, _} ->
E;
{ok, Release1} ->
@@ -299,12 +301,41 @@ add_hooks(Hooks, State) ->
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 | _]=FileNames) when erlang:is_list(H) ->
- FileNames;
+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].
@@ -328,7 +359,8 @@ merge_configs([{Key, Value} | CliTerms], ConfigTerms) ->
end;
overlay_vars ->
case lists:keyfind(overlay_vars, 1, ConfigTerms) of
- {_, [H | _] = Vars} when is_list(H) ->
+ {_, [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) ->
@@ -337,10 +369,23 @@ merge_configs([{Key, Value} | CliTerms], ConfigTerms) ->
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;
@@ -352,3 +397,20 @@ parse_vsn({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.
diff --git a/src/rlx_depsolver.erl b/src/rlx_depsolver.erl
index 8a0f632..0bde8c7 100644
--- a/src/rlx_depsolver.erl
+++ b/src/rlx_depsolver.erl
@@ -1,5 +1,5 @@
%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*-
-%% ex: ts=4 sx=4 et
+%% ex: ts=4 sw=4 et
%%
%% Copyright 2012 Opscode, Inc. All Rights Reserved.
%%
@@ -88,7 +88,7 @@
add_package_version/3,
add_package_version/4,
parse_version/1,
- is_valid_constraint/1,
+ is_valid_raw_constraint/1,
filter_packages/2]).
%% Internally Exported API. This should *not* be used outside of the rlx_depsolver
@@ -132,7 +132,7 @@
-type raw_constraint() :: pkg_name()
| {pkg_name(), raw_vsn()}
| {pkg_name(), raw_vsn(), constraint_op()}
- | {pkg_name(), raw_vsn(), vsn(), between}.
+ | {pkg_name(), raw_vsn(), raw_vsn(), between}.
-type constraint() :: pkg_name()
| {pkg_name(), vsn()}
@@ -272,39 +272,14 @@ parse_version(Vsn)
when erlang:is_tuple(Vsn) ; erlang:is_atom(Vsn) ->
Vsn.
-%% @doc check that a specified constraint is a valid constraint.
--spec is_valid_constraint(constraint()) -> boolean().
-is_valid_constraint(Pkg) when is_atom(Pkg) orelse is_binary(Pkg) ->
- true;
-is_valid_constraint({_Pkg, Vsn}) when is_tuple(Vsn) ->
- true;
-is_valid_constraint({_Pkg, Vsn, '='}) when is_tuple(Vsn) ->
- true;
-is_valid_constraint({_Pkg, _LVsn, gte}) ->
- true;
-is_valid_constraint({_Pkg, _LVsn, '>='}) ->
- true;
-is_valid_constraint({_Pkg, _LVsn, lte}) ->
- true;
-is_valid_constraint({_Pkg, _LVsn, '<='}) ->
- true;
-is_valid_constraint({_Pkg, _LVsn, gt}) ->
- true;
-is_valid_constraint({_Pkg, _LVsn, '>'}) ->
- true;
-is_valid_constraint({_Pkg, _LVsn, lt}) ->
- true;
-is_valid_constraint({_Pkg, _LVsn, '<'}) ->
- true;
-is_valid_constraint({_Pkg, _LVsn, pes}) ->
- true;
-is_valid_constraint({_Pkg, _LVsn, '~>'}) ->
- true;
-is_valid_constraint({_Pkg, _LVsn1, _LVsn2, between}) ->
- true;
-is_valid_constraint(_InvalidConstraint) ->
- false.
-
+-spec is_valid_raw_constraint(raw_constraint()) -> true; (any()) -> false.
+is_valid_raw_constraint(RawConstraint) ->
+ try fix_con(RawConstraint)
+ of
+ Constraint -> is_valid_constraint(Constraint)
+ catch
+ error:function_clause -> false
+ end.
%% @doc given a list of package name version pairs, and a list of constraints
%% return every member of that list that matches all constraints.
@@ -357,9 +332,9 @@ format_version(Version) ->
rlx_depsolver_culprit:format_version(Version).
%% @doc A formatted constraint tuple
--spec format_constraint(constraint()) -> iolist().
-format_constraint(Constraint) ->
- rlx_depsolver_culprit:format_constraint(Constraint).
+-spec format_constraint(raw_constraint()) -> iolist().
+format_constraint(RawConstraint) ->
+ rlx_depsolver_culprit:format_constraint(fix_con(RawConstraint)).
%%====================================================================
%% Internal Functions
@@ -470,6 +445,38 @@ dep_pkg({Pkg, _Vsn1, _Vsn2, _}) ->
dep_pkg(Pkg) when is_atom(Pkg) orelse is_binary(Pkg) ->
Pkg.
+-spec is_valid_constraint(constraint()) -> boolean().
+is_valid_constraint(Pkg) when is_atom(Pkg) orelse is_binary(Pkg) ->
+ true;
+is_valid_constraint({_Pkg, Vsn}) when is_tuple(Vsn) ->
+ true;
+is_valid_constraint({_Pkg, Vsn, '='}) when is_tuple(Vsn) ->
+ true;
+is_valid_constraint({_Pkg, _LVsn, gte}) ->
+ true;
+is_valid_constraint({_Pkg, _LVsn, '>='}) ->
+ true;
+is_valid_constraint({_Pkg, _LVsn, lte}) ->
+ true;
+is_valid_constraint({_Pkg, _LVsn, '<='}) ->
+ true;
+is_valid_constraint({_Pkg, _LVsn, gt}) ->
+ true;
+is_valid_constraint({_Pkg, _LVsn, '>'}) ->
+ true;
+is_valid_constraint({_Pkg, _LVsn, lt}) ->
+ true;
+is_valid_constraint({_Pkg, _LVsn, '<'}) ->
+ true;
+is_valid_constraint({_Pkg, _LVsn, pes}) ->
+ true;
+is_valid_constraint({_Pkg, _LVsn, '~>'}) ->
+ true;
+is_valid_constraint({_Pkg, _LVsn1, _LVsn2, between}) ->
+ true;
+is_valid_constraint(_InvalidConstraint) ->
+ false.
+
-spec add_constraint(pkg_name(), vsn(), [constraint()],constraint()) -> ordered_constraints().
add_constraint(SrcPkg, SrcVsn, PkgsConstraints, PkgConstraint) ->
case is_valid_constraint(PkgConstraint) of
diff --git a/src/rlx_depsolver_culprit.erl b/src/rlx_depsolver_culprit.erl
index cf6dcb2..6368d24 100644
--- a/src/rlx_depsolver_culprit.erl
+++ b/src/rlx_depsolver_culprit.erl
@@ -1,5 +1,5 @@
%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*-
-%% ex: ts=4 sx=4 et
+%% ex: ts=4 sw=4 et
%%
%% @author Eric Merritt <[email protected]>
%%
diff --git a/src/rlx_prv_archive.erl b/src/rlx_prv_archive.erl
index e1735d1..8fd03c1 100644
--- a/src/rlx_prv_archive.erl
+++ b/src/rlx_prv_archive.erl
@@ -141,9 +141,10 @@ update_tar(State, TempDir, OutputDir, Name, Vsn, ErtsVersion) ->
config_files(Vsn, OutputDir) ->
VMArgs = {filename:join(["releases", Vsn, "vm.args"]), filename:join([OutputDir, "releases", Vsn, "vm.args"])},
+ VMArgsSrc = {filename:join(["releases", Vsn, "vm.args.src"]), filename:join([OutputDir, "releases", Vsn, "vm.args.src"])},
VMArgsOrig = {filename:join(["releases", Vsn, "vm.args.orig"]), filename:join([OutputDir, "releases", Vsn, "vm.args.orig"])},
SysConfigOrig = {filename:join(["releases", Vsn, "sys.config.orig"]), filename:join([OutputDir, "releases", Vsn, "sys.config.orig"])},
- [{NameInArchive, Filename} || {NameInArchive, Filename} <- [VMArgs, VMArgsOrig, SysConfigOrig], filelib:is_file(Filename)].
+ [{NameInArchive, Filename} || {NameInArchive, Filename} <- [VMArgsSrc, VMArgs, VMArgsOrig, SysConfigOrig], filelib:is_file(Filename)].
overlay_files(_, undefined, _) ->
diff --git a/src/rlx_prv_assembler.erl b/src/rlx_prv_assembler.erl
index cb5bbed..4f9a41e 100644
--- a/src/rlx_prv_assembler.erl
+++ b/src/rlx_prv_assembler.erl
@@ -433,13 +433,13 @@ write_bin_file(State, Release, OutputDir, RelDir) ->
win32 -> rlx_string:concat(VsnRel, ".cmd")
end,
ok = file:write_file(VsnRelStartFile, StartFile),
- ok = file:change_mode(VsnRelStartFile, 8#777),
+ ok = file:change_mode(VsnRelStartFile, 8#755),
BareRelStartFile = case OsFamily of
unix -> BareRel;
win32 -> rlx_string:concat(BareRel, ".cmd")
end,
ok = file:write_file(BareRelStartFile, StartFile),
- ok = file:change_mode(BareRelStartFile, 8#777)
+ ok = file:change_mode(BareRelStartFile, 8#755)
end,
ReleasesDir = filename:join(OutputDir, "releases"),
generate_start_erl_data_file(Release, ReleasesDir),
diff --git a/src/rlx_prv_overlay.erl b/src/rlx_prv_overlay.erl
index 92ff69f..2b91c0b 100644
--- a/src/rlx_prv_overlay.erl
+++ b/src/rlx_prv_overlay.erl
@@ -142,7 +142,8 @@ get_overlay_vars_from_file(State, OverlayVars) ->
OverlayVars;
[] ->
OverlayVars;
- [H | _]=FileNames when is_list(H) ->
+ [H | _]=FileNames when is_list(H) ;
+ is_tuple(H) ->
read_overlay_vars(State, OverlayVars, FileNames);
FileName when is_list(FileName) ->
read_overlay_vars(State, OverlayVars, [FileName])
@@ -178,30 +179,24 @@ check_overlay_inclusion(_State, _RelativeRoot, [], Terms) ->
proplists:proplist().
merge_overlay_vars(State, FileNames) ->
RelativeRoot = get_relative_root(State),
- lists:foldl(fun(FileName, Acc) ->
- RelativePath = filename:join(RelativeRoot, erlang:iolist_to_binary(FileName)),
+ lists:foldl(fun(FileName, Acc) when is_list(FileName) ->
+ RelativePath = filename:join(RelativeRoot, iolist_to_binary(FileName)),
case file:consult(RelativePath) of
- %% {ok, [Terms]} ->
- %% lists:ukeymerge(1, lists:ukeysort(1, Terms), Acc);
- %% % the location of the included overlay files will be relative
- %% %% to the current one being read
- %% %% OverlayRelativeRoot = filename:dirname(FileName),
- %% %% NewTerms = check_overlay_inclusion(State, OverlayRelativeRoot, Terms),
-
- %% %% lists:ukeymerge(1, lists:ukeysort(1, NewTerms), Acc);
{ok, Terms} ->
%% the location of the included overlay files will be relative
%% to the current one being read
OverlayRelativeRoot = filename:dirname(FileName),
NewTerms = check_overlay_inclusion(State, OverlayRelativeRoot, Terms),
lists:foldl(fun(NewTerm, A) ->
- lists:keystore(element(1, NewTerm), 1, A, NewTerm)
+ lists:keystore(element(1, NewTerm), 1, A, NewTerm)
end, Acc, NewTerms);
{error, Reason} ->
ec_cmd_log:warn(rlx_state:log(State),
format_error({unable_to_read_varsfile, FileName, Reason})),
Acc
- end
+ end;
+ (Var, Acc) ->
+ lists:keystore(element(1, Var), 1, Acc, Var)
end, [], FileNames).
-spec render_overlay_vars(proplists:proplist(), proplists:proplist(),
diff --git a/src/rlx_prv_release.erl b/src/rlx_prv_release.erl
index 8de1a51..9be190e 100644
--- a/src/rlx_prv_release.erl
+++ b/src/rlx_prv_release.erl
@@ -168,12 +168,14 @@ solve_release(State0, DepGraph, RelName, RelVsn) ->
%% get per release config values and override the State with them
Config = rlx_release:config(Release),
{ok, State1} = lists:foldl(fun rlx_config:load_terms/2, {ok, State0}, Config),
- Goals = rlx_release:goals(Release),
- case Goals of
+ Goals = rlx_release:constraints(Release),
+ GlobalGoals = rlx_state:goals(State1),
+ MergedGoals = rlx_release:merge_application_goals(Goals, GlobalGoals),
+ case MergedGoals of
[] ->
?RLX_ERROR(no_goals_specified);
_ ->
- case rlx_depsolver:solve(DepGraph, Goals) of
+ case rlx_depsolver:solve(DepGraph, MergedGoals) of
{ok, Pkgs} ->
set_resolved(State1, Release, Pkgs);
{error, Error} ->
diff --git a/src/rlx_release.erl b/src/rlx_release.erl
index a183043..e22fdd4 100644
--- a/src/rlx_release.erl
+++ b/src/rlx_release.erl
@@ -30,6 +30,8 @@
erts/1,
goals/2,
goals/1,
+ constraints/1,
+ merge_application_goals/2,
name/1,
vsn/1,
realize/3,
@@ -138,7 +140,11 @@ goals(Release, Goals0) ->
{ok, Release}, Goals0).
-spec goals(t()) -> [application_goal()].
-goals(#release_t{goals=Goals}) ->
+goals(#release_t{goals=Goals, annotations=Annots}) ->
+ [application_goal(Goal, Annots) || Goal <- Goals].
+
+-spec constraints(t()) -> [rlx_depsolver:raw_constraint()].
+constraints(#release_t{goals=Goals}) ->
Goals.
-spec realize(t(), [{app_name(), app_vsn()}], [rlx_app_info:t()]) ->
@@ -364,6 +370,14 @@ parse_goal0({Constraint0, Annots, Incls}, {ok, Release})
Error ->
Error
end;
+parse_goal0({Constraint0, Incls}, {ok, Release})
+ when erlang:is_list(Incls), Incls == [] orelse is_atom(hd(Incls)) ->
+ case parse_constraint(Constraint0) of
+ {ok, Constraint1} ->
+ parse_goal1(Release, Constraint1, {void, Incls});
+ Error ->
+ Error
+ end;
parse_goal0(Constraint0, {ok, Release}) ->
case parse_constraint(Constraint0) of
{ok, Constraint1} ->
@@ -400,12 +414,11 @@ parse_constraint(Constraint0)
parse_constraint(Constraint0)
when erlang:is_tuple(Constraint0);
erlang:is_atom(Constraint0) ->
- Constraint1 = parse_version(Constraint0),
- case rlx_depsolver:is_valid_constraint(Constraint1) of
+ case rlx_depsolver:is_valid_raw_constraint(Constraint0) of
false ->
?RLX_ERROR({invalid_constraint, 2, Constraint0});
true ->
- {ok, Constraint1}
+ {ok, Constraint0}
end;
parse_constraint(Constraint) ->
?RLX_ERROR({invalid_constraint, 3, Constraint}).
@@ -423,22 +436,44 @@ get_app_name({AppName, _, _, _}) when erlang:is_atom(AppName) ->
get_app_name(V) ->
?RLX_ERROR({invalid_constraint, 4, V}).
--spec parse_version(rlx_depsolver:raw_constraint()) ->
- rlx_depsolver:constraint().
-parse_version({AppName, Version})
- when erlang:is_binary(Version);
- erlang:is_list(Version) ->
- {AppName, rlx_depsolver:parse_version(Version)};
-parse_version({AppName, Version, Constraint})
- when erlang:is_binary(Version);
- erlang:is_list(Version) ->
- {AppName, rlx_depsolver:parse_version(Version), Constraint};
-parse_version({AppName, Version, Constraint0, Constraint1})
- when erlang:is_binary(Version);
- erlang:is_list(Version) ->
- {AppName, rlx_depsolver:parse_version(Version), Constraint1, Constraint0};
-parse_version(Constraint) ->
- Constraint.
+-spec get_goal_app_name(application_goal()) -> atom() | relx:error().
+get_goal_app_name({Constraint, Annots})
+ when Annots =:= permanent;
+ Annots =:= transient;
+ Annots =:= temporary;
+ Annots =:= load;
+ Annots =:= none ->
+ get_app_name(Constraint);
+get_goal_app_name({Constraint, Annots, Incls})
+ when (Annots =:= permanent orelse
+ Annots =:= transient orelse
+ Annots =:= temporary orelse
+ Annots =:= load orelse
+ Annots =:= none),
+ erlang:is_list(Incls) ->
+ get_app_name(Constraint);
+get_goal_app_name({Constraint, Incls})
+ when erlang:is_list(Incls), Incls == [] orelse is_atom(hd(Incls)) ->
+ get_app_name(Constraint);
+get_goal_app_name(Constraint) ->
+ get_app_name(Constraint).
+
+-spec application_goal(rlx_depsolver:raw_constraint(), annotations()) -> application_goal().
+application_goal(Constraint, Annots) ->
+ AppName = get_app_name(Constraint),
+ try ec_dictionary:get(AppName, Annots) of
+ {void, void} ->
+ Constraint;
+ {void, Incls} ->
+ {Constraint, Incls};
+ {Type, void} ->
+ {Constraint, Type};
+ {Type, Incls} ->
+ {Constraint, Type, Incls}
+ catch
+ throw:not_found ->
+ Constraint
+ end.
to_atom(RelName)
when erlang:is_list(RelName) ->
@@ -446,3 +481,15 @@ to_atom(RelName)
to_atom(Else)
when erlang:is_atom(Else) ->
Else.
+
+-spec merge_application_goals([application_goal()], [application_goal()]) -> [application_goal()].
+merge_application_goals(Goals, BaseGoals) ->
+ Goals ++ lists:foldl(fun filter_goal_by_name/2, BaseGoals, Goals).
+
+filter_goal_by_name(AppGoal, GoalList) when is_list(GoalList) ->
+ case get_goal_app_name(AppGoal) of
+ AppName when is_atom(AppName) ->
+ lists:filter(fun(Goal) -> get_goal_app_name(Goal) /= AppName end, GoalList);
+ _Error ->
+ GoalList
+ end.
diff --git a/src/rlx_state.erl b/src/rlx_state.erl
index 5488a41..cab55f6 100644
--- a/src/rlx_state.erl
+++ b/src/rlx_state.erl
@@ -105,7 +105,7 @@
lib_dirs=[] :: [file:name()],
config_file=[] :: file:filename() | undefined,
cli_args=[] :: proplists:proplist(),
- goals=[] :: [rlx_depsolver:constraint()],
+ goals=[] :: [rlx_depsolver:raw_constraint()],
providers=[] :: [providers:t()],
available_apps=[] :: [rlx_app_info:t()],
default_configured_release :: {rlx_release:name() | undefined, rlx_release:vsn() |undefined} | undefined,
@@ -254,11 +254,11 @@ lib_dirs(#state_t{lib_dirs=LibDir}) ->
add_lib_dirs(State=#state_t{lib_dirs=LibDir}, Dirs) ->
State#state_t{lib_dirs=lists:umerge(lists:sort(LibDir), lists:sort(Dirs))}.
--spec goals(t()) -> [rlx_depsolver:constraint()].
+-spec goals(t()) -> [rlx_depsolver:raw_constraint()].
goals(#state_t{goals=TS}) ->
TS.
--spec goals(t(), [rlx_depsolver:constraint()]) -> t().
+-spec goals(t(), [rlx_depsolver:raw_constraint()]) -> t().
goals(State, Goals) ->
State#state_t{goals=Goals}.
diff --git a/src/rlx_string.erl b/src/rlx_string.erl
index 1f9cc0c..d5f5046 100644
--- a/src/rlx_string.erl
+++ b/src/rlx_string.erl
@@ -2,14 +2,22 @@
%% OTP-19 and OTP-21, where Unicode support means the deprecation
%% of a lot of string functions.
-module(rlx_string).
--export([concat/2, lexemes/2, join/2]).
+-export([concat/2, lexemes/2, join/2, trim/3]).
-ifdef(unicode_str).
concat(Str1, Str2) -> unicode:characters_to_list([Str1,Str2]).
lexemes(Str, Separators) -> string:lexemes(Str, Separators).
+trim(Str, Direction, Cluster=[_]) -> string:trim(Str, Direction, Cluster).
-else.
concat(Str1, Str2) -> string:concat(Str1, Str2).
lexemes(Str, Separators) -> string:tokens(Str, Separators).
+trim(Str, Direction, [Char]) ->
+ Dir = case Direction of
+ both -> both;
+ leading -> left;
+ trailing -> right
+ end,
+ string:strip(Str, Dir, Char).
-endif.
%% string:join/2 copy; string:join/2 is getting obsoleted
diff --git a/src/rlx_util.erl b/src/rlx_util.erl
index b3fc2b7..5d3744d 100644
--- a/src/rlx_util.erl
+++ b/src/rlx_util.erl
@@ -45,6 +45,8 @@
-define(DFLT_INTENSITY, high).
-define(ONE_LEVEL_INDENT, " ").
+
+-include_lib("kernel/include/file.hrl").
%%============================================================================
%% types
%%============================================================================
@@ -233,81 +235,65 @@ symlink_or_copy(Source, Target) ->
ok;
{error, eexist} ->
{error, eexist};
- {error, _} ->
- case os:type() of
- {win32, _} ->
- S = unicode:characters_to_list(Source),
- T = unicode:characters_to_list(Target),
- win32_symlink(filename:nativename(S), filename:nativename(T));
+ {error, Err} ->
+ case {os:type(), Err} of
+ {{win32, _}, eperm} ->
+ % We get eperm on Windows if we do not have
+ % SeCreateSymbolicLinkPrivilege
+ % Try the next alternative
+ win32_make_junction_or_copy(Source, Target);
_ ->
- case filelib:is_dir(Target) of
- true -> ok;
- false ->
- cp_r([Source], Target)
- end
+ % On other systems we try to copy next
+ cp_r(Source, Target)
end
end.
+cp_r(Source, Target) ->
+ ec_file:copy(Source, Target, [{recursive, true}, {fileinfo, [mode, time, owner, group]}]).
-win32_symlink(Source, Target) ->
- os:cmd("cmd /c mklink /j " ++ Target ++ " " ++ Source),
- ok.
-
--spec cp_r(list(string()), file:filename()) -> 'ok'.
-cp_r(Sources, Dest) ->
- case os:type() of
- {unix, _} ->
- ok;
- {win32, _} ->
- lists:foreach(fun(Src) -> ok = cp_r_win32(Src,Dest) end, Sources),
- ok
+win32_make_junction_or_copy(Source, Target) ->
+ case filelib:is_dir(Source) of
+ true ->
+ win32_make_junction(Source, Target);
+ _ ->
+ cp_r(Source, Target)
end.
-xcopy_win32(Source,Dest)->
- %% "xcopy \"~s\" \"~s\" /q /y /e 2> nul", Chanegd to robocopy to
- %% handle long names. May have issues with older windows.
- os:cmd("robocopy " ++ Source ++ " " ++ Dest ++ " /e /is"),
- ok.
-
-cp_r_win32({true, SourceDir}, {true, DestDir}) ->
- %% from directory to directory
- ok = case file:make_dir(DestDir) of
- {error, eexist} -> ok;
- Other -> Other
- end,
- ok = xcopy_win32(SourceDir, DestDir);
-cp_r_win32({false, Source} = S,{true, DestDir}) ->
- %% from file to directory
- cp_r_win32(S, {false, filename:join(DestDir, filename:basename(Source))});
-cp_r_win32({false, Source},{false, Dest}) ->
- %% from file to file
- {ok,_} = file:copy(Source, Dest),
- ok;
-cp_r_win32({true, SourceDir}, {false, DestDir}) ->
- case filelib:is_regular(DestDir) of
- true ->
- %% From directory to file? This shouldn't happen
- {error, lists:flatten(
- io_lib:format("Cannot copy dir (~p) to file (~p)\n",
- [SourceDir, DestDir]))};
- false ->
- %% Specifying a target directory that doesn't currently exist.
- %% So let's attempt to create this directory
- case filelib:ensure_dir(filename:join(DestDir, "dummy")) of
- ok ->
- ok = xcopy_win32(SourceDir, DestDir);
+win32_make_junction(Source, Target) ->
+ % The mklink will fail if the target already exists, check for that first
+ case file:read_link_info(Target) of
+ {error, enoent} ->
+ win32_make_junction_cmd(Source, Target);
+ {ok, #file_info{type = symlink}} ->
+ case file:read_link(Target) of
+ {ok, Source} ->
+ ok;
+ {ok, _} ->
+ ok = file:del_dir(Target),
+ win32_make_junction_cmd(Source, Target);
{error, Reason} ->
- {error, lists:flatten(
- io_lib:format("Unable to create dir ~p: ~p\n",
- [DestDir, Reason]))}
- end
- end;
-cp_r_win32(Source,Dest) ->
- Dst = {filelib:is_dir(Dest), Dest},
- lists:foreach(fun(Src) ->
- ok = cp_r_win32({filelib:is_dir(Src), Src}, Dst)
- end, filelib:wildcard(Source)),
- ok.
+ {error, {readlink, Reason}}
+ end;
+ {ok, #file_info{type = _Type}} ->
+ % Directory already exists, so we overwrite the copy
+ cp_r(Source, Target);
+ Error ->
+ Error
+ end.
+
+win32_make_junction_cmd(Source, Target) ->
+ S = unicode:characters_to_list(Source),
+ T = unicode:characters_to_list(Target),
+ Cmd = "cmd /c mklink /j " ++ filename:nativename(T) ++ " " ++ filename:nativename(S),
+ case os:cmd(Cmd) of
+ "Junction created " ++ _ ->
+ ok;
+ [] ->
+ % When mklink fails it prints the error message to stderr which
+ % is not picked up by os:cmd() hence this case switch is for
+ % an empty message
+ cp_r(Source, Target)
+ end.
%% @doc Returns the color intensity, we first check the application envorinment
%% if that is not set we check the environment variable RELX_COLOR.