Browse Source

v2.10 by @5HT, without cubical; with fix warnings output and get settings by 221V

221V 1 year ago
parent
commit
4aaaf56627

+ 2 - 7
.travis.yml

@@ -1,9 +1,4 @@
 language: erlang
 otp_release:
-  - 18.0
-  - R16B02
-  - R16B01
-notifications:
-  email: false
-  irc: "chat.freenode.net#n2o"
-script: "make"
+  - 19.3
+script: "curl -fsSL https://raw.github.com/synrc/mad/master/mad > mad && chmod +x mad && ./mad dep com"

+ 1 - 1
LICENSE

@@ -1,4 +1,4 @@
-Copyright (c) 2013—2015 Maxim Sokhatsky, Synrc Research Center
+Copyright (c) 2013—2018 Maxim Sokhatsky, Synrc Research Center
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal

+ 1 - 1
README.md

@@ -3,7 +3,7 @@ MAD
 
 A simple rebar-compatible dependency manager and developer tool with plugins.
 
-![MAD](http://synrc.com/images/mad.png)
+![MAD](http://synrc.space/images/mad.png)
 
 Goals
 -----

+ 8 - 0
escript.config

@@ -0,0 +1,8 @@
+% MAD default:
+%
+{ emu_args, "+pc unicode" }.
+
+% Daemonizable escript:
+%
+% { emu_args, "+pc unicode -detached" }.
+

+ 1 - 1
include/mad.hrl

@@ -1 +1 @@
--define(VERSION,"2f5d1f").
+-define(VERSION,"3a6c99").

+ 190 - 0
index.html

@@ -0,0 +1,190 @@
+<!DOCTYPE html>
+<html >
+<head>
+
+  <link rel=stylesheet type="text/css" href="https://synrc.com/synrc.css?v=1">
+  <meta name="Author" content="5HT">
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+  <meta property="og:image" content="images/s_64.png"/>
+  <meta http-equiv="X-UA-Compatible" content="IE=IE10,chrome=1" />
+  <title>MAD</title>
+</head>
+<body >
+<!--HEVEA command line is: hevea index.tex -o index.htm -->
+<!--CUT STYLE article--><!--HTMLHEAD-->
+
+<div class="nonselectedwrapper white" style="padding: 10px 0px 10px 0px;margin: 0px 0px 10px 0px;">
+    <a href="//synrc.com/">
+    <img style="float:left; margin-left: 55px; margin-top: 5px; margin-bottom:-5px;" src="http://synrc.com/images/synrc.png" border="0"></a>
+
+    <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top" style="display:none;margin-top:39px;">
+      <input type="hidden" name="cmd" value="_s-xclick">
+      <input type="hidden" name="hosted_button_id" value="P8WQHAQK5HWWW">
+      <input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif" border="0" name="submit" style="width:74px;margin-top:0px;">
+     </form>
+
+    <div align=right style="float:right;width:700px;height: auto; margin: 20px 50px 0px 0px;">
+          <script type="text/javascript">
+
+        var args = (window.location).toString().split('/');
+        var page_name = args[args.length-1];
+        var menu = {'https://synrc.com/index.htm':    'Home',
+                    'https://synrc.com/research.htm': 'Showcase',
+                    'https://synrc.com/apps/':   'Apps',
+                    'https://synrc.com/feedback.htm': 'Contacts', };
+
+        Object.keys(menu).forEach(function (key) {
+            if (page_name == key) { document.write('<div class="menu" style="font-weight: bold;">'+menu[key]+'</div>'); }
+            else { document.write('<a class="menu" href="'+key+'">'+menu[key]+'</a>'); }
+        });
+
+        </script>
+    </div>
+</div>
+
+<hr size="1">
+
+<!--ENDHTML-->
+<!--CUT DEF section 1 --><div class="nonselectedwrapper">
+<div class="article">
+<div class="toc">
+<!--TOC section id="sec1" TOC-->
+<h2 id="sec1" class="section">TOC</h2><!--SEC END -->
+<!--TOC paragraph id="sec2" -->
+<!--SEC END --><p>
+<a href="https://synrc.com/apps/mad/doc/web">Overview</a> <br>
+
+<a href="https://synrc.com/apps/mad/doc/web/setup.htm">1. Setup</a> <br>
+
+<a href="https://synrc.com/apps/mad/doc/web/deps.htm">2. Deps</a> <br>
+
+<a href="https://synrc.com/apps/mad/doc/web/config.htm">3. Configuration</a> <br>
+
+<a href="https://synrc.com/apps/mad/doc/web/commands.htm">4. Commands</a> <br>
+
+<a href="https://synrc.com/apps/mad/doc/web/bundles.htm">5. Bundles</a> <br>
+
+<a href="https://synrc.com/apps/mad/doc/book.pdf">Download PDF</a> <br>
+
+</p></div>
+
+<div class="articlecol">
+
+<!--TOC section id="sec3" MAD: Erlang Containers-->
+<h2 id="sec3" class="section">MAD: Erlang Containers</h2><!--SEC END -->
+<!--TOC subsection id="sec4" Purpose-->
+<h3 id="sec4" class="subsection">Purpose</h3><!--SEC END --><p>
+We were trying to make something minimalistic that fits out <a href="https://github.com/synrc">application stack</a>.
+The main idea of mad is to provide clean and simple rebar-like fast dependency manager that
+is able to build several types of packages and provides interface of containered deployments
+to virtualiezed environments.</p>
+<!--TOC subsection id="sec5" Several Types of Packaging-->
+<h3 id="sec5" class="subsection">Several Types of Packaging</h3><!--SEC END --><p>
+The key feature of mad is ability to create single-file bundled web sites.
+This target escript is ready to run on Windows, Linux and Mac.</p>
+<!--TOC subsection id="sec6" Deployment Options-->
+<h3 id="sec6" class="subsection">Deployment Options</h3><!--SEC END --><p>
+As a deploy tool mad is also supposed to launch, start, stop and manage containers, locally or remote.
+You can make containers from different type of packages, like making runc container with beam release.</p>
+<!--TOC subsection id="sec7" OTP Compliant-->
+<h3 id="sec7" class="subsection">OTP Compliant</h3><!--SEC END --><p>
+Mad supports ERTS boot files generation with systools and erlang application format used by OTP.
+This is the main format of application repository. Also boot files are suported on both LING and BEAM.</p>
+<!--TOC subsection id="sec8" Tiny Size-->
+<h3 id="sec8" class="subsection">Tiny Size</h3><!--SEC END --><p>
+And the good part:</p><div class="lstlisting">                      Sources        Binary
+    mad               967 LOC        52 KB
+    rebar             7717 LOC       181 KB</div>
+<!--TOC subsection id="sec9" History-->
+<h3 id="sec9" class="subsection">History</h3><!--SEC END --><p>We came to conclusion that no matter how perfect your libraries are,
+the comfort and ease come mostly from developing tools.
+Everything got started when <a href="https://github.com/proger">Vladimir Kirillov</a> decided to
+replace Rusty’s sync beam reloader. As you know sync uses
+filesystem polling which is neither energy-efficient nor elegant. Also
+sync is only able to recompile separate modules while
+common use-case in N2O is to recompile DTL templates
+and LESS/SCSS stylesheets. That is why we need to recompile
+the whole project. That’s the story how <a href="https://github.com/synrc/active">active</a> emerged.
+Under the hood active is a client subscriber
+of <a href="https://github.com/synrc/fs">fs</a> library, native filesystem listener for Linux, Windows and Mac.</p><p>De-facto standard in Erlang world is rebar.
+We love rebar interface despite its implementation.
+First we plugged rebar into active and then decided to drop its support,
+it was slow, especially in cold recompilation.
+It was designed to be a stand-alone tool, so it has some
+glitches while using as embedded library.
+Later we switched to Makefile-based build tool <a href="https://github.com/synrc/otp.mk">otp.mk</a>.</p><p>The idea to build rebar replacement was up in the air for a long time.
+The best minimal approach was picked up by <a href="https://github.com/s1n4">Sina Samavati</a>,
+who implemented the first prototype called ’mad’. Initially mad
+was able to compile DTL templates, YECC files, escript (like
+bundled in gproc), also it had support for caching with side-effects.
+In a month I forked mad and took over the development under the same name.</p><div class="center">Listing 1: Example of building N2O sample</div><p><br>
+</p><div class="lstlisting">                                   Cold       Hot
+    rebar get-deps compile         53.156s    4.714s
+    mad deps compile               54.097s    0.899s</div><div class="center">Listing 2: Example of building Cowboy</div><p><br>
+</p><div class="lstlisting">                                   Hot
+    make (erlang.mk)               2.588s
+    mad compile                    2.521s</div><p>
+
+<br><br>
+<h2><a name="testimonials"><b>About MAD</b></a></h2><blockquote>At the beginning, Mad was supposed to be only a Rebar-compatible dependency manager, after a while,
+I realized compiling applications as fast as possible with it could be a fascinating work as well.
+<div align=right>Sina Samavati,
+<a href="https://github.com/s1n4">Original MAD Author</a></div></blockquote>
+
+
+
+    <!--div id="disqus_thread"></div>
+    <script type="text/javascript">
+        var disqus_shortname = 'synrc'; // required: replace example with your forum shortname
+        (function() {
+            var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
+            dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
+            (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
+        })();
+    </script>
+    <noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
+    <a href="https://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a-->
+
+
+</p></div>
+</div>
+</div><div class="clear"> </div><!--CUT END -->
+<!--HTMLFOOT-->
+
+<div class="nonselectedwrapper">
+<div class="verywidecol">
+
+    <div style="width:100%;height:300px;float:left;font-size:16pt;" align=center>
+    	<hr size=1>
+    	<br><br><br>
+        <a href="//synrc.com/news/index.htm">Events</a> |
+    	<a href="//synrc.com/privacy.htm">Privacy Policy</a> |
+        <a href="//synrc.com/feedback.htm">Feedback</a> |
+        <a href="//synrc.com/brandbook.htm">Brandbook</a><br>
+    	Copyright &copy; 2005&ndash;2016 <a href="//synrc.com/index.htm"> Synrc Research Center s.r.o.</a>
+    </div>
+
+</div>
+</div>
+
+
+<div class="clear"></div>
+
+<script type="text/javascript">
+
+  var _gaq = _gaq || [];
+    _gaq.push(['_setAccount', 'UA-29227518-1']);
+      _gaq.push(['_trackPageview']);
+      
+        (function() {
+            var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+                ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'https://www') + '.google-analytics.com/ga.js';
+                    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+                      })();
+                      
+                      </script>
+
+<script type="text/javascript" src="https://synrc.com/hi.js"></script>
+<!--ENDHTML-->
+</body>
+</html>

BIN
mad


+ 1 - 1
mix.exs

@@ -3,7 +3,7 @@ defmodule MAD.Mixfile do
 
   def project do
     [app: :mad,
-     version: "2.3.0",
+     version: "2.10.0",
      description: "Small and fast rebar replacement",
      package: package]
   end

+ 29 - 31
src/compile/mad_compile.erl

@@ -1,7 +1,9 @@
 -module(mad_compile).
 %%-author('Sina Samavati').
+
 -compile([export_all, nowarn_export_all]).
 
+
 compile(Params) ->
     { Cwd, ConfigFile, Conf } = mad_utils:configs(),
     Res = case Params of
@@ -26,58 +28,54 @@ bool({ok,_}) -> false;
 bool({error,_}) -> true.
 
 dep(Cwd, _Conf, ConfigFile, Name) ->
-
     DepsDir = filename:join([mad_utils:get_value(deps_dir, _Conf, ["deps"])]),
     DepPath = filename:join([Cwd, DepsDir, Name]),
     mad:info("==> ~p~n",[Name]),
 
-    DepConfigFile = filename:join(DepPath, ConfigFile),
-    Conf = mad_utils:consult(DepConfigFile),
-    Conf1 = mad_script:script(DepConfigFile, Conf, Name),
-    Deps = mad_utils:get_value(deps, Conf1, []),
-    DepsRes = bool(deps(Cwd, Conf, ConfigFile, Deps)),
-
-    SrcDir = filename:join([mad_utils:src(DepPath)]),
-    AllFiles = files(SrcDir,".yrl") ++ 
-               files(SrcDir,".xrl") ++ 
-               files(SrcDir,".erl") ++ % comment this to build with erlc/1
-               files(SrcDir,".app.src"),
-    Files = case mad_utils:get_value(erl_first_files, Conf1, []) of
-              []         -> AllFiles;
-              FirstFiles ->
-                FirstFiles1 = lists:map(fun (F) -> filename:join(SrcDir, F ++ ".erl") end, FirstFiles),
-                FirstFiles1 ++ lists:filter(fun (F) -> lists:member(F, FirstFiles) == false end, AllFiles)
-            end,
+    DepConfig = filename:join(DepPath, ConfigFile),
+    Conf      = mad_utils:consult(DepConfig),
+    Conf1     = mad_script:script(DepConfig, Conf, Name),
+    Deps      = mad_utils:get_value(deps, Conf1, []),
+    DepsRes   = bool(deps(Cwd, Conf, ConfigFile, Deps)),
+    SrcDir    = filename:join([mad_utils:src(DepPath)]),
+
+    AllFiles  = files(SrcDir,".yrl") ++
+                files(SrcDir,".xrl") ++
+                files(SrcDir,".erl"),
+
+    AppSrcFiles = files(SrcDir,".app.src"),
+    FirstFiles = [ filename:join([SrcDir,Y]) || Y <- mad_utils:get_value(erl_first_files, Conf1, []) ],
+    Files = lists:filter(fun (F) -> lists:member(F, FirstFiles) == false end, AllFiles),
 
     case Files of
         [] -> {ok,Name};
         Files ->
-            IncDir = mad_utils:include(DepPath),
-            EbinDir = mad_utils:ebin(DepPath),
-            LibDirs = mad_utils:get_value(lib_dirs, Conf, []),
+            IncDir   = mad_utils:include(DepPath),
+            EbinDir  = mad_utils:ebin(DepPath),
+            LibDirs  = mad_utils:get_value(lib_dirs, Conf, []),
             Includes = lists:flatten([
-                [{i,filename:join([DepPath,L,D,include])} || D<-mad_utils:raw_deps(Deps) ] % for -include
-             ++ [{i,filename:join([DepPath,L])}] || L <- LibDirs ]), % for -include_lib
-            %mad:info("DepPath ~p~n Includes: ~p~nLibDirs: ~p~n",[DepPath,Includes,LibDirs]),
+                       [{i,filename:join([DepPath,L,D,include])} || D <- mad_utils:raw_deps(Deps) ]
+                    ++ [{i,filename:join([DepPath,L])}] || L <- LibDirs ]),
 
