diff --git a/THANKS b/THANKS index e20f433..80bc830 100644 --- a/THANKS +++ b/THANKS @@ -43,3 +43,4 @@ Jesper Louis Andersen Richard Jones Tim Watson Anders 'andekar' +Christopher Brown diff --git a/include/rebar.hrl b/include/rebar.hrl index d5eaff5..66d6318 100644 --- a/include/rebar.hrl +++ b/include/rebar.hrl @@ -15,4 +15,3 @@ -define(ERROR(Str, Args), rebar_log:log(error, Str, Args)). -define(FMT(Str, Args), lists:flatten(io_lib:format(Str, Args))). - diff --git a/inttest/ct2/ct2_rt.erl b/inttest/ct2/ct2_rt.erl new file mode 100644 index 0000000..2b14ff9 --- /dev/null +++ b/inttest/ct2/ct2_rt.erl @@ -0,0 +1,26 @@ +-module(ct2_rt). + +-compile(export_all). + + +files() -> + [{create, "ebin/foo.app", app(foo)}, + {copy, "../../rebar", "rebar"}, + {copy, "foo.test.spec", "test/foo.test.spec"}, + {copy, "foo_SUITE.erl", "test/foo_SUITE.erl"}]. + +run(_Dir) -> + {ok, _} = retest:sh("./rebar compile ct -v"), + ok. + +%% +%% Generate the contents of a simple .app file +%% +app(Name) -> + App = {application, Name, + [{description, atom_to_list(Name)}, + {vsn, "1"}, + {modules, []}, + {registered, []}, + {applications, [kernel, stdlib]}]}, + io_lib:format("~p.\n", [App]). diff --git a/inttest/ct2/foo.test.spec b/inttest/ct2/foo.test.spec new file mode 100644 index 0000000..f3e4cb0 --- /dev/null +++ b/inttest/ct2/foo.test.spec @@ -0,0 +1 @@ +{suites, "test", all}. diff --git a/inttest/ct2/foo_SUITE.erl b/inttest/ct2/foo_SUITE.erl new file mode 100644 index 0000000..d03aedf --- /dev/null +++ b/inttest/ct2/foo_SUITE.erl @@ -0,0 +1,10 @@ +-module(foo_SUITE). + +-include_lib("common_test/include/ct.hrl"). + +-compile(export_all). + +all() -> [foo]. + +foo(Config) -> + io:format("Test: ~p\n", [Config]). diff --git a/src/rebar_appups.erl b/src/rebar_appups.erl index ab5af29..079d596 100644 --- a/src/rebar_appups.erl +++ b/src/rebar_appups.erl @@ -55,14 +55,8 @@ NewName == OldName, "Reltool and .rel release names do not match~n", []), - %% Get lists of the old and new app files - OldAppFiles = rebar_utils:find_files( - filename:join([OldVerPath, "lib"]), "^.*.app$"), - NewAppFiles = rebar_utils:find_files( - filename:join([NewName, "lib"]), "^.*.app$"), - %% Find all the apps that have been upgraded - UpgradedApps = get_upgraded_apps(OldAppFiles, NewAppFiles), + UpgradedApps = get_upgraded_apps(Name, OldVerPath, NewVerPath), %% Get a list of any appup files that exist in the new release NewAppUpFiles = rebar_utils:find_files( @@ -85,18 +79,24 @@ %% Internal functions %% =================================================================== -get_upgraded_apps(OldAppFiles, NewAppFiles) -> - OldAppsVer = [{rebar_app_utils:app_name(AppFile), - rebar_app_utils:app_vsn(AppFile)} || AppFile <- OldAppFiles], - NewAppsVer = [{rebar_app_utils:app_name(AppFile), - rebar_app_utils:app_vsn(AppFile)} || AppFile <- NewAppFiles], - UpgradedApps = lists:subtract(NewAppsVer, OldAppsVer), - lists:map( - fun({App, NewVer}) -> - {App, OldVer} = proplists:lookup(App, OldAppsVer), - {App, {OldVer, NewVer}} - end, - UpgradedApps). +get_upgraded_apps(Name, OldVerPath, NewVerPath) -> + OldApps = rebar_rel_utils:get_rel_apps(Name, OldVerPath), + NewApps = rebar_rel_utils:get_rel_apps(Name, NewVerPath), + + Sorted = lists:umerge(lists:sort(NewApps), lists:sort(OldApps)), + AddedorChanged = lists:subtract(Sorted, OldApps), + DeletedorChanged = lists:subtract(Sorted, NewApps), + ?DEBUG("Added or Changed: ~p~n", [AddedorChanged]), + ?DEBUG("Deleted or Changed: ~p~n", [DeletedorChanged]), + + AddedDeletedChanged = lists:ukeysort(1, lists:append(DeletedorChanged, + AddedorChanged)), + UpgradedApps = lists:subtract(AddedorChanged, AddedDeletedChanged), + ?DEBUG("Upgraded Apps:~p~n", [UpgradedApps]), + + [{AppName, {proplists:get_value(AppName, OldApps), NewVer}} + || {AppName, NewVer} <- UpgradedApps]. + file_to_name(File) -> filename:rootname(filename:basename(File)). diff --git a/src/rebar_core.erl b/src/rebar_core.erl index db3e0b4..d92af34 100644 --- a/src/rebar_core.erl +++ b/src/rebar_core.erl @@ -76,6 +76,10 @@ process_commands([Command | Rest]) -> lists:foreach(fun (D) -> erlang:erase({skip_dir, D}) end, skip_dirs()), Operations = erlang:get(operations), + %% Convert the code path so that all the entries are absolute paths. + %% If not, code:set_path() may choke on invalid relative paths when trying + %% to restore the code path from inside a subdirectory. + true = rebar_utils:expand_code_path(), _ = process_dir(rebar_utils:get_cwd(), rebar_config:new(), Command, sets:new()), case erlang:get(operations) of @@ -280,8 +284,8 @@ expand_lib_dirs([Dir | Rest], Root, Acc) -> select_modules([], _Command, Acc) -> lists:reverse(Acc); select_modules([Module | Rest], Command, Acc) -> - Exports = Module:module_info(exports), - case lists:member({Command, 2}, Exports) of + {module, Module} = code:ensure_loaded(Module), + case erlang:function_exported(Module, Command, 2) of true -> select_modules(Rest, Command, [Module | Acc]); false -> diff --git a/src/rebar_ct.erl b/src/rebar_ct.erl index 8f7c71a..a66bdce 100644 --- a/src/rebar_ct.erl +++ b/src/rebar_ct.erl @@ -141,32 +141,57 @@ make_cmd(TestDir, Config) -> CodeDirs = [io_lib:format("\"~s\"", [Dir]) || Dir <- [EbinDir|NonLibCodeDirs]], CodePathString = string:join(CodeDirs, " "), - Cmd = ?FMT("erl " % should we expand ERL_PATH? - " -noshell -pa ~s ~s" - " -s ct_run script_start -s erlang halt" - " -name test@~s" - " -logdir \"~s\"" - " -env TEST_DIR \"~s\"", - [CodePathString, - Include, - net_adm:localhost(), - LogDir, - filename:join(Cwd, TestDir)]) ++ - get_cover_config(Config, Cwd) ++ - get_ct_config_file(TestDir) ++ - get_config_file(TestDir) ++ - get_suite(TestDir) ++ - get_case(), + Cmd = case get_ct_specs(Cwd) of + undefined -> + ?FMT("erl " % should we expand ERL_PATH? + " -noshell -pa ~s ~s" + " -s ct_run script_start -s erlang halt" + " -name test@~s" + " -logdir \"~s\"" + " -env TEST_DIR \"~s\"", + [CodePathString, + Include, + net_adm:localhost(), + LogDir, + filename:join(Cwd, TestDir)]) ++ + get_cover_config(Config, Cwd) ++ + get_ct_config_file(TestDir) ++ + get_config_file(TestDir) ++ + get_suite(TestDir) ++ + get_case(); + SpecFlags -> + ?FMT("erl " % should we expand ERL_PATH? + " -noshell -pa ~s ~s" + " -s ct_run script_start -s erlang halt" + " -name test@~s" + " -logdir \"~s\"" + " -env TEST_DIR \"~s\"", + [CodePathString, + Include, + net_adm:localhost(), + LogDir, + filename:join(Cwd, TestDir)]) ++ + SpecFlags ++ get_cover_config(Config, Cwd) + end, RawLog = filename:join(LogDir, "raw.log"), {Cmd, RawLog}. +get_ct_specs(Cwd) -> + case collect_glob(Cwd, ".*\.test\.spec\$") of + [] -> undefined; + [Spec] -> + " -spec " ++ Spec; + Specs -> + " -spec " ++ + lists:flatten([io_lib:format("~s ", [Spec]) || Spec <- Specs]) + end. + get_cover_config(Config, Cwd) -> case rebar_config:get_local(Config, cover_enabled, false) of false -> ""; true -> - case filelib:fold_files(Cwd, ".*cover\.spec\$", - true, fun collect_ct_specs/2, []) of + case collect_glob(Cwd, ".*cover\.spec\$") of [] -> ?DEBUG("No cover spec found: ~s~n", [Cwd]), ""; @@ -178,15 +203,18 @@ get_cover_config(Config, Cwd) -> end end. -collect_ct_specs(F, Acc) -> +collect_glob(Cwd, Glob) -> + filelib:fold_files(Cwd, Glob, true, fun collect_files/2, []). + +collect_files(F, Acc) -> %% Ignore any specs under the deps/ directory. Do this pulling %% the dirname off the the F and then splitting it into a list. Parts = filename:split(filename:dirname(F)), case lists:member("deps", Parts) of true -> - Acc; % There is a directory named "deps" in path + Acc; % There is a directory named "deps" in path false -> - [F | Acc] % No "deps" directory in path + [F | Acc] % No "deps" directory in path end. get_ct_config_file(TestDir) -> diff --git a/src/rebar_deps.erl b/src/rebar_deps.erl index 69f5fc1..22ef09e 100644 --- a/src/rebar_deps.erl +++ b/src/rebar_deps.erl @@ -131,7 +131,7 @@ compile(Config, AppFile) -> 'delete-deps'(Config, _) -> %% Delete all the available deps in our deps/ directory, if any - DepsDir = get_deps_dir(), + {true, DepsDir} = get_deps_dir(), Deps = rebar_config:get_local(Config, deps, []), {AvailableDeps, _} = find_deps(find, Deps), _ = [delete_dep(D) @@ -154,9 +154,21 @@ set_global_deps_dir(_Config, _DepsDir) -> ok. get_deps_dir() -> + get_deps_dir(""). + +get_deps_dir(App) -> BaseDir = rebar_config:get_global(base_dir, []), DepsDir = rebar_config:get_global(deps_dir, "deps"), - filename:join(BaseDir, DepsDir). + {true, filename:join([BaseDir, DepsDir, App])}. + +get_lib_dir(App) -> + % Find App amongst the reachable lib directories + % Returns either the found path or a tagged tuple with a boolean + % to match get_deps_dir's return type + case code:lib_dir(App) of + {error, bad_name} -> {false, bad_name}; + Path -> {true, Path} + end. update_deps_code_path([]) -> ok; @@ -189,24 +201,42 @@ find_deps(Mode, [{App, VsnRegex, Source} | Rest], Acc) -> Dep = #dep { app = App, vsn_regex = VsnRegex, source = Source }, - case is_app_available(App, VsnRegex) of - {true, AppDir} -> - find_deps(Mode, Rest, acc_deps(Mode, avail, Dep, AppDir, Acc)); - {false, _} -> - AppDir = filename:join(get_deps_dir(), Dep#dep.app), - case is_app_available(App, VsnRegex, AppDir) of - {true, AppDir} -> - find_deps(Mode, Rest, - acc_deps(Mode, avail, Dep, AppDir, Acc)); - {false, _} -> - find_deps(Mode, Rest, - acc_deps(Mode, missing, Dep, AppDir, Acc)) - end - end; + {Availability, FoundDir} = find_dep(Dep), + find_deps(Mode, Rest, acc_deps(Mode, Availability, Dep, FoundDir, Acc)); find_deps(_Mode, [Other | _Rest], _Acc) -> ?ABORT("Invalid dependency specification ~p in ~s\n", [Other, rebar_utils:get_cwd()]). +find_dep(Dep) -> + % Find a dep based on its source, + % e.g. {git, "https://github.com/mochi/mochiweb.git", "HEAD"} + % Deps with a source must be found (or fetched) locally. + % Those without a source may be satisfied from lib directories (get_lib_dir). + find_dep(Dep, Dep#dep.source). + +find_dep(Dep, undefined) -> + % 'source' is undefined. If Dep is not satisfied locally, + % go ahead and find it amongst the lib_dir's. + case find_dep_in_dir(Dep, get_deps_dir(Dep#dep.app)) of + {avail, Dir} -> {avail, Dir}; + {missing, _} -> find_dep_in_dir(Dep, get_lib_dir(Dep#dep.app)) + end; +find_dep(Dep, _Source) -> + % _Source is defined. Regardless of what it is, we must find it + % locally satisfied or fetch it from the original source + % into the project's deps + find_dep_in_dir(Dep, get_deps_dir(Dep#dep.app)). + +find_dep_in_dir(_Dep, {false, Dir}) -> + {missing, Dir}; +find_dep_in_dir(Dep, {true, Dir}) -> + App = Dep#dep.app, + VsnRegex = Dep#dep.vsn_regex, + case is_app_available(App, VsnRegex, Dir) of + {true, _AppFile} -> {avail, Dir}; + {false, _} -> {missing, Dir} + end. + acc_deps(find, avail, Dep, AppDir, {Avail, Missing}) -> {[Dep#dep { dir = AppDir } | Avail], Missing}; acc_deps(find, missing, Dep, AppDir, {Avail, Missing}) -> @@ -227,15 +257,8 @@ require_source_engine(Source) -> true = source_engine_avail(Source), ok. -is_app_available(App, VsnRegex) -> - case code:lib_dir(App) of - {error, bad_name} -> - {false, bad_name}; - Path -> - is_app_available(App, VsnRegex, Path) - end. - is_app_available(App, VsnRegex, Path) -> + ?DEBUG("is_app_available, looking for App ~p with Path ~p~n", [App, Path]), case rebar_app_utils:is_app_dir(Path) of {true, AppFile} -> case rebar_app_utils:app_name(AppFile) of @@ -293,7 +316,7 @@ use_source(Dep, Count) -> false -> ?CONSOLE("Pulling ~p from ~p\n", [Dep#dep.app, Dep#dep.source]), require_source_engine(Dep#dep.source), - TargetDir = filename:join(get_deps_dir(), Dep#dep.app), + {true, TargetDir} = get_deps_dir(Dep#dep.app), download_source(TargetDir, Dep#dep.source), use_source(Dep#dep { dir = TargetDir }, Count-1) end. @@ -335,7 +358,7 @@ update_source(Dep) -> %% VCS directory, such as when a source archive is built of a project, with %% all deps already downloaded/included. So, verify that the necessary VCS %% directory exists before attempting to do the update. - AppDir = filename:join(get_deps_dir(), Dep#dep.app), + {true, AppDir} = get_deps_dir(Dep#dep.app), case has_vcs_dir(element(1, Dep#dep.source), AppDir) of true -> ?CONSOLE("Updating ~p from ~p\n", [Dep#dep.app, Dep#dep.source]), diff --git a/src/rebar_lfe_compiler.erl b/src/rebar_lfe_compiler.erl index b100e3d..06b0f08 100644 --- a/src/rebar_lfe_compiler.erl +++ b/src/rebar_lfe_compiler.erl @@ -41,27 +41,40 @@ compile(Config, _AppFile) -> rebar_base_compiler:run(Config, FirstFiles, "src", ".lfe", "ebin", ".beam", fun compile_lfe/3). - %% =================================================================== %% Internal functions %% =================================================================== -compile_lfe(Source, _Target, Config) -> +compile_lfe(Source, Target, Config) -> case code:which(lfe_comp) of non_existing -> - ?CONSOLE( - <<"~n===============================================~n" - " You need to install LFE to compile LFE source files~n" - "Download the latest tarball release from github~n" - " https://github.com/rvirding/lfe/downloads~n" - " and install it into your erlang library dir~n" - "===============================================~n~n">>, []), + ?CONSOLE(<< + "~n" + "*** MISSING LFE COMPILER ***~n" + " You must do one of the following:~n" + " a) Install LFE globally in your erl libs~n" + " b) Add LFE as a dep for your project, eg:~n" + " {lfe, \"0.6.1\",~n" + " {git, \"git://github.com/rvirding/lfe\",~n" + " {tag, \"v0.6.1\"}}}~n" + "~n" + >>, []), ?FAIL; _ -> Opts = [{i, "include"}, {outdir, "ebin"}, report, return] ++ rebar_config:get_list(Config, lfe_opts, []), - case lfe_comp:file(Source,Opts) of - {ok, _, []} -> ok; - _ -> ?FAIL + case lfe_comp:file(Source, Opts) of + {ok, _, []} -> + ok; + {ok, _, _Warnings} -> + case lists:member(fail_on_warning, Opts) of + true -> + ok = file:delete(Target), + ?FAIL; + false -> + ok + end; + _ -> + ?FAIL end end. diff --git a/src/rebar_rel_utils.erl b/src/rebar_rel_utils.erl index 9729e20..d3baf4d 100644 --- a/src/rebar_rel_utils.erl +++ b/src/rebar_rel_utils.erl @@ -31,7 +31,10 @@ get_reltool_release_info/1, get_rel_release_info/1, get_rel_release_info/2, - get_previous_release_path/0]). + get_rel_apps/1, + get_rel_apps/2, + get_previous_release_path/0, + get_rel_file_path/2]). -include("rebar.hrl"). @@ -70,11 +73,29 @@ get_rel_release_info(RelFile) -> %% Get release name and version from a name and a path get_rel_release_info(Name, Path) -> + RelPath = get_rel_file_path(Name, Path), + get_rel_release_info(RelPath). + +%% Get list of apps included in a release from a rel file +get_rel_apps(RelFile) -> + case file:consult(RelFile) of + {ok, [{release, _, _, Apps}]} -> + Apps; + _ -> + ?ABORT("Failed to parse ~s~n", [RelFile]) + end. + +%% Get list of apps included in a release from a name and a path +get_rel_apps(Name, Path) -> + RelPath = get_rel_file_path(Name, Path), + get_rel_apps(RelPath). + +%% Get rel file path from name and path +get_rel_file_path(Name, Path) -> [RelFile] = filelib:wildcard(filename:join([Path, "releases", "*", Name ++ ".rel"])), [BinDir|_] = re:replace(RelFile, Name ++ "\\.rel", ""), - get_rel_release_info(filename:join([binary_to_list(BinDir), - Name ++ ".rel"])). + filename:join([binary_to_list(BinDir), Name ++ ".rel"]). %% Get the previous release path from a global variable get_previous_release_path() -> diff --git a/src/rebar_utils.erl b/src/rebar_utils.erl index 2822c0f..8898b8a 100644 --- a/src/rebar_utils.erl +++ b/src/rebar_utils.erl @@ -38,7 +38,8 @@ abort/2, escript_foldl/3, find_executable/1, - prop_check/3]). + prop_check/3, + expand_code_path/0]). -include("rebar.hrl"). @@ -156,6 +157,14 @@ find_executable(Name) -> prop_check(true, _, _) -> true; prop_check(false, Msg, Args) -> ?ABORT(Msg, Args). +%% Convert all the entries in the code path to absolute paths. +expand_code_path() -> + CodePath = lists:foldl(fun (Path, Acc) -> + [filename:absname(Path) | Acc] + end, [], code:get_path()), + code:set_path(lists:reverse(CodePath)). + + %% ==================================================================== %% Internal functions %% ==================================================================== diff --git a/test/upgrade_project/rel/files/dummy b/test/upgrade_project/rel/files/dummy index ec6d7ff..43dce0f 100755 --- a/test/upgrade_project/rel/files/dummy +++ b/test/upgrade_project/rel/files/dummy @@ -7,6 +7,7 @@ RUNNER_SCRIPT_DIR=$(cd ${0%/*} && pwd) RUNNER_BASE_DIR=${RUNNER_SCRIPT_DIR%/*} RUNNER_ETC_DIR=$RUNNER_BASE_DIR/etc RUNNER_LOG_DIR=$RUNNER_BASE_DIR/log +# Note the trailing slash on $PIPE_DIR/ PIPE_DIR=/tmp/$RUNNER_BASE_DIR/ RUNNER_USER= @@ -61,8 +62,7 @@ case "$1" in HEART_COMMAND="$RUNNER_BASE_DIR/bin/$SCRIPT start" export HEART_COMMAND mkdir -p $PIPE_DIR - # Note the trailing slash on $PIPE_DIR/ - $ERTS_PATH/run_erl -daemon $PIPE_DIR/ $RUNNER_LOG_DIR "exec $RUNNER_BASE_DIR/bin/$SCRIPT console" 2>&1 + $ERTS_PATH/run_erl -daemon $PIPE_DIR $RUNNER_LOG_DIR "exec $RUNNER_BASE_DIR/bin/$SCRIPT console" 2>&1 ;; stop) @@ -147,7 +147,7 @@ case "$1" in ;; *) - echo "Usage: $SCRIPT {start|stop|restart|reboot|ping|console|attach}" + echo "Usage: $SCRIPT {start|stop|restart|reboot|ping|console|console_clean|attach}" exit 1 ;; esac