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