1-module(rebar_state).
2
3-export([new/0, new/1, new/2, new/3,
4
5         get/2, get/3, set/3,
6
7         format_error/1,
8
9         has_all_artifacts/1,
10
11         code_paths/2, code_paths/3, update_code_paths/3,
12
13         opts/1, opts/2,
14         default/1, default/2,
15
16         escript_path/1, escript_path/2,
17
18         lock/1, lock/2,
19
20         current_profiles/1, current_profiles/2,
21
22         command_args/1, command_args/2,
23         command_parsed_args/1, command_parsed_args/2,
24
25         add_to_profile/3, apply_profiles/2,
26
27         dir/1, dir/2,
28         create_logic_providers/2,
29
30         current_app/1, current_app/2,
31         project_apps/1, project_apps/2,
32         deps_to_build/1, deps_to_build/2,
33         all_plugin_deps/1, all_plugin_deps/2, update_all_plugin_deps/2,
34         all_deps/1, all_deps/2, update_all_deps/2, merge_all_deps/2,
35         all_checkout_deps/1,
36         namespace/1, namespace/2,
37
38         default_hex_repo_url_override/1,
39
40         deps_names/1,
41
42         to_list/1,
43
44         compilers/1, compilers/2,
45         prepend_compilers/2, append_compilers/2,
46
47         project_builders/1, add_project_builder/3,
48
49         create_resources/2, set_resources/2,
50         resources/1, resources/2, add_resource/2,
51         providers/1, providers/2, add_provider/2,
52         allow_provider_overrides/1, allow_provider_overrides/2
53        ]).
54
55-include("rebar.hrl").
56-include_lib("providers/include/providers.hrl").
57
58-record(state_t, {dir                               :: file:name(),
59                  opts                = dict:new()  :: rebar_dict(),
60                  code_paths          = dict:new()  :: rebar_dict(),
61                  default             = dict:new()  :: rebar_dict(),
62                  escript_path                      :: undefined | file:filename_all(),
63
64                  lock                = [],
65                  current_profiles    = [default]   :: [atom()],
66                  namespace           = default     :: atom(),
67
68                  command_args        = [],
69                  command_parsed_args = {[], []},
70
71                  current_app                       :: undefined | rebar_app_info:t(),
72                  project_apps        = []          :: [rebar_app_info:t()],
73                  deps_to_build       = []          :: [rebar_app_info:t()],
74                  all_plugin_deps     = []          :: [rebar_app_info:t()],
75                  all_deps            = []          :: [rebar_app_info:t()],
76
77                  compilers           = []          :: [module()],
78                  project_builders    = []          :: [{rebar_app_info:project_type(), module()}],
79                  resources           = [],
80                  providers           = [],
81                  allow_provider_overrides = false  :: boolean()}).
82
83-export_type([t/0]).
84
85-type t() :: #state_t{}.
86
87-spec new() -> t().
88new() ->
89    BaseState = base_state(dict:new()),
90    BaseState#state_t{dir = rebar_dir:get_cwd()}.
91
92-spec new(list()) -> t().
93new(Config) when is_list(Config) ->
94    Opts = base_opts(Config),
95    BaseState = base_state(Opts),
96    BaseState#state_t{dir=rebar_dir:get_cwd(),
97                      default=Opts}.
98
99-spec new(t() | atom(), list()) -> t().
100new(Profile, Config) when is_atom(Profile)
101                        , is_list(Config) ->
102    Opts = base_opts(Config),
103    BaseState = base_state(Opts),
104    BaseState#state_t{dir = rebar_dir:get_cwd(),
105                      current_profiles = [Profile],
106                      default = Opts};
107new(ParentState=#state_t{}, Config) ->
108    %% Load terms from rebar.config, if it exists
109    Dir = rebar_dir:get_cwd(),
110    new(ParentState, Config, Dir).
111
112-spec new(t(), list(), file:filename_all()) -> t().
113new(ParentState, Config, Dir) ->
114    new(ParentState, Config, deps_from_config(Dir, Config), Dir).
115
116new(ParentState, Config, Deps, Dir) ->
117    Opts = ParentState#state_t.opts,
118    Plugins = proplists:get_value(plugins, Config, []),
119    ProjectPlugins = proplists:get_value(project_plugins, Config, []),
120    Terms = Deps++[{{project_plugins, default}, ProjectPlugins}, {{plugins, default}, Plugins} | Config],
121    true = rebar_config:verify_config_format(Terms),
122    LocalOpts = dict:from_list(Terms),
123
124    NewOpts = rebar_opts:merge_opts(LocalOpts, Opts),
125
126    ParentState#state_t{dir=Dir
127                       ,opts=NewOpts
128                       ,default=NewOpts}.
129
130deps_from_config(Dir, Config) ->
131    case rebar_config:consult_lock_file(filename:join(Dir, ?LOCK_FILE)) of
132        [] ->
133            [{{deps, default}, proplists:get_value(deps, Config, [])}];
134        D ->
135            %% We want the top level deps only from the lock file.
136            %% This ensures deterministic overrides for configs.
137            Deps = [X || X <- D, element(3, X) =:= 0],
138            [{{locks, default}, D}, {{deps, default}, Deps}]
139    end.
140
141base_state(Opts) ->
142    #state_t{opts=Opts}.
143
144base_opts(Config) ->
145    Deps = proplists:get_value(deps, Config, []),
146    Plugins = proplists:get_value(plugins, Config, []),
147    ProjectPlugins = proplists:get_value(project_plugins, Config, []),
148    Terms = [{{deps, default}, Deps}, {{plugins, default}, Plugins},
149             {{project_plugins, default}, ProjectPlugins} | Config],
150    true = rebar_config:verify_config_format(Terms),
151    dict:from_list(Terms).
152
153get(State, Key) ->
154    {ok, Value} = dict:find(Key, State#state_t.opts),
155    Value.
156
157get(State, Key, Default) ->
158    case dict:find(Key, State#state_t.opts) of
159        {ok, Value} ->
160            Value;
161        error ->
162            Default
163    end.
164
165-spec set(t(), any(), any()) -> t().
166set(State=#state_t{opts=Opts}, Key, Value) ->
167    State#state_t{ opts = dict:store(Key, Value, Opts) }.
168
169default(#state_t{default=Opts}) ->
170    Opts.
171
172default(State, Opts) ->
173    State#state_t{default=Opts}.
174
175format_error({profile_not_list, Profile, Other}) ->
176    io_lib:format("Profile config must be a list but for profile '~p' config given as:~n~p", [Profile, Other]).
177
178-spec has_all_artifacts(#state_t{}) -> true | {false, file:filename()}.
179has_all_artifacts(State) ->
180    Artifacts = rebar_state:get(State, artifacts, []),
181    Dir = rebar_dir:base_dir(State),
182    all(Dir, Artifacts).
183
184all(_, []) ->
185    true;
186all(Dir, [File|Artifacts]) ->
187    case filelib:is_regular(filename:join(Dir, File)) of
188        false ->
189            ?DEBUG("Missing artifact ~ts", [filename:join(Dir, File)]),
190            {false, File};
191        true ->
192            all(Dir, Artifacts)
193    end.
194
195-spec code_paths(#state_t{}, atom()) -> [file:filename()].
196code_paths(#state_t{code_paths=CodePaths}, Key) ->
197    case dict:find(Key, CodePaths) of
198        {ok, CodePath} ->
199            CodePath;
200        _ ->
201            []
202    end.
203
204-spec code_paths(#state_t{}, atom(), [file:filename()]) -> #state_t{}.
205code_paths(State=#state_t{code_paths=CodePaths}, Key, CodePath) ->
206    State#state_t{code_paths=dict:store(Key, CodePath, CodePaths)}.
207
208-spec update_code_paths(#state_t{}, atom(), [file:filename()]) -> #state_t{}.
209update_code_paths(State=#state_t{code_paths=CodePaths}, Key, CodePath) ->
210    case dict:is_key(Key, CodePaths) of
211        true ->
212            State#state_t{code_paths=dict:append_list(Key, CodePath, CodePaths)};
213        false ->
214            State#state_t{code_paths=dict:store(Key, CodePath, CodePaths)}
215    end.
216
217opts(#state_t{opts=Opts}) ->
218    Opts.
219
220opts(State, Opts) ->
221    State#state_t{opts=Opts}.
222
223current_profiles(#state_t{current_profiles=Profiles}) ->
224    Profiles.
225
226current_profiles(State, Profiles) ->
227    State#state_t{current_profiles=Profiles}.
228
229lock(#state_t{lock=Lock}) ->
230    Lock.
231
232lock(State=#state_t{}, Apps) when is_list(Apps) ->
233    State#state_t{lock=Apps};
234lock(State=#state_t{lock=Lock}, App) ->
235    State#state_t{lock=[App | Lock]}.
236
237escript_path(#state_t{escript_path=EscriptPath}) ->
238    EscriptPath.
239
240escript_path(State, EscriptPath) ->
241    State#state_t{escript_path=EscriptPath}.
242
243command_args(#state_t{command_args=CmdArgs}) ->
244    CmdArgs.
245
246command_args(State, CmdArgs) ->
247    State#state_t{command_args=CmdArgs}.
248
249command_parsed_args(#state_t{command_parsed_args=CmdArgs}) ->
250    CmdArgs.
251
252command_parsed_args(State, CmdArgs) ->
253    State#state_t{command_parsed_args=CmdArgs}.
254
255add_to_profile(State, Profile, KVs) when is_atom(Profile), is_list(KVs) ->
256    Opts = rebar_opts:add_to_profile(opts(State), Profile, KVs),
257    State#state_t{opts=Opts}.
258
259apply_profiles(State, Profile) when not is_list(Profile) ->
260    apply_profiles(State, [Profile]);
261apply_profiles(State, [default]) ->
262    State;
263apply_profiles(State=#state_t{default = Defaults, current_profiles=CurrentProfiles}, Profiles) ->
264    ProvidedProfiles = lists:prefix([default|Profiles], CurrentProfiles),
265    AppliedProfiles = case Profiles of
266                          %% Head of list global profile is special, only for use by rebar3
267                          %% It does not clash if a user does `rebar3 as global...` but when
268                          %% it is the head we must make sure not to prepend `default`
269                          [global | _] ->
270                              Profiles;
271                          _ when ProvidedProfiles ->
272                              deduplicate(CurrentProfiles);
273                          _ ->
274                              deduplicate(CurrentProfiles ++ Profiles)
275                      end,
276
277    ConfigProfiles = rebar_state:get(State, profiles, []),
278
279    NewOpts =
280        lists:foldl(fun(default, OptsAcc) ->
281                            OptsAcc;
282                       (Profile, OptsAcc) ->
283                            case proplists:get_value(Profile, ConfigProfiles, []) of
284                                OptsList when is_list(OptsList) ->
285                                    ProfileOpts = dict:from_list(OptsList),
286                                    rebar_opts:merge_opts(Profile, ProfileOpts, OptsAcc);
287                                Other ->
288                                    throw(?PRV_ERROR({profile_not_list, Profile, Other}))
289                            end
290                    end, Defaults, AppliedProfiles),
291    State#state_t{current_profiles = AppliedProfiles, opts=NewOpts}.
292
293deduplicate(Profiles) ->
294    do_deduplicate(lists:reverse(Profiles), []).
295
296do_deduplicate([], Acc) ->
297    Acc;
298do_deduplicate([Head | Rest], Acc) ->
299    case lists:member(Head, Acc) of
300        true -> do_deduplicate(Rest, Acc);
301        false -> do_deduplicate(Rest, [Head | Acc])
302    end.
303
304dir(#state_t{dir=Dir}) ->
305    Dir.
306
307dir(State=#state_t{}, Dir) ->
308    State#state_t{dir=filename:absname(Dir)}.
309
310deps_names(Deps) when is_list(Deps) ->
311    lists:map(fun(Dep) when is_tuple(Dep) ->
312                      rebar_utils:to_binary(element(1, Dep));
313                 (Dep) when is_atom(Dep) ->
314                      rebar_utils:to_binary(Dep)
315              end, Deps);
316deps_names(State) ->
317    Deps = rebar_state:get(State, deps, []),
318    deps_names(Deps).
319
320current_app(#state_t{current_app=CurrentApp}) ->
321    CurrentApp.
322
323current_app(State=#state_t{}, CurrentApp) ->
324    State#state_t{current_app=CurrentApp}.
325
326project_apps(#state_t{project_apps=Apps}) ->
327    Apps.
328
329project_apps(State=#state_t{}, NewApps) when is_list(NewApps) ->
330    State#state_t{project_apps=NewApps};
331project_apps(State=#state_t{project_apps=Apps}, App) ->
332    State#state_t{project_apps=lists:keystore(rebar_app_info:name(App), 2, Apps, App)}.
333
334deps_to_build(#state_t{deps_to_build=Apps}) ->
335    Apps.
336
337deps_to_build(State=#state_t{deps_to_build=Apps}, NewApps) when is_list(NewApps) ->
338    State#state_t{deps_to_build=Apps++NewApps};
339deps_to_build(State=#state_t{deps_to_build=Apps}, App) ->
340    State#state_t{deps_to_build=lists:keystore(rebar_app_info:name(App), 2, Apps, App)}.
341
342all_deps(#state_t{all_deps=Apps}) ->
343    Apps.
344
345all_deps(State=#state_t{}, NewApps) ->
346    State#state_t{all_deps=NewApps}.
347
348all_checkout_deps(#state_t{all_deps=Apps}) ->
349    [App || App <- Apps, rebar_app_info:is_checkout(App)].
350
351all_plugin_deps(#state_t{all_plugin_deps=Apps}) ->
352    Apps.
353
354all_plugin_deps(State=#state_t{}, NewApps) ->
355    State#state_t{all_plugin_deps=NewApps}.
356
357update_all_plugin_deps(State=#state_t{all_plugin_deps=Apps}, NewApps) ->
358    State#state_t{all_plugin_deps=Apps++NewApps}.
359
360update_all_deps(State=#state_t{all_deps=Apps}, NewApps) ->
361    State#state_t{all_deps=Apps++NewApps}.
362
363merge_all_deps(State=#state_t{all_deps=Apps}, UpdatedApps) when is_list(UpdatedApps) ->
364    State#state_t{all_deps=lists:ukeymerge(2, lists:keysort(2, UpdatedApps), lists:keysort(2, Apps))}.
365
366namespace(#state_t{namespace=Namespace}) ->
367    Namespace.
368
369namespace(State=#state_t{}, Namespace) ->
370    State#state_t{namespace=Namespace}.
371
372-spec resources(t()) -> [{rebar_resource_v2:type(), module()}].
373resources(#state_t{resources=Resources}) ->
374    Resources.
375
376-spec set_resources(t(), [{rebar_resource_v2:type(), module()}]) -> t().
377set_resources(State, Resources) ->
378    State#state_t{resources=Resources}.
379
380-spec resources(t(), [{rebar_resource_v2:type(), module()}]) -> t().
381resources(State, NewResources) ->
382    lists:foldl(fun(Resource, StateAcc) ->
383                        add_resource(StateAcc, Resource)
384                end, State, NewResources).
385
386-spec add_resource(t(), {rebar_resource_v2:type(), module()}) -> t().
387add_resource(State=#state_t{resources=Resources}, {ResourceType, ResourceModule}) ->
388    _ = code:ensure_loaded(ResourceModule),
389    Resource = case erlang:function_exported(ResourceModule, init, 2) of
390                   true ->
391                       case ResourceModule:init(ResourceType, State) of
392                           {ok, R=#resource{}} ->
393                               R;
394                           _ ->
395                               %% init didn't return a resource
396                               %% must be an old resource
397                               warn_old_resource(ResourceModule),
398                               rebar_resource:new(ResourceType,
399                                                  ResourceModule,
400                                                  #{})
401                       end;
402                   false ->
403                       %% no init, must be initial implementation
404                       warn_old_resource(ResourceModule),
405                       rebar_resource:new(ResourceType,
406                                          ResourceModule,
407                                          #{})
408               end,
409    State#state_t{resources=[Resource | Resources]}.
410
411warn_old_resource(ResourceModule) ->
412    ?WARN("Using custom resource ~s that implements a deprecated api. "
413          "It should be upgraded to rebar_resource_v2.", [ResourceModule]).
414
415%% @doc get a list of all registered compiler modules, which should implement
416%% the `rebar_compiler' behaviour
417-spec compilers(t()) -> [module()].
418compilers(#state_t{compilers=Compilers}) ->
419    Compilers.
420
421%% @doc register compiler modules prior to the existing ones. Each compiler
422%% module should implement the `rebar_compiler' behaviour. Use this when
423%% your custom compiler generates .erl files (or files of another type) and
424%% that should run before other compiler modules.
425-spec prepend_compilers(t(), [module()]) -> t().
426prepend_compilers(State=#state_t{compilers=Compilers}, NewCompilers) ->
427    State#state_t{compilers=NewCompilers++Compilers}.
428
429%% @doc register compiler modules. Each compiler
430%% module should implement the `rebar_compiler' behaviour. Use this when
431%% your custom compiler generates binary artifacts and does not have
432%% a particular need to run before any other compiler.
433-spec append_compilers(t(), [module()]) -> t().
434append_compilers(State=#state_t{compilers=Compilers}, NewCompilers) ->
435    State#state_t{compilers=Compilers++NewCompilers}.
436
437%% @private reset all compiler modules by replacing them by a list of
438%% modules that implement the `rebar_compiler' behaviour.
439-spec compilers(t(), [module()]) -> t().
440compilers(State, Compilers) ->
441    State#state_t{compilers=Compilers}.
442
443project_builders(#state_t{project_builders=ProjectBuilders}) ->
444    ProjectBuilders.
445
446add_project_builder(State=#state_t{project_builders=ProjectBuilders}, Type, Module) ->
447    _ = code:ensure_loaded(Module),
448    case erlang:function_exported(Module, build, 1) of
449        true ->
450            State#state_t{project_builders=[{Type, Module} | ProjectBuilders]};
451        false ->
452            ?WARN("Unable to add project builder for type ~s, required function ~s:build/1 not found.",
453                  [Type, Module]),
454            State
455    end.
456
457create_resources(Resources, State) ->
458    lists:foldl(fun(R, StateAcc) ->
459                        add_resource(StateAcc, R)
460                end, State, Resources).
461
462providers(#state_t{providers=Providers}) ->
463    Providers.
464
465providers(State, NewProviders) ->
466    State#state_t{providers=NewProviders}.
467
468allow_provider_overrides(#state_t{allow_provider_overrides=Allow}) ->
469    Allow.
470
471allow_provider_overrides(State, Allow) ->
472    State#state_t{allow_provider_overrides=Allow}.
473
474-spec add_provider(t(), providers:t()) -> t().
475add_provider(State=#state_t{providers=Providers, allow_provider_overrides=true}, Provider) ->
476    State#state_t{providers=[Provider | Providers]};
477add_provider(State=#state_t{providers=Providers, allow_provider_overrides=false}, Provider) ->
478    Name = providers:impl(Provider),
479    Namespace = providers:namespace(Provider),
480    Module = providers:module(Provider),
481    case lists:any(fun(P) ->
482                           case {providers:impl(P), providers:namespace(P)} of
483                               {Name, Namespace} ->
484                                   ?DEBUG("Not adding provider ~p ~p from module ~p because it already exists from module ~p",
485                                          [Namespace, Name, Module, providers:module(P)]),
486                                   true;
487                               _ ->
488                                   false
489                           end
490                   end, Providers) of
491        true ->
492            State;
493        false ->
494            State#state_t{providers=[Provider | Providers]}
495    end.
496
497-spec default_hex_repo_url_override(t()) -> binary().
498default_hex_repo_url_override(State) ->
499    CDN = rebar_state:get(State, rebar_packages_cdn, ?DEFAULT_CDN),
500    rebar_utils:to_binary(CDN).
501
502-dialyzer({no_match, create_logic_providers/2}). % we want to be permissive with providers:new/2
503create_logic_providers(ProviderModules, State0) ->
504    try
505        lists:foldl(fun(ProviderMod, StateAcc) ->
506                            case providers:new(ProviderMod, StateAcc) of
507                                {error, {Mod, Error}} ->
508                                    ?WARN("~ts", [Mod:format_error(Error)]),
509                                    StateAcc;
510                                {error, Reason} ->
511                                    ?WARN(Reason++"~n", []),
512                                    StateAcc;
513                                {ok, StateAcc1} ->
514                                    StateAcc1
515                            end
516                    end, State0, ProviderModules)
517    catch
518        ?WITH_STACKTRACE(C,T,S)
519            ?DEBUG("~p: ~p ~p", [C, T, S]),
520            ?CRASHDUMP("~p: ~p~n~p~n~n~p", [C, T, S, State0]),
521            throw({error, "Failed creating providers. Run with DIAGNOSTIC=1 for stacktrace or consult rebar3.crashdump."})
522    end.
523
524to_list(#state_t{} = State) ->
525    Fields = record_info(fields, state_t),
526    Values = tl(tuple_to_list(State)),
527    lists:zip(Fields, [reformat(I) || I <- Values]).
528
529reformat({K,V}) when is_list(V) ->
530    {K, [reformat(I) || I <- V]};
531reformat({K,V}) ->
532    try
533        {K, [reformat(I) || I <- dict:to_list(V)]}
534    catch
535        error:{badrecord,dict} ->
536            {K,V}
537    end;
538reformat(V) ->
539    try
540        [reformat(I) || I <- dict:to_list(V)]
541    catch
542        error:{badrecord,dict} ->
543            V
544    end.
545
546%% ===================================================================
547%% Internal functions
548%% ===================================================================
549