Merge pull request #594 from matwey/cache_load_save

Introduce REBAR_VSN_CACHE_FILE env variable to load/save vsn cache
diff --git a/inttest/vsn_cache/main.erl b/inttest/vsn_cache/main.erl
new file mode 100644
index 0000000..67b6465
--- /dev/null
+++ b/inttest/vsn_cache/main.erl
@@ -0,0 +1,13 @@
+-module(main).
+-behaviour(application).
+     
+-export([start/0,start/1,start/2,stop/1]).
+
+start() ->
+	start(permanent).
+start(_Restart) ->
+	ok.
+start(_Type,_Args) ->
+	ok.
+stop(_State) ->
+	ok.
diff --git a/inttest/vsn_cache/vsn_cache_rt.erl b/inttest/vsn_cache/vsn_cache_rt.erl
new file mode 100644
index 0000000..b482888
--- /dev/null
+++ b/inttest/vsn_cache/vsn_cache_rt.erl
@@ -0,0 +1,90 @@
+%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
+%% ex: ts=4 sw=4 et
+-module(vsn_cache_rt).
+
+-compile(export_all).
+
+setup([Target]) ->
+    retest_utils:load_module(filename:join(Target, "inttest_utils.erl")),
+    ok.
+
+files() ->
+    [
+    %% Cache save check
+    {create, "save/src/save.app.src", app(save, [main])},
+    {create, "save/vsn_cache_file", ""},
+    {copy, "main.erl", "save/src/main.erl"},
+
+    %% Cache load check
+    {create, "load/src/load.app.src", app(load, [main])},
+    {copy, "main.erl", "load/src/main.erl"}
+    ] ++ inttest_utils:rebar_setup().
+
+apply_cmds([], _Params) ->
+    ok;
+apply_cmds([Cmd | Rest], Params) ->
+    io:format("Running: ~s (~p)\n", [Cmd, Params]),
+    {ok, _} = retest_sh:run(Cmd, Params),
+    apply_cmds(Rest, Params).
+
+run_save(Dir) ->
+    GitCmds = ["git init",
+               "git add -A",
+               "git config user.email 'vsn_cache@example.com'",
+               "git config user.name 'vsn_cache'",
+               "git commit -a -m \"Initial Commit\""],
+    AppDir = filename:join(Dir, "save"),
+    EbinDir = filename:join(AppDir, "ebin"),
+    AppFile = filename:join(EbinDir, "save.app"),
+    VsnCacheFile = filename:join(AppDir, "vsn_cache_file"),
+    Env = [{"REBAR_VSN_CACHE_FILE", VsnCacheFile}],
+
+    %% Initialize test git repository 
+    ok = apply_cmds(GitCmds, [{dir, AppDir}]),
+    %% Compile test project with vsn cache enabled
+    {ok, _} = retest_sh:run("../rebar -v compile", [{env, Env}, {dir, AppDir}]),
+    %% Vsn cache file has an entry
+    {ok, [{{git, AppDir}, Hash}]} = file:consult(VsnCacheFile),
+    %% This vsn entry must coincide with entry from ebin/save.app
+    {ok, [{application, save, PropList}]} = file:consult(AppFile),
+    Hash = proplists:get_value(vsn, PropList),
+    ok.
+
+run_load(Dir) ->
+    AppDir = filename:join(Dir, "load"),
+    EbinDir = filename:join(AppDir, "ebin"),
+    AppFile = filename:join(EbinDir, "load.app"),
+    VsnCacheFile = filename:join(AppDir, "vsn_cache_file"),
+    Hash = "deadbeef",
+    CacheEntries = [{{git, AppDir}, Hash}],
+    Env = [{"REBAR_VSN_CACHE_FILE", VsnCacheFile}],
+
+    %% Initialize dummy vsn cache file
+    vsn_cache_file(VsnCacheFile, CacheEntries),
+    %% Compile test project with vsn cache enabled
+    {ok, _} = retest_sh:run("../rebar -v compile", [{env, Env}, {dir, AppDir}]),
+    %% This vsn entry in cache file must coincide with entry in ebin/load.app
+    {ok, [{application, load, PropList}]} = file:consult(AppFile),
+    Hash = proplists:get_value(vsn, PropList),
+    ok.
+
+run(Dir) ->
+    run_save(Dir),
+    run_load(Dir),
+    ok.
+
+%%
+%% Generate the contents of a simple .app file
+%%
+app(Name, Modules) ->
+    App = {application, Name,
+           [{description, atom_to_list(Name)},
+            {vsn, git},
+            {modules, Modules},
+            {registered, []},
+            {applications, [kernel, stdlib]}]},
+    io_lib:format("~p.\n", [App]).
+
+vsn_cache_file(Name, Entries) ->
+    file:write_file(Name,
+        [io_lib:format("~p.~n", [X]) || X <- Entries]).
diff --git a/src/rebar.erl b/src/rebar.erl
index 888f80d..5a809d2 100644
--- a/src/rebar.erl
+++ b/src/rebar.erl
@@ -143,7 +143,7 @@
     %% Keep track of how many operations we do, so we can detect bad commands
     BaseConfig1 = rebar_config:set_xconf(BaseConfig, operations, 0),
     %% Initialize vsn cache