-            % create EbinDir and add it to code path
             file:make_dir(EbinDir),
             code:replace_path(Name,EbinDir),
 
-            Opts = mad_utils:get_value(erl_opts, Conf1, []),
-            FilesStatus = compile_files(Files,IncDir, EbinDir, Opts,Includes),
-            DTLStatus = mad_dtl:compile(DepPath,Conf1),
-            PortStatus = lists:any(fun(X)->X end,mad_port:compile(DepPath,Conf1)),
+            PortStatus  = lists:any(fun(X)->X end,mad_port:compile(DepPath,Conf1)),
+            Opts        = mad_utils:get_value(erl_opts, Conf1, []),
+            DTLStatus   = mad_dtl:compile(DepPath,Conf1),
+            FilesStatus = compile_files(FirstFiles ++ Files ++ AppSrcFiles,
+                                        IncDir, EbinDir, Opts,Includes),
 
             put(Name, compiled),
-            case DepsRes orelse FilesStatus orelse DTLStatus orelse PortStatus of
+            case (DepsRes orelse FilesStatus orelse DTLStatus orelse PortStatus)
+                 andalso filelib:is_dir(Name)==false of
                  true -> {error,Name};
                  false -> {ok,Name} end end.
 
 compile_files([],_,_,_,_) -> false;
 compile_files([File|Files],Inc,Bin,Opt,Deps) ->
     case (module(filetype(File))):compile(File,Inc,Bin,Opt,Deps) of
