Permalink
Browse files

implement task hooks (closes #476)

for local tasks, hooks are declared using the new '-hook' attribute:

	-hook({<task>, run_before, <other task>}).
	-hook({<task>, run_after, <other task>}).

see src/tetrapak.app.src for an example of a hook declaration
for builtin tasks.
  • Loading branch information...
1 parent 1829c47 commit e19d3103a3438967898a8b77af1e8bb2b57b4f63 Felix Lange committed Nov 15, 2011
Showing with 193 additions and 78 deletions.
  1. +10 −6 include/tetrapak.hrl
  2. +2 −2 src/tetrapak.app.src
  3. +4 −1 src/tetrapak.erl
  4. +66 −28 src/tetrapak_context.erl
  5. +27 −6 src/tetrapak_task.erl
  6. +73 −24 src/tetrapak_task_boot.erl
  7. +6 −9 src/tetrapak_task_erlc.erl
  8. +5 −2 src/tpk_file.erl
View
16 include/tetrapak.hrl
@@ -1,13 +1,17 @@
-record(task, {
- name :: string(),
- module :: atom(),
- description = "" :: string(),
- origin = builtin :: local | library | builtin
+ name :: string(),
+ module :: atom(),
+ description = "" :: string(),
+ origin = builtin :: local | library | builtin,
+ pre_hooks = [] :: [string(), ...],
+ post_hooks = [] :: [string(), ...],
+ must_run_before = [] :: [string(), ...],
+ must_run_after = [] :: [string(), ...]
}).
-record(config, {
- objects = [] :: [{{string(), string()}, [{string(), term()}]}],
- values = [] :: [{string(), term()}]
+ objects = [] :: [{{string(), string()}, [{string(), term()}]}],
+ values = [] :: [{string(), term()}]
}).
-define(TASK_FAIL, '$__tetrapak_task_fail').
View
4 src/tetrapak.app.src
@@ -14,8 +14,8 @@
{"config:appfile", tetrapak_task_config, "Read the application resource file"},
{"config:vcs", tetrapak_task_config, "Gather information from the VCS"},
{"build:erlang", tetrapak_task_erlc, "Compile Erlang modules"},
- {"build:yecc", tetrapak_task_erlc, "Compile yecc parsers (.yrl) to Erlang"},
- {"build:leex", tetrapak_task_erlc, "Compile lexical analysers (.xrl) to Erlang"},
+ {"build:yecc", tetrapak_task_erlc, "Compile yecc parsers (.yrl) to Erlang", [{run_before, ["build:erlang"]}]},
+ {"build:leex", tetrapak_task_erlc, "Compile lexical analysers (.xrl) to Erlang", [{run_before, ["build:erlang"]}]},
{"build:appfile", tetrapak_task_appsrc, "Generate the application resource file"},
{"clean:erlang", tetrapak_task_erlc, "Delete compiled Erlang modules"},
{"clean:yecc", tetrapak_task_erlc, "Delete compiled yecc parsers"},
View
5 src/tetrapak.erl
@@ -36,7 +36,10 @@ run(Directory, TaskCmds) ->
case tetrapak_context:run_sequentially(Context, RunTasks) of
ok -> ok;
{error, {unknown_key, Key}} -> {unknown, Key};
- {error, _} -> error
+ {error, _} -> error;
+ {context_exit, Reason} ->
+ io:format("!!! context died: ~p~n", [Reason]),
+ error
end.
cli_main([_ | CliArgs]) ->
View
94 src/tetrapak_context.erl
@@ -44,6 +44,10 @@ get_tasks(Ctx) ->
import_config(Ctx, Config = #config{}) ->
call(Ctx, {import_config, Config}).
+-spec run_sequentially(pid(), [Key, ...]) -> ok | {error, Error} | CtxExit when
+ Error :: {failed, Key} | {cycle, [Key, ...]} | {unknown_key, Key},
+ Key :: string(),
+ CtxExit :: {context_exit, term()}.
run_sequentially(Context, []) ->
shutdown(Context);
run_sequentially(Context, [Task | Rest]) ->
@@ -55,41 +59,48 @@ run_sequentially(Context, [Task | Rest]) ->
{error, {unknown_key, Key}};
{error, {failed, Key}} ->
wait_shutdown(Context),
- ?DEBUG("run_sequentially/1: wait shutdown complete"),
- {error, {failed, Key}}
+ {error, {failed, Key}};
+ Exit = {context_exit, Reason} ->
+ Exit
end.
-spec shutdown(pid()) -> ok.
-
shutdown(Context) ->
cast(Context, shutdown),
wait_shutdown(Context).
--spec wait_for(pid(), [Key, ...]) -> ok | {error, Error} when
- Error :: {failed, Key} | {cycle, [Key, ...]} | {unknown_key, Key},
- Key :: string().
-
+-spec wait_for(pid(), [Key, ...]) -> ok | {error, Error} | CtxExit when
+ Error :: {failed, Key} | {cycle, [Key, ...]} | {unknown_key, Key},
+ Key :: string(),
+ CtxExit :: {context_exit, term()}.
wait_for(Ctx, Keys) ->
case call(Ctx, {wait_for, Keys}) of
{error, Error} ->
{error, Error};
{wait, WaitPids} ->
- wait_tasks_down(Ctx, ordsets:from_list(WaitPids), ok)
+ wait_tasks_down(Ctx, ordsets:from_list(WaitPids))
end.
-wait_tasks_down(_Ctx, [], Result) ->
+wait_tasks_down(Ctx, WaitPids) ->
+ CtxMRef = monitor(process, Ctx),
+ wait_tasks_down(Ctx, CtxMRef, WaitPids, ok).
+
+wait_tasks_down(_Ctx, CtxMRef, [], Result) ->
+ erlang:demonitor(CtxMRef, [flush]),
Result;
-wait_tasks_down(Ctx, WaitPids, Result) ->
- ?DEBUG("wait for: ~p", [{WaitPids, Result}]),
+wait_tasks_down(Ctx, CtxMRef, WaitPids, Result) ->
receive
{Ctx, done, Pid} when is_pid(Pid) ->
- wait_tasks_down(Ctx, ordsets:del_element(Pid, WaitPids), Result);
+ wait_tasks_down(Ctx, CtxMRef, ordsets:del_element(Pid, WaitPids), Result);
{Ctx, failed, Pid, TaskName} when is_pid(Pid) ->
- ?DEBUG("got failed: ~p", [{Pid, TaskName}]),
- wait_tasks_down(Ctx, ordsets:del_element(Pid, WaitPids), {error, {failed, TaskName}})
+ wait_tasks_down(Ctx, CtxMRef, ordsets:del_element(Pid, WaitPids), {error, {failed, TaskName}});
+ {'DOWN', CtxMRef, process, Ctx, Reason} ->
+ ?DEBUG("wait_tasks_down: context died: ~p", [Reason]),
+ {context_exit, Reason}
end.
wait_shutdown(Process) ->
+ ?DEBUG("wait_shutdown: ~p", [Process]),
MRef = monitor(process, Process),
receive
{'DOWN', MRef, process, Process, _Info} -> ok
@@ -99,6 +110,7 @@ wait_shutdown(Process) ->
%% -- server loop
-record(st, {
directory :: string(),
+ parent :: pid(),
tasks :: [{string(), #task{}}],
cache :: ets:tid(),
rungraph :: digraph(),
@@ -110,18 +122,19 @@ new(Directory) ->
init(Parent, Directory) ->
process_flag(trap_exit, true),
- Tasks = tetrapak_task_boot:initial_tmap(),
- CacheTab = ets:new(?MODULE, [protected, ordered_set]),
- RunGraph = digraph:new([acyclic]),
- digraph:add_vertex(RunGraph, pid_to_list(Parent), Parent),
- InitialState = #st{directory = Directory, tasks = Tasks, cache = CacheTab, rungraph = RunGraph},
+ InitialState = #st{directory = Directory, parent = Parent,
+ tasks = tetrapak_task_boot:initial_tmap(),
+ cache = ets:new(?MODULE, [protected, ordered_set]),
+ rungraph = digraph:new([acyclic])},
+ digraph:add_vertex(InitialState#st.rungraph, pid_to_list(Parent), Parent),
loop(InitialState).
loop(LoopState = #st{cache = CacheTable, tasks = TaskMap, rungraph = RunGraph}) ->
receive
{request, FromPid, {register_tasks, TList}} ->
reply(FromPid, ok),
- loop(LoopState#st{tasks = lists:keymerge(1, lists:ukeysort(1, TList), TaskMap)});
+ NewTasks = import_tasks(TList, TaskMap),
+ loop(LoopState#st{tasks = NewTasks});
{request, FromPid, {import_config, Config}} ->
lists:foreach(fun ({Key, Value}) ->
@@ -184,7 +197,7 @@ handle_exit(RunGraph, DeadPid, SendDoneMsg) ->
DepTasks = [digraph:edge(RunGraph, E) || E <- digraph:in_edges(RunGraph, TaskName)],
lists:foreach(fun ({_, Dep, _, _}) ->
{_, DependencyPid} = digraph:vertex(RunGraph, Dep),
- SendDoneMsg(DependencyPid, TaskName, DeadPid)
+ is_pid(DependencyPid) andalso SendDoneMsg(DependencyPid, TaskName, DeadPid)
end, DepTasks);
_Else ->
ok
@@ -195,24 +208,26 @@ send_done(DependencyPid, _DeadName, DeadPid) ->
send_failed(DependencyPid, DeadName, DeadPid) ->
DependencyPid ! {self(), failed, DeadPid, DeadName}.
-do_shutdown(#st{rungraph = RunGraph, io_workers = IOWorkers}, FailedPid, FailedTask) ->
- Workers = [Pid || {Name, Pid} <- digraph:vertices(RunGraph),
- is_list(Name), Pid /= FailedPid, Pid /= done],
+do_shutdown(#st{rungraph = RunGraph, io_workers = IOWorkers, parent = Parent}, FailedPid, FailedTask) ->
+ Vertices = [digraph:vertex(RunGraph, V) || V <- digraph:vertices(RunGraph), is_list(V)],
+ Workers = [Pid || {_, Pid} <- Vertices, is_pid(Pid), Pid /= FailedPid, Pid /= Parent],
+ ?DEBUG("shutdown: killing task workers: ~p", [Workers]),
+ lists:foreach(fun (P) -> erlang:exit(P, kill) end, Workers),
shutdown_loop(Workers ++ IOWorkers, RunGraph, FailedTask).
shutdown_loop([], _RunGraph, _FailedTask) ->
ok;
shutdown_loop(Workers, RunGraph, FailedTask) ->
+ ?DEBUG("shutdown: ~p", [Workers]),
receive
{'EXIT', Pid, normal} ->
+ ?DEBUG("shutdown EXIT normal: ~p", [Pid]),
handle_exit(RunGraph, Pid, fun send_done/3),
shutdown_loop(lists:delete(Pid, Workers), RunGraph, FailedTask);
{'EXIT', Pid, _OtherReason} ->
+ ?DEBUG("shutdown EXIT ~p: ~p", [_OtherReason, Pid]),
handle_exit(RunGraph, Pid, fun send_failed/3),
shutdown_loop(lists:delete(Pid, Workers), RunGraph, FailedTask);
- {request, FromPid, {wait_for, _Keys}} ->
- reply(FromPid, {error, {failed, FailedTask}}),
- shutdown_loop(Workers, RunGraph, FailedTask);
{cast, FromPid, register_io_worker} ->
shutdown_loop([FromPid | Workers], RunGraph, FailedTask);
Other ->
@@ -239,7 +254,7 @@ start_deps(Dependencies, Caller, State = #st{rungraph = Graph}) ->
end.
add_edges([Task | Rest], Caller, Graph) ->
- case catch digraph:add_edge(Graph, Caller, Task#task.name) of
+ case digraph:add_edge(Graph, Caller, Task#task.name) of
{error, {bad_edge, Cycle}} ->
{cycle, Cycle};
['$e' | _] ->
@@ -291,6 +306,29 @@ descending_lookup(TaskMap, Prefix, KeyRest) ->
{_, [Next | KR]} -> descending_lookup(Matches, Prefix ++ [Next], KR)
end.
+import_tasks(NewTasks, TaskMap) ->
+ MergedTaskMap = lists:keymerge(1, lists:ukeysort(1, NewTasks), TaskMap),
+ lists:foldl(fun ({_, Task}, TMAcc1) ->
+ TMAcc2 = apply_hooks(Task, #task.must_run_before, #task.pre_hooks, TMAcc1),
+ apply_hooks(Task, #task.must_run_after, #task.post_hooks, TMAcc2)
+ end, MergedTaskMap, NewTasks).
+
+%% preprend Task's name to the ToHookField list of every task given in FromHookField
+%% this is so we don't have to write the same code twice for
+%% run_before_other_task_hooks and run_after_other_task_hooks
+apply_hooks(Task, FromHookField, ToHookField, TaskMap) ->
+ lists:foldl(fun (Hooked, TMAcc) ->
+ HookedName = tetrapak_task:split_name(Hooked),
+ case orddict:find(HookedName, TMAcc) of
+ {ok, HookedTask} ->
+ NewHookList = [Task#task.name | element(ToHookField, HookedTask)],
+ NewHookedTask = setelement(ToHookField, HookedTask, NewHookList),
+ orddict:store(HookedName, NewHookedTask, TMAcc);
+ false ->
+ TMAcc
+ end
+ end, TaskMap, element(FromHookField, Task)).
+
%% ------------------------------------------------------------
%% -- micro gen_server
call(Ctx, Request) ->
View
33 src/tetrapak_task.erl
@@ -1,3 +1,4 @@
+
% Copyright 2010-2011, Travelping GmbH <info@travelping.com>
% Permission is hereby granted, free of charge, to any person obtaining a
@@ -56,25 +57,32 @@ cache_table() ->
Table -> Table
end.
-worker(#task{name = TaskName, module = TaskModule}, Context, Directory, CacheTab) ->
+worker(Task = #task{name = TaskName, module = TaskModule}, Context, Directory, CacheTab) ->
?DEBUG("worker for ~s", [TaskName]),
- OutputCollector = spawn_link(?MODULE, output_collector, [Context, TaskName, self()]),
- group_leader(OutputCollector, self()),
-
erlang:put(?CTX, Context),
erlang:put(?DIRECTORY, Directory),
erlang:put(?CACHE_TAB, CacheTab),
+ OutputCollector = spawn_link(?MODULE, output_collector, [Context, TaskName, self()]),
+ group_leader(OutputCollector, self()),
+
+ ?DEBUG("require pre hooks: ~p", [Task#task.pre_hooks]),
+ run_hooks(Task#task.pre_hooks),
+
case try_check(TaskModule, TaskName) of
{done, Variables} ->
?DEBUG("worker: check/1 -> done"),
- tetrapak_context:update_cache(Context, Variables);
+ tetrapak_context:update_cache(Context, Variables),
+ ?DEBUG("require post hooks: ~p", [Task#task.post_hooks]),
+ run_hooks(Task#task.post_hooks);
{needs_run, TaskData} ->
case try_run(TaskModule, TaskName, TaskData) of
{done, Variables} ->
?DEBUG("worker: run/2 -> done"),
- tetrapak_context:update_cache(Context, Variables)
+ tetrapak_context:update_cache(Context, Variables),
+ ?DEBUG("require post hooks: ~p", [Task#task.post_hooks]),
+ run_hooks(Task#task.post_hooks)
end
end.
@@ -133,6 +141,14 @@ try_run(TaskModule, TaskName, TaskData) ->
handle_error(Function, Class, Exn)
end.
+run_hooks(Hooks) ->
+ try
+ require_all(Hooks)
+ catch
+ Class:Exn ->
+ handle_error(tpk_util:f("~s:run_hooks/3", [?MODULE]), Class, Exn)
+ end.
+
handle_error(_Function, throw, {?TASK_FAIL, undefined}) ->
exit(failed);
handle_error(_Function, throw, {?TASK_FAIL, Message}) ->
@@ -179,11 +195,15 @@ get_config(Key) ->
[{_K, Value}] -> {ok, Value}
end.
+require_all([]) ->
+ ok;
require_all(Keys) when is_list(Keys) ->
KList = lists:map(fun str/1, Keys),
case tetrapak_context:wait_for(context(), KList) of
ok ->
ok;
+ {context_exit, _Reason} ->
+ error(context_died);
{error, {failed, Other}} ->
fail("required task '~s' failed", [Other]);
{error, {cycle, Cycle}} ->
@@ -197,6 +217,7 @@ format_cycle(Cycle) ->
normalize_name(Key) ->
string:to_lower(string:strip(str(Key))).
+
split_name(Key) ->
SplitName = re:split(normalize_name(Key), ":", [{return, list}]),
lists:filter(fun ([]) -> false;
View
97 src/tetrapak_task_boot.erl
@@ -25,9 +25,9 @@
-include("tetrapak.hrl").
initial_tmap() ->
- [{["clean", "taskcache"], #task{name = "clean:taskcache", module = ?MODULE, description = "Delete the local task cache"}},
- {["tetrapak", "boot"], #task{name = "tetrapak:boot", module = ?MODULE, description = "The root of all evil"}},
- {["tetrapak", "info"], #task{name = "tetrapak:info", module = ?MODULE, description = "Show version and tasks"}}].
+ [{["clean", "taskcache"], #task{name = "clean:taskcache", module = ?MODULE, description = "Delete the local task cache"}},
+ {["tetrapak", "boot"], #task{name = "tetrapak:boot", module = ?MODULE, description = "The root of all evil"}},
+ {["tetrapak", "info"], #task{name = "tetrapak:info", module = ?MODULE, description = "Show version and tasks"}}].
run("tetrapak:boot", _) ->
Props = load_appdata(),
@@ -65,23 +65,38 @@ show_tmap(TMap) ->
hd(Key) /= "tetrapak"], %% exclude internal tasks
MaxWidth = lists:foldl(fun ({K, _, _}, Max) -> erlang:max(iolist_size(K), Max) end, 0, Lis),
lists:foldr(fun ({Name, Desc, Origin}, Acc) ->
- Space = lists:duplicate(MaxWidth - iolist_size(Name), $ ),
- [" " ++ Name ++ " " ++ Space ++ ostr(Origin) ++ " - " ++ Desc ++ "\n" | Acc]
+ Space = lists:duplicate(MaxWidth - iolist_size(Name), $ ),
+ [" " ++ Name ++ " " ++ Space ++ ostr(Origin) ++ " - " ++ Desc ++ "\n" | Acc]
end, [], Lis).
ostr(builtin) -> " ";
ostr(local) -> "*";
ostr(library) -> "+".
builtin_tasks(Tasks) ->
- lists:foldl(fun ({TaskName, Module, Desc}, Acc) ->
- NewTask = #task{name = tetrapak_task:normalize_name(TaskName),
- module = Module,
- description = Desc,
- origin = builtin},
- AddModule = fun (_) -> NewTask end,
- pl_update(tetrapak_task:split_name(TaskName), AddModule, NewTask, Acc)
- end, [], Tasks).
+ lists:foldl(fun add_builtin_task/2, [], Tasks).
+
+add_builtin_task({TaskName, Module, Desc}, Acc) ->
+ add_builtin_task({TaskName, Module, Desc, []}, Acc);
+add_builtin_task({TaskName, Module, Desc, Options}, Acc) ->
+ NewTask1 = #task{name = tetrapak_task:normalize_name(TaskName),
+ module = Module,
+ description = Desc,
+ origin = builtin},
+ NewTask2 = apply_hook_options(Options, NewTask1),
+ AddModule = fun (_) -> NewTask2 end,
+ pl_update(tetrapak_task:split_name(TaskName), AddModule, NewTask2, Acc);
+add_builtin_task(TaskTuple, _Acc) ->
+ tetrapak:fail("bad task definition tuple in application environment: ~n ~p", [TaskTuple]).
+
+apply_hook_options([{run_before, HookList} | Rest], Task) ->
+ apply_hook_options(Rest, Task#task{must_run_before = HookList});
+apply_hook_options([{run_after, HookList} | Rest], Task) ->
+ apply_hook_options(Rest, Task#task{must_run_after = HookList});
+apply_hook_options([Option | _], Task) ->
+ tetrapak:fail("(task: ~s) unknown task option: ~p", [Task#task.name, Option]);
+apply_hook_options([], Task) ->
+ Task.
pl_update(Key, AddItem, NewItem, Proplist) ->
case proplists:get_value(Key, Proplist) of
@@ -107,7 +122,7 @@ load_appdata() ->
%% -- LOCAL TASKS
%% changing this record def will invalidate all caches
--record(tmod, {file, md5, module, tasks = [], includes = [], code}).
+-record(tmod, {file, md5, module, tasks = [], hooks = [], includes = [], code}).
scan_local_tasks(Dir) ->
case filelib:wildcard(filename:join(Dir, "*.erl")) of
@@ -143,6 +158,8 @@ compile_and_load_local_tasks(Files, Cache) ->
_OtherMd5 ->
compile_and_load(File, Cache)
end;
+ [_StaleTmod] ->
+ compile_and_load(File, Cache);
[] ->
compile_and_load(File, Cache)
end
@@ -153,17 +170,22 @@ compile_and_load(File, Cache) ->
dets:insert(Cache, NewTMod),
load_local_task(File, NewTMod).
-load_local_task(File, #tmod{module = ModuleName, tasks = Tasks, code = Code}) ->
+load_local_task(File, #tmod{module = ModuleName, tasks = Tasks, hooks = Hooks, code = Code}) ->
code:purge(ModuleName),
case code:load_binary(ModuleName, File, Code) of
{module, ModuleName} ->
- [{tetrapak_task:split_name(TN), #task{module = ModuleName, name = TN, description = TD, origin = local}} || {TN, TD} <- Tasks];
+ lists:map(fun ({TN, TD}) ->
+ Key = tetrapak_task:split_name(TN),
+ Task1 = #task{module = ModuleName, name = TN, description = TD, origin = local},
+ Task2 = apply_hook_options(proplists:get_all_values(TN, Hooks), Task1),
+ {Key, Task2}
+ end, Tasks);
{error, Error} ->
tetrapak:fail("failed to load local task ~s: ~p", [tpk_file:rebase_filename(File, tetrapak:dir(), ""), Error])
end.
compile_local_task(File) ->
- FileDisplayPath = tpk_file:rebase_filename(File, tetrapak:dir(), ""),
+ FileDisplayPath = tpk_file:relative_path(File, tetrapak:dir()),
{PreDefMacros, ModuleName} = predef_macros(File),
IncludePath = [tetrapak:subdir("include")],
case epp:parse_file(File, IncludePath, PreDefMacros) of
@@ -183,7 +205,7 @@ compile_local_task(File) ->
tetrapak:fail("task compilation failed")
end;
{error, Error} ->
- tetrapak:fail("failed to open local task source ~s: ~s", [FileDisplayPath, file:format_error(Error)])
+ tetrapak:fail("failed to open local task file ~s: ~s", [FileDisplayPath, file:format_error(Error)])
end.
predef_macros(File) ->
@@ -200,25 +222,52 @@ predef_macros(File) ->
{PreDefMacros, Module}.
process_forms(File, ModuleName, Forms) ->
- {Found, TMod, Code} = lists:foldr(fun (Form, {FoundM, TMod, CodeAcc}) ->
+ {Found, TMod, Code} = lists:foldl(fun (Form, {FoundM, TMod, CodeAcc}) ->
do_form(File, Form, FoundM, TMod, CodeAcc)
end, {false, #tmod{module = ModuleName}, []}, Forms),
- case Found of
- true -> TMod#tmod{code = Code};
- false -> TMod#tmod{code = [{attribute, 1, module, ModuleName} | Code]}
- end.
+ case Found of
+ true -> TMod#tmod{code = lists:reverse(Code)};
+ false -> TMod#tmod{code = [{attribute, 1, module, ModuleName} | lists:reverse(Code)]}
+ end.
do_form(File, Attr = {attribute, _L, file, {IncludeFile, _}}, FoundM, TMod, Code) when File /= IncludeFile ->
NewTMod = TMod#tmod{includes = [IncludeFile | TMod#tmod.includes]},
{FoundM, NewTMod, [Attr | Code]};
do_form(_File, {attribute, L, module, _ModName}, _FoundM, TMod, Code) ->
{true, TMod, [{attribute, L, module, TMod#tmod.module} | Code]};
-do_form(_File, {attribute, _L, task, {TaskName, TaskDesc}}, FoundM, TMod, Code) ->
+do_form(_File, {attribute, _L, task, {TaskName, TaskDesc}}, FoundM, TMod, Code) when is_list(TaskName), is_list(TaskDesc) ->
NewTMod = TMod#tmod{tasks = [{tetrapak_task:normalize_name(TaskName), TaskDesc} | TMod#tmod.tasks]},
{FoundM, NewTMod, Code};
+do_form(File, {attribute, Line, task, OtherTerm}, _FoundM, _TMod, _Code) ->
+ tcom_fail(File, Line, "bad task attribute: ~p", [OtherTerm]);
+do_form(File, {attribute, Line, hook, HookAttr}, FoundM, TMod, Code) ->
+ NewTMod = TMod#tmod{hooks = do_hook_attribute(File, Line, HookAttr, TMod)},
+ {FoundM, NewTMod, Code};
do_form(_File, OtherForm, FoundM, TMod, Code) ->
{FoundM, TMod, [OtherForm | Code]}.
+do_hook_attribute(File, Line, {TaskName, HookType, HookTarget}, TMod) when is_list(TaskName), is_atom(HookType), is_list(HookTarget) ->
+ case proplists:get_value(tetrapak_task:normalize_name(TaskName), TMod#tmod.tasks) of
+ undefined ->
+ tcom_fail(File, Line, "hook specification for (yet) unknown local task: ~s", [TaskName]);
+ _ ->
+ check_hook_type(File, Line, HookType),
+ (tetrapak_task:normalize_name(TaskName) == tetrapak_task:normalize_name(HookTarget))
+ andalso tcom_fail(File, Line, "cannot hook a task to itself", []),
+ [{TaskName, {HookType, [HookTarget]}} | TMod#tmod.hooks]
+ end;
+do_hook_attribute(File, Line, OtherTerm, _HookAcc) ->
+ tcom_fail(File, Line, "bad hook specification: ~p", [OtherTerm]).
+
+check_hook_type(_File, _Line, run_before) -> ok;
+check_hook_type(_File, _Line, run_after) -> ok;
+check_hook_type(File, Line, HT) ->
+ tcom_fail(File, Line, "bad hook type (allowed: run_before, run_after): ~p", [HT]).
+
+tcom_fail(File, Line, Fmt, Args) ->
+ io:format("~s:~b: Error: " ++ Fmt ++ "\n", [tpk_file:relative_path(File, tetrapak:dir()), Line | Args]),
+ tetrapak:fail("task compilation failed").
+
%% ------------------------------------------------------------
%% -- Config files
project_config_path(Filename) ->
View
15 src/tetrapak_task_erlc.erl
@@ -39,7 +39,6 @@
%% ------------------------------------------------------------
%% -- Task API
check("build:erlang") ->
- tetrapak:require(["build:yecc"]),
EbinDir = tetrapak:subdir("ebin"),
SrcDir = tetrapak:subdir("src"),
ExtraCompileOptions = tetrapak:config("build.erlc_options", []),
@@ -116,7 +115,7 @@ compile_foreach(Function, List) ->
run_compiler(M, F, A = [File | _]) ->
BaseDir = tetrapak:dir(),
- io:format("Compiling ~s~n", [tpk_file:rebase_filename(File, BaseDir, "")]),
+ io:format("Compiling ~s~n", [tpk_file:relative_path(File, BaseDir)]),
case apply(M, F, A) of
{ok, _Module} -> ok;
{ok, _Module, Warnings} ->
@@ -132,13 +131,11 @@ run_compiler(M, F, A = [File | _]) ->
show_errors(BaseDir, Prefix, Errors) ->
lists:foreach(fun ({FileName, FileErrors}) ->
- case lists:prefix(BaseDir, FileName) of
- true ->
- Path = tpk_file:rebase_filename(FileName, BaseDir, "");
- false ->
- Path = FileName
- end,
- lists:foreach(fun (Error) -> tpk_util:show_error_info(Path, Prefix, Error) end, FileErrors)
+ lists:foreach(fun (Error) ->
+ DisplayPath = tpk_file:relative_path(FileName, BaseDir),
+ ?DEBUG("show error: ~p", [{BaseDir, FileName, DisplayPath}]) ,
+ tpk_util:show_error_info(DisplayPath, Prefix, Error)
+ end, FileErrors)
end, Errors).
compile_order(File1, _File2) ->
View
7 src/tpk_file.erl
@@ -19,7 +19,7 @@
% DEALINGS IN THE SOFTWARE.
-module(tpk_file).
--export([size/1, mtime/1, md5sum/1, basename/1, rebase_filename/3]).
+-export([size/1, mtime/1, md5sum/1, basename/1, relative_path/2, rebase_filename/3]).
-export([temp_name/0, temp_name/1, mkdir/1, with_temp_dir/1,
dir_contents/1, dir_contents/2, dir_contents/3,
wildcard/2]).
@@ -44,6 +44,9 @@ mtime(Filename) ->
{ok, #file_info{mtime = MTime}} = file:read_file_info(Filename),
MTime.
+relative_path(Filename, Dir) ->
+ rebase_filename(Filename, Dir, "").
+
rebase_filename(FName, FromDir, ToDir) ->
FromDirPath = filename:split(FromDir),
FPath = filename:split(FName),
@@ -57,7 +60,7 @@ rebase_filename(FName, FromDir, ToDir) ->
{_, _} -> Joined
end;
false ->
- exit(bad_filename)
+ FName
end.
temp_name() -> temp_name("/tmp").

0 comments on commit e19d310

Please sign in to comment.