mad_compile.erl 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. -module(mad_compile).
  2. -copyright('Sina Samavati').
  3. -compile(export_all).
  4. -define(COMPILE_OPTS(Inc, Ebin, Opts), [report, {i, Inc}, {outdir, Ebin}] ++ Opts).
  5. -type directory() :: string().
  6. -type filename() :: string().
  7. %% compile dependencies
  8. -spec deps(directory(), any(), filename(), [mad_deps:dependency()]) -> ok.
  9. deps(_, _, _, []) -> ok;
  10. deps(Cwd, Conf, ConfigFile, [H|T]) ->
  11. {Name, _} = mad_deps:name_and_repo(H),
  12. case get(mad_utils:to_atom(Name)) of
  13. compiled -> ok;
  14. _ -> dep(Cwd, Conf, ConfigFile, Name) end,
  15. deps(Cwd, Conf, ConfigFile, T).
  16. %% compile a dependency
  17. -spec dep(directory(), any(), filename(), string()) -> ok.
  18. dep(Cwd, _Conf, ConfigFile, Name) ->
  19. io:format("==> ~p~n",[Name]),
  20. %% check dependencies of the dependency
  21. DepsDir = filename:join([mad_utils:get_value(deps_dir, _Conf, ["deps"])]),
  22. DepPath = filename:join([Cwd, DepsDir, Name]),
  23. DepConfigFile = filename:join(DepPath, ConfigFile),
  24. Conf = mad_utils:consult(DepConfigFile),
  25. Conf1 = mad_utils:script(DepConfigFile, Conf),
  26. deps(Cwd, Conf, ConfigFile, mad_utils:get_value(deps, Conf1, [])),
  27. %% add lib_dirs to path
  28. LibDirs = mad_utils:lib_dirs(DepPath, Conf1),
  29. code:add_paths(LibDirs),
  30. SrcDir = filename:join([mad_utils:src(DepPath)]),
  31. Files = yrl_files(SrcDir) ++ erl_files(SrcDir) ++ app_src_files(SrcDir),
  32. case Files of
  33. [] -> ok;
  34. Files ->
  35. IncDir = mad_utils:include(DepPath),
  36. EbinDir = mad_utils:ebin(DepPath),
  37. %% create EbinDir and add it to code path
  38. file:make_dir(EbinDir),
  39. code:add_path(EbinDir),
  40. Opts = mad_utils:get_value(erl_opts, Conf1, []),
  41. lists:foreach(compile_fun(IncDir, EbinDir, Opts), Files),
  42. dtl(DepPath,Conf1),
  43. put(mad_utils:to_atom(Name), compiled),
  44. ok
  45. end.
  46. dtl(Dir,Config) ->
  47. case mad_utils:get_value(erlydtl_opts, Config, []) of
  48. [] -> skip;
  49. X -> compile_erlydtl_files(validate_erlydtl_opts(Dir,X)) end.
  50. -spec validate_property({atom(), term()}, term()) -> {atom(), term()}.
  51. validate_property({modules, _}, Modules) -> {modules, Modules};
  52. validate_property(Else, _) -> Else.
  53. -spec compile_fun(directory(), directory(), [compile:option()]) ->
  54. fun((file:name(),string(),string(),list(tuple(any(),any())),string()) -> ok).
  55. compile_fun(Inc,Bin,Opt) -> fun(File) -> compile(File,Inc,Bin,Opt,filetype(File)) end.
  56. filetype(File) -> L=length(hd(string:tokens(File,"."))), string:substr(File,L+1,length(File)).
  57. compile(File,Inc,Bin,Opt,".yrl") ->
  58. ErlFile = yrl_to_erl(File),
  59. Compiled = is_compiled(ErlFile,File),
  60. if Compiled == false ->
  61. yecc:file(File),
  62. compile(ErlFile,Inc,Bin,Opt,".erl"); true -> ok end;
  63. compile(File,Inc,Bin,Opt,".erl") ->
  64. BeamFile = erl_to_beam(Bin, File),
  65. Compiled = is_compiled(BeamFile, File),
  66. if Compiled =:= false ->
  67. io:format("Compiling ~s~n", [File]),
  68. Opts1 = ?COMPILE_OPTS(Inc, Bin, Opt),
  69. compile:file(File, Opts1),
  70. ok;
  71. true -> ok end;
  72. compile(File,_Inc,Bin,_Opt,".app.src") ->
  73. AppFile = filename:join(Bin, app_src_to_app(File)),
  74. Compiled = is_compiled(AppFile, File),
  75. if Compiled =:= false ->
  76. io:format("Writing ~s~n", [AppFile]),
  77. BeamFiles = filelib:wildcard("*.beam", Bin),
  78. Modules = [list_to_atom(filename:basename(X, ".beam")) || X <- BeamFiles],
  79. [Struct|_] = mad_utils:consult(File),
  80. {application, AppName, Props} = Struct,
  81. Props1 = add_modules_property(Props),
  82. Props2 = [validate_property(X, Modules) || X <- Props1],
  83. Struct1 = {application, AppName, Props2},
  84. file:write_file(AppFile, io_lib:format("~p.~n", [Struct1])),
  85. ok;
  86. true -> ok end;
  87. compile(File,_Inc,_Bin,_Opt,_) ->
  88. io:format("Unknown file type: ~p~n",[File]).
  89. -spec erl_files(directory()) -> [file:name()].
  90. -spec app_src_files(directory()) -> [file:name()].
  91. -spec app_src_to_app(file:name()) -> file:name().
  92. -spec erl_to_beam(directory(), file:name()) -> file:name().
  93. -spec is_compiled(directory(), file:name()) -> boolean().
  94. -spec add_modules_property([{atom(), term()}]) -> [{atom(), term()}].
  95. -spec sort([file:name()]) -> [file:name()].
  96. -spec sort_by_priority([file:name()], [file:name()], [file:name()], [file:name()]) -> [file:name()].
  97. erl_files(Dir) -> filelib:fold_files(Dir, ".erl", true, fun(F, Acc) -> [F|Acc] end, []).
  98. yrl_files(Dir) -> filelib:fold_files(Dir, ".yrl", true, fun(F, Acc) -> [F|Acc] end, []).
  99. app_src_files(Dir) -> filelib:fold_files(Dir, ".app.src", false, fun(F, Acc) -> [F|Acc] end, []).
  100. app_src_to_app(Filename) -> filename:basename(Filename, ".app.src") ++ ".app".
  101. yrl_to_erl(Filename) -> filename:join(filename:dirname(Filename),filename:basename(Filename, ".yrl")) ++ ".erl".
  102. erl_to_beam(Bin, Filename) -> filename:join(Bin, filename:basename(Filename, ".erl") ++ ".beam").
  103. is_compiled(BeamFile, File) -> mad_utils:last_modified(BeamFile) > mad_utils:last_modified(File).
  104. add_modules_property(Properties) ->
  105. case lists:keyfind(modules, 1, Properties) of
  106. {modules, _} -> Properties;
  107. _ -> Properties ++ [{modules, []}] end.
  108. -spec foreach(fun((directory(), filename()) -> ok), [filename()], any(), filename()) -> ok.
  109. foreach(_, [], _, _) -> ok;
  110. foreach(Fun, [Dir|T], Config, ConfigFile) ->
  111. Fun(Dir, Config, ConfigFile),
  112. foreach(Fun, T, Config, ConfigFile).
  113. get_kv(K, Opts, Default) ->
  114. V = mad_utils:get_value(K, Opts, Default),
  115. KV = {K, V},
  116. {KV, Opts -- [KV]}.
  117. validate_erlydtl_opts(Cwd, Opts) ->
  118. DefaultDocRoot = filename:join("priv", "templates"),
  119. {DocRoot, Opts1} = get_kv(doc_root, Opts, DefaultDocRoot),
  120. {OutDir, Opts2} = get_kv(out_dir, Opts1, "ebin"),
  121. {CompilerOpts, Opts3} = get_kv(compiler_options, Opts2, []),
  122. {SourceExt, Opts4} = get_kv(source_ext, Opts3, ".dtl"),
  123. {ModuleExt, Opts5} = get_kv(module_ext, Opts4, ""),
  124. {_, DocRootDir} = DocRoot,
  125. DocRoot1 = {doc_root, filename:join(Cwd, DocRootDir)},
  126. {_, OutDir1} = OutDir,
  127. OutDir2 = {out_dir, filename:join(Cwd, OutDir1)},
  128. [DocRoot1, OutDir2, CompilerOpts, SourceExt, ModuleExt|Opts5].
  129. module_name(File, Ext, NewExt) ->
  130. list_to_atom(filename:basename(File, Ext) ++ NewExt).
  131. compile_erlydtl_files(Opts) ->
  132. {{_, DocRoot}, Opts1} = get_kv(doc_root, Opts, ""),
  133. {{_, SourceExt}, Opts2} = get_kv(source_ext, Opts1, ""),
  134. {{_, ModuleExt}, Opts3} = get_kv(module_ext, Opts2, ""),
  135. {{_, OutDir}, _} = get_kv(out_dir, Opts3, ""),
  136. Files = filelib:fold_files(DocRoot, SourceExt, true,
  137. fun(F, Acc) -> [F|Acc] end, []),
  138. Compile = fun(F) ->
  139. ModuleName = module_name(F, SourceExt, ModuleExt),
  140. BeamFile = erl_to_beam(OutDir, atom_to_list(ModuleName)),
  141. Compiled = is_compiled(BeamFile, F),
  142. if Compiled =:= false ->
  143. io:format("DTL Compiling ~s~n", [F]),
  144. erlydtl:compile(F, ModuleName, Opts3);
  145. true -> ok end
  146. end,
  147. lists:foreach(Compile, Files).