-         true -> true;
+         true -> io:format("Error: ~p~n",[[File,Inc,Bin,Opt,Deps]]), true;
          false -> compile_files(Files,Inc,Bin,Opt,Deps);
          X -> mad:info("Compilation Error: ~p~n",[{X,File}]), true end.
 

+ 6 - 2
src/compile/mad_dtl.erl

@@ -1,7 +1,9 @@
 -module(mad_dtl).
--copyright('Sina Samavati').
+%%-copyright('Sina Samavati').
+
 -compile([export_all, nowarn_export_all]).
 
+
 compile(Dir,Config) ->
     case mad_utils:get_value(erlydtl_opts, Config, []) of
         [] -> false;
@@ -59,7 +61,9 @@ compile_erlydtl_files(Opts) ->
              true -> ok end
     end,
 
-    lists:any(fun({error, _}) -> true;
+    lists:any(fun({error,_}) -> true;
+                 ({error,_,_}) -> true;
                  ({ok, _}) -> false;
                  (ok) -> false
               end, [Compile(F) || F <- Files]).
+

+ 21 - 7
src/compile/mad_erl.erl

@@ -6,28 +6,42 @@
 ]).
 
 
--define(COMPILE_OPTS(Inc, Ebin, Opts, Deps), [return_errors, return_warnings, warnings_as_errors, {i, [Inc]}, {outdir, Ebin}] ++ Opts ++ Deps).
+-define(COMPILE_OPTS(Inc, Ebin, Opts, Deps),
+  [return_errors, return_warnings, warn_export_all, warn_export_all, warn_unused_import,
+   {i, [Inc]}, {outdir, Ebin}] ++ Opts ++ Deps). %% warnings_as_errors
 
 
 erl_to_beam(Bin, F) ->
   filename:join(Bin, filename:basename(F, ".erl") ++ ".beam").
 
-
 compile(File, Inc, Bin, Opt, Deps) ->
   BeamFile = erl_to_beam(Bin, File),
   Compiled = mad_compile:is_compiled(BeamFile, File),
   case Compiled of
     false ->
-      Opts1 = ?COMPILE_OPTS(Inc, Bin, Opt, Deps),
+      Opt1 = case {application:get_env(mad, debug, false), application:get_env(mad, warnings_as_errors, false)} of
+        {true, true} -> [ debug_info, warnings_as_errors ];
+        {true, false} -> [ debug_info ];
+        {false, true} -> [ warnings_as_errors ];
+        _ -> []
+      end ++ Opt,
+      Opts2 = ?COMPILE_OPTS(Inc, Bin, Opt1, Deps),
       mad:info("Compiling ~s~n", [File -- mad_utils:cwd()]),
-      ret(compile:file(File, Opts1));
+      R1 = compile:file(File, Opts2),
+      %mad:info("compile: ~p~n", [R1]),
+      ret(R1);
     _ -> false
   end.
 
 
-ret(error) -> true;
-ret({error, X}) -> lines(error, X);
-ret({error, X, _}) -> lines(error, X);
+ret(error) -> mad:info("compile: error~n", []), true;
+ret({error, X} = _R) ->
+  %%mad:info("compile: ~p~n", [R]),
+  lines(error, X);
+ret({error, X, Y} = _R) ->
+  %%mad:info("compile: ~p~n", [R]),
+  lines(error, X),
+  lines(error, Y);
 ret({ok, _}) -> false;
 ret({ok, _, []}) -> false;
 ret({ok, _, X}) -> lines(warning, X), false;

+ 143 - 22
src/compile/mad_port.erl

@@ -1,27 +1,148 @@
 -module(mad_port).
--copyright('Maxim Sokhatsky').
+%%-copyright('Maxim Sokhatsky').
+
 -compile([export_all, nowarn_export_all]).
 
-replace_env(String, []) -> String;
-replace_env(String, [{K,V}|Env]) ->
-   replace_env(re:replace(String, K, V, [global, {return, list}]),Env).
 
 compile(Dir,Config) ->
