summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/rebar_prv_install_deps.erl159
-rw-r--r--src/rebar_prv_lock.erl2
-rw-r--r--src/rebar_prv_upgrade.erl109
-rw-r--r--src/rebar_state.erl8
4 files changed, 195 insertions, 83 deletions
diff --git a/src/rebar_prv_install_deps.erl b/src/rebar_prv_install_deps.erl
index 025d32a..dab4c24 100644
--- a/src/rebar_prv_install_deps.erl
+++ b/src/rebar_prv_install_deps.erl
@@ -44,6 +44,8 @@
-define(PROVIDER, install_deps).
-define(DEPS, [app_discovery]).
+-define(APP_NAME_INDEX, 2).
+
-type src_dep() :: {atom(), {atom(), string(), string()}}
| {atom(), string(), {atom(), string(), string()}}.
-type pkg_dep() :: {atom(), binary()} | atom().
@@ -132,16 +134,17 @@ handle_deps(Profile, State, Deps) ->
-spec handle_deps(atom(), rebar_state:t(), list(), list() | boolean()) ->
{ok, [rebar_app_info:t()], rebar_state:t()} | {error, string()}.
-handle_deps(Profile, State, Deps, Update) when is_boolean(Update) ->
- handle_deps(Profile, State, Deps, Update, []);
+handle_deps(Profile, State, Deps, Upgrade) when is_boolean(Upgrade) ->
+ handle_deps(Profile, State, Deps, Upgrade, []);
handle_deps(Profile, State, Deps, Locks) when is_list(Locks) ->
- handle_deps(Profile, State, Deps, false, Locks).
+ Upgrade = rebar_state:get(State, upgrade, false),
+ handle_deps(Profile, State, Deps, Upgrade, Locks).
-spec handle_deps(atom(), rebar_state:t(), list(), boolean() | {true, binary(), integer()}, list())
-> {ok, [rebar_app_info:t()], rebar_state:t()} | {error, string()}.
handle_deps(_Profile, State, [], _, _) ->
{ok, [], State};
-handle_deps(Profile, State, Deps, Update, Locks) ->
+handle_deps(Profile, State, Deps, Upgrade, Locks) ->
%% Read in package index and dep graph
{Packages, Graph} = rebar_packages:get_packages(State),
%% Split source deps from pkg deps, needed to keep backwards compatibility
@@ -150,7 +153,7 @@ handle_deps(Profile, State, Deps, Update, Locks) ->
%% Fetch transitive src deps
{State1, SrcApps, PkgDeps1, Seen} =
- update_src_deps(Profile, 0, SrcDeps, PkgDeps, [], State, Update, sets:new(), Locks),
+ update_src_deps(Profile, 0, SrcDeps, PkgDeps, [], State, Upgrade, sets:new(), Locks),
{Solved, State2} = case PkgDeps1 of
[] -> %% No pkg deps
@@ -166,7 +169,7 @@ handle_deps(Profile, State, Deps, Update, Locks) ->
[warn_skip_pkg(Pkg) || Pkg <- Discarded],
Solution
end,
- update_pkg_deps(Profile, S, Packages, Update, Seen, State1)
+ update_pkg_deps(Profile, S, Packages, Upgrade, Seen, State1)
end,
AllDeps = lists:ukeymerge(2
@@ -181,7 +184,7 @@ handle_deps(Profile, State, Deps, Update, Locks) ->
%% Internal functions
%% ===================================================================
-update_pkg_deps(Profile, Pkgs, Packages, Update, Seen, State) ->
+update_pkg_deps(Profile, Pkgs, Packages, Upgrade, Seen, State) ->
%% Create app_info record for each pkg dep
DepsDir = rebar_dir:deps_dir(State),
{Solved, _, State1}
@@ -190,7 +193,7 @@ update_pkg_deps(Profile, Pkgs, Packages, Update, Seen, State) ->
,Packages
,Pkg),
{SeenAcc1, StateAcc1} = maybe_lock(Profile, AppInfo, SeenAcc, StateAcc, 0),
- case maybe_fetch(AppInfo, Update, SeenAcc1) of
+ case maybe_fetch(AppInfo, Upgrade, SeenAcc1) of
true ->
{[AppInfo | Acc], SeenAcc1, StateAcc1};
false ->
@@ -236,7 +239,7 @@ package_to_app(DepsDir, Packages, {Name, Vsn}) ->
end.
-spec update_src_deps(atom(), non_neg_integer(), list(), list(), list(), rebar_state:t(), boolean(), sets:set(binary()), list()) -> {rebar_state:t(), list(), list(), sets:set(binary())}.
-update_src_deps(Profile, Level, SrcDeps, PkgDeps, SrcApps, State, Update, Seen, Locks) ->
+update_src_deps(Profile, Level, SrcDeps, PkgDeps, SrcApps, State, Upgrade, Seen, Locks) ->
case lists:foldl(fun(AppInfo, {SrcDepsAcc, PkgDepsAcc, SrcAppsAcc, StateAcc, SeenAcc, LocksAcc}) ->
%% If not seen, add to list of locks to write out
Name = rebar_app_info:name(AppInfo),
@@ -249,15 +252,29 @@ update_src_deps(Profile, Level, SrcDeps, PkgDeps, SrcApps, State, Update, Seen,
true ->
ok
end,
- {SrcDepsAcc, PkgDepsAcc, SrcAppsAcc, StateAcc, SeenAcc, LocksAcc};
+ %% scan for app children here if upgrading
+ case Upgrade of
+ false ->
+ {SrcDepsAcc, PkgDepsAcc, SrcAppsAcc, StateAcc, SeenAcc, LocksAcc};
+ true ->
+ {SrcDepsAcc1, PkgDepsAcc1, SrcAppsAcc1, StateAcc2, LocksAcc1} =
+ handle_dep(AppInfo
+ ,SrcDepsAcc
+ ,PkgDepsAcc
+ ,SrcAppsAcc
+ ,Level
+ ,StateAcc
+ ,LocksAcc),
+
+ {SrcDepsAcc1, PkgDepsAcc1, SrcAppsAcc1, StateAcc2, SeenAcc, LocksAcc1}
+ end;
false ->
{SeenAcc1, StateAcc1} = maybe_lock(Profile, AppInfo, SeenAcc, StateAcc, Level),
{SrcDepsAcc1, PkgDepsAcc1, SrcAppsAcc1, StateAcc2, LocksAcc1} =
- case Update of
- {true, UpdateName, UpdateLevel} ->
- handle_update(AppInfo
- ,UpdateName
- ,UpdateLevel
+ case Upgrade of
+ true ->
+ %{true, UpgradeName, UpgradeLevel} ->
+ handle_upgrade(AppInfo
,SrcDepsAcc
,PkgDepsAcc
,SrcAppsAcc
@@ -278,20 +295,18 @@ update_src_deps(Profile, Level, SrcDeps, PkgDeps, SrcApps, State, Update, Seen,
end
end,
{[], PkgDeps, SrcApps, State, Seen, Locks},
- lists:sort(SrcDeps)) of
+ sort_deps(SrcDeps)) of
{[], NewPkgDeps, NewSrcApps, State1, Seen1, _NewLocks} ->
{State1, NewSrcApps, NewPkgDeps, Seen1};
{NewSrcDeps, NewPkgDeps, NewSrcApps, State1, Seen1, NewLocks} ->
- update_src_deps(Profile, Level+1, NewSrcDeps, NewPkgDeps, NewSrcApps, State1, Update, Seen1, NewLocks)
+ update_src_deps(Profile, Level+1, NewSrcDeps, NewPkgDeps, NewSrcApps, State1, Upgrade, Seen1, NewLocks)
end.
-handle_update(AppInfo, UpdateName, UpdateLevel, SrcDeps, PkgDeps, SrcApps, Level, State, Locks) ->
+handle_upgrade(AppInfo, SrcDeps, PkgDeps, SrcApps, Level, State, Locks) ->
Name = rebar_app_info:name(AppInfo),
- {_, _, _, DepLevel} = lists:keyfind(Name, 1, Locks),
- case UpdateLevel < DepLevel
- orelse Name =:= UpdateName of
- true ->
- case maybe_fetch(AppInfo, true, []) of
+ case lists:keyfind(Name, 1, Locks) of
+ false ->
+ case maybe_fetch(AppInfo, true, sets:new()) of
true ->
handle_dep(AppInfo
,SrcDeps
@@ -302,10 +317,10 @@ handle_update(AppInfo, UpdateName, UpdateLevel, SrcDeps, PkgDeps, SrcApps, Level
,Locks);
false ->
- {SrcDeps, PkgDeps, SrcApps, State, Locks}
+ {[AppInfo|SrcDeps], PkgDeps, SrcApps, State, Locks}
end;
- false ->
- {SrcDeps, PkgDeps, SrcApps, State, Locks}
+ _StillLocked ->
+ {[AppInfo|SrcDeps], PkgDeps, SrcApps, State, Locks}
end.
handle_dep(AppInfo, SrcDeps, PkgDeps, SrcApps, Level, State, Locks) ->
@@ -334,7 +349,7 @@ handle_dep(State, DepsDir, AppInfo, Locks, Level) ->
AppInfo1 = rebar_app_info:state(AppInfo, S3),
Deps = rebar_state:get(S3, deps, []),
- %% Update lock level to be the level the dep will have in this dep tree
+ %% Upgrade lock level to be the level the dep will have in this dep tree
NewLocks = [{DepName, Source, LockLevel+Level} ||
{DepName, Source, LockLevel} <- rebar_state:get(S3, {locks, default}, [])],
AppInfo2 = rebar_app_info:deps(AppInfo1, rebar_state:deps_names(Deps)),
@@ -343,7 +358,7 @@ handle_dep(State, DepsDir, AppInfo, Locks, Level) ->
-spec maybe_fetch(rebar_app_info:t(), boolean() | {true, binary(), integer()},
sets:set(binary())) -> boolean().
-maybe_fetch(AppInfo, Update, Seen) ->
+maybe_fetch(AppInfo, Upgrade, Seen) ->
AppDir = ec_cnv:to_list(rebar_app_info:dir(AppInfo)),
Apps = rebar_app_discover:find_apps(["_checkouts"], all),
case rebar_app_utils:find(rebar_app_info:name(AppInfo), Apps) of
@@ -351,46 +366,15 @@ maybe_fetch(AppInfo, Update, Seen) ->
%% Don't fetch dep if it exists in the _checkouts dir
false;
error ->
- Exists = case rebar_app_utils:is_app_dir(filename:absname(AppDir)++"-*") of
- {true, _} ->
- true;
- _ ->
- case rebar_app_utils:is_app_dir(filename:absname(AppDir)) of
- {true, _} ->
- true;
- _ ->
- false
- end
- end,
-
- case not Exists orelse Update of
+ case not app_exists(AppDir) of
true ->
- ?INFO("Fetching ~s", [rebar_app_info:name(AppInfo)]),
- Source = rebar_app_info:source(AppInfo),
- case rebar_fetch:download_source(AppDir, Source) of
- {error, Reason} ->
- throw(Reason);
- Result ->
- Result
- end;
- _ ->
+ fetch_app(AppInfo, AppDir);
+ false ->
case sets:is_element(rebar_app_info:name(AppInfo), Seen) of
true ->
false;
false ->
- Source = rebar_app_info:source(AppInfo),
- case rebar_fetch:needs_update(AppDir, Source) of
- true ->
- ?INFO("Updating ~s", [rebar_app_info:name(AppInfo)]),
- case rebar_fetch:download_source(AppDir, Source) of
- {error, Reason} ->
- throw(Reason);
- Result ->
- Result
- end;
- false ->
- false
- end
+ maybe_upgrade(AppInfo, AppDir, Upgrade)
end
end
end.
@@ -460,6 +444,55 @@ new_dep(DepsDir, Name, Vsn, Source, State) ->
rebar_state:overrides(S, ParentOverrides++Overrides)),
rebar_app_info:source(Dep1, Source).
+app_exists(AppDir) ->
+ case rebar_app_utils:is_app_dir(filename:absname(AppDir)++"-*") of
+ {true, _} ->
+ true;
+ _ ->
+ case rebar_app_utils:is_app_dir(filename:absname(AppDir)) of
+ {true, _} ->
+ true;
+ _ ->
+ false
+ end
+ end.
+
+fetch_app(AppInfo, AppDir) ->
+ ?INFO("Fetching ~s (~p)", [rebar_app_info:name(AppInfo), rebar_app_info:source(AppInfo)]),
+ Source = rebar_app_info:source(AppInfo),
+ case rebar_fetch:download_source(AppDir, Source) of
+ {error, Reason} ->
+ throw(Reason);
+ Result ->
+ Result
+ end.
+
+maybe_upgrade(AppInfo, AppDir, false) ->
+ Source = rebar_app_info:source(AppInfo),
+ rebar_fetch:needs_update(AppDir, Source);
+maybe_upgrade(AppInfo, AppDir, true) ->
+ Source = rebar_app_info:source(AppInfo),
+ case rebar_fetch:needs_update(AppDir, Source) of
+ true ->
+ ?INFO("Updating ~s", [rebar_app_info:name(AppInfo)]),
+ case rebar_fetch:download_source(AppDir, Source) of
+ {error, Reason} ->
+ throw(Reason);
+ Result ->
+ Result
+ end;
+ false ->
+ false
+ end.
+
+sort_deps(Deps) ->
+ %% We need a sort stable, based on the name. So that for multiple deps on
+ %% the same level with the same name, we keep the order the parents had.
+ %% `lists:keysort/2' is documented as stable in the stdlib.
+ %% The list of deps is revered when we get it. For the proper stable
+ %% result, re-reverse it.
+ lists:keysort(?APP_NAME_INDEX, lists:reverse(Deps)).
+
-spec parse_goal(binary(), binary()) -> pkg_dep().
parse_goal(Name, Constraint) ->
case re:run(Constraint, "([^\\d]*)(\\d.*)", [{capture, [1,2], binary}]) of
diff --git a/src/rebar_prv_lock.erl b/src/rebar_prv_lock.erl
index 2f26a7f..8a69434 100644
--- a/src/rebar_prv_lock.erl
+++ b/src/rebar_prv_lock.erl
@@ -41,7 +41,7 @@ do(State) ->
,rebar_app_info:dep_level(Dep)}
end, AllDeps),
Dir = rebar_state:dir(State),
- file:write_file(filename:join(Dir, "rebar.lock"), io_lib:format("~p.~n", [Locks])),
+ file:write_file(filename:join(Dir, ?LOCK_FILE), io_lib:format("~p.~n", [Locks])),
{ok, State}.
-spec format_error(any()) -> iolist().
diff --git a/src/rebar_prv_upgrade.erl b/src/rebar_prv_upgrade.erl
index c0a56c3..1cae5aa 100644
--- a/src/rebar_prv_upgrade.erl
+++ b/src/rebar_prv_upgrade.erl
@@ -13,6 +13,9 @@
-define(PROVIDER, upgrade).
-define(DEPS, []).
+%% Also only upgrade top-level (0) deps. Transitive deps shouldn't be
+%% upgradable -- if the user wants this, they should declare it at the
+%% top level and then upgrade.
%% ===================================================================
%% Public API
@@ -26,33 +29,105 @@ init(State) ->
{module, ?MODULE},
{bare, false},
{deps, ?DEPS},
- {example, "rebar upgrade cowboy"},
- {short_desc, "Upgrade dependency."},
- {desc, ""},
+ {example, "rebar3 upgrade [cowboy[,ranch]]"},
+ {short_desc, "Upgrade dependencies."},
+ {desc, "Upgrade project dependecies. Mentioning no application "
+ "will upgrade all dependencies. To upgrade specific dependencies, "
+ "their names can be listed in the command."},
{opts, [
- {package, undefined, undefined, string, "Package to upgrade."}
+ {package, undefined, undefined, string,
+ "List of packages to upgrade. If not specified, all dependencies are upgraded."}
]}])),
{ok, State1}.
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
{Args, _} = rebar_state:command_parsed_args(State),
- Name = ec_cnv:to_binary(proplists:get_value(package, Args)),
- Locks = rebar_state:get(State, locks, []),
+ Locks = rebar_state:get(State, {locks, default}, []),
+ Deps = rebar_state:get(State, deps),
+ Names = parse_names(ec_cnv:to_binary(proplists:get_value(package, Args, <<"">>)), Locks),
+ case prepare_locks(Names, Deps, Locks, []) of
+ {error, Reason} ->
+ {error, Reason};
+ {Locks0, _Unlocks0} ->
+ Deps0 = top_level_deps(Deps, Locks),
+ State1 = rebar_state:set(State, {deps, default}, Deps0),
+ State2 = rebar_state:set(State1, {locks, default}, Locks0),
+ State3 = rebar_state:set(State2, upgrade, true),
+ Res = rebar_prv_install_deps:do(State3),
+ case Res of
+ {ok, State4} ->
+ info_useless(
+ [element(1,Lock) || Lock <- Locks],
+ [rebar_app_info:name(App) || App <- rebar_state:lock(State4)]
+ ),
+ rebar_prv_lock:do(State4);
+ _ ->
+ Res
+ end
+ end.
+
+-spec format_error(any()) -> iolist().
+format_error({unknown_dependency, Name}) ->
+ io_lib:format("Dependency ~ts not found", [Name]);
+format_error({transitive_dependency, Name}) ->
+ io_lib:format("Dependency ~ts is transient and cannot be safely upgraded. "
+ "Promote it to your top-level rebar.config file to upgrade it.",
+ [Name]);
+format_error(Reason) ->
+ io_lib:format("~p", [Reason]).
+
+
+parse_names(Bin, Locks) ->
+ case lists:usort(re:split(Bin, <<" *, *">>, [trim])) of
+ %% Nothing submitted, use *all* apps
+ [<<"">>] -> [Name || {Name, _, 0} <- Locks];
+ [] -> [Name || {Name, _, 0} <- Locks];
+ %% Regular options
+ Other -> Other
+ end.
+
+prepare_locks([], _, Locks, Unlocks) ->
+ {Locks, Unlocks};
+prepare_locks([Name|Names], Deps, Locks, Unlocks) ->
case lists:keyfind(Name, 1, Locks) of
- {_, _, _, Level} ->
- Deps = rebar_state:get(State, deps),
- case lists:keyfind(binary_to_atom(Name, utf8), 1, Deps) of
+ {_, _, 0} = Lock ->
+ AtomName = binary_to_atom(Name, utf8),
+ case lists:keyfind(AtomName, 1, Deps) of
false ->
- {error, io_lib:format("No such dependency ~s~n", [Name])};
+ {error, {?MODULE, {unknown_dependency, Name}}};
Dep ->
- rebar_prv_install_deps:handle_deps(State, [Dep], {true, Name, Level}),
- {ok, State}
+ Source = case Dep of
+ {_, Src} -> Src;
+ {_, _, Src} -> Src
+ end,
+ {NewLocks, NewUnlocks} = unlock_higher_than(0, Locks -- [Lock]),
+ prepare_locks(Names,
+ Deps,
+ NewLocks,
+ [{Name, Source, 0} | NewUnlocks ++ Unlocks])
end;
- _ ->
- {error, io_lib:format("No such dependency ~s~n", [Name])}
+ {_, _, Level} when Level > 0 ->
+ {error, {?MODULE, {transitive_dependency,Name}}};
+ false ->
+ {error, {?MODULE, {unknown_dependency,Name}}}
end.
--spec format_error(any()) -> iolist().
-format_error(Reason) ->
- io_lib:format("~p", [Reason]).
+top_level_deps(Deps, Locks) ->
+ [Dep || Dep <- Deps, lists:keymember(0, 3, Locks)].
+
+unlock_higher_than(Level, Locks) -> unlock_higher_than(Level, Locks, [], []).
+
+unlock_higher_than(_, [], Locks, Unlocks) ->
+ {Locks, Unlocks};
+unlock_higher_than(Level, [App = {_,_,AppLevel} | Apps], Locks, Unlocks) ->
+ if AppLevel > Level -> unlock_higher_than(Level, Apps, Locks, [App | Unlocks]);
+ AppLevel =< Level -> unlock_higher_than(Level, Apps, [App | Locks], Unlocks)
+ end.
+
+info_useless(Old, New) ->
+ [?INFO("App ~ts is no longer needed and can be deleted.", [Name])
+ || Name <- Old,
+ not lists:member(Name, New)],
+ ok.
+
diff --git a/src/rebar_state.erl b/src/rebar_state.erl
index 29b7c3f..70aba51 100644
--- a/src/rebar_state.erl
+++ b/src/rebar_state.erl
@@ -89,8 +89,10 @@ new(ParentState, Config, Dir) ->
Opts = ParentState#state_t.opts,
LocalOpts = case rebar_config:consult_file(filename:join(Dir, ?LOCK_FILE)) of
[D] ->
- LockedDeps = [X || X <- D, element(3, X) =:= 0],
- dict:from_list([{{locks, default}, LockedDeps}, {{deps, default}, D} | Config]);
+ %% We want the top level deps only from the lock file.
+ %% This ensures deterministic overrides for configs.
+ Deps = [X || X <- D, element(3, X) =:= 0],
+ dict:from_list([{{locks, default}, D}, {{deps, default}, Deps} | Config]);
_ ->
D = proplists:get_value(deps, Config, []),
dict:from_list([{{deps, default}, D} | Config])
@@ -133,6 +135,8 @@ current_profiles(#state_t{current_profiles=Profiles}) ->
lock(#state_t{lock=Lock}) ->
Lock.
+lock(State=#state_t{}, Apps) when is_list(Apps) ->
+ State#state_t{lock=Apps};
lock(State=#state_t{lock=Lock}, App) ->
State#state_t{lock=[App | Lock]}.