From cb4599f828f0ec13a4ea18bcb1b0de4938d8dc7e Mon Sep 17 00:00:00 2001 From: Andrew Thompson Date: Thu, 19 Sep 2013 17:36:02 -0400 Subject: [PATCH] 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. --- src/rebar_core.erl | 3 +- src/rebar_deps.erl | 96 +++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 89 insertions(+), 10 deletions(-) diff --git a/src/rebar_core.erl b/src/rebar_core.erl index fcfa62a..863d1d5 100644 --- a/src/rebar_core.erl +++ b/src/rebar_core.erl @@ -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 diff --git a/src/rebar_deps.erl b/src/rebar_deps.erl index be6283d..82c5061 100644 --- a/src/rebar_deps.erl +++ b/src/rebar_deps.erl @@ -97,8 +97,22 @@ preprocess(Config, _) -> %% deps-related can be executed on their directories. NonRawAvailableDeps = [D || D <- AvailableDeps, not D#dep.is_raw], - %% Return all the available dep directories for process - {ok, NewConfig, dep_dirs(NonRawAvailableDeps)}. + 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)} + 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