-    case mad_utils:get_value(port_specs, Config, []) of
-        [] -> [false];
-         X -> compile_port(Dir,X,Config) end.
-
-compile_port(Dir,Specs,Config) ->
-    {_,S} = os:type(),
-    System = atom_to_list(S),
-    filelib:ensure_dir(Dir ++ "/priv/"),
-    Env = [ {Var,Val} || {Sys,Var,Val} <- mad_utils:get_value(port_env, Config, []), Sys == System ],
-    [ begin
-           Template = string:join(filelib:wildcard(Dir ++ "/" ++ Files)," ") 
-              ++ " CFLAGS LDFLAGS -o " ++ Dir ++ "/" ++ Out,
-       Args = string:strip(replace_env(Template,Env),both,32),
-       {_,Status,Report} = sh:run("cc",string:tokens(Args," "),binary,Dir,Env),
-       case Status of
-          0 -> false;
-          _ -> mad:info("Port Compilation Error: ~p",[Report]), true end
-      end || {Sys,Out,Files} <- Specs, Sys == System].
+  case mad_utils:get_value(port_specs, Config, []) of
+      [] -> [false];
+       X -> compile_port(Dir,X,Config) end.
+
+compile_port(Dir,Specs0,Config) ->
+  {_,Flavour} = os:type(),
+  System = erlang:atom_to_list(Flavour),
+  Specs = [ {O,F} || {Sys,O,F} <- Specs0, Sys == System] ++
+          [ {O,F} || {O,F} <- Specs0],
+  filelib:ensure_dir(Dir ++ "/priv/"),
+  Env = [ {Var,Val} || {Sys,Var,Val} <- mad_utils:get_value(port_env, Config, []), system(Sys,System) ] ++
+        [ {Var,Val} || {Var,Val} <- mad_utils:get_value(port_env, Config, []) ],
+
+  Job = fun({Target, Patterns}) ->
+          Files      = files(Dir,Patterns),
+          LinkLang   = link_lang(Files),
+          TargetType = target_type(extension(Target)),
+
+          Compile = fun(F) ->
+                       Obj = to_obj(F),
+                       case is_compiled(Obj,F) of
+                        false ->
+                           Ext   = extension(F),
+                           CC    = compiler(Ext),
+                           TplCC = tpl_cc(TargetType,CC),
+                           Env1  = [{"PORT_IN_FILES", F},{"PORT_OUT_FILE", Obj}] ++ Env ++ default_env(),
+                           CmdCC = string:strip(expand(System,TplCC,Env1)),
+                           Cmd = expand(System,CmdCC,[{"CXXFLAGS",""},{"LDFLAGS",""},{"CFLAGS",""}]),
+                           {_,Status,Report} = sh:run("cc",string:tokens(Cmd," "),binary,Dir,Env),
+                           case Status of 
+                            0 -> {ok,Obj};
+                            _ -> {error, Report}
+                           end;
+                        true -> {even,Obj}
+                       end 
+                    end,
+
+          {EvenOrOk,Errors} = lists:partition(fun({ok,_})   -> true;
+                                                 ({even,_}) -> true;
+                                                 ({error,_})-> false
+                                              end,[Compile(F) || F <- Files]),
+          %mad:info("EvenOrOk: ~p~nErrors: ~p~n",[EvenOrOk,Errors]),
+          case Errors of
+            [] -> case lists:any(fun({ok,_}) -> true;(_)->false end, EvenOrOk) of
+                   false ->  false; %% all even, no need to link target
+                   true  ->  Objs = [O||{_,O} <-EvenOrOk],
+                             Env2  = [{"PORT_IN_FILES", string:join(Objs," ")},
+                                      {"PORT_OUT_FILE", Target}] ++ Env ++ default_env(),
+                             TplLD = tpl_ld(TargetType,LinkLang),
+                             CmdLD = string:strip(expand(System,TplLD,Env2)),
+                             Cmd = expand(System,CmdLD,[{"CXXFLAGS",""},{"LDFLAGS",""},{"CFLAGS",""}]),
+                             mad:info("cc ~s~n",[Cmd]),
+                             {_,Status,Report} = sh:run("cc",string:tokens(Cmd," "),binary,Dir,Env),
+                             case Status of 
+                              0 -> false;
+                              _ -> mad:info("Port Compilation Error:~n" ++ io_lib:format("~ts",[Report]),[]),
+                                   true % {error, Report}
+                             end                      
+                  end;
+            Errors -> mad:info("Port Compilation Error:~p~n",[Errors]),
+                      true
+          end
+        end,
+  [Job(S)||S <- Specs].
+
+to_obj(F) -> filename:rootname(F) ++ ".o".
+
+%%FIXME
+expand(_System, String, []) -> String;
+expand(System, String, [{K,V}|Env]) ->
+  New = re:replace(String, io_lib:format("\\${?(~s)}?",[K]), V, [global, {return, list}]),
+  expand(System,New,Env);
+expand(System, String, [{Sys,K,V}|Env]) -> 
+  case system(Sys,System) of
+    true -> New = re:replace(String, io_lib:format("\\${?(~s)}?",[K]), V, [global, {return, list}]),
+            expand(System,New,Env);
+    false -> expand(System,String,Env)
+  end.
+
+extension(F)       -> filename:extension(F).
+is_compiled(O,F)   -> filelib:is_file(O) andalso (mad_utils:last_modified(O) >= mad_utils:last_modified(F)).
+join(A,B)          -> filename:join(A,B).
+concat(X)          -> lists:concat(X).
+system(Sys,System) -> Sys == System orelse match(Sys,System).
+match(Re,System)   -> case re:run(System, Re, [{capture,none}]) of match -> true; nomatch -> false end.
+erts_dir()         -> join(code:root_dir(), concat(["erts-", erlang:system_info(version)])).
+erts_dir(include)  -> " -I"++join(erts_dir(), "include").
+ei_dir()           -> case code:lib_dir(erl_interface) of {error,bad_name} -> ""; E -> E end.
+ei_dir(include)    -> case ei_dir() of "" -> ""; E -> " -I"++join(E,"include") end;
+ei_dir(lib)        -> case ei_dir() of "" -> ""; E -> " -L"++join(E,"lib") end.
+link_lang(Files)   -> lists:foldl(fun(_F,cxx) -> cxx;
+                                     (F,cc) -> case compiler(extension(F)) == "$CXX" of 
+                                                true -> cxx;false -> cc end
+                                  end,cc,Files).
+
+files(Dir,Patterns)-> files(Dir,Patterns, []).
+files(_,[], Acc) -> lists:reverse(Acc);
+files(D,[H|T], Acc) ->
+  files(D,T,filelib:wildcard(join(D,H)) ++ Acc). 
+
+
+target_type(".so")   -> drv;
+target_type(".dll")  -> drv;
+target_type("")      -> exe;
+target_type(".exe")  -> exe.
+
+tpl_cc(drv,"$CC") -> " -c $CFLAGS $DRV_CFLAGS $PORT_IN_FILES -o $PORT_OUT_FILE";  
+tpl_cc(drv,"$CXX")-> " -c $CXXFLAGS $DRV_CFLAGS $PORT_IN_FILES -o $PORT_OUT_FILE";  
+tpl_cc(exe,"$CC") -> " -c $CFLAGS $EXE_CFLAGS $PORT_IN_FILES -o $PORT_OUT_FILE";  
+tpl_cc(exe,"$CXX")-> " -c $CXXFLAGS $EXE_CFLAGS $PORT_IN_FILES -o $PORT_OUT_FILE".  
+
+tpl_ld(drv,cc)  -> " $PORT_IN_FILES $LDFLAGS $DRV_LDFLAGS -o $PORT_OUT_FILE";
+tpl_ld(drv,cxx) -> " $PORT_IN_FILES $LDFLAGS $DRV_LDFLAGS -o $PORT_OUT_FILE";
+tpl_ld(exe,cc)  -> " $PORT_IN_FILES $LDFLAGS $EXE_LDFLAGS -o $PORT_OUT_FILE";
+tpl_ld(exe,cxx) -> " $PORT_IN_FILES $LDFLAGS $EXE_LDFLAGS -o $PORT_OUT_FILE".
+
+erl_ldflag() -> concat([ei_dir(include), erts_dir(include), " "]).
+
+default_env() ->
+    %Arch = os:getenv("REBAR_TARGET_ARCH"),
+    %Vsn = os:getenv("REBAR_TARGET_ARCH_VSN"),
+    [
+     {"darwin", "DRV_LDFLAGS", "-bundle -flat_namespace -undefined suppress " ++ei_dir(lib) ++" -lerl_interface -lei"},
+     {"DRV_CFLAGS" , "-g -Wall -fPIC -MMD " ++ erl_ldflag()},
+     {"DRV_LDFLAGS", "-shared " ++ erl_ldflag()},
+     {"EXE_CFLAGS" , "-g -Wall -fPIC -MMD " ++ erl_ldflag()},
+     {"EXE_LDFLAGS", ei_dir(lib)++" -lerl_interface -lei"},
+     {"ERL_EI_LIBDIR", ei_dir(lib)}
+    ].
+
+compiler(".cc")  -> "$CXX";
+compiler(".cp")  -> "$CXX";
+compiler(".cxx") -> "$CXX";
+compiler(".cpp") -> "$CXX";
+compiler(".CPP") -> "$CXX";
+compiler(".c++") -> "$CXX";
+compiler(".C")   -> "$CXX";
+compiler(cxx)    -> "$CXX";
+compiler(cc)     -> "$CC";
+compiler(_)      -> "$CC".
+

