-
Notifications
You must be signed in to change notification settings - Fork 221
/
inject-deps.escript
executable file
·139 lines (120 loc) · 4.61 KB
/
inject-deps.escript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#!/usr/bin/env escript
%% This script injects implicit relup dependencies for emqx applications.
%%
%% By 'implicit', it means that it is not feasible to define application
%% dependencies in .app.src files.
%%
%% For instance, during upgrade/downgrade, emqx_dashboard usually requires
%% a restart after (but not before) all plugins are upgraded (and maybe
%% restarted), however, the dependencies are not resolvable at build time
%% when relup is generated.
%%
%% This script is to be executed after compile, with the profile given as the
%% first argument. For each dependency overlay, it modifies the .app file to
%% have the 'relup_deps' list extended.
-mode(compile).
usage() ->
"Usage: " ++ escript:script_name() ++ " emqx|emqx-edge".
-type app() :: atom().
-type deps_overlay() :: {re, string()} | app().
%% deps/0 returns the dependency overlays.
%% {re, Pattern} to match application names using regexp pattern
-spec deps(string()) -> [{app(), [deps_overlay()]}].
deps("emqx-edge" ++ _) ->
%% special case for edge
base_deps() ++ [{{re, ".+"}, [{exclude, emqx_reloader}]}];
deps(_Profile) ->
base_deps().
base_deps() ->
[ {emqx_dashboard, [{re, "emqx_.*"}]}
, {emqx_management, [{re, "emqx_.*"}, {exclude, emqx_dashboard}]}
, {{re, "emqx_.*"}, [emqx]}
, {{re, "emqx_auth_.*"}, [emqx_passwd]}
].
main([Profile | _]) ->
ok = inject(Profile);
main(_Args) ->
io:format(standard_error, "~s", [usage()]),
erlang:halt(1).
expand_names({Name, Deps}, AppNames) ->
Names = match_pattern(Name, AppNames),
[{N, Deps} || N <- Names].
%% merge k-v pairs with v1 ++ v2
merge([], Acc) -> Acc;
merge([{K, V0} | Rest], Acc) ->
V = case lists:keyfind(K, 1, Acc) of
{K, V1} -> V1 ++ V0;
false -> V0
end,
NewAcc = lists:keystore(K, 1, Acc, {K, V}),
merge(Rest, NewAcc).
expand_deps([], _AppNames, Acc) -> Acc;
expand_deps([{exclude, Dep} | Deps], AppNames, Acc) ->
Matches = expand_deps([Dep], AppNames, []),
expand_deps(Deps, AppNames, Acc -- Matches);
expand_deps([Dep | Deps], AppNames, Acc) ->
NewAcc = add_to_list(Acc, match_pattern(Dep, AppNames)),
expand_deps(Deps, AppNames, NewAcc).
inject(Profile) ->
LibDir = lib_dir(Profile),
AppNames = list_apps(LibDir),
Deps0 = lists:flatmap(fun(Dep) -> expand_names(Dep, AppNames) end, deps(Profile)),
Deps1 = merge(Deps0, []),
Deps2 = lists:map(fun({Name, DepsX}) ->
NewDeps = expand_deps(DepsX, AppNames, []),
{Name, NewDeps}
end, Deps1),
lists:foreach(fun({App, Deps}) -> inject(App, Deps, LibDir) end, Deps2).
%% list the profile/lib dir to get all apps
list_apps(LibDir) ->
Apps = filelib:wildcard("*", LibDir),
lists:foldl(fun(App, Acc) -> [App || is_app(LibDir, App)] ++ Acc end, [], Apps).
is_app(_LibDir, "." ++ _) -> false; %% ignore hidden dir
is_app(LibDir, AppName) ->
filelib:is_regular(filename:join([ebin_dir(LibDir, AppName), AppName ++ ".app"])) orelse
error({unknown_app, AppName}). %% wtf
lib_dir(Profile) ->
filename:join(["_build", Profile, lib]).
ebin_dir(LibDir, AppName) -> filename:join([LibDir, AppName, "ebin"]).
inject(App0, DepsToAdd, LibDir) ->
App = str(App0),
AppEbinDir = ebin_dir(LibDir, App),
[AppFile0] = filelib:wildcard("*.app", AppEbinDir),
AppFile = filename:join(AppEbinDir, AppFile0),
{ok, [{application, AppName, Props}]} = file:consult(AppFile),
Deps0 = case lists:keyfind(relup_deps, 1, Props) of
{_, X} -> X;
false -> []
end,
%% merge extra deps, but do not self-include
Deps = add_to_list(Deps0, DepsToAdd) -- [App0],
case Deps =:= [] of
true -> ok;
_ ->
NewProps = lists:keystore(relup_deps, 1, Props, {relup_deps, Deps}),
AppSpec = {application, AppName, NewProps},
AppSpecIoData = io_lib:format("~p.", [AppSpec]),
io:format(user, "updated_relup_deps for ~p~n", [App]),
file:write_file(AppFile, AppSpecIoData)
end.
str(A) when is_atom(A) -> atom_to_list(A).
match_pattern({re, Re}, AppNames) ->
Match = fun(AppName) -> re:run(AppName, Re) =/= nomatch end,
AppNamesToAdd = lists:filter(Match, AppNames),
AppsToAdd = lists:map(fun(N) -> list_to_atom(N) end, AppNamesToAdd),
case AppsToAdd =:= [] of
true -> error({nomatch, Re});
false -> AppsToAdd
end;
match_pattern(NameAtom, AppNames) ->
case lists:member(str(NameAtom), AppNames) of
true -> [NameAtom];
false -> error({notfound, NameAtom})
end.
%% Append elements to list without duplication. No reordering.
add_to_list(List, []) -> List;
add_to_list(List, [H | T]) ->
case lists:member(H, List) of
true -> add_to_list(List, T);
false -> add_to_list(List ++ [H], T)
end.