Packages
relx
4.10.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_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, undefined}) ->
io_lib:format("Application needed for release not found: ~p", [AppName]);
format_error({app_not_found, AppName, AppVsn}) ->
io_lib:format("Application needed for release not found: ~p-~s", [AppName, AppVsn]).
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),
{CheckCodeLibDirs, LibDirs1} =
case rlx_state:system_libs(State0) of
B when is_boolean(B) ->
%% even if we don't include system libs (`system_libs' being false)
%% here we still want to check for the system apps in the code path.
%% so return `true' for `CheckCodeLibDirs'
{true, LibDirs};
SystemLibs ->
?log_debug("System libs dir to search for apps ~ts", [SystemLibs]),
{false, [SystemLibs | LibDirs]}
end,
ExcludeApps = rlx_state:exclude_apps(State1),
Pkgs = subset(Goals, AllApps, LibDirs1, CheckCodeLibDirs, ExcludeApps),
set_resolved(Release, Pkgs, State1)
end.
%% find the app_info records for each application and its deps needed for the release
subset(Goals, World, LibDirs, CheckCodeLibDirs, ExcludeApps) ->
{Apps, _} = fold_apps(Goals, World, sets:new(), LibDirs, CheckCodeLibDirs, [], ExcludeApps),
Apps.
subset(Goal, World, Seen, LibDirs, CheckCodeLibDirs, OptionalApplications, ExcludeApps) ->
{Name, Vsn} = name_version(Goal),
case sets:is_element(Name, Seen) of
true ->
{[], Seen};
_ ->
case find_app(Name, Vsn, World, LibDirs, CheckCodeLibDirs) of
not_found ->
case lists:member(Name, OptionalApplications) of
true ->
%% don't add to Seen since optional applications are only
%% per-application and not global, so another app could
%% depend on this dependency
{[], Seen};
false ->
erlang:error(?RLX_ERROR({app_not_found, Name, Vsn}))
end;
AppInfo=#{applications := Applications,
included_applications := IncludedApplications,
optional_applications := OptionalApplications0} ->
{Apps, Seen2} = fold_apps(Applications ++ IncludedApplications ++ OptionalApplications0,
World,
sets:add_element(Name, Seen),
LibDirs,
CheckCodeLibDirs,
OptionalApplications0,
ExcludeApps),
%% don't add excluded apps
%% TODO: should an excluded app's deps also be excluded?
case lists:member(Name, ExcludeApps) of
true ->
{Apps, Seen2};
false ->
%% place the deps of the App before it
{Apps ++ [AppInfo], Seen2}
end
end
end.
fold_apps(Apps, World, Seen, LibDirs, CheckCodeLibDirs, OptionalApplications, ExcludeApps) ->
lists:foldl(fun(App, {AppAcc, SeenAcc}) ->
{NewApps, SeenAcc1} = subset(App, World, SeenAcc, LibDirs,
CheckCodeLibDirs, OptionalApplications, ExcludeApps),
%% put new apps after the existing list to keep the user defined order
{AppAcc ++ NewApps, SeenAcc1}
end, {[], Seen}, Apps).
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}.
%% 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, CheckCodeLibDirs) ->
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, CheckCodeLibDirs)
end;
error ->
search_for_app(Name, Vsn, LibDirs, CheckCodeLibDirs)
end.
search_for_app(Name, Vsn, LibDirs, CheckCodeLibDirs) ->
case find_app_in_dir(Name, Vsn, LibDirs) of
not_found when CheckCodeLibDirs =:= true ->
find_app_in_code_path(Name, Vsn);
not_found when CheckCodeLibDirs =:= false ->
%% app not found in any lib dir we are configured to search
%% and user set a custom `system_libs' directory so we do
%% not look in `code:lib_dir'
not_found;
AppInfo ->
case check_app(Name, Vsn, AppInfo) of
true ->
AppInfo;
false when CheckCodeLibDirs =:= true ->
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} ->
not_found;
Dir ->
case to_app(Name, Vsn, filename:join([Dir, "ebin", [Name, ".app"]])) of
{true, AppInfo} ->
AppInfo;
false ->
not_found
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) ->
AppData = case file:consult(AppFilePath) of
{ok, [{application, _Name, Data}]} -> Data;
Other -> erlang:error(?RLX_ERROR({bad_app_file, AppFilePath, Other}))
end,
Applications = proplists:get_value(applications, AppData, []),
IncludedApplications = proplists:get_value(included_applications, AppData, []),
OptionalApplications = proplists:get_value(optional_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,
optional_applications => OptionalApplications,
dir => filename:dirname(filename:dirname(AppFilePath)),
link => false}};
_ ->
false
end.