+ 1 - 1
src/compile/mad_yecc.erl

@@ -8,6 +8,6 @@ compile(File,Inc,Bin,Opt,Deps) ->
     ErlFile = yrl_to_erl(File),
     Compiled = mad_compile:is_compiled(ErlFile,File),
     if Compiled == false ->
-        yecc:file(File),
+        yecc:file(File,[{verbose,true}]),
         mad_erl:compile(ErlFile,Inc,Bin,Opt,Deps); true -> false end.
 

+ 128 - 0
src/hooks/mad_hooks.erl

@@ -0,0 +1,128 @@
+-module(mad_hooks).
+
+-export([run_hooks/2]).
+
+
+-spec run_hooks(pre|post, atom()) -> any().
+run_hooks(Type, Command) ->
+    {_Cwd, _ConfigFile, Config} = mad_utils:configs(),
+    Dir = mad_utils:cwd(),
+    run_hooks(Dir, Type, Command, Config).
+
+run_hooks(Dir, pre, Command, Config) ->
+    run_hooks(Dir, pre_hooks, Command, Config);
+run_hooks(Dir, post, Command, Config) ->
+    run_hooks(Dir, post_hooks, Command, Config);
+run_hooks(Dir, Type, Command, Config) ->
+    MaybeHooks = mad_utils:get_value(Type, Config, []),
+    apply_hooks(Dir, Command, Config, MaybeHooks).
+
+apply_hooks(_, _, _, []) ->
+    done;
+apply_hooks(Dir, Command, Config, Hooks) ->
+    Env = create_env(Config),
+    lists:foreach(fun({_, C, _} = Hook) when C =:= Command ->
+        apply_hook(Dir, Env, Hook);
+    ({C, _} = Hook) when C =:= Command ->
+        apply_hook(Dir, Env, Hook);
+    (_) ->
+        continue
+    end, Hooks).
+
+apply_hook(Dir, Env, {Arch, Command, Hook}) ->
+    case is_arch(Arch) of
+        true ->
+            apply_hook(Dir, Env, {Command, Hook});
+        false ->
+            ok
+    end;
+apply_hook(Dir, Env, {Command, Hook}) ->
+    sh(Command, Hook, Dir, Env).
+
+%% Can be expanded
+create_env(_Config) -> [].
+
+%% SOURCE: 
+%%  https://github.com/erlang/rebar3/blob/master/src/rebar_utils.erl 
+is_arch(ArchRegex) ->
+    case re:run(get_arch(), ArchRegex, [{capture, none}]) of
+        match ->
+            true;
+        nomatch ->
+            false
+    end.
+
+get_arch() ->
+    Words = wordsize(),
+    otp_release() ++ "-"
+        ++ erlang:system_info(system_architecture) ++ "-" ++ Words.
+
+wordsize() ->
+    try erlang:system_info({wordsize, external}) of
+        Val ->
+            integer_to_list(8 * Val)
+    catch
+        error:badarg ->
+            integer_to_list(8 * erlang:system_info(wordsize))
+    end.
+
+otp_release() ->
+    otp_release1(erlang:system_info(otp_release)).
+
+%% If OTP <= R16, otp_release is already what we want.
+otp_release1([$R,N|_]=Rel) when is_integer(N) ->
+    Rel;
+%% If OTP >= 17.x, erlang:system_info(otp_release) returns just the
+%% major version number, we have to read the full version from
+%% a file. See http://www.erlang.org/doc/system_principles/versions.html
+%% Read vsn string from the 'OTP_VERSION' file and return as list without
+%% the "\n".
+otp_release1(Rel) ->
+    File = filename:join([code:root_dir(), "releases", Rel, "OTP_VERSION"]),
+    case file:read_file(File) of
+        {error, _} ->
+            Rel;
+        {ok, Vsn} ->
+            %% It's fine to rely on the binary module here because we can
+            %% be sure that it's available when the otp_release string does
+            %% not begin with $R.
+            Size = byte_size(Vsn),
+            %% The shortest vsn string consists of at least two digits
+            %% followed by "\n". Therefore, it's safe to assume Size >= 3.
+            case binary:part(Vsn, {Size, -3}) of
+                <<"**\n">> ->
+                    %% The OTP documentation mentions that a system patched
+                    %% using the otp_patch_apply tool available to licensed
+                    %% customers will leave a '**' suffix in the version as a
+                    %% flag saying the system consists of application versions
+                    %% from multiple OTP versions. We ignore this flag and
+                    %% drop the suffix, given for all intents and purposes, we
+                    %% cannot obtain relevant information from it as far as
+                    %% tooling is concerned.
+                    binary:bin_to_list(Vsn, {0, Size - 3});
+                _ ->
+                    binary:bin_to_list(Vsn, {0, Size - 1})
+            end
+    end.
+%% END of SOURCE
+
+sh(Command, Hook, Dir, Env) ->
+    Port = erlang:open_port({spawn, Hook},
+        [
+            stream,
+            stderr_to_stdout,
+            binary,
+            exit_status,
+            {cd, Dir},
+            {env, Env}
+        ]
+    ),
+    {done, Status, Out} = sh:sh_loop(Port, binary),
+    case Status of
+        0 -> 
+            mad:info("~s~n", [Out]);
+        _ ->
+            mad:info("Failed hook for ~p with ~s~n", [Command, Out]),
+            exit({error, Out})
+    end. 
+

