|
| 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