8
8
,project_apps_install /1
9
9
,install /2
10
10
,handle_plugins /3
11
- ,handle_plugins /4 ]).
11
+ ,handle_plugins /4
12
+ ,discover_plugins /1 ]).
12
13
13
14
-include (" rebar.hrl" ).
14
15
@@ -94,10 +95,11 @@ handle_plugins(Profile, Plugins, State, Upgrade) ->
94
95
Locks = rebar_state :lock (State ),
95
96
DepsDir = rebar_state :get (State , deps_dir , ? DEFAULT_DEPS_DIR ),
96
97
State1 = rebar_state :set (State , deps_dir , ? DEFAULT_PLUGINS_DIR ),
98
+ SrcPlugins = discover_plugins (Plugins , State ),
97
99
% % Install each plugin individually so if one fails to install it doesn't effect the others
98
100
{_PluginProviders , State2 } =
99
101
lists :foldl (fun (Plugin , {PluginAcc , StateAcc }) ->
100
- {NewPlugins , NewState } = handle_plugin (Profile , Plugin , StateAcc , Upgrade ),
102
+ {NewPlugins , NewState } = handle_plugin (Profile , Plugin , StateAcc , SrcPlugins , Upgrade ),
101
103
NewState1 = rebar_state :create_logic_providers (NewPlugins , NewState ),
102
104
{PluginAcc ++ NewPlugins , NewState1 }
103
105
end , {[], State1 }, Plugins ),
@@ -106,24 +108,34 @@ handle_plugins(Profile, Plugins, State, Upgrade) ->
106
108
State3 = rebar_state :set (State2 , deps_dir , DepsDir ),
107
109
rebar_state :lock (State3 , Locks ).
108
110
109
- handle_plugin (Profile , Plugin , State , Upgrade ) ->
111
+ handle_plugin (Profile , Plugin , State , SrcPlugins , Upgrade ) ->
110
112
try
111
- {Apps , State2 } = rebar_prv_install_deps :handle_deps_as_profile (Profile , State , [Plugin ], Upgrade ),
112
- {no_cycle , Sorted } = rebar_prv_install_deps :find_cycles (Apps ),
113
+ % % Inject top-level src plugins as project apps, so that they get skipped
114
+ % % by the installation as already seen
115
+ ProjectApps = rebar_state :project_apps (State ),
116
+ State0 = rebar_state :project_apps (State , SrcPlugins ),
117
+ % % We however have to pick the deps of top-level apps and promote them
118
+ % % directly to make sure they are installed if they were not also at the top level
119
+ TopDeps = top_level_deps (State , SrcPlugins ),
120
+ % % Install the plugins
121
+ {Apps , State1 } = rebar_prv_install_deps :handle_deps_as_profile (Profile , State0 , [Plugin |TopDeps ], Upgrade ),
122
+ {no_cycle , Sorted } = rebar_prv_install_deps :find_cycles (SrcPlugins ++ Apps ),
113
123
ToBuild = rebar_prv_install_deps :cull_compile (Sorted , []),
124
+ % % Return things to normal
125
+ State2 = rebar_state :project_apps (State1 , ProjectApps ),
114
126
115
127
% % Add already built plugin deps to the code path
116
128
ToBuildPaths = [rebar_app_info :ebin_dir (A ) || A <- ToBuild ],
117
- PreBuiltPaths = [Ebin || A <- Apps ,
129
+ PreBuiltPaths = [Ebin || A <- Sorted ,
118
130
Ebin <- [rebar_app_info :ebin_dir (A )],
119
131
not lists :member (Ebin , ToBuildPaths )],
120
132
code :add_pathsa (PreBuiltPaths ),
121
133
122
134
% % Build plugin and its deps
123
- build_plugins (ToBuild , Apps , State2 ),
135
+ build_plugins (ToBuild , Sorted , State2 ),
124
136
125
137
% % Add newly built deps and plugin to code path
126
- State3 = rebar_state :update_all_plugin_deps (State2 , Apps ),
138
+ State3 = rebar_state :update_all_plugin_deps (State2 , Sorted ),
127
139
NewCodePaths = [rebar_app_info :ebin_dir (A ) || A <- ToBuild ],
128
140
129
141
% % Store plugin code paths so we can remove them when compiling project apps
@@ -172,3 +184,96 @@ validate_plugin(Plugin) ->
172
184
end
173
185
end .
174
186
187
+ discover_plugins ([], _ ) ->
188
+ % % don't search if nothing is declared
189
+ [];
190
+ discover_plugins (_ , State ) ->
191
+ discover_plugins (State ).
192
+
193
+ discover_plugins (State ) ->
194
+ % % only support this mode in an umbrella project to avoid cases where
195
+ % % this is used in a project intended to be an installed dependency and accidentally
196
+ % % relies on vendoring when not intended. Also skip for global plugins, this would
197
+ % % make no sense.
198
+ case lists :member (global , rebar_state :current_profiles (State )) orelse not is_umbrella (State ) of
199
+ true ->
200
+ [];
201
+ false ->
202
+ % % Inject source paths for plugins to allow vendoring and umbrella
203
+ % % top-level declarations
204
+ BaseDir = rebar_state :dir (State ),
205
+ LibDirs = rebar_dir :project_plugin_dirs (State ),
206
+ Dirs = [filename :join (BaseDir , LibDir ) || LibDir <- LibDirs ],
207
+ RebarOpts = rebar_state :opts (State ),
208
+ SrcDirs = rebar_dir :src_dirs (RebarOpts , [" src" ]),
209
+ Found = rebar_app_discover :find_apps (Dirs , SrcDirs , all , State ),
210
+ ? DEBUG (" Found local plugins: ~p~n "
211
+ " \t using config: {project_plugin_dirs, ~p }" ,
212
+ [[rebar_utils :to_atom (rebar_app_info :name (F )) || F <- Found ],
213
+ LibDirs ]),
214
+ PluginsDir = rebar_dir :plugins_dir (State ),
215
+ SetUp = lists :map (fun (App ) ->
216
+ Name = rebar_app_info :name (App ),
217
+ OutDir = filename :join (PluginsDir , Name ),
218
+ prepare_plugin (rebar_app_info :out_dir (App , OutDir ))
219
+ end , Found ),
220
+ rebar_utils :sort_deps (SetUp )
221
+ end .
222
+
223
+ is_umbrella (State ) ->
224
+ % % We can't know if this is an umbrella project before running app discovery,
225
+ % % but plugins are installed before app discovery. So we do a heuristic.
226
+ % % The lib dirs we search contain things such as apps/, lib/, etc.
227
+ % % which contain sub-applications. Then there's a final search for the
228
+ % % local directory ("."), which finds the top-level app in a non-umbrella
229
+ % % project.
230
+ % %
231
+ % % So what we do here is look for the library directories without the ".",
232
+ % % and if none of these paths exist but one of the src_dirs exist, then
233
+ % % we know this is not an umbrella application.
234
+ Root = rebar_dir :root_dir (State ),
235
+ LibPaths = lists :usort (rebar_dir :lib_dirs (State )) -- [" ." ],
236
+ SrcPaths = rebar_dir :src_dirs (rebar_state :opts (State ), [" src" ]),
237
+ lists :any (fun (Dir ) -> [] == filelib :wildcard (filename :join (Root , Dir )) end , LibPaths )
238
+ andalso
239
+ lists :all (fun (Dir ) -> not filelib :is_dir (filename :join (Root , Dir )) end , SrcPaths ).
240
+
241
+ prepare_plugin (AppInfo ) ->
242
+ % % We need to handle plugins as dependencies to avoid re-building them
243
+ % % continuously. So here we copy the app directories to the dep location
244
+ % % and then change the AppInfo record to be redirected to the dep location.
245
+ AppDir = rebar_app_info :dir (AppInfo ),
246
+ OutDir = rebar_app_info :out_dir (AppInfo ),
247
+ rebar_prv_compile :copy_app_dirs (AppInfo , AppDir , OutDir ),
248
+ Relocated = rebar_app_info :dir (AppInfo , OutDir ),
249
+ case needs_rebuild (AppInfo ) of
250
+ true -> rebar_app_info :valid (Relocated , false ); % force recompilation
251
+ false -> rebar_app_info :valid (Relocated , undefined ) % force revalidation
252
+ end .
253
+
254
+ top_level_deps (State , Apps ) ->
255
+ CurrentProfiles = rebar_state :current_profiles (State ),
256
+ Keys = lists :append ([[{plugins , P }, {deps , P }] || P <- CurrentProfiles ]),
257
+ RawDeps = lists :foldl (fun (App , Acc ) ->
258
+ % % Only support the profiles we would with regular plugins?
259
+ lists :append ([rebar_app_info :get (App , Key , []) || Key <- Keys ]) ++ Acc
260
+ end , [], Apps ),
261
+ rebar_utils :tup_dedup (RawDeps ).
262
+
263
+ needs_rebuild (AppInfo ) ->
264
+ % % if source files are newer than built files then the code was edited
265
+ % % and can't be considered valid -- force a rebuild.
266
+ % %
267
+ % % we do this by reusing the compiler code for Erlang as a heuristic for
268
+ % % files to check. The actual compiler provider will do an in-depth
269
+ % % validation of each module that may or may not need recompiling.
270
+ #{src_dirs := SrcD , include_dirs := InclD ,
271
+ out_mappings := List } = rebar_compiler_erl :context (AppInfo ),
272
+ SrcDirs = SrcD ++ InclD ,
273
+ OutDirs = [Dir || {_Ext , Dir } <- List ],
274
+ newest_stamp (OutDirs ) < newest_stamp (SrcDirs ).
275
+
276
+ newest_stamp (DirList ) ->
277
+ lists :max ([0 ] ++
278
+ [filelib :last_modified (F )
279
+ || F <- rebar_utils :find_files_in_dirs (DirList , " .+" , true )]).
0 commit comments