+ 2 - 2
src/mad.app.src

@@ -1,6 +1,6 @@
 {application, mad,
- [{description, "MAD VXZ Build Tool - Erlang/OTP Containers"},
-  {vsn, "2.3"},
+ [{description, "MAD VXZ Build Tool - Managing Dependencies"},
+  {vsn, "2.10"},
   {registered, []},
   {applications, [kernel,stdlib,inets]},
   {env, []}]}.

+ 19 - 15
src/mad.erl

@@ -16,19 +16,21 @@ main(Params) ->
          {[X|C], R}
     end, {[], []}, lists:map(fun atomize/1, Params) ),
   
-  return(lists:any(
-    fun({error, _}) -> true;
-       (_) -> false
-    end,
-    lists:flatten(
-      lists:foldl(
-        fun({Fun, Arg}, []) ->
-             errors((profile()):Fun(Arg));
-           ({_, _}, Err) ->
-             errors(Invalid),
-             {return, Err}
-        end, [], Valid)
-      ))).
+  return(
+        lists:any(fun({error,_}) -> true; (_) -> false end,
+            lists:flatten(
+                lists:foldl(fun({Fun,Arg},[]) ->
+                    mad_hooks:run_hooks(pre, Fun),
+                    Errors = errors((profile()):Fun(Arg)),
+                    mad_hooks:run_hooks(post, Fun),
+                    Errors;
+                ({_,_},Err) ->
+                    errors(Invalid), {return,Err}
+                end,
+                [], Valid)
+           )
+        )
+    ).
 
 
 atomize("static") -> 'static';
@@ -38,6 +40,7 @@ atomize("dep")    -> 'deps';
 atomize("deps")   -> 'deps';
 atomize("cle"++_) -> 'clean';
 atomize("com"++_) -> 'compile';
+atomize("eunit")  -> 'eunit';
 atomize("up")     -> 'up';
 atomize("rel"++_) -> 'release';
 atomize("bun"++_) -> 'release';
@@ -47,6 +50,7 @@ atomize("att"++_) -> 'attach';
 atomize("sh")     -> 'sh';
 atomize("rep"++_) -> 'sh';
 atomize("pla"++_) -> 'resolve';
+atomize("str"++_) -> 'strip';
 atomize(Else)     -> Else.
 
 profile()         -> application:get_env(mad,profile,mad_local).
@@ -81,7 +85,7 @@ help()            -> info("MAD Container Tool version ~s~n",[?VERSION]),
                      info("~n"),
                      info("    invoke = mad params~n"),
                      info("    params = [] | command [ options  ] params ~n"),
-                     info("   command = app     | deps  | clean | compile | up~n"),
+                     info("   command = app     | deps  | clean | compile | up | eunit | strip~n"),
                      info("           | release [ beam  | ling  | script  | runc | depot ]~n"),
-                     info("           | deploy  | start | stop  | attach  | sh ~n"),
+                     info("           | deploy  | start | stop  | attach  | sh | static [ <watch|min> ] ~n"),
                      return(false).

+ 1 - 0
src/mad_release.erl

@@ -9,4 +9,5 @@ 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(["depot",N])     -> mad_synrc:depot_release(N);
 release([X])             -> release(["script",X]).

+ 2 - 2
src/mad_resolve.erl

@@ -49,6 +49,6 @@ system_deps(A) ->
 
 main(_) ->
     case orderapps() of
-         {ok,Ordered}   -> mad:info("Ordered: ~p~n",[Ordered]),
-                           file:write_file(".applist",io_lib:format("~w",[Ordered])), {ok,Ordered};
+         {ok,Ordered}   -> mad:info("Generating .applist ~p~n",[Ordered]),
+                           file:write_file(".applist", io_lib:format("~w",[Ordered])), {ok,Ordered};
          {error,Reason} -> {error,Reason} end.

+ 23 - 5
src/mad_static.erl

@@ -1,7 +1,23 @@
 -module(mad_static).
--copyright('Yuri Artemev').
+%-author('Yuri Artemev').
+
 -compile([export_all, nowarn_export_all]).
--define(NODE(Bin), "node_modules/.bin/"++Bin).
+
+-define(NODE(Bin), "node_modules/.bin/" ++ Bin).
+
+
+main(_Config, ["min"]) ->
+    {ok,[SysConfig]} = file:consult("sys.config"),
+    N2O     = proplists:get_value(n2o,SysConfig,[]),
+    AppName = proplists:get_value(app,N2O,sample),
+    Minify  = proplists:get_value(minify,N2O,[]),
+    Command = lists:concat(["uglify -s ", string:join(erlang:element(2, Minify),", "),
+                            " -o ", erlang:element(1, Minify), "/", AppName, ".min.js"]),
+    io:format("Minify: ~p~n",[Command]),
+    case sh:run(Command) of
+         {_,0,_} -> {ok,static};
+         {_,_,_} -> mad:info("minifyjs not installed. try `npm install -g uglify`~n"), {error,minifier}
+    end;
 
 main(Config, ["watch"]) ->
     case mad_utils:get_value(static, Config, []) of
@@ -48,9 +64,11 @@ app(Params) ->
     mad_repl:load(),
     Apps = ets:tab2list(filesystem),
     [ case string:str(File,"priv/web") of
-       1 -> Relative = Name ++ string:substr(File, 9),
+       1 -> Relative = unicode:characters_to_list(Name ++ string:replace(string:substr(File, 9), "sample", Name, all), utf8),
             mad:info("Create File: ~p~n",[Relative]),
             filelib:ensure_dir(Relative),
-            file:write_file(Relative,Bin);
+            BinNew = string:replace(Bin, "sample", Name, all),
+            file:write_file(Relative, BinNew);
        _ -> skip
-       end || {File,Bin} <- Apps ], {ok,Name}.
+       end || {File,Bin} <- Apps, erlang:is_list(File) ], {ok,Name}.
+

