Packages
relx
4.0.2
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_resolve.erl
-module(rlx_resolve).
-export([solve_release/2,
format_error/1]).
-include("relx.hrl").
-include("rlx_log.hrl").
-spec format_error(ErrorDetail::term()) -> iolist().
format_error({no_goals_specified, {RelName, RelVsn}}) ->
io_lib:format("No applications configured to be included in release ~s-~s", [RelName, RelVsn]);
format_error({release_erts_error, Dir}) ->
io_lib:format("Unable to find erts in ~s", [Dir]);
format_error({app_not_found, AppName, AppVsn}) ->
io_lib:format("Application needed for release not found: ~p-~s", [AppName, AppVsn]);
format_error({app_not_found, AppName}) ->
io_lib:format("Application needed for release not found: ~p", [AppName]).
solve_release(Release, State0) ->
RelName = rlx_release:name(Release),
RelVsn = rlx_release:vsn(Release),
?log_debug("Solving Release ~p-~s", [RelName, RelVsn]),
AllApps = rlx_state:available_apps(State0),
%% get per release config values and override the State with them
Config = rlx_release:config(Release),
{ok, State1} = lists:foldl(fun rlx_config:load/2, {ok, State0}, Config),
case rlx_release:goals(Release) of
[] ->
erlang:error(?RLX_ERROR({no_goals_specified, {RelName, RelVsn}}));
Goals ->
LibDirs = rlx_state:lib_dirs(State1),
Pkgs = subset(Goals, AllApps, LibDirs),
Pkgs1 = remove_exclude_apps(Pkgs, State1),
set_resolved(Release, Pkgs1, State1)
end.
%% find the app_info records for each application and its deps needed for the release
subset(Apps, World, LibDirs) ->
subset(Apps, World, sets:new(), LibDirs, []).
subset([], _World, _Seen, _LibDirs, Acc) ->
Acc;
subset([Goal | Rest], World, Seen, LibDirs, Acc) ->
{Name, Vsn} = name_version(Goal),
case sets:is_element(Name, Seen) of
true ->
subset(Rest, World, Seen, LibDirs, Acc);
_ ->
AppInfo=#{applications := Applications,
included_applications := IncludedApplications} = find_app(Name, Vsn, World, LibDirs),
subset(Rest ++ Applications ++ IncludedApplications,
World,
sets:add_element(Name, Seen),
LibDirs,
Acc ++ [AppInfo])
end.
set_resolved(Release0, Pkgs, State) ->
case rlx_release:realize(Release0, Pkgs) of
{ok, Release1} ->
?log_debug("Resolved ~p-~s", [rlx_release:name(Release1), rlx_release:vsn(Release1)]),
?log_debug("~s", [rlx_release:format(Release1)]),
case rlx_state:include_erts(State) of
IncludeErts when is_atom(IncludeErts) ->
{ok, Release1, rlx_state:add_realized_release(State, Release1)};
ErtsDir ->
try
%% figure out erts version from the path given
[Erts | _] = filelib:wildcard(filename:join(ErtsDir, "erts-*")),
[_, ErtsVsn] = rlx_string:lexemes(filename:basename(Erts), "-"),
Release2 = rlx_release:erts(Release1, ErtsVsn),
{ok, Release2, rlx_state:add_realized_release(State, Release2)}
catch
_:_ ->
erlang:error(?RLX_ERROR({release_erts_error, ErtsDir}))
end
end
end.
name_version(Name) when is_atom(Name) ->
{Name, undefined};
name_version({Name, #{vsn := Vsn}}) ->
{Name, Vsn}.
remove_exclude_apps(AllApps, State) ->
ExcludeApps = rlx_state:exclude_apps(State),
lists:foldl(fun(AppName, Acc) ->
find_and_remove(AppName, Acc)
end, AllApps, ExcludeApps).
find_and_remove(_, []) ->
[];
find_and_remove(ExcludeName, [#{name := Name} | Rest]) when ExcludeName =:= Name ->
Rest;
find_and_remove(ExcludeName, [H | Rest]) ->
[H | find_and_remove(ExcludeName, Rest)].
%% Applications are first searched for in the `Apps' variable which is a map
%% of application name to `rlx_app_info' map. This variable is passed to `relx'
%% function calls to build the release or tarball
%%
%% If the application isn't found in the `Apps' map then the `lib_dirs' list of
%% directories is searched for the application under `<dir>/*/ebin/Name.app'.
%% Lastly, if the application still isn't found then the code path is checked
%% using `code:lib_dir'.
find_app(Name, Vsn, Apps, LibDirs) ->
case maps:find(Name, Apps) of
{ok, AppInfo} ->
%% verify the app is the version we want and if not try
%% finding the app-vsn needed in the configured paths
case check_app(Name, Vsn, AppInfo) of
true ->
AppInfo;
false ->
search_for_app(Name, Vsn, LibDirs)
end;
error ->
search_for_app(Name, Vsn, LibDirs)
end.
search_for_app(Name, Vsn, LibDirs) ->
case find_app_in_dir(Name, Vsn, LibDirs) of
not_found ->
find_app_in_code_path(Name, Vsn);
AppInfo ->
case check_app(Name, Vsn, AppInfo) of
true ->
AppInfo;
false ->
find_app_in_code_path(Name, Vsn)
end
end.
find_app_in_dir(_Name, _Vsn, []) ->
not_found;
find_app_in_dir(Name, Vsn, [Dir | Rest]) ->
AppFile = filename:join([Dir, "*", "ebin", [Name, ".app"]]),
case filelib:wildcard(AppFile) of
[] ->
find_app_in_dir(Name, Vsn, Rest);
Matches ->
case rlx_util:list_search(fun(AppFilePath) ->
to_app(Name, Vsn, AppFilePath)
end, Matches) of
{value, App} ->
App;
false ->
find_app_in_dir(Name, Vsn, Rest)
end
end.
find_app_in_code_path(Name, Vsn) ->
case code:lib_dir(Name) of
{error, bad_name} ->
erlang:error(?RLX_ERROR({app_not_found, Name}));
Dir ->
case to_app(Name, Vsn, filename:join([Dir, "ebin", [Name, ".app"]])) of
{true, AppInfo} ->
AppInfo;
false ->
erlang:error(?RLX_ERROR({app_not_found, Name}))
end
end.
%% returns true if the AppInfo is the same name and version as the
%% arguments where `undefined' means any version.
check_app(Name, undefined, App) ->
rlx_app_info:name(App) =:= Name;
check_app(Name, Vsn, App) ->
rlx_app_info:name(App) =:= Name
andalso rlx_app_info:vsn(App) =:= Vsn.
to_app(Name, Vsn, AppFilePath) ->
{ok, [{application, _AppName, AppData}]} = file:consult(AppFilePath),
Applications = proplists:get_value(applications, AppData, []),
IncludedApplications = proplists:get_value(included_applications, AppData, []),
case lists:keyfind(vsn, 1, AppData) of
{_, Vsn1} when Vsn =:= undefined ;
Vsn =:= Vsn1 ->
{true, #{name => Name,
vsn => Vsn1,
applications => Applications,
included_applications => IncludedApplications,
dir => filename:dirname(filename:dirname(AppFilePath)),
link => false}};
_ ->
false
end.