Skip to content

Commit 55e3c41

Browse files
authored
Merge pull request #2689 from ferd/vendoring
Experimental vendoring provider
2 parents 63c98e2 + 9a13df4 commit 55e3c41

File tree

3 files changed

+121
-1
lines changed

3 files changed

+121
-1
lines changed

src/rebar.app.src.script

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
rebar_prv_unlock,
8383
rebar_prv_update,
8484
rebar_prv_upgrade,
85+
rebar_prv_vendor,
8586
rebar_prv_version,
8687
rebar_prv_xref,
8788
rebar_prv_alias]} % must run last to prevent overloads

src/rebar_app_discover.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
%% @doc from the base directory, find all the applications
2020
%% at the top level and their dependencies based on the configuration
2121
%% and profile information.
22-
-spec do(rebar_state:t(), [file:filename()]) -> rebar_state:t().
22+
-spec do(rebar_state:t(), [file:filename()]) -> rebar_state:t() | no_return().
2323
do(State, LibDirs) ->
2424
BaseDir = rebar_state:dir(State),
2525
Dirs = [filename:join(BaseDir, LibDir) || LibDir <- LibDirs],

src/rebar_prv_vendor.erl

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
-module(rebar_prv_vendor).
2+
-behaviour(provider).
3+
4+
-export([init/1,
5+
do/1,
6+
format_error/1]).
7+
8+
-include("rebar.hrl").
9+
-include_lib("providers/include/providers.hrl").
10+
11+
-define(PROVIDER, vendor).
12+
-define(NAMESPACE, experimental).
13+
-define(DEPS, []).
14+
15+
%% ===================================================================
16+
%% Public API
17+
%% ===================================================================
18+
19+
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
20+
init(State) ->
21+
State1 = rebar_state:add_provider(
22+
State,
23+
providers:create([{name, ?PROVIDER},
24+
{namespace, ?NAMESPACE},
25+
{module, ?MODULE},
26+
{bare, true},
27+
{deps, ?DEPS},
28+
{example, ""},
29+
{short_desc, "Turns dependencies into top-level apps"},
30+
{desc, "Turns dependencies into top-level applications"},
31+
{opts, []}])
32+
),
33+
{ok, State1}.
34+
35+
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
36+
do(State) ->
37+
%% Only vendor default profile runs
38+
case rebar_state:current_profiles(State) of
39+
[default] ->
40+
case check_project_layout(State) of
41+
umbrella -> do_(State);
42+
_ -> ?PRV_ERROR(not_umbrella)
43+
end;
44+
Profiles ->
45+
?DEBUG("Profiles found: ~p, skipping", [Profiles]),
46+
{ok, State}
47+
end.
48+
49+
%% @doc convert a given exception's payload into an io description.
50+
-spec format_error(any()) -> iolist().
51+
format_error(not_umbrella) ->
52+
io_lib:format("Vendoring can only work on umbrella applications", []).
53+
54+
do_(InitState) ->
55+
%% TODO: figure out how to vendor and local-load plugins
56+
%% delete the vendored files (or move them to another place)
57+
RootDir = rebar_dir:root_dir(InitState),
58+
VendorDir = filename:join(RootDir, "vendor"),
59+
VendorBak = filename:join(RootDir, "_vendor"),
60+
catch rebar_file_utils:rm_rf(VendorBak),
61+
catch rebar_file_utils:mv(VendorDir, VendorBak),
62+
filelib:ensure_dir(filename:join(VendorDir, ".touch")),
63+
%% remove the src_dirs option for vendored files
64+
CleanDirs = rebar_dir:lib_dirs(InitState) -- ["vendor/*"],
65+
CleanState = rebar_state:set(InitState, project_app_dirs, CleanDirs),
66+
%% re-run discovery
67+
{ok, TmpState1} = rebar_prv_app_discovery:do(CleanState),
68+
%% run a full fetch (which implicitly upgrades, since the lock file
69+
%% should be unset for any vendored app)
70+
{ok, TmpState2} = rebar_prv_install_deps:do(TmpState1),
71+
%% move the libs to the vendor path
72+
AllDeps = rebar_state:lock(TmpState2),
73+
[begin
74+
AppDir = rebar_app_info:dir(Dep),
75+
NewAppDir = filename:join(VendorDir, filename:basename(AppDir)),
76+
rebar_file_utils:mv(AppDir, NewAppDir)
77+
end || Dep <- AllDeps, not(rebar_app_info:is_checkout(Dep))],
78+
%% add the src_dirs options to the rebar.config file
79+
%% -- we don't actually want to mess with the user's file so we have to
80+
%% let them know what it should be:
81+
NewAppDirs = CleanDirs ++ ["vendor/*"],
82+
?CONSOLE("Vendoring in place. To use the vendored libraries, configure "
83+
"the source application directories for your project with:~n~n"
84+
"{project_app_dirs, ~p}.~n~n"
85+
"and move the {deps, ...} tuple to the rebar.config files "
86+
"of the proper top-level applications rather than the project root.",
87+
[NewAppDirs]),
88+
State1 = rebar_state:set(InitState, project_app_dirs, NewAppDirs),
89+
{ok, State1}.
90+
91+
%% ignore the badmatch there; the error value is real, but comes from
92+
%% exceptions at the call-sites of sub-functions within
93+
%% `rebar_app_discovery:do/1' and Dialyzer loses track of this being
94+
%% a possibility and elides the `{error, _}' return tuple from the
95+
%% signature while it is real.
96+
-dialyzer({no_match, check_project_layout/1}).
97+
check_project_layout(State) ->
98+
%% Do a project_app_discover run, look for the project root,
99+
%% then drop the state.
100+
%% No need to drop the vendor config here if it exists because it
101+
%% only exists if we have the right structure.
102+
case rebar_prv_app_discovery:do(State) of
103+
{error, Reason} ->
104+
{error, Reason};
105+
{ok, TmpState} ->
106+
Apps = rebar_state:project_apps(TmpState),
107+
%% Code duplicated from rebar_prv_lock:define_root_app/2
108+
RootDir = rebar_dir:root_dir(TmpState),
109+
case ec_lists:find(fun(X) ->
110+
ec_file:real_dir_path(rebar_app_info:dir(X)) =:=
111+
ec_file:real_dir_path(RootDir)
112+
end, Apps) of
113+
{ok, _App} ->
114+
non_umbrella;
115+
error ->
116+
umbrella
117+
end
118+
end.
119+

0 commit comments

Comments
 (0)