+ 7 - 0
src/mad_strip.erl

@@ -0,0 +1,7 @@
+-module(mad_strip).
+-export([main/1]).
+
+main(_) ->
+ Res = beam_lib:strip_files(mad_repl:wildcards(["{apps,deps,lib}/*/ebin/*.beam","ebin/*.beam"])),
+ mad:info("~p~n",[Res]),
+ false.

+ 1 - 0
src/mad_utils.erl

@@ -25,6 +25,7 @@ consult(File) ->
 src(Dir) -> filename:join(Dir, "src").
 include(Dir) -> filename:join(Dir, "include").
 ebin(Dir) -> filename:join(Dir, "ebin").
+priv(Dir) -> filename:join(Dir, "priv").
 deps(File) -> get_value(deps, consult(File), []).
 
 get_value(Key, Opts, undefined) -> get_value(Key, Opts, []);

+ 20 - 9
src/package/mad_escript.erl

@@ -3,23 +3,34 @@
 -compile([export_all, nowarn_export_all]).
 
 main(N) ->
-    App = filename:basename(case N of [] -> mad_utils:cwd(); E -> E end),
+        App = filename:basename(case N of [] -> mad_utils:cwd(); E -> E end),
     mad_resolve:main([]),
-    EmuArgs = "+pc unicode",
+    DefaultEmuArgs = "+pc unicode",
+    EmuArgs = case file:consult( "escript.config" ) of
+       { ok, Terms } -> proplists:get_value( emu_args, Terms, DefaultEmuArgs );
+       _ -> DefaultEmuArgs
+    end,
     Files = static() ++ beams(fun filename:basename/1, fun read_file/1) ++ overlay(),
-    %[ io:format("Escript: ~ts~n", [File]) || { File, _ } <- Files ],
+%   [ 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.
+
+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 } ].
+  Name = "static.gz",
+  {ok, {_, Bin}} = zip:create(Name,
+    [ { erlang:binary_to_list(base64:encode(unicode:characters_to_binary(F))), erlang:element(2, file:read_file(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) ->

+ 7 - 7
src/package/mad_systools.erl

@@ -7,12 +7,12 @@
 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"}].
+    [{"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,
@@ -48,7 +48,7 @@ beam_release(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}"]) ] ++
+            "/bin/{epmd,erlexec,run_erl,to_erl,escript,beam.smp,erl_child_setup,inet_gethost}"]) ] ++
         apps(Apps) ++ scripts(N),
     erl_tar:create(N ++ ".tgz",Files,[compressed]),
     mad:info("~s.boot: ~p~n",[N,Res]),

+ 4 - 0
src/profile/mad_local.erl

@@ -13,3 +13,7 @@ sh(Params)        -> mad_repl:sh(Params).
 deps(Params)      -> mad_git:deps(Params).
 up(Params)        -> mad_git:up(Params).
 fetch(Params)     -> mad_git:fetch(Params).
+static(Params)    -> mad_static:main([], Params).
+eunit(Params)     -> mad_eunit:main_test(Params).
+strip(Params)     -> mad_strip:main(Params).
+

+ 119 - 61
src/provision/mad_repl.erl

@@ -6,17 +6,17 @@
 
 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].
+             wx,ssl,runtime_tools,public_key,observer,inets,asn1,et,eunit,hipe,os_mon,parsetools,odbc,snmp].
 
 local_app() ->
     case filename:basename(filelib:wildcard("ebin/*.app"),".app") of
          [] -> [];
-         A -> list_to_atom(A) end.
+         A -> erlang:list_to_atom(A) end.
 
 applist() ->
     Name = ".applist",
     case file:read_file(Name) of
-         {ok,Binary} -> parse_applist(Binary); 
+         {ok,Binary} -> parse_applist(Binary);
          {error,_} ->
            case mad_repl:load_file(Name) of
               {error,_} -> mad_resolve:main([]);
@@ -28,54 +28,73 @@ 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() ->
+load_sysconfig() ->
    Config = wildcards(["sys.config", lists:concat( ["etc/", mad:host(), "/sys.config"] )]),
-   Apps = case Config of
-        [] -> case mad_repl:load_file("sys.config") of
-              {error,_} -> [];
-              {ok,Bin} -> parse(unicode:characters_to_list(Bin)) end;
+   case Config of
+      [] -> case mad_repl:load_file("sys.config") of
+             {error,_} -> [];
+             {ok,Bin} -> parse(unicode:characters_to_list(Bin)) end;
       File -> case file:consult(hd(File)) of
               {error,_} -> [];
-              {ok,[A]} -> A end end,
-  load_config(Apps, []).
-
-
-load_config([H|T], Apps2) ->
-    App2 = case H of
-        {App,Cfg} -> [application:set_env(App,K,V) || {K,V} <- Cfg], [H];
-        File when erlang:is_list(File) ->
-            Apps = case file:consult(File) of
-                {error,_} -> [];
-                {ok,[A]} -> A end,
-            load_config(Apps, []);
-        _ -> []
-    end,
-    load_config(T, Apps2 ++ App2);
-load_config([], Apps2) ->
-    Apps2.
+              {ok,[A]} -> merge_include(A, []) end end.
+
+
+application_config(AppConfigs) ->
+  [ [ application:set_env(App,K,V) || {K,V} <- Cfg] || {App,Cfg} <- AppConfigs].
+
+merge_include([], Acc) -> Acc;
+merge_include([H | Rest], Acc) -> merge_include(Rest, merge_config(H, Acc)).
+
+merge_config({App, NewConfig} = Add, Acc) ->
+    lists:keystore(App, 1, Acc, case lists:keyfind(App, 1, Acc) of
+        false ->  Add;
+        {App, AppConfigs} -> merge_config(App, AppConfigs, NewConfig)
+    end);
+
+merge_config(File, Acc) when is_list(File) ->
+    BFName = filename:basename(File, ".config"),
+    FName = filename:join(filename:dirname(File), BFName ++ ".config"),
+    case file:consult(FName) of
+        {ok,[A]} -> merge_include(A, Acc);
+        _ -> Acc
+    end.
+
+merge_config(App, AppConfigs, []) -> {App, AppConfigs};
+merge_config(App, AppConfigs, [{Key, _} = Tuple | Rest]) ->
+    merge_config(App, lists:keystore(Key, 1, AppConfigs, Tuple), Rest).
 
 
 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.
+  application:ensure_all_started(A), Acc.
+
+% for system application we just start, forgot about env merging
+
+load(true,A,Acc,_Config) ->
+    acc_start(A,Acc);
+
+% for user application we should merge app from ebin and from sys.config
+% and start application using tuple argument in app controller
+
+load(X,A,Acc,Config) ->
+    try {application,Name,Map} = load_config(A),
+        NewEnv = merge(Config,Map,Name),
+        acc_start({application,Name,set_value(env,1,Map,{env,NewEnv})},Acc)
+    catch _E:_R -> io:format("Application Load Error: ~p",[{X,A,Acc}]) end.
+
+merge(Config,Map,Name) ->
+    lists:foldl(fun({Name2,E},Acc2) when Name2 =:= Name ->
+    lists:foldl(fun({K,V},Acc1)      -> set_value(K,1,Acc1,{K,V}) end,Acc2,E);
+                          (_,Acc2)   -> Acc2 end, proplists:get_value(env,Map,[]), Config).
+
+load_apps([],Config,Acc)             -> [ load(lists:member(A,system()),A,Acc,Config) || A <- applist()];
+load_apps(["applist"],Config,Acc)    -> load_apps([],Config,Acc);
+load_apps(Params,_,_Acc)             -> [ application:ensure_all_started(list_to_atom(A))||A<-Params].
+
+set_value(Name,Pos,List,New)         -> add_replace(lists:keyfind(Name,Pos,List),Name,Pos,List,New).
+add_replace(false,_Name,_Pos,List,New) -> [New|List];
+add_replace(_,Name,Pos,List,New)     -> lists:keyreplace(Name,Pos,List,New).
+
+cwd() -> case file:get_cwd() of {ok, Cwd} -> Cwd; _ -> "." end.
 
 sh(Params) ->
     { _Cwd,_ConfigFileName,_Config } = mad_utils:configs(),
@@ -85,20 +104,53 @@ sh(Params) ->
     code:set_path(SystemPath++UserPath),
     code:add_path(filename:join([cwd(),filename:basename(escript:script_name())])),
     load(),
-    Config = load_config(),
+    Config = load_sysconfig(),
+    application_config(Config),
     Driver = mad_utils:get_value(shell_driver,_Config,user_drv),
-    repl_intro(),
+    repl_intro(Config),
     case os:type() of
       {win32,nt} ->
         os:cmd("chcp 65001"),
         shell:start();
       _ ->
-        Driver:start()
+        O = erlang:whereis(user),
+        supervisor:terminate_child(kernel_sup, user),
+        Driver:start(),
+        wait(3000),
+        rewrite_leaders(O, erlang:whereis(user))
     end,
     load_apps(Params,Config,[]),
     case Params of
         ["applist"] -> skip;
         _ ->  timer:sleep(infinity) end.
+remove(0) -> skip;
+remove(N) -> case gen_event:delete_handler(error_logger, error_logger, []) of
+                  {error, module_not_found} -> ok;
+                  {error_logger, _} -> remove(N-1) end.
+
+wait(0) -> erlang:error(timeout);
+wait(Timeout) -> case erlang:whereis(user) of undefined -> timer:sleep(100), wait(Timeout - 100); _ -> ok end.
+
+rewrite_leaders(OldUser, NewUser) ->
+    _ = [catch erlang:group_leader(NewUser, Pid)
+         || Pid <- erlang:processes(),
+            proplists:get_value(group_leader, erlang:process_info(Pid)) == OldUser,
+            erlang:is_process_alive(Pid)],
+    OldMasters = [Pid
+         || Pid <- erlang:processes(),
+            Pid < NewUser, % only change old masters
+            {_,Dict} <- [erlang:process_info(Pid, dictionary)],
+            {application_master,init,4} == proplists:get_value('$initial_call', Dict)],
+    _ = [catch erlang:group_leader(NewUser, Pid)
+         || Pid <- erlang:processes(),
+            lists:member(proplists:get_value(group_leader, erlang:process_info(Pid)),
+                         OldMasters)],
+    try   error_logger:swap_handler(tty),
+          remove(3)
+    catch _E:_R ->
+      hope_for_best
+    end.
+
 
 load() ->
     ets_created(),
@@ -107,13 +159,19 @@ load() ->
     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].
