Make update-deps traverse deps breadth-first, top-down

This ensures that deps of deps are updated AFTER the dep listing them
is, so that a complicated project with many layers of deps will be
updated correctly. Any new deps encountered along the way are also
cloned, and THEIR deps are also evaluated.

Also added was conflict detection, if a dep has differing versions or
source information, inherited from different places, that will be logged
at the end of update-deps, along with the origin of each conflicting
dep.
This commit is contained in:
Andrew Thompson 2013-09-19 17:36:02 -04:00
parent 620c4b01c6
commit cb4599f828
2 changed files with 89 additions and 10 deletions

View file

@ -172,8 +172,9 @@ skip_or_process_dir1(AppFile, ModuleSet, Config, CurrentCodePath,
CurrentCodePath, ModuleSet)
end.
process_dir1(Dir, Command, DirSet, Config0, CurrentCodePath,
process_dir1(Dir, Command, DirSet, Config, CurrentCodePath,
{DirModules, ModuleSetFile}) ->
Config0 = rebar_config:set(Config, command, Command),
%% Get the list of modules for "any dir". This is a catch-all list
%% of modules that are processed in addition to modules associated
%% with this directory type. These any_dir modules are processed

View file

@ -97,8 +97,22 @@ preprocess(Config, _) ->
%% deps-related can be executed on their directories.
NonRawAvailableDeps = [D || D <- AvailableDeps, not D#dep.is_raw],
case rebar_config:get(Config, command, undefined) of
'update-deps' ->
%% Skip ALL of the dep folders, we do this because we don't want
%% any other calls to preprocess() for update-deps beyond the
%% toplevel directory. They aren't actually harmful, but they slow
%% things down unnecessarily.
NewConfig2 = lists:foldl(fun(D, Acc) ->
rebar_config:set_skip_dir(Acc, D#dep.dir)
end, NewConfig, collect_deps(rebar_utils:get_cwd(),NewConfig)),
%% Return the empty list, as we don't want anything processed before
%% us.
{ok, NewConfig2, []};
_ ->
%% Return all the available dep directories for process
{ok, NewConfig, dep_dirs(NonRawAvailableDeps)}.
{ok, NewConfig, dep_dirs(NonRawAvailableDeps)}
end.
postprocess(Config, _) ->
case rebar_config:get_xconf(Config, ?MODULE, undefined) of
@ -169,17 +183,23 @@ do_check_deps(Config) ->
{ok, save_dep_dirs(Config2, lists:reverse(PulledDeps))}.
'update-deps'(Config, _) ->
%% Determine what deps are required
RawDeps = rebar_config:get_local(Config, deps, []),
{Config1, Deps} = find_deps(Config, read, RawDeps),
{Config2, UpdatedDeps} = update_deps_int(rebar_config:set(Config, depowner, dict:new()), []),
DepOwners = rebar_config:get(Config2, depowner, dict:new()),
%% Update each dep
UpdatedDeps = [update_source(Config1, D)
|| D <- Deps, D#dep.source =/= undefined],
%% check for conflicting deps
[?ERROR("Conflicting dependencies for ~p: ~p~n", [K,
[{"From: " ++ string:join(dict:fetch(D,
DepOwners),
", "),
{D#dep.vsn_regex,
D#dep.source}} || D <- V]]) ||
{K, V} <- dict:to_list(lists:foldl(fun(Dep, Acc) ->
dict:append(Dep#dep.app, Dep, Acc)
end, dict:new(), UpdatedDeps)), length(V) > 1],
%% Add each updated dep to our list of dirs for post-processing. This yields
%% the necessary transitivity of the deps
{ok, save_dep_dirs(Config1, UpdatedDeps)}.
{ok, save_dep_dirs(Config, UpdatedDeps)}.
'delete-deps'(Config, _) ->
%% Delete all the available deps in our deps/ directory, if any
@ -566,6 +586,64 @@ update_source1(AppDir, {fossil, _Url, Version}) ->
rebar_utils:sh("fossil pull", [{cd, AppDir}]),
rebar_utils:sh(?FMT("fossil update ~s", [Version]), []).
%% Recursively update deps, this is not done via rebar's usual dep traversal as
%% that is the wrong order (tips are updated before branches). Instead we do a
%% traverse the deps at each level completely before traversing *their* deps.
%% This allows updates to actually propogate down the tree, rather than fail to
%% flow up the tree, which was the previous behaviour.
update_deps_int(Config0, UDD) ->
%% Determine what deps are required
ConfDir = filename:basename(rebar_utils:get_cwd()),
RawDeps = rebar_config:get_local(Config0, deps, []),
{Config1, Deps} = find_deps(Config0, read, RawDeps),
%% Update each dep
UpdatedDeps = [update_source(Config1, D)
|| D <- Deps, D#dep.source =/= undefined, not lists:member(D, UDD)],
lists:foldl(fun(Dep, {Config, Updated}) ->
{true, AppDir} = get_deps_dir(Config, Dep#dep.app),
Config2 = case has_vcs_dir(element(1, Dep#dep.source), AppDir) of
false ->
%% If the dep did not exist (maybe it was added)
%% clone it. We'll traverse ITS deps below. and
%% clone them if needed.
{C1, _D1} = use_source(Config, Dep),
C1;
true ->
Config
end,
ok = file:set_cwd(AppDir),
Config3 = rebar_config:new(Config2),
%% track where a dep comes from...
Config4 = rebar_config:set(Config3, depowner,
dict:append(Dep, ConfDir,
rebar_config:get(Config3,
depowner,
dict:new()))),
{Config5, Res} = update_deps_int(Config4, Updated),
{Config5, lists:umerge(lists:sort(Res),
lists:sort(Updated))}
end, {Config1, lists:umerge(lists:sort(UpdatedDeps),
lists:sort(UDD))}, UpdatedDeps).
%% Recursively walk the deps and build a list of them.
collect_deps(Dir, C) ->
case file:set_cwd(Dir) of
ok ->
Config = rebar_config:new(C),
RawDeps = rebar_config:get_local(Config, deps, []),
{Config1, Deps} = find_deps(Config, read, RawDeps),
lists:flatten(Deps ++ [begin
{true, AppDir} = get_deps_dir(Config1, Dep#dep.app),
collect_deps(AppDir, C)
end || Dep <- Deps]);
_ ->
[]
end.
%% ===================================================================
%% Source helper functions