From d903c6e52fd7a28e5cee8d0459f0e2ee795f2c93 Mon Sep 17 00:00:00 2001 From: AlexSedov Date: Tue, 31 Jul 2018 18:31:25 +0300 Subject: Make {goals, ...} option add goals to release depsolver. Additionally, provides parsing for in-config goals, and better goal merging. Closes #571. This reverts commit 0ffe8cd1a2a4e039a48d1096fc250b3f6bd3dcd1. --- src/rlx_config.erl | 42 ++++++++++++++++++---- src/rlx_depsolver.erl | 83 +++++++++++++++++++++++-------------------- src/rlx_prv_release.erl | 8 +++-- src/rlx_release.erl | 87 +++++++++++++++++++++++++++++++++++----------- src/rlx_state.erl | 6 ++-- test/rlx_release_SUITE.erl | 34 +++++++++++++++++- 6 files changed, 189 insertions(+), 71 deletions(-) diff --git a/src/rlx_config.erl b/src/rlx_config.erl index ee58db5..c67bf16 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,6 +301,34 @@ 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([]) -> diff --git a/src/rlx_depsolver.erl b/src/rlx_depsolver.erl index 8a0f632..88f2da4 100644 --- a/src/rlx_depsolver.erl +++ b/src/rlx_depsolver.erl @@ -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_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/test/rlx_release_SUITE.erl b/test/rlx_release_SUITE.erl index affc0ad..f0c10c3 100644 --- a/test/rlx_release_SUITE.erl +++ b/test/rlx_release_SUITE.erl @@ -40,6 +40,7 @@ make_implicit_config_release/1, overlay_release/1, make_goalless_release/1, + make_external_goal_release/1, make_depfree_release/1, make_invalid_config_release/1, make_relup_release/1, @@ -87,7 +88,7 @@ all() -> make_overridden_release, make_auto_skip_empty_app_release, make_skip_app_release, make_exclude_app_release, make_app_type_none_release, make_implicit_config_release, make_rerun_overridden_release, - overlay_release, make_goalless_release, make_depfree_release, + overlay_release, make_goalless_release, make_external_goal_release, make_depfree_release, make_invalid_config_release, make_relup_release, make_relup_release2, make_one_app_top_level_release, make_dev_mode_release, make_dev_mode_template_release, make_config_script_release, make_release_twice, make_release_twice_dev_mode, @@ -781,6 +782,37 @@ make_goalless_release(Config) -> relx:do(undefined, undefined, [], [LibDir1], 3, OutputDir, ConfigFile)). +make_external_goal_release(Config) -> + LibDir1 = proplists:get_value(lib1, Config), + + rlx_test_utils:create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + rlx_test_utils:create_app(LibDir1, "lib_dep_1", "0.0.1", [], []), + rlx_test_utils:create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + rlx_test_utils:create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + rlx_test_utils:create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + + ConfigFile = filename:join([LibDir1, "relx.config"]), + + ConfigFile = filename:join([LibDir1, "relx.config"]), + rlx_test_utils:write_config(ConfigFile, + [{goals, [{goal_app_2, "0.0.1"}]}, + {release, {foo, "0.0.1"}, + [goal_app_1]}]), + OutputDir = filename:join([proplists:get_value(priv_dir, Config), + rlx_test_utils:create_random_name("relx-output")]), + {ok, State} = relx:do(undefined, undefined, [], [LibDir1], 3, + OutputDir, ConfigFile), + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rlx_state:realized_releases(State)), + AppSpecs = rlx_release:applications(Release), + ?assert(lists:keymember(stdlib, 1, AppSpecs)), + ?assert(lists:keymember(kernel, 1, AppSpecs)), + ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), + ?assert(lists:member({non_goal_2, "0.0.1"}, AppSpecs)), + ?assert(lists:member({goal_app_1, "0.0.1"}, AppSpecs)), + ?assert(lists:member({goal_app_2, "0.0.1"}, AppSpecs)), + ?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)). + + make_depfree_release(Config) -> LibDir1 = proplists:get_value(lib1, Config), -- cgit v1.2.3