Namdak Tonpa 9 years ago
parent
commit
b823f9fda5

+ 12 - 0
src/mad_release.erl

@@ -0,0 +1,12 @@
+-module(mad_release).
+-compile(export_all).
+
+release([])              -> release(["script"]);
+release(["depot"])       -> release(["depot", "sample"]);
+release(["beam"])        -> release(["beam",  "sample"]);
+release(["ling"])        -> release(["ling",  "sample"]);
+release(["script"])      -> release(["script","sample"]);
+release(["ling",N])      -> mad_ling:ling(N);
+release(["script",N])    -> mad_escript:main(N);
+release(["beam",N])      -> mad_systools:beam_release(N);
+release([X])             -> release(["script",X]).

+ 42 - 0
src/package/mad_escript.erl

@@ -0,0 +1,42 @@
+-module(mad_escript).
+-description("ESCRIPT bundles").
+-compile(export_all).
+
+main(N) ->
+    App = filename:basename(case N of [] -> mad_utils:cwd(); E -> E end),
+    mad_resolve:main([]),
+    EmuArgs = "-noshell -noinput +pc unicode",
+    Files = static() ++ beams(fun filename:basename/1, fun read_file/1) ++ overlay(),
+%   [ io:format("Escript: ~ts~n",[File]) || { File, _ } <- Files ],
+    escript:create(App,[shebang,{comment,""},{emu_args,EmuArgs},{archive,Files,[memory]}]),
+    file:change_mode(App, 8#764),
+    {ok,App}.
+
+id(X) -> X.
+read_file(File) -> {ok, Bin} = file:read_file(filename:absname(File)), Bin.
+static() ->
+    Name = "static.gz",
+    {ok,{_,Bin}} = zip:create(Name,
+        [ F || F <- mad_repl:wildcards(["{apps,deps}/*/priv/**","priv/**"]), not filelib:is_dir(F) ],
+        [{compress,all},memory]),
+    [ { Name, Bin } ].
+
+beams() -> beams(fun id/1,  fun read_file/1).
+beams(Fun,Read) ->
+    [ { Fun(F), Read(F) } ||
+        F <- mad_repl:wildcards(["ebin/*","{apps,deps}/*/ebin/*","sys.config",".applist"]) ].
+
+privs() -> privs(fun id/1,  fun read_file/1).
+privs(Fun,Read) ->
+    [ { Fun(F), Read(F) } ||
+        F <- mad_repl:wildcards(["{apps,deps}/*/priv/**","priv/**"]), not filelib:is_dir(F) ].
+
+system_files() -> lists:flatten([system_files(A) || A<- mad_repl:applist(), lists:member(A,mad_repl:system()) ]).
+system_files(App) ->
+    [ { F, mad_bundle:read_file(F) } ||
+        F <- mad_repl:wildcards([lists:concat([code:lib_dir(App),"/ebin/*.{app,beam}"])]) ].
+
+overlay() -> overlay(fun id/1,  fun read_file/1).
+overlay(Fun,Read) ->
+    [ { Fun(F), Read(F) } ||
+        F <- mad_repl:wildcards(["deps/ling/apps/*/ebin/*.beam"]) ].

+ 138 - 0
src/package/mad_ling.erl

@@ -0,0 +1,138 @@
+-module(mad_ling).
+-description("LING Erlang Virtual Machine Bundle Packaging").
+-copyright('Cloudozer, LLP').
+-compile(export_all).
+-define(ARCH, list_to_atom( case os:getenv("ARCH") of false -> "posix"; A -> A end)).
+
+ling(Params) ->
+    mad_resolve:main(),
+    _App = filename:basename(case Params of [] ->   mad_utils:cwd(); E -> E end),
+    mad:info("ARCH: ~p~n",         [?ARCH]),
+    mad:info("Bundle Name: ~p~n",  [mad_repl:local_app()]),
+    mad:info("System: ~p~n",       [mad_repl:system()]),
+    mad:info("Apps: ~p~n",         [mad_repl:applist()]),
+%    mad:info("Overlay: ~p~n",      [[{filename:basename(N),size(B)}||{N,B} <- mad_bundle:overlay()]]),
+%    mad:info("Files: ~p~n",        [[{filename:basename(N),size(B)}||{N,B} <- bundle()]]),
+    mad:info("Overlay: ~p~n",      [[filename:basename(N)||{N,_B} <- mad_bundle:overlay()]]),
+    add_apps(),
+    {ok,_App}.
+
+cache_dir()       -> ".madaline/".
+local_map(Bucks)  -> list_to_binary(lists:map(fun({B,M,_}) -> io_lib:format("~s /~s\n",[M,B]) end,Bucks)).
+bundle()          -> lists:flatten([ mad_bundle:X() || X <- [beams,privs,system_files,overlay] ]).
+library(Filename) -> case filename:split(Filename) of
+    ["deps","ling","apps",Lib|_] -> list_to_atom(Lib);
+                      ["ebin"|_] -> mad_repl:local_app();
+                      ["priv"|_] -> mad_repl:local_app();
+           A when length(A) >= 3 -> list_to_atom(hd(string:tokens(lists:nth(3,lists:reverse(A)),"-")));
+                  ["apps",Lib|_] -> list_to_atom(Lib);
+                  ["deps",Lib|_] -> list_to_atom(Lib);
+                               _ -> mad_repl:local_app() end.
+
+apps(Ordered) ->
+    Overlay = [ {filename:basename(N),B} || {N,B} <- mad_bundle:overlay() ],
+    lists:foldl(fun({N,B},Acc) ->
+        A = library(N),
+        Base = filename:basename(N),
+        Body = case lists:keyfind(Base,1,Overlay) of
+                    false -> B;
+                    {Base,Bin} -> 'overlay', Bin end,
+         case lists:keyfind(A,1,Acc) of
+              false -> [{A,[{A,Base,Body}]}|Acc];
+              {A,Files} -> lists:keyreplace(A,1,Acc,{A,[{A,Base,Body}|Files]}) end
+    end,lists:zip(Ordered,lists:duplicate(length(Ordered),[])),bundle()).
+
+lib({App,Files}) ->
+    { App, lists:concat(["/erlang/lib/",App,"/ebin"]), Files }.
+
+boot(Ordered) ->
+    BootCode = element(2,file:read_file(lists:concat([code:root_dir(),"/bin/start.boot"]))),
+    { script, Erlang, Boot } = binary_to_term(BootCode),
+    AutoLaunch = {script,Erlang,Boot++[{apply,{application,start,[App]}} || App <- Ordered]},
+    mad:info("Boot Code: ~p~n",[AutoLaunch]),
+    { boot, "start.boot", term_to_binary(AutoLaunch) }.
+
+add_apps() ->
+    {ok,Ordered} = mad_resolve:orderapps(),
+    Bucks = [{boot,"/boot",[local_map, boot(Ordered)]}] ++ [ lib(E) || E <- apps(Ordered) ],
+    %mad:info("Bucks: ~p~n",[[{App,Mount,[{filename:basename(F),size(Bin)}||{_,F,Bin}<-Files]}||{App,Mount,Files}<-Bucks]]),
+    mad:info("Bucks: ~p~n",[[{App,Mount,length(Files)}||{App,Mount,Files}<-Bucks]]),
+    filelib:ensure_dir(cache_dir()),
+    EmbedFsPath = lists:concat([cache_dir(),"/embed.fs"]),
+    mad:info("Initializing EMBED.FS:"),
+    Res = embed_fs(EmbedFsPath,Bucks),
+	{ok, EmbedFsObject} = embedfs_object(EmbedFsPath),
+	Oneliner = ld() ++
+               ["../deps/ling/core/vmling.o"] ++
+               ["-lm", "-lpthread", "-ldl"] ++
+               [EmbedFsObject, "-o", "../" ++ atom_to_list(mad_repl:local_app()) ++ ".img"],
+    mad:info("LD: ~p~n",[Oneliner]),
+	Res = case sh:oneliner(Oneliner,cache_dir()) of
+	           {_,0,_} -> ok;
+	           {_,_,M} -> binary_to_list(M) end,
+    mad:info("Linking Image: ~p~n",[Res]).
+
+embed_fs(EmbedFsPath,Bucks)  ->
+    {ok, EmbedFs} = file:open(EmbedFsPath, [write]),
+    BuckCount = length(Bucks),
+    BinCount = lists:foldl(fun({_,_,Bins},Count) -> Count + length(Bins) end,0,Bucks),
+    file:write(EmbedFs, <<BuckCount:32>>),
+	file:write(EmbedFs, <<BinCount:32>>),
+    lists:foreach(fun({Buck,_,Bins}) ->
+          BuckName = binary:list_to_bin(atom_to_list(Buck)),
+          BuckNameSize = size(BuckName),
+          BuckBinCount = length(Bins),
+          file:write(EmbedFs, <<BuckNameSize, BuckName/binary, BuckBinCount:32>>),
+          lists:foreach(fun
+                    (local_map) -> LocalMap = local_map(Bucks),
+                                   mad:info("~nMount View:~n ~s",[LocalMap]),
+                                   write_bin(EmbedFs, "local.map", LocalMap);
+                  ({_App,F,Bin}) -> write_bin(EmbedFs, filename:basename(F), Bin)
+          end,Bins)
+    end,Bucks),
+    file:close(EmbedFs),
+	ok.
+
+embedfs_object(EmbedFsPath) ->
+	EmbedCPath  = filename:join(filename:absname(cache_dir()), "embedfs.c"),
+	OutPath     = filename:join(filename:absname(cache_dir()), "embedfs.o"),
+	{ok, Embed} = file:read_file(EmbedFsPath),
+	mad:info("Creating EMBED.FS C file: ..."),
+	Res = bfd_objcopy:blob_to_src(EmbedCPath, "_binary_embed_fs", Embed),
+    mad:info("~p~n",[Res]),
+	mad:info("Compilation of Filesystem object: ..."),
+	Res = case sh:oneliner(cc() ++ ["-o", OutPath, "-c", EmbedCPath]) of
+	           {_,0,_} -> ok;
+	           {_,_,M} -> binary_to_list(M) end,
+	mad:info("~p~n",[Res]),
+	mad:info("Out Path: ~p~n",[OutPath]),
+	{ok, OutPath}.
+
+write_bin(Dev, F, Bin) ->
+    {ListName,Data} = case filename:extension(F) of
+        ".beam" ->  { filename:rootname(F) ++ ".ling", beam_to_ling(Bin) };
+              _ ->  { F, Bin } end,
+    Name = binary:list_to_bin(ListName),
+    NameSize = size(Name),
+    DataSize = size(Data),
+    file:write(Dev, <<NameSize, Name/binary, DataSize:32, Data/binary>>).
+
+beam_to_ling(B) ->
+    ling_lib:specs_to_binary(element(2,ling_code:ling_to_specs(element(2,ling_code:beam_to_ling(B))))).
+
+gold() -> gold("ld").
+gold(Prog) -> [Prog, "-T", "ling.lds", "-nostdlib"].
+
+ld() -> ld(?ARCH).
+ld(arm) -> gold("arm-none-eabi-ld");
+ld(xen_x86) -> case os:type() of {unix, darwin} -> ["x86_64-pc-linux-ld"]; _ -> gold() end;
+ld(posix) -> case os:type() of {unix, darwin} ->
+    ["clang","-image_base","0x8000","-pagezero_size","0x8000","-arch","x86_64"];
+	_ -> gold() end;
+ld(_) -> gold().
+
+cc() -> cc(?ARCH).
+cc(arm) -> ["arm-none-eabi-gcc", "-mfpu=vfp", "-mfloat-abi=hard"];
+cc(xen_x86) -> case os:type() of {unix, darwin} -> ["x86_64-pc-linux-gcc"]; _ -> ["cc"] end;
+cc(_) -> ["cc"].
+

+ 51 - 0
src/package/mad_systools.erl

@@ -0,0 +1,51 @@
+-module(mad_systools).
+-description("ERTS releases with systools").
+-compile(export_all).
+
+% beam releases
+
+scripts(N) ->
+    mad_repl:load(),
+    {ok,Bin} = mad_repl:load_file("priv/systools/start"),
+    [{"/bin/start",list_to_binary(re:replace(binary_to_list(Bin),"{release}",N,[global,{return,list}]))},
+     {"/bin/attach",element(2,mad_repl:load_file("priv/systools/attach"))},
+     {"/bin/daemon",element(2,mad_repl:load_file("priv/systools/daemon"))},
+     {"/etc/"++N++".boot",N++".boot"},
+     {"/etc/vm.args","vm.args"},
+     {"/etc/sys.config","sys.config"}].
+
+apps(List) ->
+    lists:flatten([[[ {filename:join([lib,
+        lists:concat([App,"-",Version]),Class,filename:basename(F)]),F}
+    || F <- mad_repl:wildcards([filename:join([Dir,Class,"*"])]) ]
+    || {App,{Version,Dir}} <- List ] || Class <- [ebin,priv] ]).
+
+release(Name) ->
+    Triples = mad_resolve:triples(),
+    Apps = lists:usort(fun({Name,_},{Name2,_})-> Name =< Name2 end,
+                [{A,{B,F}}||{_,A,{B,F}}<-Triples]) ++
+      [{kernel,{proplists:get_value(vsn,element(2,
+                application:get_all_key(kernel)),[]),
+                filename:absname(code:lib_dir(kernel))}}],
+    Sorted = [ lists:keyfind(A,1,Apps) || A <- element(2,mad_resolve:orderapps())],
+    {L,R}     = lists:unzip(Sorted),
+    {Ver,Dir} = lists:unzip(R),
+    NameVer   = [ X || X <- lists:zip(L,Ver), element(1,X) /= active,
+                                              element(1,X) /= fs ],
+    {{release,{Name,"1"},{erts,erlang:system_info(version)},NameVer},Sorted}.
+
+beam_release(N) ->
+    mad_resolve:main([]),
+    Directories = mad_repl:wildcards(["{deps,apps}/*/ebin","ebin"]),
+    code:add_paths(Directories),
+    {Release,Apps} = release(N),
+    file:write_file(N ++ ".rel",io_lib:format("~p.",[Release])),
+    Res = systools:make_script(N),
+    Files = [ {"/bin/" ++ filename:basename(F), F}
+        || F <- mad_repl:wildcards([code:root_dir() ++
+            "/erts-" ++ erlang:system_info(version) ++
+            "/bin/{epmd,erlexec,run_erl,to_erl,escript,beam.smp}"]) ] ++
+        apps(Apps) ++ scripts(N),
+    erl_tar:create(N ++ ".tgz",Files,[compressed]),
+    mad:info("~s.boot: ~p~n",[N,Res]),
+    {ok,N}.

+ 14 - 0
src/profile/mad_local.erl

@@ -0,0 +1,14 @@
+-module(mad_local).
+-compile(export_all).
+
+compile(Params)   -> mad_compile:compile(Params).
+app(Params)       -> mad_static:app(Params).
+release(Params)   -> mad_release:release(Params).
+clean(Params)     -> mad_run:clean(Params).
+start(Params)     -> mad_run:start(Params).
+attach(Params)    -> mad_run:attach(Params).
+stop(Params)      -> mad_run:stop(Params).
+sh(Params)        -> mad_repl:sh(Params).
+deps(Params)      -> mad_git:deps(Params).
+up(Params)        -> mad_git:up(Params).
+fetch(Params)     -> mad_git:fetch(Params).

+ 139 - 0
src/provision/mad_repl.erl

@@ -0,0 +1,139 @@
+-module(mad_repl).
+-copyright('Maxim Sokhatsky').
+-compile(export_all).
+
+disabled() -> [].
+system() -> [compiler,syntax_tools,sasl,tools,mnesia,reltool,xmerl,crypto,kernel,stdlib,ssh,eldap,
+             wx,webtool,ssl,runtime_tools,public_key,observer,inets,asn1,et,eunit,hipe,os_mon].
+
+local_app() ->
+    case filename:basename(filelib:wildcard("ebin/*.app"),".app") of
+         [] -> [];
+         A -> list_to_atom(A) end.
+
+applist() ->
+    Name = ".applist",
+    case file:read_file(Name) of
+         {ok,Binary} -> parse_applist(Binary); 
+         {error,_} ->
+           case mad_repl:load_file(Name) of
+              {error,_} -> mad_plan:main([]);
+              {ok,Plan} -> parse_applist(Plan) end end.
+
+wildcards(List) -> lists:concat([filelib:wildcard(X)||X<-List]).
+
+parse_applist(AppList) ->
+   Res = string:tokens(string:strip(string:strip(binary_to_list(AppList),right,$]),left,$[),","),
+   [ list_to_atom(R) || R <-Res ]  -- disabled().
+
+load_config() ->
+   Config = wildcards(["sys.config"]),
+   Apps = case Config of
+        [] -> case mad_repl:load_file("sys.config") of
+              {error,_} -> [];
+              {ok,Bin} -> parse(binary_to_list(Bin)) end;
+      File -> case file:consult(File) of
+              {error,_} -> [];
+              {ok,[A]} -> A end end,
+ [ begin [ application:set_env(App,K,V) || {K,V} <- Cfg ], {App,Cfg} end || {App,Cfg} <- Apps ].
+
+acc_start(A,Acc) ->
+   case application:start(A) of
+         {error,{already_started,_}} -> Acc;
+         {error,{_,{{M,_F,_},_Ret}}} -> [M|Acc];
+         {error,{_Reason,Name}} when is_atom(_Reason) -> [Name|Acc];
+         ok -> Acc;
+         _  -> Acc end.
+
+load_apps([],_,_Acc) ->
+  Res = lists:foldl(fun(A,Acc) -> case lists:member(A,system()) of
+       true -> acc_start(A,Acc);
+          _ -> case load_config(A) of
+                    [] -> acc_start(A,Acc);
+                    _E -> acc_start(_E,Acc) end end end,[], applist()),
+  case Res of
+       [] -> ok;
+       _ -> mad:info("~nApps couldn't be loaded: ~p~n",[Res]) end;
+load_apps(["applist"],Config,Acc) -> load_apps([],Config,Acc);
+load_apps(Params,_,_Acc) -> [ application:ensure_all_started(list_to_atom(A))||A<-Params].
+
+cwd() -> case  file:get_cwd() of {ok, Cwd} -> Cwd; _ -> "." end.
+
+sh(Params) ->
+    { _Cwd,_ConfigFileName,_Config } = mad_utils:configs(),
+    mad_plan:main([]),
+    SystemPath = filelib:wildcard(code:root_dir() ++ "/lib/{"
+              ++ string:join([atom_to_list(X)||X<-mad_repl:system()],",") ++ "}-*/ebin"),
+    UserPath   = wildcards(["{apps,deps}/*/ebin","ebin"]),
+    code:set_path(SystemPath++UserPath),
+    code:add_path(filename:join([cwd(),filename:basename(escript:script_name())])),
+    load(),
+    Config = load_config(),
+    Driver = mad_utils:get_value(shell_driver,_Config,user_drv),
+    pre(Driver),
+    case os:type() of
+         {win32,nt} -> shell:start();
+                  _ -> Driver:start() end,
+    post(Driver),
+    load_apps(Params,Config,[]),
+    case Params of
+        ["applist"] -> skip;
+        _ ->  timer:sleep(infinity) end.
+
+load() ->
+    ets_created(),
+    {ok,Sections} = escript:extract(escript:script_name(),[]),
+    [Bin] = [B||{archive,B}<-Sections],
+    unfold_zips(Bin).
+
+unfold_zips(Bin) ->
+    {ok,Unzip} = zip:unzip(Bin,[memory]),
+    [ begin
+        ets:insert(filesystem,{U,FileBin}),
+        case U of
+            "static.gz" -> unfold_zips(FileBin);
+            _ -> skip end
+      end || {U,FileBin} <- Unzip].
+
+ets_created() ->
+    case ets:info(filesystem) of
+         undefined -> ets:new(filesystem,[set,named_table,{keypos,1},public]);
+         _ -> skip end.
+
+load_file(Name)  ->
+    ets_created(),
+    case ets:lookup(filesystem,Name) of
+        [{Name,Bin}] -> {ok,Bin};
+        _ -> {error,etsfs} end.
+
+load_config(A) when is_atom(A) -> load_config(atom_to_list(A));
+load_config(A) when is_list(A) ->
+    AppFile = A ++".app",
+    Name = wildcards(["{apps,deps}/*/ebin/"++AppFile,"ebin/"++AppFile]),
+    case file:read_file(Name) of
+         {ok,Bin} -> parse(binary_to_list(Bin));
+         {error,_} -> case ets:lookup(filesystem,AppFile) of
+                          [{Name,Bin}] -> parse(binary_to_list(Bin));
+                          _ -> [] end end.
+
+parse(String) ->
+    {ok,Tokens,_EndLine} = erl_scan:string(String),
+    {ok,AbsForm} = erl_parse:parse_exprs(Tokens),
+    {value,Value,_Bs} = erl_eval:exprs(AbsForm, erl_eval:new_bindings()),
+    Value.
+
+% we need to call printing before starting driver for user_drv
+% but for start_kjell we should call after, that's why we have pre and post here.
+
+pre(start_kjell) -> [];
+pre(user_drv) -> unregister(user), appconfig(user_drv);
+pre(Driver) -> appconfig(Driver).
+post(start_kjell) -> appconfig(start_kjell);
+post(_) -> [].
+print(Label,Value,start_kjell) -> io:requests([{put_chars,Label ++ normalize(length(Label)+1,Value) ++ "\n\r"}]);
+print(Label,Value,_) -> mad:info("~s~p~n",[Label,Value]).
+normalize(Padding,V) -> [ case X of 10 -> [13,10]; E -> E end || X <- lists:flatten(pp(Padding,V) )].
+pp(Padding,V) -> k_io_lib_pretty:print(V, Padding, 80, 30, 60, fun(_,_)-> no end).
+appconfig(Driver) ->
+    print("Configuration: ", load_config(), Driver),
+    print("Applications: ",  applist(),     Driver).

+ 22 - 0
src/provision/mad_run.erl

@@ -0,0 +1,22 @@
+-module(mad_run).
+-compile(export_all).
+
+start(App) ->                            % run_dir > < log_dir
+    mad_plan:main([]),
+    mad:info("Scripting: ~p~n",[escript:script_name()]),
+    {_,Status,X} = sh:run("run_erl",["-daemon",".",".","exec "++escript:script_name()++" sh"],
+      binary,".",
+        [{"RUN_ERL_LOG_GENERATIONS","1000"},
+         {"RUN_ERL_LOG_MAXSIZE","20000000"},
+         {"ERL_LIBS","apps:deps"}]),
+    case Status == 0 of
+         true -> {ok,App};
+         false -> mad:info("Shell Error: ~s~n",[binary_to_list(X)]), {error,X} end.
+
+attach(_) -> mad:info("to_erl .~n"). % use like $(mad attach)
+
+stop(_) -> {ok,[]}.
+
+clean(_) -> [ file:delete(X) || X <- filelib:wildcard("{apps,deps}/*/ebin/**") ++
+                                     filelib:wildcard("ebin/**")], {ok,[]}.
+

+ 25 - 0
src/provision/mad_vz.erl

@@ -0,0 +1,25 @@
+-module(mad_vz).
+-compile(export_all).
+
+create(App,_) -> create(App).
+create(App) ->
+    Name = filename:basename(App,".tgz"),
+    mad:info("Unpack Container: ~p~n",[Name]),
+    {ok,Bin} = file:read_file(App),
+    erl_tar:extract({binary,zlib:gunzip(Bin)},[{cwd,lists:concat(["apps/",Name])}]).
+
+start(App) ->
+    mad:info("App: ~p~n",[App]),
+    {ok,Bin}  = file:read_file(lists:concat(["apps/",App,"/config.json"])),
+    {Json   } = jsone:decode(Bin),
+    {Process} = proplists:get_value(<<"process">>,Json),
+    Args      = proplists:get_value(<<"args">>,Process),
+    Concat    = string:join(lists:map(fun(X) -> binary_to_list(X) end,Args)," "),
+    {_,R,S}   = sh:run(Concat,<<"log">>,lists:concat(["apps/",App])),
+    mad:info("Oneliner: ~p~n",[Concat]),
+    {ret(R),S}.
+
+stop(App) -> ok.
+
+ret(0) -> ok;
+ret(_) -> error.

+ 105 - 0
src/sources/mad_git.erl

@@ -0,0 +1,105 @@
+-module(mad_git).
+-compile(export_all).
+
+deps(Params) ->
+    { Cwd, ConfigFile, Conf } = mad_utils:configs(),
+    case mad_utils:get_value(deps, Conf, []) of
+        [] -> {ok,[]};
+        Deps -> file:make_dir(mad_utils:get_value(deps_dir, Conf, ["deps"])),
+                (mad:profile()):fetch([Cwd, Conf, ConfigFile, Deps]) end.
+
+fetch([Cwd, Conf, ConfigFile, Deps]) -> fetch(Cwd, Conf, ConfigFile, Deps).
+fetch(_, _Config, _, []) -> false;
+fetch(Cwd, Config, ConfigFile, [H|T]) when is_tuple(H) =:= false -> fetch(Cwd, Config, ConfigFile, T);
+fetch(Cwd, Config, ConfigFile, [H|T]) ->
+    {Name, Repo} = name_and_repo(H),
+    Res = case get(Name) of
+        fetched -> {ok,Name};
+        _ ->
+            {Cmd, Uri, Co} = case Repo of
+                                 V={_, _, _}          -> V;
+                                 {_Cmd, _Url, _Co, _} -> {_Cmd, _Url, _Co};
+                                 {_Cmd, _Url}         -> {_Cmd, _Url, "master"}
+                             end,
+            Cmd1 = atom_to_list(Cmd),
+            Cache = mad_utils:get_value(cache, Config, deps_fetch),
+            fetch_dep(Cwd, Config, ConfigFile, Name, Cmd1, Uri, Co, Cache)
+    end,
+    case Res of
+         {error,E} -> {error,E};
+         {ok,_} -> fetch(Cwd, Config, ConfigFile, T) end.
+
+git_clone(Uri,Fast,TrunkPath,Rev) when Rev == "head" orelse Rev == "HEAD" orelse Rev == "master" ->
+    {["git clone ",Fast,Uri," ",TrunkPath],Rev};
+git_clone(Uri,_Fast,TrunkPath,Rev) ->
+    {["git clone ",Uri," ",TrunkPath," && cd ",TrunkPath," && git checkout \"",Rev,"\"" ],Rev}.
+
+fetch_dep(Cwd, Config, ConfigFile, Name, Cmd, Uri, Co, Cache) ->
+
+    TrunkPath = case Cache of
+        deps_fetch -> filename:join([mad_utils:get_value(deps_dir,Config,"deps"),Name]);
+        Dir -> filename:join([Dir,get_publisher(Uri),Name]) end,
+
+    mad:info("==> dependency: ~p tag: ~p~n", [Uri,Co]),
+
+    Fast = case mad_utils:get_value(fetch_speed,Config,[]) of
+                fast_master -> " --depth=1 ";
+                    _  -> "" end,
+
+    {R,Co1} = case Co of
+        X when is_list(X) -> git_clone(Uri,Fast,TrunkPath,X);
+        {_,Rev} -> git_clone(Uri,Fast,TrunkPath,Rev);
+        Master  -> git_clone(Uri,Fast,TrunkPath,Master) end,
+
+    %mad:info("Fetch: ~s~n",[R]),
+
+    FetchStatus = case filelib:is_dir(TrunkPath) of
+                       true -> {skip,0,list_to_binary("Directory "++TrunkPath++" exists.")};
+                       false -> sh:run(lists:concat(R)) end,
+
+    case FetchStatus of
+         {_,0,_} -> put(Name, fetched),
+
+                    %% check dependencies of the dependency
+                    TrunkConfigFile = filename:join(TrunkPath, ConfigFile),
+                    Conf = mad_utils:consult(TrunkConfigFile),
+                    Conf1 = mad_utils:script(TrunkConfigFile, Conf, Name),
+                    fetch(Cwd, Config, ConfigFile, mad_utils:get_value(deps, Conf1, [])),
+                    case Cache of
+                         deps_fetch -> {ok,Name};
+                         CacheDir -> build_dep(Cwd, Config, ConfigFile,
+                                        get_publisher(Uri), Name, Cmd, Co1, CacheDir)
+                    end;
+    {_,_,FetchError} -> mad:info("Fetch Error: ~s~n",[binary_to_list(FetchError)]),
+                        {error,binary_to_list(FetchError)} end.
+
+%% build dependency based on branch/tag/commit
+build_dep(Cwd, Conf, _ConfFile, Publisher, Name, _Cmd, _Co, Dir) ->
+    TrunkPath = filename:join([Dir, Publisher, Name]),
+    DepsDir = filename:join([mad_utils:get_value(deps_dir, Conf, ["deps"]),Name]),
+    os:cmd(["cp -r ", TrunkPath, " ", DepsDir]),
+    ok = file:set_cwd(DepsDir),
+    ok = file:set_cwd(Cwd),
+    {ok,Name}.
+
+%% internal
+name_and_repo(X) -> mad_utils:name_and_repo(X).
+get_publisher(Uri) -> case string:tokens(Uri,"@:/") of [_Proto,_Server,Publisher|_RepoPath] -> Publisher; _ -> core end.
+
+pull(_,[])         -> {ok,[]};
+pull(Config,[F|T]) ->
+    mad:info("==> up: ~p~n", [F]),
+    {_,Status,Message} = sh:run(lists:concat(["cd ",F," && git pull && cd -"])),
+    case Status of
+         0 -> mad_utils:verbose(Config,Message), pull(Config,T);
+         _ -> case binary:match(Message,[<<"You are not currently on a branch">>]) of
+                   nomatch -> mad_utils:verbose(Config,Message), true;
+                   _ -> pull(Config,T) end end.
+
+up(Params) ->
+  { _Cwd,_ConfigFileName,Config } = mad_utils:configs(),
+  List = case Params of
+                [] -> [ F || F <- mad_repl:wildcards(["deps/*"]), filelib:is_dir(F) ];
+                Apps -> [ "deps/" ++ A || A <- Apps ] end ++ ["."],
+    pull(Config,List).
+

+ 33 - 0
src/sources/mad_synrc.erl

@@ -0,0 +1,33 @@
+-module(mad_synrc).
+-compile(export_all).
+
+deps(Params) ->
+    { Cwd, ConfigFile, Conf } = mad_utils:configs(),
+    case mad_utils:get_value(deps, Conf, []) of
+        [] -> {ok,[]};
+        Deps -> file:make_dir(mad_utils:get_value(deps_dir, Conf, ["deps"])),
+                (mad:profile()):fetch([Cwd, Conf, ConfigFile, Deps]) end.
+
+contains(X,String,Acc) ->
+    case string:str(String,atom_to_list(X)) > 0 of true -> [{X}|Acc]; _ -> [] end.
+
+wildcards(Depot,X,Pattern) ->
+    mad_repl:wildcards([Depot++atom_to_list(X)++Pattern]).
+
+atomlist(TARGETS) ->
+    string:join(lists:map(fun(X) -> atom_to_list(X) end,TARGETS),",").
+
+depot_release(Name) ->
+    mad_resolve:main([]),
+    TARGETS   = [beam,ling],
+    HOSTS     = [mac,bsd,windows],
+    Depot     = "/Users/5HT/depot/synrc/synrc.com/apps/",
+    {ok,Apps} = file:consult(Depot++"index.txt"),
+    Files     = lists:flatten([[
+                    lists:foldl(fun(A,Acc) -> [{A}|Acc] end,
+                        [], wildcards(Depot,X,lists:concat(["/ebin/**/*.{app,",atomlist(TARGETS),"}"]))),
+                    lists:foldl(fun(B,Acc)->[contains(P,B,Acc)||P<-HOSTS] end,
+                        [], wildcards(Depot,X,"/{bin,priv}/**/*")) ]
+    || {_,[X],_} <- lists:flatten(Apps) ]),
+    io:format("DEPOT Apps: ~p~n",[Files]),
+    {ok,Name}.