-    rebar_config:set_xconf(BaseConfig1, vsn_cache, dict:new()).
+    rebar_utils:init_vsn_cache(BaseConfig1).
 
 init_config1(BaseConfig) ->
     %% Determine the location of the rebar executable; important for pulling
@@ -288,6 +288,7 @@
     ?CONSOLE(
        "Environment variables:~n"
        "  REBAR_DEPS_PREFER_LIBS to look for dependecies in system libs prior fetching.~n"
+       "  REBAR_VSN_CACHE_FILE to load vsn cache from and save to specified file.~n"
        "~n", []).
 
 %%
diff --git a/src/rebar_core.erl b/src/rebar_core.erl
index 0650430..6cc8d38 100644
--- a/src/rebar_core.erl
+++ b/src/rebar_core.erl
@@ -106,7 +106,7 @@
                                                       ParentConfig2),
             %% Wipe out vsn cache to avoid invalid hits when
             %% dependencies are updated
-            rebar_config:set_xconf(ParentConfig3, vsn_cache, dict:new())
+            rebar_utils:init_vsn_cache(ParentConfig3)
         catch
             throw:rebar_abort ->
                 case rebar_config:get_xconf(ParentConfig1, keep_going, false) of
diff --git a/src/rebar_utils.erl b/src/rebar_utils.erl
index 1fb5419..c3ebfe5 100644
--- a/src/rebar_utils.erl
+++ b/src/rebar_utils.erl
@@ -67,7 +67,9 @@
          processing_base_dir/1,
          processing_base_dir/2,
          patch_env/2,
-         cleanup_code_path/1
+         cleanup_code_path/1,
+         init_vsn_cache/1,
+         save_vsn_cache/1
         ]).
 
 %% for internal use only
@@ -268,6 +270,23 @@
             re:replace(InStr, RegEx, [VarValue, "\\2"], ReOpts)
     end.
 
+init_vsn_cache(Config) ->
+    init_vsn_cache(Config, os:getenv("REBAR_VSN_CACHE_FILE")).
+init_vsn_cache(Config, false) ->
+    rebar_config:set_xconf(Config, vsn_cache, dict:new());
+init_vsn_cache(Config, CacheFile) ->
+    {ok, CacheList} = file:consult(CacheFile),
+    CacheDict = dict:from_list(CacheList),
+    rebar_config:set_xconf(Config, vsn_cache, CacheDict).
+
+save_vsn_cache(Config) ->
+    save_vsn_cache(Config, os:getenv("REBAR_VSN_CACHE_FILE")).
+save_vsn_cache(_Config, false) ->
+    ok;
+save_vsn_cache(Config, CacheFile) ->
+    file:write_file(CacheFile,
+        [io_lib:format("~p.~n", [X]) || X <- dict:to_list(rebar_config:get_xconf(Config, vsn_cache))]).
+
 vcs_vsn(Config, Vsn, Dir) ->
     Key = {Vsn, Dir},
     Cache = rebar_config:get_xconf(Config, vsn_cache),
@@ -276,6 +295,7 @@
             VsnString = vcs_vsn_1(Vsn, Dir),
             Cache1 = dict:store(Key, VsnString, Cache),
             Config1 = rebar_config:set_xconf(Config, vsn_cache, Cache1),
+            save_vsn_cache(Config1),
             {Config1, VsnString};
         {ok, VsnString} ->
             {Config, VsnString}