erlang自动编译与加载
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

986 rivejä
38 KiB

5 vuotta sitten
4 vuotta sitten
5 vuotta sitten
5 vuotta sitten
4 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
4 vuotta sitten
5 vuotta sitten
4 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
4 vuotta sitten
5 vuotta sitten
5 vuotta sitten
4 vuotta sitten
4 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
4 vuotta sitten
5 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
5 vuotta sitten
4 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
4 vuotta sitten
5 vuotta sitten
5 vuotta sitten
4 vuotta sitten
5 vuotta sitten
4 vuotta sitten
5 vuotta sitten
4 vuotta sitten
5 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
5 vuotta sitten
4 vuotta sitten
5 vuotta sitten
5 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
4 vuotta sitten
  1. -module(esUtils).
  2. -include("eSync.hrl").
  3. -compile(inline).
  4. -compile({inline_size, 128}).
  5. -compile([export_all, nowarn_export_all]).
  6. getModSrcDir(Module) ->
  7. case code:is_loaded(Module) of
  8. {file, _} ->
  9. try
  10. %% Get some module info...
  11. Props = Module:module_info(compile),
  12. Source = proplists:get_value(source, Props, ""),
  13. %% Ensure that the file exists, is a decendent of the tree, and how to deal with that
  14. IsFile = filelib:is_regular(Source),
  15. IsDescendant = isDescendent(Source),
  16. Descendant = ?esCfgSync:getv(?descendant),
  17. LastSource =
  18. case {IsFile, IsDescendant, Descendant} of
  19. %% is file and descendant, we're good to go
  20. {true, true, _} -> Source;
  21. %% is not a descendant, but we allow them, so good to go
  22. {true, false, allow} -> Source;
  23. %% is not a descendant, and we fix non-descendants, so let's fix it
  24. {_, false, fix} -> fixDescendantSource(Source, IsFile);
  25. %% Anything else, and we don't know what to do, so let's just bail.
  26. _ -> undefined
  27. end,
  28. case LastSource of
  29. undefined ->
  30. undefined;
  31. _ ->
  32. %% Get the source dir...
  33. Dir = filename:dirname(LastSource),
  34. getSrcDir(Dir)
  35. end
  36. catch _ : _ ->
  37. undefined
  38. end;
  39. _ ->
  40. undefined
  41. end.
  42. getModOptions(Module) ->
  43. case code:is_loaded(Module) of
  44. {file, _} ->
  45. try
  46. Props = Module:module_info(compile),
  47. BeamDir = filename:dirname(code:which(Module)),
  48. Options1 = proplists:get_value(options, Props, []),
  49. %% transform `outdir'
  50. Options2 = transformOutdir(BeamDir, Options1),
  51. Options3 = ensureInclude(Options2),
  52. %% transform the include directories
  53. Options4 = transformAllIncludes(Module, BeamDir, Options3),
  54. %% maybe_add_compile_info
  55. Options5 = maybeAddCompileInfo(Options4),
  56. %% add filetype to options (DTL, LFE, erl, etc)
  57. Options6 = addFileType(Module, Options5),
  58. {ok, Options6}
  59. catch ExType:Error ->
  60. logWarnings("~p:0: ~p looking for options: ~p. ~n", [Module, ExType, Error]),
  61. undefined
  62. end;
  63. _ ->
  64. undefined
  65. end.
  66. tryGetModOptions(Module) ->
  67. try
  68. Props = Module:module_info(compile),
  69. BeamDir = filename:dirname(code:which(Module)),
  70. Options1 = proplists:get_value(options, Props, []),
  71. %% transform `outdir'
  72. Options2 = transformOutdir(BeamDir, Options1),
  73. Options3 = ensureInclude(Options2),
  74. %% transform the include directories
  75. Options4 = transformAllIncludes(Module, BeamDir, Options3),
  76. %% maybe_add_compile_info
  77. Options5 = maybeAddCompileInfo(Options4),
  78. %% add filetype to options (DTL, LFE, erl, etc)
  79. Options6 = addFileType(Module, Options5),
  80. {ok, Options6}
  81. catch _ExType:_Error ->
  82. undefiend
  83. end.
  84. tryGetSrcOptions(SrcDir) ->
  85. %% Then we dig back through the parent directories until we find our include directory
  86. NewDirName = filename:dirname(SrcDir),
  87. case getOptions(NewDirName) of
  88. {ok, _Options} = Opts ->
  89. Opts;
  90. _ ->
  91. BaseName = filename:basename(SrcDir),
  92. IsBaseSrcDir = BaseName == ?rootSrcDir,
  93. case NewDirName =/= SrcDir andalso not IsBaseSrcDir of
  94. true ->
  95. tryGetSrcOptions(NewDirName);
  96. _ when IsBaseSrcDir ->
  97. try filelib:fold_files(SrcDir, ".*\\.(erl|dtl|lfe|ex)$", true,
  98. fun(OneFile, Acc) ->
  99. Mod = binary_to_atom(filename:basename(OneFile, filename:extension(OneFile))),
  100. case tryGetModOptions(Mod) of
  101. {ok, _Options} = Opts ->
  102. throw(Opts);
  103. _ ->
  104. Acc
  105. end
  106. end, undefined)
  107. catch
  108. {ok, _Options} = Opts ->
  109. Opts;
  110. _ExType:_Error ->
  111. logWarnings("looking src options error ~p:~p. ~n", [_ExType, _Error]),
  112. undefined
  113. end;
  114. _ ->
  115. undefined
  116. end
  117. end.
  118. transformOutdir(BeamDir, Options) ->
  119. [{outdir, BeamDir} | proplists:delete(outdir, Options)].
  120. ensureInclude(Options) ->
  121. case proplists:get_value(i, Options) of
  122. undefined -> [{i, "include"} | Options];
  123. _ -> Options
  124. end.
  125. transformAllIncludes(Module, BeamDir, Options) ->
  126. [begin
  127. case Opt of
  128. {i, IncludeDir} ->
  129. {ok, SrcDir} = getModSrcDir(Module),
  130. {ok, IncludeDir2} = determineIncludeDir(IncludeDir, BeamDir, SrcDir),
  131. {i, IncludeDir2};
  132. _ ->
  133. Opt
  134. end
  135. end || Opt <- Options].
  136. maybeAddCompileInfo(Options) ->
  137. case lists:member(compile_info, Options) of
  138. true -> Options;
  139. false -> addCompileInfo(Options)
  140. end.
  141. addCompileInfo(Options) ->
  142. CompInfo = [{K, V} || {K, V} <- Options, lists:member(K, [outdir, i])],
  143. [{compile_info, CompInfo} | Options].
  144. addFileType(Module, Options) ->
  145. Type = getFileType(Module),
  146. [{type, Type} | Options].
  147. %% This will check if the given module or source file is an ErlyDTL template.
  148. %% Currently, this is done by checking if its reported source path ends with
  149. %% ".dtl.erl".
  150. getFileType(Module) when is_atom(Module) ->
  151. Props = Module:module_info(compile),
  152. Source = proplists:get_value(source, Props, ""),
  153. getFileType(Source);
  154. getFileType(Source) ->
  155. Ext = filename:extension(Source),
  156. Root = filename:rootname(Source),
  157. SecondExt = filename:extension(Root),
  158. case Ext of
  159. <<".erl">> when SecondExt =:= <<".dtl">> -> dtl;
  160. <<".dtl">> -> dtl;
  161. <<".erl">> -> erl;
  162. <<".lfe">> -> lfe;
  163. <<".ex">> -> elixir;
  164. ".erl" when SecondExt =:= ".dtl" -> dtl;
  165. ".dtl" -> dtl;
  166. ".erl" -> erl;
  167. ".lfe" -> lfe;
  168. ".ex" -> elixir
  169. end.
  170. %% This will search back to find an appropriate include directory, by
  171. %% searching further back than "..". Instead, it will extract the basename
  172. %% (probably "include" from the include pathfile, and then search backwards in
  173. %% the directory tree until it finds a directory with the same basename found
  174. %% above.
  175. determineIncludeDir(IncludeDir, BeamDir, SrcDir) ->
  176. IncludeBase = filename:basename(IncludeDir),
  177. case determineIncludeDirFromBeamDir(IncludeBase, IncludeDir, BeamDir) of
  178. {ok, _Dir} = RetD -> RetD;
  179. undefined ->
  180. {ok, Cwd} = file:get_cwd(),
  181. % Cwd2 = normalizeCaseWindowsDir(Cwd),
  182. % SrcDir2 = normalizeCaseWindowsDir(SrcDir),
  183. % IncludeBase2 = normalizeCaseWindowsDir(IncludeBase),
  184. case findIncludeDirFromAncestors(SrcDir, Cwd, IncludeBase) of
  185. {ok, _Dir} = RetD -> RetD;
  186. undefined -> {ok, IncludeDir} %% Failed, just stick with original
  187. end
  188. end.
  189. %% First try to see if we have an include file alongside our ebin directory, which is typically the case
  190. determineIncludeDirFromBeamDir(IncludeBase, IncludeDir, BeamDir) ->
  191. BeamBasedIncDir = filename:join(filename:dirname(BeamDir), IncludeBase),
  192. case filelib:is_dir(BeamBasedIncDir) of
  193. true -> {ok, BeamBasedIncDir};
  194. false ->
  195. BeamBasedIncDir2 = filename:join(filename:dirname(BeamDir), IncludeDir),
  196. case filelib:is_dir(BeamBasedIncDir2) of
  197. true -> {ok, BeamBasedIncDir2};
  198. _ ->
  199. undefined
  200. end
  201. end.
  202. %% get the src dir
  203. getRootSrcDirFromSrcDir(SrcDir) ->
  204. NewDirName = filename:dirname(SrcDir),
  205. BaseName = filename:basename(SrcDir),
  206. case BaseName of
  207. ?rootSrcDir ->
  208. NewDirName;
  209. _ ->
  210. case NewDirName =/= SrcDir of
  211. true ->
  212. getRootSrcDirFromSrcDir(NewDirName);
  213. _ ->
  214. undefined
  215. end
  216. end.
  217. %% Then we dig back through the parent directories until we find our include directory
  218. findIncludeDirFromAncestors(Cwd, Cwd, _) -> undefined;
  219. findIncludeDirFromAncestors("/", _, _) -> undefined;
  220. findIncludeDirFromAncestors(".", _, _) -> undefined;
  221. findIncludeDirFromAncestors("", _, _) -> undefined;
  222. findIncludeDirFromAncestors(Dir, Cwd, IncludeBase) ->
  223. NewDirName = filename:dirname(Dir),
  224. AttemptDir = filename:join(NewDirName, IncludeBase),
  225. case filelib:is_dir(AttemptDir) of
  226. true ->
  227. {ok, AttemptDir};
  228. false ->
  229. case NewDirName =/= Dir of
  230. true ->
  231. findIncludeDirFromAncestors(NewDirName, Cwd, IncludeBase);
  232. _ ->
  233. undefined
  234. end
  235. end.
  236. % normalizeCaseWindowsDir(Dir) ->
  237. % case os:type() of
  238. % {win32, _} -> Dir; %string:to_lower(Dir);
  239. % {unix, _} -> Dir
  240. % end.
  241. %% This is an attempt to intelligently fix paths in modules when a
  242. %% release is moved. Essentially, it takes a module name and its original path
  243. %% from Module:module_info(compile), say
  244. %% "/some/original/path/site/src/pages/somepage.erl", and then breaks down the
  245. %% path one by one prefixing it with the current working directory until it
  246. %% either finds a match, or fails. If it succeeds, it returns the Path to the
  247. %% new Source file.
  248. fixDescendantSource([], _IsFile) ->
  249. undefined;
  250. fixDescendantSource(Path, IsFile) ->
  251. {ok, Cwd} = file:get_cwd(),
  252. PathParts = filename:split(Path),
  253. case makeDescendantSource(PathParts, Cwd) of
  254. undefined -> case IsFile of true -> Path; _ -> undefined end;
  255. FoundPath -> FoundPath
  256. end.
  257. makeDescendantSource([], _Cwd) ->
  258. undefined;
  259. makeDescendantSource([_ | T], Cwd) ->
  260. PathAttempt = filename:join([Cwd | T]),
  261. case filelib:is_regular(PathAttempt) of
  262. true -> PathAttempt;
  263. false -> makeDescendantSource(T, Cwd)
  264. end.
  265. isDescendent(Path) ->
  266. {ok, Cwd} = file:get_cwd(),
  267. lists:sublist(Path, length(Cwd)) == Cwd.
  268. %% @private Find the src directory for the specified Directory; max 15 iterations
  269. getSrcDir(Dir) ->
  270. getSrcDir(Dir, 15).
  271. getSrcDir(_Dir, 0) ->
  272. undefined;
  273. getSrcDir(Dir, Ctr) ->
  274. HasCode = filelib:wildcard("*.erl", Dir) /= [] orelse
  275. filelib:wildcard("*.hrl", Dir) /= [] orelse
  276. filelib:wildcard("*.dtl", Dir) /= [] orelse
  277. filelib:wildcard("*.lfe", Dir) /= [] orelse
  278. filelib:wildcard("*.ex", Dir) /= [],
  279. if
  280. HasCode -> {ok, Dir};
  281. true -> getSrcDir(filename:dirname(Dir), Ctr - 1)
  282. end.
  283. mergeExtraDirs(IsAddPath) ->
  284. case ?esCfgSync:getv(?extraDirs) of
  285. undefined ->
  286. {[], [], []};
  287. ExtraList ->
  288. FunMerge =
  289. fun(OneExtra, {AddDirs, OnlyDirs, DelDirs} = AllAcc) ->
  290. case OneExtra of
  291. {add, DirsAndOpts} ->
  292. Adds =
  293. [
  294. begin
  295. case IsAddPath of
  296. true ->
  297. case proplists:get_value(outdir, Opts) of
  298. undefined ->
  299. true;
  300. Path ->
  301. ok = filelib:ensure_dir(Path),
  302. true = code:add_pathz(Path)
  303. end;
  304. _ ->
  305. ignore
  306. end,
  307. filename:absname(Dir)
  308. end || {Dir, Opts} <- DirsAndOpts
  309. ],
  310. setelement(1, AllAcc, Adds ++ AddDirs);
  311. {only, DirsAndOpts} ->
  312. Onlys =
  313. [
  314. begin
  315. case IsAddPath of
  316. true ->
  317. case proplists:get_value(outdir, Opts) of
  318. undefined ->
  319. true;
  320. Path ->
  321. ok = filelib:ensure_dir(Path),
  322. true = code:add_pathz(Path)
  323. end;
  324. _ ->
  325. ignore
  326. end,
  327. filename:absname(Dir)
  328. end || {Dir, Opts} <- DirsAndOpts
  329. ],
  330. setelement(2, AllAcc, Onlys ++ OnlyDirs);
  331. {del, DirsAndOpts} ->
  332. Dels =
  333. [
  334. begin
  335. filename:absname(Dir)
  336. end || {Dir, _Opts} <- DirsAndOpts
  337. ],
  338. setelement(3, AllAcc, Dels ++ DelDirs)
  339. end
  340. end,
  341. lists:foldl(FunMerge, {[], [], []}, ExtraList)
  342. end.
  343. -define(IIF(Cond, Ret1, Ret2), (case Cond of true -> Ret1; _ -> Ret2 end)).
  344. collSrcFiles(IsAddPath) ->
  345. {AddSrcDirs, OnlySrcDirs, DelSrcDirs} = mergeExtraDirs(IsAddPath),
  346. CollFiles = filelib:fold_files(filename:absname(<<"./">>), ".*\\.(erl|hrl|beam|config|dtl|lfe|ex)$", true,
  347. fun(OneFile, {Srcs, Hrls, Configs, Beams} = Acc) ->
  348. case isOnlyDir(OnlySrcDirs, OneFile) andalso (not isDelDir(DelSrcDirs, OneFile)) of
  349. true ->
  350. MTimeSec = esUtils:dateTimeToSec(filelib:last_modified(OneFile)),
  351. case filename:extension(OneFile) of
  352. <<".beam">> ->
  353. BeamMod = binary_to_atom(filename:basename(OneFile, <<".beam">>)),
  354. setelement(4, Acc, Beams#{BeamMod => MTimeSec});
  355. <<".config">> ->
  356. setelement(3, Acc, Configs#{OneFile => MTimeSec});
  357. <<".hrl">> ->
  358. setelement(2, Acc, Hrls#{OneFile => MTimeSec});
  359. <<>> ->
  360. Acc;
  361. _ ->
  362. RootSrcDir =
  363. case getRootSrcDirFromSrcDir(OneFile) of
  364. undefined ->
  365. filename:dirname(OneFile);
  366. RetSrcDir ->
  367. RetSrcDir
  368. end,
  369. case getOptions(RootSrcDir) of
  370. undefined ->
  371. Mod = binary_to_atom(filename:basename(OneFile, filename:extension(OneFile))),
  372. case getModOptions(Mod) of
  373. {ok, Options} ->
  374. setOptions(RootSrcDir, Options);
  375. _ ->
  376. ignore
  377. end;
  378. _ ->
  379. ignore
  380. end,
  381. setelement(1, Acc, Srcs#{OneFile => MTimeSec})
  382. end;
  383. _ ->
  384. Acc
  385. end
  386. end, {#{}, #{}, #{}, #{}}),
  387. FunCollAdds =
  388. fun(OneDir, FilesAcc) ->
  389. filelib:fold_files(?IIF(is_list(OneDir), list_to_binary(OneDir), OneDir), ".*\\.(erl|hrl|beam|config|dtl|lfe|ex)$", true,
  390. fun(OneFile, {Srcs, Hrls, Configs, Beams} = Acc) ->
  391. MTimeSec = esUtils:dateTimeToSec(filelib:last_modified(OneFile)),
  392. case filename:extension(OneFile) of
  393. <<".beam">> ->
  394. BeamMod = binary_to_atom(filename:basename(OneFile, <<".beam">>)),
  395. setelement(4, Acc, Beams#{BeamMod => MTimeSec});
  396. <<".config">> ->
  397. setelement(3, Acc, Configs#{OneFile => MTimeSec});
  398. <<".hrl">> ->
  399. setelement(2, Acc, Hrls#{OneFile => MTimeSec});
  400. <<>> ->
  401. Acc;
  402. _ ->
  403. setelement(1, Acc, Srcs#{OneFile => MTimeSec})
  404. end
  405. end, FilesAcc)
  406. end,
  407. lists:foldl(FunCollAdds, CollFiles, AddSrcDirs).
  408. isOnlyDir([], _) ->
  409. true;
  410. isOnlyDir(ReplaceDirs, SrcDir) ->
  411. isMatchDir(ReplaceDirs, SrcDir).
  412. isDelDir([], _) ->
  413. false;
  414. isDelDir(ReplaceDirs, SrcDir) ->
  415. isMatchDir(ReplaceDirs, SrcDir).
  416. isMatchDir([], _SrcDir) ->
  417. false;
  418. isMatchDir([SrcDir | _ReplaceDirs], SrcDir) ->
  419. true;
  420. isMatchDir([OneDir | ReplaceDirs], SrcDir) ->
  421. case re:run(SrcDir, OneDir) of
  422. nomatch -> isMatchDir(ReplaceDirs, SrcDir);
  423. _ -> true
  424. end.
  425. getEnv(Var, Default) ->
  426. case application:get_env(eSync, Var) of
  427. {ok, Value} ->
  428. Value;
  429. _ ->
  430. Default
  431. end.
  432. setEnv(Var, Val) ->
  433. ok = application:set_env(eSync, Var, Val).
  434. logSuccess(Format) ->
  435. canLog(success) andalso logger:notice(Format).
  436. logSuccess(Format, Args) ->
  437. canLog(success) andalso logger:notice(Format, Args).
  438. logErrors(Format) ->
  439. canLog(errors) andalso logger:error(Format).
  440. logErrors(Format, Args) ->
  441. canLog(errors) andalso logger:error(Format, Args).
  442. logWarnings(Format) ->
  443. canLog(warnings) andalso logger:warning(Format) .
  444. logWarnings(Format, Args) ->
  445. canLog(warnings) andalso logger:warning(Format, Args) .
  446. canLog(MsgType) ->
  447. case esSyncSrv:getLog() of
  448. true -> true;
  449. all -> true;
  450. none -> false;
  451. false -> false;
  452. skip_success -> MsgType == errors orelse MsgType == warnings;
  453. L when is_list(L) -> lists:member(MsgType, L);
  454. _ -> false
  455. end.
  456. %% 注意 map类型的数据不能当做key
  457. -type key() :: atom() | binary() | bitstring() | float() | integer() | list() | tuple().
  458. -type value() :: atom() | binary() | bitstring() | float() | integer() | list() | tuple() | map().
  459. -spec load(term(), [{key(), value()}]) -> ok.
  460. load(Module, KVs) ->
  461. Forms = forms(Module, KVs),
  462. {ok, Module, Bin} = compile:forms(Forms),
  463. code:soft_purge(Module),
  464. {module, Module} = code:load_binary(Module, atom_to_list(Module), Bin),
  465. ok.
  466. forms(Module, KVs) ->
  467. %% -module(Module).
  468. Mod = erl_syntax:attribute(erl_syntax:atom(module), [erl_syntax:atom(Module)]),
  469. %% -export([getv/0]).
  470. ExportList = [erl_syntax:arity_qualifier(erl_syntax:atom(getv), erl_syntax:integer(1))],
  471. Export = erl_syntax:attribute(erl_syntax:atom(export), [erl_syntax:list(ExportList)]),
  472. %% getv(K) -> V
  473. Function = erl_syntax:function(erl_syntax:atom(getv), lookup_clauses(KVs, [])),
  474. [erl_syntax:revert(X) || X <- [Mod, Export, Function]].
  475. lookup_clause(Key, Value) ->
  476. Var = erl_syntax:abstract(Key),
  477. Body = erl_syntax:abstract(Value),
  478. erl_syntax:clause([Var], [], [Body]).
  479. lookup_clause_anon() ->
  480. Var = erl_syntax:variable("_"),
  481. Body = erl_syntax:atom(undefined),
  482. erl_syntax:clause([Var], [], [Body]).
  483. lookup_clauses([], Acc) ->
  484. lists:reverse(lists:flatten([lookup_clause_anon() | Acc]));
  485. lookup_clauses([{Key, Value} | T], Acc) ->
  486. lookup_clauses(T, [lookup_clause(Key, Value) | Acc]).
  487. getOptions(SrcDir) ->
  488. case erlang:get(SrcDir) of
  489. undefined ->
  490. undefined;
  491. Options ->
  492. {ok, Options}
  493. end.
  494. setOptions(SrcDir, Options) ->
  495. case erlang:get(SrcDir) of
  496. undefined ->
  497. erlang:put(SrcDir, Options);
  498. OldOptions ->
  499. NewOptions =
  500. case lists:keytake(compile_info, 1, Options) of
  501. {value, {compile_info, ValList1}, Options1} ->
  502. case lists:keytake(compile_info, 1, OldOptions) of
  503. {value, {compile_info, ValList2}, Options2} ->
  504. [{compile_info, lists:usort(ValList1 ++ ValList2)} | lists:usort(Options1 ++ Options2)];
  505. _ ->
  506. lists:usort(Options ++ OldOptions)
  507. end;
  508. _ ->
  509. lists:usort(Options ++ OldOptions)
  510. end,
  511. erlang:put(SrcDir, NewOptions)
  512. end.
  513. loadCfg() ->
  514. KVs = [{Key, esUtils:getEnv(Key, DefVal)} || {Key, DefVal} <- ?DefCfgList],
  515. esUtils:load(?esCfgSync, KVs).
  516. %% ******************************* 加载与编译相关 **********************************************************************
  517. errorNoFile(Module) ->
  518. esUtils:logWarnings(["~p Couldn't load module: nofile", [Module]]).
  519. printResults(_Module, SrcFile, [], []) ->
  520. esUtils:logSuccess("~s Recompiled", [SrcFile]);
  521. printResults(_Module, SrcFile, [], Warnings) ->
  522. formatErrors(fun esUtils:logWarnings/2, SrcFile, [], Warnings);
  523. printResults(_Module, SrcFile, Errors, Warnings) ->
  524. formatErrors(fun esUtils:logErrors/2, SrcFile, Errors, Warnings).
  525. %% @private Print error messages in a pretty and user readable way.
  526. formatErrors(LogFun, File, Errors, Warnings) ->
  527. AllErrors1 = lists:sort(lists:flatten([X || {_, X} <- Errors])),
  528. AllErrors2 = [{Line, "Error", Module, Description} || {Line, Module, Description} <- AllErrors1],
  529. AllWarnings1 = lists:sort(lists:flatten([X || {_, X} <- Warnings])),
  530. AllWarnings2 = [{Line, "Warning", Module, Description} || {Line, Module, Description} <- AllWarnings1],
  531. Everything = lists:sort(AllErrors2 ++ AllWarnings2),
  532. FPck =
  533. fun({Line, Prefix, Module, ErrorDescription}) ->
  534. Msg = formatError(Module, ErrorDescription),
  535. LogFun("~s: ~p: ~s: ~s", [File, Line, Prefix, Msg])
  536. end,
  537. [FPck(X) || X <- Everything],
  538. ok.
  539. formatError(Module, ErrorDescription) ->
  540. case erlang:function_exported(Module, format_error, 1) of
  541. true -> Module:format_error(ErrorDescription);
  542. false -> io_lib:format("~s", [ErrorDescription])
  543. end.
  544. fireOnSync(OnSyncFun, Modules) ->
  545. case OnSyncFun of
  546. undefined -> ok;
  547. Funs when is_list(Funs) -> onSyncApplyList(Funs, Modules);
  548. Fun -> onSyncApply(Fun, Modules)
  549. end.
  550. onSyncApplyList(Funs, Modules) ->
  551. [onSyncApply(Fun, Modules) || Fun <- Funs].
  552. onSyncApply({M, F}, Modules) ->
  553. try erlang:apply(M, F, [Modules])
  554. catch
  555. C:R:S ->
  556. esUtils:logErrors("apply sync fun ~p:~p(~p) error ~p", [M, F, Modules, {C, R, S}])
  557. end;
  558. onSyncApply(Fun, Modules) when is_function(Fun) ->
  559. try Fun(Modules)
  560. catch
  561. C:R:S ->
  562. esUtils:logErrors("apply sync fun ~p(~p) error ~p", [Fun, Modules, {C, R, S}])
  563. end.
  564. reloadChangedMod([], _SwSyncNode, OnSyncFun, Acc) ->
  565. fireOnSync(OnSyncFun, Acc);
  566. reloadChangedMod([Module | LeftMod], SwSyncNode, OnSyncFun, Acc) ->
  567. case code:get_object_code(Module) of
  568. error ->
  569. esUtils:logErrors("Error loading object code for ~p", [Module]),
  570. reloadChangedMod(LeftMod, SwSyncNode, OnSyncFun, Acc);
  571. {Module, Binary, Filename} ->
  572. case code:load_binary(Module, Filename, Binary) of
  573. {module, Module} ->
  574. esUtils:logSuccess("Reloaded(Beam changed) Mod: ~s Success", [Module]),
  575. syncLoadModOnAllNodes(SwSyncNode, Module, Binary, changed);
  576. {error, What} ->
  577. esUtils:logErrors("Reloaded(Beam changed) Mod: ~s Errors Reason:~p", [Module, What])
  578. end,
  579. reloadChangedMod(LeftMod, SwSyncNode, OnSyncFun, [Module | Acc])
  580. end.
  581. getNodes() ->
  582. lists:usort(lists:flatten(nodes() ++ [rpc:call(X, erlang, nodes, []) || X <- nodes()])) -- [node()].
  583. syncLoadModOnAllNodes(false, _Module, _Binary, _Reason) ->
  584. ignore;
  585. syncLoadModOnAllNodes(true, Module, Binary, Reason) ->
  586. %% Get a list of nodes known by this node, plus all attached nodes.
  587. Nodes = getNodes(),
  588. NumNodes = length(Nodes),
  589. [
  590. begin
  591. esUtils:logSuccess("Do Reloading '~s' on ~p", [Module, Node]),
  592. rpc:call(Node, code, ensure_loaded, [Module]),
  593. case rpc:call(Node, code, which, [Module]) of
  594. Filename when is_binary(Filename) orelse is_list(Filename) ->
  595. %% File exists, overwrite and load into VM.
  596. ok = rpc:call(Node, file, write_file, [Filename, Binary]),
  597. rpc:call(Node, code, purge, [Module]),
  598. case rpc:call(Node, code, load_file, [Module]) of
  599. {module, Module} ->
  600. esUtils:logSuccess("Reloaded(Beam ~p) Mod:~s and write Success on node:~p", [Reason, Module, Node]);
  601. {error, What} ->
  602. esUtils:logErrors("Reloaded(Beam ~p) Mod:~s and write Errors on node:~p Reason:~p", [Module, Node, What])
  603. end;
  604. _ ->
  605. %% File doesn't exist, just load into VM.
  606. case rpc:call(Node, code, load_binary, [Module, undefined, Binary]) of
  607. {module, Module} ->
  608. esUtils:logSuccess("Reloaded(Beam ~p) Mod:~s Success on node:~p", [Reason, Module, Node]);
  609. {error, What} ->
  610. esUtils:logErrors("Reloaded(Beam ~p) Mod:~s Errors on node:~p Reason:~p", [Reason, Module, Node, What])
  611. end
  612. end
  613. end || Node <- Nodes
  614. ],
  615. esUtils:logSuccess("Reloaded(Beam changed) Mod: ~s on ~p nodes:~p", [Module, NumNodes, Nodes]).
  616. recompileChangeSrcFile(Iterator, SwSyncNode) ->
  617. case maps:next(Iterator) of
  618. {File, _V, NextIterator} ->
  619. recompileSrcFile(File, SwSyncNode),
  620. recompileChangeSrcFile(NextIterator, SwSyncNode);
  621. _ ->
  622. ok
  623. end.
  624. erlydtlCompile(SrcFile, Options) ->
  625. F =
  626. fun({outdir, OutDir}, Acc) -> [{out_dir, OutDir} | Acc];
  627. (OtherOption, Acc) -> [OtherOption | Acc]
  628. end,
  629. DtlOptions = lists:foldl(F, [], Options),
  630. Module = binary_to_atom(lists:flatten(filename:basename(SrcFile, ".dtl") ++ "_dtl")),
  631. Compiler = erlydtl,
  632. Compiler:compile(SrcFile, Module, DtlOptions).
  633. elixir_compile(SrcFile, Options) ->
  634. Outdir = proplists:get_value(outdir, Options),
  635. Compiler = ':Elixir.Kernel.ParallelCompiler',
  636. Modules = Compiler:files_to_path([SrcFile], Outdir),
  637. Loader =
  638. fun(Module) ->
  639. Outfile = code:which(Module),
  640. Binary = file:read_file(Outfile),
  641. {Module, Binary}
  642. end,
  643. Results = lists:map(Loader, Modules),
  644. {ok, multiple, Results, []}.
  645. lfe_compile(SrcFile, Options) ->
  646. Compiler = lfe_comp,
  647. Compiler:file(SrcFile, Options).
  648. getCompileFunAndModuleName(SrcFile) ->
  649. case esUtils:getFileType(SrcFile) of
  650. erl ->
  651. {fun compile:file/2, binary_to_atom(filename:basename(SrcFile, <<".erl">>))};
  652. dtl ->
  653. {fun erlydtlCompile/2, list_to_atom(lists:flatten(binary_to_list(filename:basename(SrcFile, <<".dtl">>)) ++ "_dtl"))};
  654. lfe ->
  655. {fun lfe_compile/2, binary_to_atom(filename:basename(SrcFile, <<".lfe">>))};
  656. elixir ->
  657. {fun elixir_compile/2, binary_to_atom(filename:basename(SrcFile, <<".ex">>))}
  658. end.
  659. getObjectCode(Module) ->
  660. case code:get_object_code(Module) of
  661. {Module, B, Filename} -> {B, Filename};
  662. _ -> {undefined, undefined}
  663. end.
  664. reloadIfNecessary(Module, OldBinary, Binary, Filename, SwSyncNode) ->
  665. case Binary =/= OldBinary of
  666. true ->
  667. %% Try to load the module...
  668. case code:ensure_loaded(Module) of
  669. {module, Module} ->
  670. case code:load_binary(Module, Filename, Binary) of
  671. {module, Module} ->
  672. esUtils:logSuccess("Reloaded(Beam recompiled) Mod:~s Success", [Module]),
  673. syncLoadModOnAllNodes(SwSyncNode, Module, Binary, recompiled);
  674. {error, What} ->
  675. esUtils:logErrors("Reloaded(Beam recompiled) Mod:~s Errors Reason:~p", [Module, What])
  676. end;
  677. {error, nofile} ->
  678. case code:load_binary(Module, Filename, Binary) of
  679. {module, Module} ->
  680. esUtils:logSuccess("Reloaded(Beam recompiled) Mod:~s Success", [Module]),
  681. syncLoadModOnAllNodes(SwSyncNode, Module, Binary, recompiled);
  682. {error, What} ->
  683. esUtils:logErrors("Reloaded(Beam recompiled) Mod:~s Errors Reason:~p", [Module, What])
  684. end;
  685. {error, embedded} ->
  686. case code:load_file(Module) of %% Module is not yet loaded, load it.
  687. {module, Module} -> ok;
  688. {error, nofile} ->
  689. case code:load_binary(Module, Filename, Binary) of
  690. {module, Module} ->
  691. esUtils:logSuccess("Reloaded(Beam recompiled) Mod:~s Success", [Module]),
  692. syncLoadModOnAllNodes(SwSyncNode, Module, Binary, recompiled);
  693. {error, What} ->
  694. esUtils:logErrors("Reloaded(Beam recompiled) Mod:~s Errors Reason:~p", [Module, What])
  695. end
  696. end
  697. end;
  698. _ ->
  699. ignore
  700. end.
  701. recompileSrcFile(SrcFile, SwSyncNode) ->
  702. %% Get the module, src dir, and options...
  703. RootSrcDir =
  704. case getRootSrcDirFromSrcDir(SrcFile) of
  705. undefined ->
  706. filename:dirname(SrcFile);
  707. RetSrcDir ->
  708. RetSrcDir
  709. end,
  710. CurSrcDir = filename:dirname(SrcFile),
  711. {CompileFun, Module} = getCompileFunAndModuleName(SrcFile),
  712. {OldBinary, Filename} = getObjectCode(Module),
  713. case getOptions(RootSrcDir) of
  714. {ok, Options} ->
  715. RightFileDir = binary_to_list(filename:join(CurSrcDir, filename:basename(SrcFile))),
  716. case CompileFun(RightFileDir, [binary, return | Options]) of
  717. {ok, Module, Binary, Warnings} ->
  718. printResults(Module, RightFileDir, [], Warnings),
  719. reloadIfNecessary(Module, OldBinary, Binary, Filename, SwSyncNode),
  720. {ok, [], Warnings};
  721. {ok, [{ok, Module, Binary, Warnings}], Warnings2} ->
  722. printResults(Module, RightFileDir, [], Warnings ++ Warnings2),
  723. reloadIfNecessary(Module, OldBinary, Binary, Filename, SwSyncNode),
  724. {ok, [], Warnings ++ Warnings2};
  725. {ok, multiple, Results, Warnings} ->
  726. printResults(Module, RightFileDir, [], Warnings),
  727. [reloadIfNecessary(CompiledModule, OldBinary, Binary, Filename, SwSyncNode) || {CompiledModule, Binary} <- Results],
  728. {ok, [], Warnings};
  729. {ok, OtherModule, _Binary, Warnings} ->
  730. Desc = io_lib:format("Module definition (~p) differs from expected (~s)", [OtherModule, filename:rootname(filename:basename(RightFileDir))]),
  731. Errors = [{RightFileDir, {0, Module, Desc}}],
  732. printResults(Module, RightFileDir, Errors, Warnings),
  733. {ok, Errors, Warnings};
  734. {error, Errors, Warnings} ->
  735. printResults(Module, RightFileDir, Errors, Warnings),
  736. {ok, Errors, Warnings};
  737. _Err ->
  738. esUtils:logErrors("compile Mod:~s Errors Reason:~p", [Module, _Err])
  739. end;
  740. undefined ->
  741. case esUtils:tryGetModOptions(Module) of
  742. {ok, Options} ->
  743. setOptions(RootSrcDir, Options),
  744. recompileSrcFile(SrcFile, SwSyncNode);
  745. _ ->
  746. case esUtils:tryGetSrcOptions(CurSrcDir) of
  747. {ok, Options} ->
  748. setOptions(RootSrcDir, Options),
  749. recompileSrcFile(SrcFile, SwSyncNode);
  750. _ ->
  751. esUtils:logErrors("Unable to determine options for ~s", [SrcFile])
  752. end
  753. end
  754. end.
  755. collIncludeCHrls([], AllHrls, CHrls, NewAddMap) ->
  756. case maps:size(NewAddMap) > 0 of
  757. true ->
  758. collIncludeCHrls(maps:keys(NewAddMap), AllHrls, CHrls, #{});
  759. _ ->
  760. CHrls
  761. end;
  762. collIncludeCHrls([OneHrl | LeftCHrls], AllHrls, CHrls, NewAddMap) ->
  763. {NewCHrls, NNewAddMap} = whoInclude(OneHrl, AllHrls, CHrls, NewAddMap),
  764. collIncludeCHrls(LeftCHrls, AllHrls, NewCHrls, NNewAddMap).
  765. collIncludeCErls([], _SrcFiles, CSrcs, _NewAddMap) ->
  766. CSrcs;
  767. collIncludeCErls([Hrl | LeftHrl], SrcFiles, CSrcs, NewAddMap) ->
  768. {NewCSrcs, NNewAddMap} = whoInclude(Hrl, SrcFiles, CSrcs, NewAddMap),
  769. collIncludeCErls(LeftHrl, SrcFiles, NewCSrcs, NNewAddMap).
  770. whoInclude(HrlFile, AllFiles, CFiles, NewAddMap) ->
  771. HrlFileBaseName = filename:basename(HrlFile),
  772. doMathEveryFile(maps:iterator(AllFiles), HrlFileBaseName, CFiles, NewAddMap).
  773. doMathEveryFile(Iterator, HrlFileBaseName, CFiles, NewAddMap) ->
  774. case maps:next(Iterator) of
  775. {OneFile, _V, NextIterator} ->
  776. case file:open(OneFile, [read, binary]) of
  777. {ok, IoDevice} ->
  778. IsInclude = doMathEveryLine(IoDevice, HrlFileBaseName),
  779. file:close(IoDevice),
  780. case IsInclude of
  781. true ->
  782. case maps:is_key(OneFile, CFiles) of
  783. true ->
  784. doMathEveryFile(NextIterator, HrlFileBaseName, CFiles, NewAddMap);
  785. _ ->
  786. doMathEveryFile(NextIterator, HrlFileBaseName, CFiles#{OneFile => 1}, NewAddMap#{OneFile => 1})
  787. end;
  788. _ ->
  789. doMathEveryFile(NextIterator, HrlFileBaseName, CFiles, NewAddMap)
  790. end;
  791. _ ->
  792. doMathEveryFile(NextIterator, HrlFileBaseName, CFiles, NewAddMap)
  793. end;
  794. _ ->
  795. {CFiles, NewAddMap}
  796. end.
  797. %% 注释
  798. %% whoInclude(HrlFile, SrcFiles) ->
  799. %% HrlFileBaseName = filename:basename(HrlFile),
  800. %% Pred =
  801. %% fun(SrcFile, _) ->
  802. %% {ok, Forms} = epp_dodger:parse_file(SrcFile),
  803. %% isInclude(binary_to_list(HrlFileBaseName), Forms)
  804. %% end,
  805. %% maps:filter(Pred, SrcFiles).
  806. %% isInclude(_HrlFile, []) ->
  807. %% false;
  808. %% isInclude(HrlFile, [{tree, attribute, _, {attribute, _, [{_, _, IncludeFile}]}} | Forms]) when is_list(IncludeFile) ->
  809. %% IncludeFileBaseName = filename:basename(IncludeFile),
  810. %% case IncludeFileBaseName of
  811. %% HrlFile -> true;
  812. %% _ -> isInclude(HrlFile, Forms)
  813. %% end;
  814. %% isInclude(HrlFile, [_SomeForm | Forms]) ->
  815. %% isInclude(HrlFile, Forms).
  816. doMathEveryLine(IoDevice, HrlFileBaseName) ->
  817. case file:read_line(IoDevice) of
  818. {ok, Data} ->
  819. case re:run(Data, HrlFileBaseName) of
  820. nomatch ->
  821. case re:run(Data, <<"->">>) of
  822. nomatch ->
  823. doMathEveryLine(IoDevice, HrlFileBaseName);
  824. _ ->
  825. false
  826. end;
  827. _ ->
  828. true
  829. end;
  830. _ ->
  831. false
  832. end.
  833. classifyChangeFile([], Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams) ->
  834. {Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams};
  835. classifyChangeFile([OneFile | LeftFile], Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams) ->
  836. CurMTimeSec = esUtils:dateTimeToSec(filelib:last_modified(OneFile)),
  837. case filename:extension(OneFile) of
  838. <<".beam">> ->
  839. BinMod = binary_to_atom(filename:basename(OneFile, <<".beam">>)),
  840. case ColBeams of
  841. #{BinMod := OldMTimeSec} ->
  842. case CurMTimeSec =/= OldMTimeSec andalso CurMTimeSec =/= 0 of
  843. true ->
  844. classifyChangeFile(LeftFile, [BinMod | Beams], Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams#{BinMod := CurMTimeSec});
  845. _ ->
  846. classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams)
  847. end;
  848. _ ->
  849. classifyChangeFile(LeftFile, [BinMod | Beams], Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams#{BinMod => CurMTimeSec})
  850. end;
  851. <<".config">> ->
  852. AbsFile = filename:absname(OneFile),
  853. case ColConfigs of
  854. #{AbsFile := OldMTimeSec} ->
  855. case CurMTimeSec =/= OldMTimeSec andalso CurMTimeSec =/= 0 of
  856. true ->
  857. CfgMod = erlang:binary_to_atom(filename:basename(AbsFile, <<".config">>), utf8),
  858. classifyChangeFile(LeftFile, Beams, [CfgMod | Configs], Hrls, Srcs, ColSrcs, ColHrls, ColConfigs#{AbsFile := CurMTimeSec}, ColBeams);
  859. _ ->
  860. classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams)
  861. end;
  862. _ ->
  863. CfgMod = erlang:binary_to_atom(filename:basename(AbsFile, <<".config">>), utf8),
  864. classifyChangeFile(LeftFile, Beams, [CfgMod | Configs], Hrls, Srcs, ColSrcs, ColHrls, ColConfigs#{AbsFile => CurMTimeSec}, ColBeams)
  865. end;
  866. <<".hrl">> ->
  867. AbsFile = filename:absname(OneFile),
  868. case ColHrls of
  869. #{AbsFile := OldMTimeSec} ->
  870. case CurMTimeSec =/= OldMTimeSec andalso CurMTimeSec =/= 0 of
  871. true ->
  872. classifyChangeFile(LeftFile, Beams, Configs, Hrls#{AbsFile => 1}, Srcs, ColSrcs, ColHrls#{AbsFile := CurMTimeSec}, ColConfigs, ColBeams);
  873. _ ->
  874. classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams)
  875. end;
  876. _ ->
  877. classifyChangeFile(LeftFile, Beams, Configs, Hrls#{AbsFile => 1}, Srcs, ColSrcs, ColHrls#{AbsFile => CurMTimeSec}, ColConfigs, ColBeams)
  878. end;
  879. <<>> ->
  880. classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams);
  881. _ ->
  882. AbsFile = filename:absname(OneFile),
  883. case ColSrcs of
  884. #{AbsFile := OldMTimeSec} ->
  885. case CurMTimeSec =/= OldMTimeSec andalso CurMTimeSec =/= 0 of
  886. true ->
  887. classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs#{AbsFile => 1}, ColSrcs#{AbsFile := CurMTimeSec}, ColHrls, ColConfigs, ColBeams);
  888. _ ->
  889. classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams)
  890. end;
  891. _ ->
  892. classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs#{AbsFile => 1}, ColSrcs#{AbsFile => CurMTimeSec}, ColHrls, ColConfigs, ColBeams)
  893. end
  894. end.
  895. fileSyncPath(ExecName) ->
  896. case code:priv_dir(?MODULE) of
  897. {error, _} ->
  898. case code:which(?MODULE) of
  899. Filename when is_list(Filename) ->
  900. filename:join([filename:dirname(filename:dirname(Filename)), "priv", ExecName]);
  901. _ ->
  902. filename:join("../priv", ExecName)
  903. end;
  904. Dir ->
  905. filename:join(Dir, ExecName)
  906. end.
  907. dateTimeToSec(DateTime) ->
  908. if
  909. DateTime == 0 ->
  910. 0;
  911. true ->
  912. erlang:universaltime_to_posixtime(DateTime)
  913. end.