1%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- 2%% ex: ts=4 sw=4 et 3%% ------------------------------------------------------------------- 4%% 5%% rebar: Erlang Build Tools 6%% 7%% Copyright (c) 2009 Dave Smith (dizzyd@dizzyd.com) 8%% 9%% Permission is hereby granted, free of charge, to any person obtaining a copy 10%% of this software and associated documentation files (the "Software"), to deal 11%% in the Software without restriction, including without limitation the rights 12%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13%% copies of the Software, and to permit persons to whom the Software is 14%% furnished to do so, subject to the following conditions: 15%% 16%% The above copyright notice and this permission notice shall be included in 17%% all copies or substantial portions of the Software. 18%% 19%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25%% THE SOFTWARE. 26%% ------------------------------------------------------------------- 27-module(rebar_deps). 28 29-include("rebar.hrl"). 30 31-export([preprocess/2, 32 postprocess/2, 33 compile/2, 34 setup_env/1, 35 'check-deps'/2, 36 'get-deps'/2, 37 'update-deps'/2, 38 'delete-deps'/2, 39 'list-deps'/2]). 40 41%% for internal use only 42-export([info/2]). 43-export([get_deps_dir/1]). 44 45-record(dep, { dir, 46 app, 47 vsn_regex, 48 source, 49 is_raw }). %% is_raw = true means non-Erlang/OTP dependency 50 51%% =================================================================== 52%% Public API 53%% =================================================================== 54 55preprocess(Config, _) -> 56 %% Side effect to set deps_dir globally for all dependencies from 57 %% top level down. Means the root deps_dir is honoured or the default 58 %% used globally since it will be set on the first time through here 59 Config1 = set_shared_deps_dir(Config, get_shared_deps_dir(Config, [])), 60 61 %% Check whether user forced deps resolution via system wide libs 62 Config2 = set_deps_prefer_libs(Config1, get_deps_prefer_libs(Config1, undefined)), 63 64 %% Get the list of deps for the current working directory and identify those 65 %% deps that are available/present. 66 Deps = rebar_config:get_local(Config2, deps, []), 67 {Config3, {AvailableDeps, MissingDeps}} = find_deps(Config2, find, Deps), 68 69 ?DEBUG("Available deps: ~p\n", [AvailableDeps]), 70 ?DEBUG("Missing deps : ~p\n", [MissingDeps]), 71 72 %% Add available deps to code path 73 Config4 = update_deps_code_path(Config3, AvailableDeps), 74 75 %% Filtering out 'raw' dependencies so that no commands other than 76 %% deps-related can be executed on their directories. 77 NonRawAvailableDeps = [D || D <- AvailableDeps, not D#dep.is_raw], 78 79 case rebar_config:get_xconf(Config, current_command, undefined) of 80 'update-deps' -> 81 %% Skip ALL of the dep folders, we do this because we don't want 82 %% any other calls to preprocess() for update-deps beyond the 83 %% toplevel directory. They aren't actually harmful, but they slow 84 %% things down unnecessarily. 85 NewConfig = lists:foldl( 86 fun(D, Acc) -> 87 rebar_config:set_skip_dir(Acc, D#dep.dir) 88 end, 89 Config4, 90 collect_deps(rebar_utils:get_cwd(), Config4)), 91 %% Return the empty list, as we don't want anything processed before 92 %% us. 93 {ok, NewConfig, []}; 94 _ -> 95 %% If skip_deps=true, mark each dep dir as a skip_dir w/ the core 96 %% so that the current command doesn't run on the dep dir. 97 %% However, pre/postprocess WILL run (and we want it to) for 98 %% transitivity purposes. 99 %% 100 %% Also, if skip_deps=comma,separated,app,list, then only the given 101 %% dependencies are skipped. 102 NewConfig = 103 case rebar_config:get_global(Config4, skip_deps, false) of 104 "true" -> 105 lists:foldl( 106 fun(#dep{dir = Dir}, C) -> 107 rebar_config:set_skip_dir(C, Dir) 108 end, Config4, AvailableDeps); 109 Apps when is_list(Apps) -> 110 SkipApps = [list_to_atom(App) || 111 App <- string:tokens(Apps, ",")], 112 lists:foldl( 113 fun(#dep{dir = Dir, app = App}, C) -> 114 case lists:member(App, SkipApps) of 115 true -> rebar_config:set_skip_dir(C, Dir); 116 false -> C 117 end 118 end, Config4, AvailableDeps); 119 _ -> 120 Config4 121 end, 122 123 %% Return all the available dep directories for process 124 {ok, NewConfig, dep_dirs(NonRawAvailableDeps)} 125 end. 126 127postprocess(Config, _) -> 128 case rebar_config:get_xconf(Config, ?MODULE, undefined) of 129 undefined -> 130 {ok, []}; 131 Dirs -> 132 NewConfig = rebar_config:erase_xconf(Config, ?MODULE), 133 {ok, NewConfig, Dirs} 134 end. 135 136compile(Config, _) -> 137 {Config1, _AvailDeps} = do_check_deps(Config), 138 {ok, Config1}. 139 140%% set REBAR_DEPS_DIR and ERL_LIBS environment variables 141setup_env(Config) -> 142 {true, DepsDir} = get_deps_dir(Config), 143 %% include rebar's DepsDir in ERL_LIBS 144 Separator = case os:type() of 145 {win32, nt} -> 146 ";"; 147 _ -> 148 ":" 149 end, 150 ERL_LIBS = case os:getenv("ERL_LIBS") of 151 false -> 152 {"ERL_LIBS", DepsDir}; 153 PrevValue -> 154 {"ERL_LIBS", DepsDir ++ Separator ++ PrevValue} 155 end, 156 [{"REBAR_DEPS_DIR", DepsDir}, ERL_LIBS]. 157 158%% common function used by 'check-deps' and 'compile' 159do_check_deps(Config) -> 160 %% Get the list of immediate (i.e. non-transitive) deps that are missing 161 Deps = rebar_config:get_local(Config, deps, []), 162 case find_deps(Config, find, Deps) of 163 {Config1, {AvailDeps, []}} -> 164 %% No missing deps 165 {Config1, AvailDeps}; 166 {_Config1, {_, MissingDeps}} -> 167 lists:foreach(fun (#dep{app=App, vsn_regex=Vsn, source=Src}) -> 168 ?CONSOLE("Dependency not available: " 169 "~p-~s (~p)\n", [App, Vsn, Src]) 170 end, MissingDeps), 171 ?FAIL 172 end. 173 174'check-deps'(Config, _) -> 175 {Config1, AvailDeps} = do_check_deps(Config), 176 {ok, save_dep_dirs(Config1, AvailDeps)}. 177 178'get-deps'(Config, _) -> 179 %% Determine what deps are available and missing 180 Deps = rebar_config:get_local(Config, deps, []), 181 {Config1, {_AvailableDeps, MissingDeps}} = find_deps(Config, find, Deps), 182 MissingDeps1 = [D || D <- MissingDeps, D#dep.source =/= undefined], 183 184 %% For each missing dep with a specified source, try to pull it. 185 {Config2, PulledDeps} = 186 lists:foldl(fun(D, {C, PulledDeps0}) -> 187 {C1, D1} = use_source(C, D), 188 {C1, [D1 | PulledDeps0]} 189 end, {Config1, []}, MissingDeps1), 190 191 %% Add each pulled dep to our list of dirs for post-processing. This yields 192 %% the necessary transitivity of the deps 193 {ok, save_dep_dirs(Config2, lists:reverse(PulledDeps))}. 194 195'update-deps'(Config, _) -> 196 Config1 = rebar_config:set_xconf(Config, depowner, dict:new()), 197 {Config2, UpdatedDeps} = update_deps_int(Config1, []), 198 DepOwners = rebar_config:get_xconf(Config2, depowner, dict:new()), 199 200 %% check for conflicting deps 201 _ = [?ERROR("Conflicting dependencies for ~p: ~p~n", 202 [K, [{"From: " ++ string:join(dict:fetch(D, DepOwners), ", "), 203 {D#dep.vsn_regex, D#dep.source}} || D <- V]]) 204 || {K, V} <- dict:to_list( 205 lists:foldl( 206 fun(Dep, Acc) -> 207 dict:append(Dep#dep.app, Dep, Acc) 208 end, dict:new(), UpdatedDeps)), 209 length(V) > 1], 210 211 %% Add each updated dep to our list of dirs for post-processing. This yields 212 %% the necessary transitivity of the deps 213 {ok, save_dep_dirs(Config, UpdatedDeps)}. 214 215'delete-deps'(Config, _) -> 216 %% Delete all the available deps in our deps/ directory, if any 217 {true, DepsDir} = get_deps_dir(Config), 218 Deps = rebar_config:get_local(Config, deps, []), 219 {Config1, {AvailableDeps, _}} = find_deps(Config, find, Deps), 220 _ = [delete_dep(D) 221 || D <- AvailableDeps, 222 lists:prefix(DepsDir, D#dep.dir)], 223 {ok, Config1}. 224 225'list-deps'(Config, _) -> 226 Deps = rebar_config:get_local(Config, deps, []), 227 case find_deps(Config, find, Deps) of 228 {Config1, {AvailDeps, []}} -> 229 lists:foreach(fun(Dep) -> print_source(Dep) end, AvailDeps), 230 {ok, save_dep_dirs(Config1, AvailDeps)}; 231 {_, MissingDeps} -> 232 ?ABORT("Missing dependencies: ~p\n", [MissingDeps]) 233 end. 234 235%% =================================================================== 236%% Internal functions 237%% =================================================================== 238 239info(help, compile) -> 240 info_help("Display to be fetched dependencies"); 241info(help, 'check-deps') -> 242 info_help("Display to be fetched dependencies"); 243info(help, 'get-deps') -> 244 info_help("Fetch dependencies"); 245info(help, 'update-deps') -> 246 info_help("Update fetched dependencies"); 247info(help, 'delete-deps') -> 248 info_help("Delete fetched dependencies"); 249info(help, 'list-deps') -> 250 info_help("List dependencies"). 251 252info_help(Description) -> 253 ?CONSOLE( 254 "~s.~n" 255 "~n" 256 "Valid rebar.config options:~n" 257 " ~p~n" 258 " ~p~n" 259 "Valid command line options:~n" 260 " deps_dir=\"deps\" (override default or rebar.config deps_dir)~n" 261 "Environment variables:~n" 262 " REBAR_DEPS_PREFER_LIBS to look for dependecies in system libs prior fetching.~n", 263 [ 264 Description, 265 {deps_dir, "deps"}, 266 {deps, 267 [app_name, 268 {rebar, "1.0.*"}, 269 {rebar, ".*", 270 {git, "git://github.com/rebar/rebar.git"}}, 271 {rebar, 272 {git, "git://github.com/rebar/rebar.git"}}, 273 {rebar, ".*", 274 {git, "git://github.com/rebar/rebar.git", "Rev"}}, 275 {rebar, 276 {git, "git://github.com/rebar/rebar.git", "Rev"}}, 277 {rebar, "1.0.*", 278 {git, "git://github.com/rebar/rebar.git", {branch, "master"}}}, 279 {rebar, 280 {git, "git://github.com/rebar/rebar.git", {branch, "master"}}}, 281 {rebar, "1.0.0", 282 {git, "git://github.com/rebar/rebar.git", {tag, "1.0.0"}}}, 283 {rebar, "", 284 {git, "git://github.com/rebar/rebar.git", {branch, "master"}}, 285 [raw]}, 286 {rebar, 287 {git, "git://github.com/rebar/rebar.git", {branch, "master"}}, 288 [raw]}, 289 {app_name, ".*", {hg, "https://www.example.org/url"}}, 290 {app_name, ".*", {rsync, "Url"}}, 291 {app_name, ".*", {svn, "https://www.example.org/url"}}, 292 {app_name, ".*", {svn, "svn://svn.example.org/url"}}, 293 {app_name, ".*", {bzr, "https://www.example.org/url", "Rev"}}, 294 {app_name, ".*", {fossil, "https://www.example.org/url"}}, 295 {app_name, ".*", {fossil, "https://www.example.org/url", "Vsn"}}, 296 {app_name, ".*", {p4, "//depot/subdir/app_dir"}}]} 297 ]). 298 299%% Added because of trans deps, 300%% need all deps in same dir and should be the one set by the root rebar.config 301%% In case one is given globally, it has higher priority 302%% Sets a default if root config has no deps_dir set 303set_shared_deps_dir(Config, []) -> 304 LocalDepsDir = rebar_config:get_local(Config, deps_dir, "deps"), 305 GlobalDepsDir = rebar_config:get_global(Config, deps_dir, LocalDepsDir), 306 DepsDir = case os:getenv("REBAR_DEPS_DIR") of 307 false -> 308 GlobalDepsDir; 309 Dir -> 310 Dir 311 end, 312 rebar_config:set_xconf(Config, deps_dir, DepsDir); 313set_shared_deps_dir(Config, _DepsDir) -> 314 Config. 315 316get_shared_deps_dir(Config, Default) -> 317 rebar_config:get_xconf(Config, deps_dir, Default). 318 319set_deps_prefer_libs(Config, undefined) -> 320 DepsPreferLibs = case os:getenv("REBAR_DEPS_PREFER_LIBS") of 321 false -> false; 322 _ -> true 323 end, 324 rebar_config:set_xconf(Config, deps_prefer_libs, DepsPreferLibs); 325set_deps_prefer_libs(Config, _DepsPreferLibs) -> 326 Config. 327 328get_deps_prefer_libs(Config, Default) -> 329 rebar_config:get_xconf(Config, deps_prefer_libs, Default). 330 331get_deps_dir(Config) -> 332 get_deps_dir(Config, ""). 333 334get_deps_dir(Config, App) -> 335 BaseDir = rebar_utils:base_dir(Config), 336 DepsDir0 = get_shared_deps_dir(Config, "deps"), 337 DepsDir = filename:dirname(filename:join([BaseDir, DepsDir0, "dummy"])), 338 {true, filename:join([DepsDir, App])}. 339 340dep_dirs(Deps) -> 341 [D#dep.dir || D <- Deps]. 342 343save_dep_dirs(Config, Deps) -> 344 rebar_config:set_xconf(Config, ?MODULE, dep_dirs(Deps)). 345 346get_lib_dir(App) -> 347 %% Find App amongst the reachable lib directories 348 %% Returns either the found path or a tagged tuple with a boolean 349 %% to match get_deps_dir's return type 350 case code:lib_dir(App) of 351 {error, bad_name} -> {false, bad_name}; 352 Path -> {true, Path} 353 end. 354 355update_deps_code_path(Config, []) -> 356 Config; 357update_deps_code_path(Config, [Dep | Rest]) -> 358 Config2 = 359 case is_app_available(Config, Dep#dep.app, 360 Dep#dep.vsn_regex, Dep#dep.dir, Dep#dep.is_raw) of 361 {Config1, {true, _}} -> 362 Dir = filename:join(Dep#dep.dir, "ebin"), 363 ok = filelib:ensure_dir(filename:join(Dir, "dummy")), 364 ?DEBUG("Adding ~s to code path~n", [Dir]), 365 true = code:add_patha(Dir), 366 Config1; 367 {Config1, {false, _}} -> 368 Config1 369 end, 370 update_deps_code_path(Config2, Rest). 371 372find_deps(Config, find=Mode, Deps) -> 373 find_deps(Config, Mode, Deps, {[], []}); 374find_deps(Config, read=Mode, Deps) -> 375 find_deps(Config, Mode, Deps, []). 376 377find_deps(Config, find, [], {Avail, Missing}) -> 378 {Config, {lists:reverse(Avail), lists:reverse(Missing)}}; 379find_deps(Config, read, [], Deps) -> 380 {Config, lists:reverse(Deps)}; 381find_deps(Config, Mode, [App | Rest], Acc) when is_atom(App) -> 382 find_deps(Config, Mode, [{App, ".*", undefined} | Rest], Acc); 383find_deps(Config, Mode, [{App, Source} | Rest], Acc) when is_tuple(Source) -> 384 find_deps(Config, Mode, [{App, ".*", Source} | Rest], Acc); 385find_deps(Config, Mode, [{App, VsnRegex} | Rest], Acc) when is_atom(App) -> 386 find_deps(Config, Mode, [{App, VsnRegex, undefined} | Rest], Acc); 387find_deps(Config, Mode, [{App, Source, Opts} | Rest], Acc) when is_tuple(Source) -> 388 find_deps(Config, Mode, [{App, ".*", Source, Opts} | Rest], Acc); 389find_deps(Config, Mode, [{App, VsnRegex, Source} | Rest], Acc) -> 390 find_deps(Config, Mode, [{App, VsnRegex, Source, []} | Rest], Acc); 391find_deps(Config, Mode, [{App, VsnRegex, Source, Opts} | Rest], Acc) 392 when is_list(Opts) -> 393 Dep = #dep { app = App, 394 vsn_regex = VsnRegex, 395 source = Source, 396 %% dependency is considered raw (i.e. non-Erlang/OTP) when 397 %% 'raw' option is present 398 is_raw = proplists:get_value(raw, Opts, false) }, 399 {Config1, {Availability, FoundDir}} = find_dep(Config, Dep), 400 find_deps(Config1, Mode, Rest, 401 acc_deps(Mode, Availability, Dep, FoundDir, Acc)); 402find_deps(_Config, _Mode, [Other | _Rest], _Acc) -> 403 ?ABORT("Invalid dependency specification ~p in ~s\n", 404 [Other, rebar_utils:get_cwd()]). 405 406find_dep(Config, Dep) -> 407 %% Find a dep based on its source, 408 %% e.g. {git, "https://github.com/mochi/mochiweb.git", "HEAD"} 409 %% Deps with a source must be found (or fetched) locally. 410 %% Those without a source may be satisfied from lib dir (get_lib_dir). 411 DepsPreferLibs = get_deps_prefer_libs(Config, false), 412 Mode = case {Dep#dep.source, DepsPreferLibs} of 413 {undefined, _DepsPreferLibs} -> maybe_in_lib; 414 {_DepSource, true} -> maybe_in_lib; 415 {_DepSource, false} -> local_only 416 end, 417 find_dep(Config, Dep, Mode). 418 419find_dep(Config, Dep, maybe_in_lib) -> 420 %% 'source' is undefined. If Dep is not satisfied locally, 421 %% go ahead and find it amongst the lib_dir's. 422 case find_dep_in_dir(Config, Dep, get_deps_dir(Config, Dep#dep.app)) of 423 {_Config1, {avail, _Dir}} = Avail -> 424 Avail; 425 {Config1, {missing, _}} -> 426 find_dep_in_dir(Config1, Dep, get_lib_dir(Dep#dep.app)) 427 end; 428find_dep(Config, Dep, local_only) -> 429 %% _Source is defined. Regardless of what it is, we must find it 430 %% locally satisfied or fetch it from the original source 431 %% into the project's deps 432 find_dep_in_dir(Config, Dep, get_deps_dir(Config, Dep#dep.app)). 433 434find_dep_in_dir(Config, _Dep, {false, Dir}) -> 435 {Config, {missing, Dir}}; 436find_dep_in_dir(Config, Dep, {true, Dir}) -> 437 App = Dep#dep.app, 438 VsnRegex = Dep#dep.vsn_regex, 439 IsRaw = Dep#dep.is_raw, 440 case is_app_available(Config, App, VsnRegex, Dir, IsRaw) of 441 {Config1, {true, _AppFile}} -> {Config1, {avail, Dir}}; 442 {Config1, {false, _}} -> {Config1, {missing, Dir}} 443 end. 444 445acc_deps(find, avail, Dep, AppDir, {Avail, Missing}) -> 446 {[Dep#dep { dir = AppDir } | Avail], Missing}; 447acc_deps(find, missing, Dep, AppDir, {Avail, Missing}) -> 448 {Avail, [Dep#dep { dir = AppDir } | Missing]}; 449acc_deps(read, _, Dep, AppDir, Acc) -> 450 [Dep#dep { dir = AppDir } | Acc]. 451 452delete_dep(D) -> 453 case filelib:is_dir(D#dep.dir) of 454 true -> 455 ?INFO("Deleting dependency: ~s\n", [D#dep.dir]), 456 rebar_file_utils:rm_rf(D#dep.dir); 457 false -> 458 ok 459 end. 460 461require_source_engine(Source) -> 462 true = source_engine_avail(Source), 463 ok. 464 465%% IsRaw = false means regular Erlang/OTP dependency 466%% 467%% IsRaw = true means non-Erlang/OTP dependency, e.g. the one that does not 468%% have a proper .app file 469is_app_available(Config, App, VsnRegex, Path, _IsRaw = false) -> 470 ?DEBUG("is_app_available, looking for App ~p with Path ~p~n", [App, Path]), 471 case rebar_app_utils:is_app_dir(Path) of 472 {true, AppFile} -> 473 case rebar_app_utils:app_name(Config, AppFile) of 474 {Config1, App} -> 475 {Config2, Vsn} = rebar_app_utils:app_vsn(Config1, AppFile), 476 ?INFO("Looking for ~s-~s ; found ~s-~s at ~s\n", 477 [App, VsnRegex, App, Vsn, Path]), 478 case re:run(Vsn, VsnRegex, [{capture, none}]) of 479 match -> 480 {Config2, {true, Path}}; 481 nomatch -> 482 ?WARN("~s has version ~p; requested regex was ~s\n", 483 [AppFile, Vsn, VsnRegex]), 484 {Config2, 485 {false, {version_mismatch, 486 {AppFile, 487 {expected, VsnRegex}, {has, Vsn}}}}} 488 end; 489 {Config1, OtherApp} -> 490 ?WARN("~s has application id ~p; expected ~p\n", 491 [AppFile, OtherApp, App]), 492 {Config1, 493 {false, {name_mismatch, 494 {AppFile, {expected, App}, {has, OtherApp}}}}} 495 end; 496 false -> 497 case filelib:is_dir(Path) of 498 true -> 499 %% Path is a directory, but it's not an app dir. 500 ?WARN("Directory expected to be an app dir, but no " 501 "app file found ~n" 502 "in ebin/ or src/:~n~s~n", 503 [Path]); 504 false -> 505 %% Path is not a directory, so it cannot be an app dir. 506 %% TODO: maybe we can avoid checking non-existing dirs 507 ?DEBUG("Directory expected to be an app dir, " 508 "but it doesn't exist (yet?):~n~s~n", [Path]) 509 end, 510 {Config, {false, {missing_app_file, Path}}} 511 end; 512is_app_available(Config, App, _VsnRegex, Path, _IsRaw = true) -> 513 ?DEBUG("is_app_available, looking for Raw Depencency ~p with Path ~p~n", 514 [App, Path]), 515 case filelib:is_dir(Path) of 516 true -> 517 %% TODO: look for version string in <Path>/VERSION file? Not clear 518 %% how to detect git/svn/hg/{cmd, ...} settings that can be passed 519 %% to rebar_utils:vcs_vsn/2 to obtain version dynamically 520 {Config, {true, Path}}; 521 false -> 522 ?WARN("Expected ~s to be a raw dependency directory, " 523 "but no directory found.\n", [Path]), 524 {Config, {false, {missing_raw_dependency_directory, Path}}} 525 end. 526 527use_source(Config, Dep) -> 528 use_source(Config, Dep, 3). 529 530use_source(_Config, Dep, 0) -> 531 ?ABORT("Failed to acquire source from ~p after 3 tries.\n", 532 [Dep#dep.source]); 533use_source(Config, Dep, Count) -> 534 case filelib:is_dir(Dep#dep.dir) of 535 true -> 536 %% Already downloaded -- verify the versioning matches the regex 537 case is_app_available(Config, Dep#dep.app, Dep#dep.vsn_regex, 538 Dep#dep.dir, Dep#dep.is_raw) of 539 {Config1, {true, _}} -> 540 Dir = filename:join(Dep#dep.dir, "ebin"), 541 ok = filelib:ensure_dir(filename:join(Dir, "dummy")), 542 %% Available version matches up -- we're good to go; 543 %% add the app dir to our code path 544 true = code:add_patha(Dir), 545 {Config1, Dep}; 546 {_Config1, {false, Reason}} -> 547 %% The app that was downloaded doesn't match up (or had 548 %% errors or something). For the time being, abort. 549 ?ABORT("Dependency dir ~s failed application validation " 550 "with reason:~n~p.\n", [Dep#dep.dir, Reason]) 551 end; 552 false -> 553 ?CONSOLE("Pulling ~p from ~p\n", [Dep#dep.app, Dep#dep.source]), 554 require_source_engine(Dep#dep.source), 555 {true, TargetDir} = get_deps_dir(Config, Dep#dep.app), 556 download_source(TargetDir, Dep#dep.source), 557 use_source(Config, Dep#dep { dir = TargetDir }, Count-1) 558 end. 559 560-record(p4_settings, { 561 client=undefined, 562 transport="tcp4:perforce:1666", 563 username, 564 password 565 }). 566init_p4_settings(Basename) -> 567 #p4_settings{client = 568 case inet:gethostname() of 569 {ok,HostName} -> 570 HostName ++ "-" 571 ++ os:getenv("USER") ++ "-" 572 ++ Basename 573 ++ "-Rebar-automated-download" 574 end}. 575 576download_source(AppDir, {p4, Url}) -> 577 download_source(AppDir, {p4, Url, "#head"}); 578download_source(AppDir, {p4, Url, Rev}) -> 579 download_source(AppDir, {p4, Url, Rev, init_p4_settings(filename:basename(AppDir))}); 580download_source(AppDir, {p4, Url, _Rev, Settings}) -> 581 ok = filelib:ensure_dir(AppDir), 582 rebar_utils:sh_send("p4 client -i", 583 ?FMT("Client: ~s~n" 584 ++"Description: generated by Rebar~n" 585 ++"Root: ~s~n" 586 ++"View:~n" 587 ++" ~s/... //~s/...~n", 588 [Settings#p4_settings.client, 589 AppDir, 590 Url, 591 Settings#p4_settings.client]), 592 []), 593 rebar_utils:sh(?FMT("p4 -c ~s sync -f", [Settings#p4_settings.client]), []); 594download_source(AppDir, {hg, Url, Rev}) -> 595 ok = filelib:ensure_dir(AppDir), 596 rebar_utils:sh(?FMT("hg clone -U ~s ~s", [Url, filename:basename(AppDir)]), 597 [{cd, filename:dirname(AppDir)}]), 598 rebar_utils:sh(?FMT("hg update ~s", [Rev]), [{cd, AppDir}]); 599download_source(AppDir, {git, Url}) -> 600 download_source(AppDir, {git, Url, {branch, "HEAD"}}); 601download_source(AppDir, {git, Url, ""}) -> 602 download_source(AppDir, {git, Url, {branch, "HEAD"}}); 603download_source(AppDir, {git, Url, {branch, Branch}}) -> 604 ok = filelib:ensure_dir(AppDir), 605 rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(AppDir)]), 606 [{cd, filename:dirname(AppDir)}]), 607 rebar_utils:sh(?FMT("git checkout -q origin/~s", [Branch]), [{cd, AppDir}]); 608download_source(AppDir, {git, Url, {tag, Tag}}) -> 609 ok = filelib:ensure_dir(AppDir), 610 rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(AppDir)]), 611 [{cd, filename:dirname(AppDir)}]), 612 rebar_utils:sh(?FMT("git checkout -q ~s", [Tag]), [{cd, AppDir}]); 613download_source(AppDir, {git, Url, Rev}) -> 614 ok = filelib:ensure_dir(AppDir), 615 rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(AppDir)]), 616 [{cd, filename:dirname(AppDir)}]), 617 rebar_utils:sh(?FMT("git checkout -q ~s", [Rev]), [{cd, AppDir}]); 618download_source(AppDir, {bzr, Url, Rev}) -> 619 ok = filelib:ensure_dir(AppDir), 620 rebar_utils:sh(?FMT("bzr branch -r ~s ~s ~s", 621 [Rev, Url, filename:basename(AppDir)]), 622 [{cd, filename:dirname(AppDir)}]); 623download_source(AppDir, {svn, Url, Rev}) -> 624 ok = filelib:ensure_dir(AppDir), 625 rebar_utils:sh(?FMT("svn checkout -r ~s ~s ~s", 626 [Rev, Url, filename:basename(AppDir)]), 627 [{cd, filename:dirname(AppDir)}]); 628download_source(AppDir, {rsync, Url}) -> 629 ok = filelib:ensure_dir(AppDir), 630 rebar_utils:sh(?FMT("rsync -az --delete ~s/ ~s", [Url, AppDir]), []); 631download_source(AppDir, {fossil, Url}) -> 632 download_source(AppDir, {fossil, Url, ""}); 633download_source(AppDir, {fossil, Url, Version}) -> 634 Repository = filename:join(AppDir, filename:basename(AppDir) ++ ".fossil"), 635 ok = filelib:ensure_dir(Repository), 636 ok = file:set_cwd(AppDir), 637 rebar_utils:sh(?FMT("fossil clone ~s ~s", [Url, Repository]), 638 [{cd, AppDir}]), 639 rebar_utils:sh(?FMT("fossil open ~s ~s --nested", [Repository, Version]), 640 []). 641 642update_source(Config, Dep) -> 643 %% It's possible when updating a source, that a given dep does not have a 644 %% VCS directory, such as when a source archive is built of a project, with 645 %% all deps already downloaded/included. So, verify that the necessary VCS 646 %% directory exists before attempting to do the update. 647 {true, AppDir} = get_deps_dir(Config, Dep#dep.app), 648 case has_vcs_dir(element(1, Dep#dep.source), AppDir) of 649 true -> 650 ?CONSOLE("Updating ~p from ~p\n", [Dep#dep.app, Dep#dep.source]), 651 require_source_engine(Dep#dep.source), 652 update_source1(AppDir, Dep#dep.source), 653 Dep; 654 false -> 655 ?WARN("Skipping update for ~p: " 656 "no VCS directory available!\n", [Dep]), 657 Dep 658 end. 659 660update_source1(AppDir, Args) when element(1, Args) =:= p4 -> 661 download_source(AppDir, Args); 662update_source1(AppDir, {git, Url}) -> 663 update_source1(AppDir, {git, Url, {branch, "HEAD"}}); 664update_source1(AppDir, {git, Url, ""}) -> 665 update_source1(AppDir, {git, Url, {branch, "HEAD"}}); 666update_source1(AppDir, {git, _Url, {branch, Branch}}) -> 667 ShOpts = [{cd, AppDir}], 668 rebar_utils:sh("git fetch origin", ShOpts), 669 rebar_utils:sh(?FMT("git checkout -q ~s", [Branch]), ShOpts), 670 rebar_utils:sh( 671 ?FMT("git pull --ff-only --no-rebase -q origin ~s", [Branch]),ShOpts); 672update_source1(AppDir, {git, _Url, {tag, Tag}}) -> 673 ShOpts = [{cd, AppDir}], 674 rebar_utils:sh("git fetch origin", ShOpts), 675 rebar_utils:sh(?FMT("git checkout -q ~s", [Tag]), ShOpts); 676update_source1(AppDir, {git, _Url, Refspec}) -> 677 ShOpts = [{cd, AppDir}], 678 rebar_utils:sh("git fetch origin", ShOpts), 679 rebar_utils:sh(?FMT("git checkout -q ~s", [Refspec]), ShOpts); 680update_source1(AppDir, {svn, _Url, Rev}) -> 681 rebar_utils:sh(?FMT("svn up -r ~s", [Rev]), [{cd, AppDir}]); 682update_source1(AppDir, {hg, _Url, Rev}) -> 683 rebar_utils:sh(?FMT("hg pull -u -r ~s", [Rev]), [{cd, AppDir}]); 684update_source1(AppDir, {bzr, _Url, Rev}) -> 685 rebar_utils:sh(?FMT("bzr update -r ~s", [Rev]), [{cd, AppDir}]); 686update_source1(AppDir, {rsync, Url}) -> 687 rebar_utils:sh(?FMT("rsync -az --delete ~s/ ~s",[Url,AppDir]),[]); 688update_source1(AppDir, {fossil, Url}) -> 689 update_source1(AppDir, {fossil, Url, ""}); 690update_source1(AppDir, {fossil, _Url, Version}) -> 691 ok = file:set_cwd(AppDir), 692 rebar_utils:sh("fossil pull", [{cd, AppDir}]), 693 rebar_utils:sh(?FMT("fossil update ~s", [Version]), []). 694 695%% Recursively update deps, this is not done via rebar's usual dep traversal as 696%% that is the wrong order (tips are updated before branches). Instead we do a 697%% traverse the deps at each level completely before traversing *their* deps. 698%% This allows updates to actually propogate down the tree, rather than fail to 699%% flow up the tree, which was the previous behaviour. 700update_deps_int(Config0, UDD) -> 701 %% Determine what deps are required 702 ConfDir = filename:basename(rebar_utils:get_cwd()), 703 RawDeps = rebar_config:get_local(Config0, deps, []), 704 {Config1, Deps} = find_deps(Config0, read, RawDeps), 705 706 %% Update each dep 707 UpdatedDeps = [update_source(Config1, D) 708 || D <- Deps, D#dep.source =/= undefined, 709 not lists:member(D, UDD), 710 not should_skip_update_dep(Config1, D) 711 ], 712 713 lists:foldl(fun(Dep, {Config, Updated}) -> 714 {true, AppDir} = get_deps_dir(Config, Dep#dep.app), 715 Config2 = case has_vcs_dir(element(1, Dep#dep.source), 716 AppDir) of 717 false -> 718 %% If the dep did not exist (maybe it 719 %% was added), clone it. 720 %% We'll traverse ITS deps below and 721 %% clone them if needed. 722 {C1, _D1} = use_source(Config, Dep), 723 C1; 724 true -> 725 Config 726 end, 727 ok = file:set_cwd(AppDir), 728 Config3 = rebar_config:new(Config2), 729 %% track where a dep comes from... 730 DepOwner = dict:append( 731 Dep, ConfDir, 732 rebar_config:get_xconf(Config3, depowner, 733 dict:new())), 734 Config4 = rebar_config:set_xconf(Config3, depowner, 735 DepOwner), 736 737 {Config5, Res} = update_deps_int(Config4, Updated), 738 {Config5, lists:umerge(lists:sort(Res), 739 lists:sort(Updated))} 740 end, {Config1, lists:umerge(lists:sort(UpdatedDeps), 741 lists:sort(UDD))}, UpdatedDeps). 742 743should_skip_update_dep(Config, Dep) -> 744 {true, AppDir} = get_deps_dir(Config, Dep#dep.app), 745 case rebar_app_utils:is_app_dir(AppDir) of 746 false -> 747 false; 748 {true, AppFile} -> 749 case rebar_app_utils:is_skipped_app(Config, AppFile) of 750 {_Config, {true, _SkippedApp}} -> 751 true; 752 _ -> 753 false 754 end 755 end. 756 757%% Recursively walk the deps and build a list of them. 758collect_deps(Dir, C) -> 759 case file:set_cwd(Dir) of 760 ok -> 761 Config = rebar_config:new(C), 762 RawDeps = rebar_config:get_local(Config, deps, []), 763 {Config1, Deps} = find_deps(Config, read, RawDeps), 764 765 lists:flatten(Deps ++ [begin 766 {true, AppDir} = get_deps_dir( 767 Config1, Dep#dep.app), 768 collect_deps(AppDir, C) 769 end || Dep <- Deps]); 770 _ -> 771 [] 772 end. 773 774 775%% =================================================================== 776%% Source helper functions 777%% =================================================================== 778 779source_engine_avail(Source) -> 780 Name = element(1, Source), 781 source_engine_avail(Name, Source). 782 783source_engine_avail(Name, Source) 784 when Name == hg; Name == git; Name == svn; Name == bzr; Name == rsync; 785 Name == fossil; Name == p4 -> 786 case vcs_client_vsn(Name) >= required_vcs_client_vsn(Name) of 787 true -> 788 true; 789 false -> 790 ?ABORT("Rebar requires version ~p or higher of ~s to process ~p\n", 791 [required_vcs_client_vsn(Name), Name, Source]) 792 end. 793 794vcs_client_vsn(false, _VsnArg, _VsnRegex) -> 795 false; 796vcs_client_vsn(Path, VsnArg, VsnRegex) -> 797 {ok, Info} = rebar_utils:sh(Path ++ VsnArg, [{env, [{"LANG", "C"}]}, 798 {use_stdout, false}]), 799 case re:run(Info, VsnRegex, [{capture, all_but_first, list}]) of 800 {match, Match} -> 801 list_to_tuple([list_to_integer(S) || S <- Match]); 802 _ -> 803 false 804 end. 805 806required_vcs_client_vsn(p4) -> {2013, 1}; 807required_vcs_client_vsn(hg) -> {1, 1}; 808required_vcs_client_vsn(git) -> {1, 5}; 809required_vcs_client_vsn(bzr) -> {2, 0}; 810required_vcs_client_vsn(svn) -> {1, 6}; 811required_vcs_client_vsn(rsync) -> {2, 0}; 812required_vcs_client_vsn(fossil) -> {1, 0}. 813 814vcs_client_vsn(p4) -> 815 vcs_client_vsn(rebar_utils:find_executable("p4"), " -V", 816 "Rev\\. .*/(\\d+)\\.(\\d)/"); 817vcs_client_vsn(hg) -> 818 vcs_client_vsn(rebar_utils:find_executable("hg"), " --version", 819 "version (\\d+).(\\d+)"); 820vcs_client_vsn(git) -> 821 vcs_client_vsn(rebar_utils:find_executable("git"), " --version", 822 "git version (\\d+).(\\d+)"); 823vcs_client_vsn(bzr) -> 824 vcs_client_vsn(rebar_utils:find_executable("bzr"), " --version", 825 "Bazaar \\(bzr\\) (\\d+).(\\d+)"); 826vcs_client_vsn(svn) -> 827 vcs_client_vsn(rebar_utils:find_executable("svn"), " --version", 828 "svn, version (\\d+).(\\d+)"); 829vcs_client_vsn(rsync) -> 830 vcs_client_vsn(rebar_utils:find_executable("rsync"), " --version", 831 "rsync version (\\d+).(\\d+)"); 832vcs_client_vsn(fossil) -> 833 vcs_client_vsn(rebar_utils:find_executable("fossil"), " version", 834 "version (\\d+).(\\d+)"). 835 836has_vcs_dir(p4, _) -> 837 true; 838has_vcs_dir(git, Dir) -> 839 filelib:is_dir(filename:join(Dir, ".git")); 840has_vcs_dir(hg, Dir) -> 841 filelib:is_dir(filename:join(Dir, ".hg")); 842has_vcs_dir(bzr, Dir) -> 843 filelib:is_dir(filename:join(Dir, ".bzr")); 844has_vcs_dir(svn, Dir) -> 845 filelib:is_dir(filename:join(Dir, ".svn")) 846 orelse filelib:is_dir(filename:join(Dir, "_svn")); 847has_vcs_dir(rsync, _) -> 848 true; 849has_vcs_dir(_, _) -> 850 true. 851 852print_source(#dep{app=App, source=Source}) -> 853 ?CONSOLE("~s~n", [format_source(App, Source)]). 854 855format_source(App, {p4, Url}) -> 856 format_source(App, {p4, Url, "#head"}); 857format_source(App, {git, Url}) -> 858 ?FMT("~p BRANCH ~s ~s", [App, "HEAD", Url]); 859format_source(App, {git, Url, ""}) -> 860 ?FMT("~p BRANCH ~s ~s", [App, "HEAD", Url]); 861format_source(App, {git, Url, {branch, Branch}}) -> 862 ?FMT("~p BRANCH ~s ~s", [App, Branch, Url]); 863format_source(App, {git, Url, {tag, Tag}}) -> 864 ?FMT("~p TAG ~s ~s", [App, Tag, Url]); 865format_source(App, {_, Url, Rev}) -> 866 ?FMT("~p REV ~s ~s", [App, Rev, Url]); 867format_source(App, {SrcType, Url}) -> 868 ?FMT("~p ~p ~s", [App, SrcType, Url]); 869format_source(App, undefined) -> 870 ?FMT("~p", [App]). 871