Skip to content

Commit

Permalink
Add support for project-local plugins
Browse files Browse the repository at this point in the history
This puts the basic scaffolding in place to allow project-local plugin
definitions. This, in turn, can be usable for vendoring mechanisms.

A few things that are still tricky with this approach:

- This is not tested aside from manual execution with a toy case
- There is no great way to only making this work with umbrella projects,
  since plugins are installed before the discovery phase that decides
  whether a project is an umbrella project or not, so there may be a
  need for an imperfect heuristic (eg. using project_src_app directories
  except the project root)
- Whether it clashes with things such as global plugins' paths and the
  general usage of rebar_paths to switch things in and out of visibility
  for projects.
- If dependencies are well handled.
- How overrides should work on this one. I'm assuming they shouldn't.
- If profiles are working well with this (and whether they even should,
  since they don't with other plugins)
- If it works with recursive plugins and local definitions

Manual executions however seem to show that this works. I'm committing
and putting it in a draft PR for awareness.
  • Loading branch information
ferd committed Apr 9, 2022
1 parent 55e3c41 commit e770176
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 9 deletions.
1 change: 1 addition & 0 deletions src/rebar.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
-define(DEFAULT_BASE_DIR, "_build").
-define(DEFAULT_ROOT_DIR, ".").
-define(DEFAULT_PROJECT_APP_DIRS, ["apps/*", "lib/*", "."]).
-define(DEFAULT_PROJECT_PLUGIN_DIRS, ["plugins/*"]).
-define(DEFAULT_CHECKOUTS_DIR, "_checkouts").
-define(DEFAULT_CHECKOUTS_OUT_DIR, "checkouts").
-define(DEFAULT_DEPS_DIR, "lib").
Expand Down
4 changes: 2 additions & 2 deletions src/rebar_app_discover.erl
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ do(State, LibDirs) ->
OutDir = filename:join(DepsDir, Name),
AppInfo2 = rebar_app_info:out_dir(AppInfo1, OutDir),
ProjectDeps1 = lists:delete(Name, ProjectDeps),
rebar_state:project_apps(StateAcc1
,rebar_app_info:deps(AppInfo2, ProjectDeps1));
rebar_state:project_apps(StateAcc1,
rebar_app_info:deps(AppInfo2, ProjectDeps1));
false ->
?INFO("Ignoring ~ts", [Name]),
StateAcc
Expand Down
7 changes: 7 additions & 0 deletions src/rebar_dir.erl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
checkouts_out_dir/2,
plugins_dir/1,
lib_dirs/1,
project_plugin_dirs/1,
home_dir/0,
global_config_dir/1,
global_config/1,
Expand Down Expand Up @@ -127,6 +128,12 @@ plugins_dir(State) ->
lib_dirs(State) ->
rebar_state:get(State, project_app_dirs, ?DEFAULT_PROJECT_APP_DIRS).

%% @doc returns the list of relative path where the project plugins can
%% be located.
-spec project_plugin_dirs(rebar_state:t()) -> file:filename_all().
project_plugin_dirs(State) ->
rebar_state:get(State, project_plugin_dirs, ?DEFAULT_PROJECT_PLUGIN_DIRS).

%% @doc returns the user's home directory.
-spec home_dir() -> file:filename_all().
home_dir() ->
Expand Down
73 changes: 66 additions & 7 deletions src/rebar_plugins.erl
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,11 @@ handle_plugins(Profile, Plugins, State, Upgrade) ->
Locks = rebar_state:lock(State),
DepsDir = rebar_state:get(State, deps_dir, ?DEFAULT_DEPS_DIR),
State1 = rebar_state:set(State, deps_dir, ?DEFAULT_PLUGINS_DIR),
SrcPlugins = discover_plugins(Plugins, State),
%% Install each plugin individually so if one fails to install it doesn't effect the others
{_PluginProviders, State2} =
lists:foldl(fun(Plugin, {PluginAcc, StateAcc}) ->
{NewPlugins, NewState} = handle_plugin(Profile, Plugin, StateAcc, Upgrade),
{NewPlugins, NewState} = handle_plugin(Profile, Plugin, StateAcc, SrcPlugins, Upgrade),
NewState1 = rebar_state:create_logic_providers(NewPlugins, NewState),
{PluginAcc++NewPlugins, NewState1}
end, {[], State1}, Plugins),
Expand All @@ -106,24 +107,34 @@ handle_plugins(Profile, Plugins, State, Upgrade) ->
State3 = rebar_state:set(State2, deps_dir, DepsDir),
rebar_state:lock(State3, Locks).

handle_plugin(Profile, Plugin, State, Upgrade) ->
handle_plugin(Profile, Plugin, State, SrcPlugins, Upgrade) ->
try
{Apps, State2} = rebar_prv_install_deps:handle_deps_as_profile(Profile, State, [Plugin], Upgrade),
{no_cycle, Sorted} = rebar_prv_install_deps:find_cycles(Apps),
%% Inject top-level src plugins as project apps, so that they get skipped
%% by the installation as already seen
ProjectApps = rebar_state:project_apps(State),
State0 = rebar_state:project_apps(State, SrcPlugins),
%% We however have to pick the deps of top-level apps and promote them
%% directly to make sure they are installed if they were not also at the top level
TopDeps = top_level_deps(SrcPlugins),
%% Install the plugins
{Apps, State1} = rebar_prv_install_deps:handle_deps_as_profile(Profile, State0, [Plugin|TopDeps], Upgrade),
{no_cycle, Sorted} = rebar_prv_install_deps:find_cycles(SrcPlugins++Apps),
ToBuild = rebar_prv_install_deps:cull_compile(Sorted, []),
%% Return things to normal
State2 = rebar_state:project_apps(State1, ProjectApps),

%% Add already built plugin deps to the code path
ToBuildPaths = [rebar_app_info:ebin_dir(A) || A <- ToBuild],
PreBuiltPaths = [Ebin || A <- Apps,
PreBuiltPaths = [Ebin || A <- Sorted,
Ebin <- [rebar_app_info:ebin_dir(A)],
not lists:member(Ebin, ToBuildPaths)],
code:add_pathsa(PreBuiltPaths),

%% Build plugin and its deps
build_plugins(ToBuild, Apps, State2),
build_plugins(ToBuild, Sorted, State2),

%% Add newly built deps and plugin to code path
State3 = rebar_state:update_all_plugin_deps(State2, Apps),
State3 = rebar_state:update_all_plugin_deps(State2, Sorted),
NewCodePaths = [rebar_app_info:ebin_dir(A) || A <- ToBuild],

%% Store plugin code paths so we can remove them when compiling project apps
Expand Down Expand Up @@ -172,3 +183,51 @@ validate_plugin(Plugin) ->
end
end.

discover_plugins([], _) ->
%% don't search if nothing is declared
[];
discover_plugins(_, State) ->
%% TODO: only support this mode in an umbrella project to avoid cases where
%% this is used in a project intended to be an installed dependency and accidentally
%% relies on vendoring when not intended.
case lists:member(global, rebar_state:current_profiles(State)) of
true ->
[];
false ->
%% Inject source paths for plugins to allow vendoring and umbrella
%% top-level declarations
BaseDir = rebar_state:dir(State),
LibDirs = rebar_dir:project_plugin_dirs(State),
?DIAGNOSTIC("Discovering local plugins in {project_plugin_dirs, ~p}", [LibDirs]),
Dirs = [filename:join(BaseDir, LibDir) || LibDir <- LibDirs],
RebarOpts = rebar_state:opts(State),
SrcDirs = rebar_dir:src_dirs(RebarOpts, ["src"]),
Found = rebar_app_discover:find_apps(Dirs, SrcDirs, all, State),
?DIAGNOSTIC(" Found: ~p", [[rebar_app_info:name(F) || F <- Found]]),
PluginsDir = rebar_dir:plugins_dir(State),
SetUp = lists:map(fun(App) ->
Name = rebar_app_info:name(App),
OutDir = filename:join(PluginsDir, Name),
prepare_plugin(rebar_app_info:out_dir(App, OutDir))
end, Found),
rebar_utils:sort_deps(SetUp)
end.

prepare_plugin(AppInfo) ->
%% We need to handle plugins as dependencies to avoid re-building them
%% continuously. So here we copy the app directories to the dep location
%% and then change the AppInfo record to be redirected to the dep location.
AppDir = rebar_app_info:dir(AppInfo),
OutDir = rebar_app_info:out_dir(AppInfo),
rebar_prv_compile:copy_app_dirs(AppInfo, AppDir, OutDir),
Relocated = rebar_app_info:dir(AppInfo, OutDir),
%% Force a revalidation from the new paths
rebar_app_info:valid(Relocated, undefined).

top_level_deps(Apps) ->
RawDeps = lists:foldl(fun(App, Acc) ->
%% Only support the profiles we would with regular plugins?
rebar_app_info:get(App, {deps, default}, []) ++
rebar_app_info:get(App, {deps, prod}, []) ++ Acc
end, [], Apps),
rebar_utils:tup_dedup(RawDeps).
1 change: 1 addition & 0 deletions src/rebar_prv_compile.erl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
format_error/1]).

-export([compile/2, compile/3, compile/4]).
-export([copy_app_dirs/3]).

-include_lib("providers/include/providers.hrl").
-include("rebar.hrl").
Expand Down

0 comments on commit e770176

Please sign in to comment.