Current section

Files

Jump to
rebar3_lfe src r3lfe_util.erl
Raw

src/r3lfe_util.erl

-module(r3lfe_util).
-export([
get_all_deps_with_profiles/1,
get_all_plugins_with_profiles/1,
ensure_app_loaded/1
]).
%% Exported for testing
-ifdef(TEST).
-export([
get_plugin_version_from_app/2,
get_plugin_version_from_file/2,
find_and_read_app_file/2
]).
-endif.
-include("r3lfe.hrl").
%%====================================================================
%% API functions
%%====================================================================
%% @doc Ensure an application is loaded before attempting to get its metadata.
-spec ensure_app_loaded(atom()) -> ok | error.
ensure_app_loaded(AppName) ->
case application:load(AppName) of
ok -> ok;
{error, {already_loaded, _}} -> ok;
_ -> error
end.
%% @doc Get all dependencies from all profiles with their version and profile info.
%% Returns a list of maps with keys: name, version, profile.
%% LFE is included in the results (caller should filter if needed).
-spec get_all_deps_with_profiles(rebar_state:t()) -> [#{name => atom(), version => string(), profile => atom()}].
get_all_deps_with_profiles(State) ->
%% Get all dependencies from rebar3 state (these are already resolved)
AllDeps = rebar_state:all_deps(State),
%% Convert to our format - we'll mark them all as default since rebar3
%% merges profiles and we can't determine which profile they came from
lists:map(
fun(AppInfo) ->
Name = rebar_app_info:name(AppInfo),
Vsn = rebar_app_info:original_vsn(AppInfo),
AppName = case is_binary(Name) of
true -> binary_to_atom(Name, utf8);
false -> Name
end,
#{name => AppName, version => Vsn, profile => default}
end,
AllDeps
).
%% @doc Get all plugins from all profiles with their version and profile info.
%% Returns a list of maps with keys: name, version, profile.
%% Includes both 'plugins' and 'project_plugins'.
-spec get_all_plugins_with_profiles(rebar_state:t()) -> [#{name => atom(), version => string(), profile => atom()}].
get_all_plugins_with_profiles(State) ->
%% Get plugin names from config
TopLevelPlugins = rebar_state:get(State, plugins, []),
TopLevelProjectPlugins = rebar_state:get(State, project_plugins, []),
%% Get profile-specific plugins
Profiles = rebar_state:get(State, profiles, []),
ProfilePluginConfigs = lists:flatten([
begin
ProfilePluginsList = proplists:get_value(plugins, ProfileConfig, []),
ProfileProjectPluginsList = proplists:get_value(project_plugins, ProfileConfig, []),
[{Plugin, ProfileName} || Plugin <- ProfilePluginsList ++ ProfileProjectPluginsList]
end
|| {ProfileName, ProfileConfig} <- Profiles
]),
%% Combine all plugin specs with profile info
AllPluginSpecs = [{P, default} || P <- TopLevelPlugins ++ TopLevelProjectPlugins] ++ ProfilePluginConfigs,
%% Extract name and version for each plugin
lists:map(
fun({PluginSpec, Profile}) ->
Name = extract_name(PluginSpec),
%% Try to get version from loaded application, then from plugins dir
Vsn = get_plugin_version_from_app(Name, State),
#{name => Name, version => Vsn, profile => Profile}
end,
AllPluginSpecs
).
%%====================================================================
%% Internal functions
%%====================================================================
%% @doc Extract the name from a plugin specification.
%% Handles formats: atom, {atom, _}, etc.
-spec extract_name(term()) -> atom().
extract_name(Name) when is_atom(Name) ->
Name;
extract_name({Name, _}) when is_atom(Name) ->
Name;
extract_name(Other) ->
?DEBUG("Unexpected plugin format: ~p", [Other]),
unknown.
%% @doc Get the version of a plugin from loaded app or from .app file in plugins dir.
-spec get_plugin_version_from_app(atom(), rebar_state:t()) -> string().
get_plugin_version_from_app(AppName, State) ->
%% First try to get from loaded application
case ensure_app_loaded(AppName) of
ok ->
case application:get_key(AppName, vsn) of
{ok, Vsn} -> Vsn;
undefined -> get_plugin_version_from_file(AppName, State)
end;
error ->
%% Try to read from .app file in plugins directory
get_plugin_version_from_file(AppName, State)
end.
%% @doc Read plugin version from .app file in the plugins directory.
-spec get_plugin_version_from_file(atom(), rebar_state:t()) -> string().
get_plugin_version_from_file(AppName, State) ->
try
BaseDir = rebar_dir:base_dir(State),
AppNameStr = atom_to_list(AppName),
AppFileName = AppNameStr ++ ".app",
%% Try multiple possible locations for the plugin's .app file
PossiblePaths = [
%% Current profile's plugins directory
filename:join([rebar_dir:plugins_dir(State), AppNameStr, "ebin", AppFileName]),
%% Default profile
filename:join([BaseDir, "default", "plugins", AppNameStr, "ebin", AppFileName]),
%% Test profile
filename:join([BaseDir, "test", "plugins", AppNameStr, "ebin", AppFileName]),
%% Dev profile
filename:join([BaseDir, "dev", "plugins", AppNameStr, "ebin", AppFileName]),
%% Global plugins directory
filename:join([rebar_dir:global_cache_dir(rebar_state:opts(State)), "plugins", AppNameStr, "ebin", AppFileName])
],
%% Try each path until we find the file
find_and_read_app_file(AppName, PossiblePaths)
catch
_:_ ->
"unknown"
end.
%% @doc Try to find and read the .app file from a list of possible paths.
-spec find_and_read_app_file(atom(), [string()]) -> string().
find_and_read_app_file(_AppName, []) ->
"not compiled";
find_and_read_app_file(AppName, [Path | Rest]) ->
case filelib:is_file(Path) of
true ->
case file:consult(Path) of
{ok, [{application, AppName, AppProps}]} ->
proplists:get_value(vsn, AppProps, "not compiled");
_ ->
find_and_read_app_file(AppName, Rest)
end;
false ->
find_and_read_app_file(AppName, Rest)
end.