+  {ok, Unzip} = zip:unzip(Bin, [memory]),
+  [begin
+   try
+     ets:insert(filesystem, {unicode:characters_to_list(base64:decode(erlang:list_to_binary(U))), FileBin})
+   catch _:_ ->
+     ets:insert(filesystem, {U, FileBin})
+   end,
+   case U of
+     "static.gz" -> unfold_zips(FileBin);
+     _ -> skip
+   end
+   end || {U, FileBin} <- Unzip].
+
 
 ets_created() ->
     case ets:info(filesystem) of
@@ -126,14 +184,14 @@ load_file(Name)  ->
         [{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) ->
+load_config(A) when erlang:is_atom(A) -> load_config(erlang:atom_to_list(A));
+load_config(A) when erlang: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));
+         {ok,Bin} -> parse(erlang:binary_to_list(Bin));
          {error,_} -> case ets:lookup(filesystem,AppFile) of
-                          [{Name,Bin}] -> parse(binary_to_list(Bin));
+                          [{AppFile,Bin}] -> parse(erlang:binary_to_list(Bin));
                           _ -> [] end end.
 
 parse(String) ->
@@ -143,7 +201,7 @@ parse(String) ->
     Value.
 
 
-repl_intro() ->
-  io:format("Configuration: ~p~n", [load_config()]),
+repl_intro(Config) ->
+  io:format("Configuration: ~p~n", [Config]),
   io:format("Applications:  ~p~n", [applist()]).
 

+ 4 - 2
src/provision/mad_run.erl

@@ -1,6 +1,8 @@
 -module(mad_run).
+
 -compile([export_all, nowarn_export_all]).
 
+
 start(App) ->                            % run_dir > < log_dir
     mad:info("Scripting: ~p~n",[escript:script_name()]),
     {_,Status,X} = sh:run("run_erl",["-daemon",".",".","exec "++escript:script_name()++" sh"],
@@ -16,6 +18,6 @@ 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,[]}.
+clean(_) -> [ file:delete(X) || X <- filelib:wildcard("{apps,deps}/*/ebin/*.beam") ++
+                                     filelib:wildcard("ebin/*.beam")], {ok,[]}.
 

+ 1 - 1
src/sources/mad_git.erl

@@ -93,7 +93,7 @@ pull(Config,[F|T]) ->
     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;
+                   nomatch -> mad_utils:verbose(Config,Message), {error,Message};
                    _ -> pull(Config,T) end end.
 
 up(Params) ->

+ 11 - 0
src/test/mad_eunit.erl

@@ -0,0 +1,11 @@
+-module(mad_eunit).
+
+-compile(export_all).
+
+
+main_test(_Params) ->
+    case application:get_application() of
+      {ok, App} -> eunit:test([{application, App}]);
+              _ -> eunit:test()
+    end.
+