You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

477 lines
16 KiB

пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 9 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
  1. -module(rebar_state).
  2. -export([new/0, new/1, new/2, new/3,
  3. get/2, get/3, set/3,
  4. format_error/1,
  5. has_all_artifacts/1,
  6. code_paths/2, code_paths/3, update_code_paths/3,
  7. opts/1, opts/2,
  8. default/1, default/2,
  9. escript_path/1, escript_path/2,
  10. lock/1, lock/2,
  11. current_profiles/1, current_profiles/2,
  12. command_args/1, command_args/2,
  13. command_parsed_args/1, command_parsed_args/2,
  14. add_to_profile/3, apply_profiles/2,
  15. dir/1, dir/2,
  16. create_logic_providers/2,
  17. current_app/1, current_app/2,
  18. project_apps/1, project_apps/2,
  19. deps_to_build/1, deps_to_build/2,
  20. all_plugin_deps/1, all_plugin_deps/2, update_all_plugin_deps/2,
  21. all_deps/1, all_deps/2, update_all_deps/2,
  22. namespace/1, namespace/2,
  23. deps_names/1,
  24. to_list/1,
  25. create_resources/2,
  26. resources/1, resources/2, add_resource/2,
  27. providers/1, providers/2, add_provider/2,
  28. allow_provider_overrides/1, allow_provider_overrides/2
  29. ]).
  30. -include("rebar.hrl").
  31. -include_lib("providers/include/providers.hrl").
  32. -record(state_t, {dir :: file:name(),
  33. opts = dict:new() :: rebar_dict(),
  34. code_paths = dict:new() :: rebar_dict(),
  35. default = dict:new() :: rebar_dict(),
  36. escript_path :: undefined | file:filename_all(),
  37. lock = [],
  38. current_profiles = [default] :: [atom()],
  39. namespace = default :: atom(),
  40. command_args = [],
  41. command_parsed_args = {[], []},
  42. current_app :: undefined | rebar_app_info:t(),
  43. project_apps = [] :: [rebar_app_info:t()],
  44. deps_to_build = [] :: [rebar_app_info:t()],
  45. all_plugin_deps = [] :: [rebar_app_info:t()],
  46. all_deps = [] :: [rebar_app_info:t()],
  47. resources = [],
  48. providers = [],
  49. allow_provider_overrides = false :: boolean()}).
  50. -export_type([t/0]).
  51. -type t() :: #state_t{}.
  52. -spec new() -> t().
  53. new() ->
  54. BaseState = base_state(dict:new()),
  55. BaseState#state_t{dir = rebar_dir:get_cwd()}.
  56. -spec new(list()) -> t().
  57. new(Config) when is_list(Config) ->
  58. Opts = base_opts(Config),
  59. BaseState = base_state(Opts),
  60. BaseState#state_t{dir=rebar_dir:get_cwd(),
  61. default=Opts}.
  62. -spec new(t() | atom(), list()) -> t().
  63. new(Profile, Config) when is_atom(Profile)
  64. , is_list(Config) ->
  65. Opts = base_opts(Config),
  66. BaseState = base_state(Opts),
  67. BaseState#state_t{dir = rebar_dir:get_cwd(),
  68. current_profiles = [Profile],
  69. default = Opts};
  70. new(ParentState=#state_t{}, Config) ->
  71. %% Load terms from rebar.config, if it exists
  72. Dir = rebar_dir:get_cwd(),
  73. new(ParentState, Config, Dir).
  74. -spec new(t(), list(), file:filename_all()) -> t().
  75. new(ParentState, Config, Dir) ->
  76. new(ParentState, Config, deps_from_config(Dir, Config), Dir).
  77. new(ParentState, Config, Deps, Dir) ->
  78. Opts = ParentState#state_t.opts,
  79. Plugins = proplists:get_value(plugins, Config, []),
  80. ProjectPlugins = proplists:get_value(project_plugins, Config, []),
  81. Terms = Deps++[{{project_plugins, default}, ProjectPlugins}, {{plugins, default}, Plugins} | Config],
  82. true = rebar_config:verify_config_format(Terms),
  83. LocalOpts = dict:from_list(Terms),
  84. NewOpts = rebar_opts:merge_opts(LocalOpts, Opts),
  85. ParentState#state_t{dir=Dir
  86. ,opts=NewOpts
  87. ,default=NewOpts}.
  88. deps_from_config(Dir, Config) ->
  89. case rebar_config:consult_lock_file(filename:join(Dir, ?LOCK_FILE)) of
  90. [] ->
  91. [{{deps, default}, proplists:get_value(deps, Config, [])}];
  92. D ->
  93. %% We want the top level deps only from the lock file.
  94. %% This ensures deterministic overrides for configs.
  95. Deps = [X || X <- D, element(3, X) =:= 0],
  96. [{{locks, default}, D}, {{deps, default}, Deps}]
  97. end.
  98. base_state(Opts) ->
  99. #state_t{opts=Opts}.
  100. base_opts(Config) ->
  101. Deps = proplists:get_value(deps, Config, []),
  102. Plugins = proplists:get_value(plugins, Config, []),
  103. ProjectPlugins = proplists:get_value(project_plugins, Config, []),
  104. Terms = [{{deps, default}, Deps}, {{plugins, default}, Plugins},
  105. {{project_plugins, default}, ProjectPlugins} | Config],
  106. true = rebar_config:verify_config_format(Terms),
  107. dict:from_list(Terms).
  108. get(State, Key) ->
  109. {ok, Value} = dict:find(Key, State#state_t.opts),
  110. Value.
  111. get(State, Key, Default) ->
  112. case dict:find(Key, State#state_t.opts) of
  113. {ok, Value} ->
  114. Value;
  115. error ->
  116. Default
  117. end.
  118. -spec set(t(), any(), any()) -> t().
  119. set(State=#state_t{opts=Opts}, Key, Value) ->
  120. State#state_t{ opts = dict:store(Key, Value, Opts) }.
  121. default(#state_t{default=Opts}) ->
  122. Opts.
  123. default(State, Opts) ->
  124. State#state_t{default=Opts}.
  125. format_error({profile_not_list, Profile, Other}) ->
  126. io_lib:format("Profile config must be a list but for profile '~p' config given as:~n~p", [Profile, Other]).
  127. -spec has_all_artifacts(#state_t{}) -> true | {false, file:filename()}.
  128. has_all_artifacts(State) ->
  129. Artifacts = rebar_state:get(State, artifacts, []),
  130. Dir = rebar_dir:base_dir(State),
  131. all(Dir, Artifacts).
  132. all(_, []) ->
  133. true;
  134. all(Dir, [File|Artifacts]) ->
  135. case filelib:is_regular(filename:join(Dir, File)) of
  136. false ->
  137. ?DEBUG("Missing artifact ~ts", [filename:join(Dir, File)]),
  138. {false, File};
  139. true ->
  140. all(Dir, Artifacts)
  141. end.
  142. -spec code_paths(#state_t{}, atom()) -> [file:filename()].
  143. code_paths(#state_t{code_paths=CodePaths}, Key) ->
  144. case dict:find(Key, CodePaths) of
  145. {ok, CodePath} ->
  146. CodePath;
  147. _ ->
  148. []
  149. end.
  150. -spec code_paths(#state_t{}, atom(), [file:filename()]) -> #state_t{}.
  151. code_paths(State=#state_t{code_paths=CodePaths}, Key, CodePath) ->
  152. State#state_t{code_paths=dict:store(Key, CodePath, CodePaths)}.
  153. -spec update_code_paths(#state_t{}, atom(), [file:filename()]) -> #state_t{}.
  154. update_code_paths(State=#state_t{code_paths=CodePaths}, Key, CodePath) ->
  155. case dict:is_key(Key, CodePaths) of
  156. true ->
  157. State#state_t{code_paths=dict:append_list(Key, CodePath, CodePaths)};
  158. false ->
  159. State#state_t{code_paths=dict:store(Key, CodePath, CodePaths)}
  160. end.
  161. opts(#state_t{opts=Opts}) ->
  162. Opts.
  163. opts(State, Opts) ->
  164. State#state_t{opts=Opts}.
  165. current_profiles(#state_t{current_profiles=Profiles}) ->
  166. Profiles.
  167. current_profiles(State, Profiles) ->
  168. State#state_t{current_profiles=Profiles}.
  169. lock(#state_t{lock=Lock}) ->
  170. Lock.
  171. lock(State=#state_t{}, Apps) when is_list(Apps) ->
  172. State#state_t{lock=Apps};
  173. lock(State=#state_t{lock=Lock}, App) ->
  174. State#state_t{lock=[App | Lock]}.
  175. escript_path(#state_t{escript_path=EscriptPath}) ->
  176. EscriptPath.
  177. escript_path(State, EscriptPath) ->
  178. State#state_t{escript_path=EscriptPath}.
  179. command_args(#state_t{command_args=CmdArgs}) ->
  180. CmdArgs.
  181. command_args(State, CmdArgs) ->
  182. State#state_t{command_args=CmdArgs}.
  183. command_parsed_args(#state_t{command_parsed_args=CmdArgs}) ->
  184. CmdArgs.
  185. command_parsed_args(State, CmdArgs) ->
  186. State#state_t{command_parsed_args=CmdArgs}.
  187. add_to_profile(State, Profile, KVs) when is_atom(Profile), is_list(KVs) ->
  188. Opts = rebar_opts:add_to_profile(opts(State), Profile, KVs),
  189. State#state_t{opts=Opts}.
  190. apply_profiles(State, Profile) when not is_list(Profile) ->
  191. apply_profiles(State, [Profile]);
  192. apply_profiles(State, [default]) ->
  193. State;
  194. apply_profiles(State=#state_t{default = Defaults, current_profiles=CurrentProfiles}, Profiles) ->
  195. ProvidedProfiles = lists:prefix([default|Profiles], CurrentProfiles),
  196. AppliedProfiles = case Profiles of
  197. %% Head of list global profile is special, only for use by rebar3
  198. %% It does not clash if a user does `rebar3 as global...` but when
  199. %% it is the head we must make sure not to prepend `default`
  200. [global | _] ->
  201. Profiles;
  202. _ when ProvidedProfiles ->
  203. deduplicate(CurrentProfiles);
  204. _ ->
  205. deduplicate(CurrentProfiles ++ Profiles)
  206. end,
  207. ConfigProfiles = rebar_state:get(State, profiles, []),
  208. NewOpts =
  209. lists:foldl(fun(default, OptsAcc) ->
  210. OptsAcc;
  211. (Profile, OptsAcc) ->
  212. case proplists:get_value(Profile, ConfigProfiles, []) of
  213. OptsList when is_list(OptsList) ->
  214. ProfileOpts = dict:from_list(OptsList),
  215. rebar_opts:merge_opts(Profile, ProfileOpts, OptsAcc);
  216. Other ->
  217. throw(?PRV_ERROR({profile_not_list, Profile, Other}))
  218. end
  219. end, Defaults, AppliedProfiles),
  220. State#state_t{current_profiles = AppliedProfiles, opts=NewOpts}.
  221. deduplicate(Profiles) ->
  222. do_deduplicate(lists:reverse(Profiles), []).
  223. do_deduplicate([], Acc) ->
  224. Acc;
  225. do_deduplicate([Head | Rest], Acc) ->
  226. case lists:member(Head, Acc) of
  227. true -> do_deduplicate(Rest, Acc);
  228. false -> do_deduplicate(Rest, [Head | Acc])
  229. end.
  230. dir(#state_t{dir=Dir}) ->
  231. Dir.
  232. dir(State=#state_t{}, Dir) ->
  233. State#state_t{dir=filename:absname(Dir)}.
  234. deps_names(Deps) when is_list(Deps) ->
  235. lists:map(fun(Dep) when is_tuple(Dep) ->
  236. rebar_utils:to_binary(element(1, Dep));
  237. (Dep) when is_atom(Dep) ->
  238. rebar_utils:to_binary(Dep)
  239. end, Deps);
  240. deps_names(State) ->
  241. Deps = rebar_state:get(State, deps, []),
  242. deps_names(Deps).
  243. current_app(#state_t{current_app=CurrentApp}) ->
  244. CurrentApp.
  245. current_app(State=#state_t{}, CurrentApp) ->
  246. State#state_t{current_app=CurrentApp}.
  247. project_apps(#state_t{project_apps=Apps}) ->
  248. Apps.
  249. project_apps(State=#state_t{}, NewApps) when is_list(NewApps) ->
  250. State#state_t{project_apps=NewApps};
  251. project_apps(State=#state_t{project_apps=Apps}, App) ->
  252. State#state_t{project_apps=lists:keystore(rebar_app_info:name(App), 2, Apps, App)}.
  253. deps_to_build(#state_t{deps_to_build=Apps}) ->
  254. Apps.
  255. deps_to_build(State=#state_t{deps_to_build=Apps}, NewApps) when is_list(NewApps) ->
  256. State#state_t{deps_to_build=Apps++NewApps};
  257. deps_to_build(State=#state_t{deps_to_build=Apps}, App) ->
  258. State#state_t{deps_to_build=lists:keystore(rebar_app_info:name(App), 2, Apps, App)}.
  259. all_deps(#state_t{all_deps=Apps}) ->
  260. Apps.
  261. all_deps(State=#state_t{}, NewApps) ->
  262. State#state_t{all_deps=NewApps}.
  263. all_plugin_deps(#state_t{all_plugin_deps=Apps}) ->
  264. Apps.
  265. all_plugin_deps(State=#state_t{}, NewApps) ->
  266. State#state_t{all_plugin_deps=NewApps}.
  267. update_all_plugin_deps(State=#state_t{all_plugin_deps=Apps}, NewApps) ->
  268. State#state_t{all_plugin_deps=Apps++NewApps}.
  269. update_all_deps(State=#state_t{all_deps=Apps}, NewApps) ->
  270. State#state_t{all_deps=Apps++NewApps}.
  271. namespace(#state_t{namespace=Namespace}) ->
  272. Namespace.
  273. namespace(State=#state_t{}, Namespace) ->
  274. State#state_t{namespace=Namespace}.
  275. -spec resources(t()) -> [{rebar_resource_v2:type(), module()}].
  276. resources(#state_t{resources=Resources}) ->
  277. Resources.
  278. -spec resources(t(), [{rebar_resource_v2:type(), module()}]) -> t().
  279. resources(State, NewResources) ->
  280. lists:foldl(fun(Resource, StateAcc) ->
  281. add_resource(StateAcc, Resource)
  282. end, State, NewResources).
  283. -spec add_resource(t(), {rebar_resource_v2:type(), module()}) -> t().
  284. add_resource(State=#state_t{resources=Resources}, {ResourceType, ResourceModule}) ->
  285. _ = code:ensure_loaded(ResourceModule),
  286. Resource = case erlang:function_exported(ResourceModule, init, 2) of
  287. true ->
  288. case ResourceModule:init(ResourceType, State) of
  289. {ok, R=#resource{}} ->
  290. R;
  291. _ ->
  292. %% init didn't return a resource
  293. %% must be an old resource
  294. warn_old_resource(ResourceModule),
  295. rebar_resource:new(ResourceType,
  296. ResourceModule,
  297. #{})
  298. end;
  299. false ->
  300. %% no init, must be initial implementation
  301. warn_old_resource(ResourceModule),
  302. rebar_resource:new(ResourceType,
  303. ResourceModule,
  304. #{})
  305. end,
  306. State#state_t{resources=[Resource | Resources]}.
  307. warn_old_resource(ResourceModule) ->
  308. ?WARN("Using custom resource ~s that implements a deprecated api. "
  309. "It should be upgraded to rebar_resource_v2.", [ResourceModule]).
  310. create_resources(Resources, State) ->
  311. lists:foldl(fun(R, StateAcc) ->
  312. add_resource(StateAcc, R)
  313. end, State, Resources).
  314. providers(#state_t{providers=Providers}) ->
  315. Providers.
  316. providers(State, NewProviders) ->
  317. State#state_t{providers=NewProviders}.
  318. allow_provider_overrides(#state_t{allow_provider_overrides=Allow}) ->
  319. Allow.
  320. allow_provider_overrides(State, Allow) ->
  321. State#state_t{allow_provider_overrides=Allow}.
  322. -spec add_provider(t(), providers:t()) -> t().
  323. add_provider(State=#state_t{providers=Providers, allow_provider_overrides=true}, Provider) ->
  324. State#state_t{providers=[Provider | Providers]};
  325. add_provider(State=#state_t{providers=Providers, allow_provider_overrides=false}, Provider) ->
  326. Name = providers:impl(Provider),
  327. Namespace = providers:namespace(Provider),
  328. Module = providers:module(Provider),
  329. case lists:any(fun(P) ->
  330. case {providers:impl(P), providers:namespace(P)} of
  331. {Name, Namespace} ->
  332. ?DEBUG("Not adding provider ~p ~p from module ~p because it already exists from module ~p",
  333. [Namespace, Name, Module, providers:module(P)]),
  334. true;
  335. _ ->
  336. false
  337. end
  338. end, Providers) of
  339. true ->
  340. State;
  341. false ->
  342. State#state_t{providers=[Provider | Providers]}
  343. end.
  344. create_logic_providers(ProviderModules, State0) ->
  345. try
  346. lists:foldl(fun(ProviderMod, StateAcc) ->
  347. case providers:new(ProviderMod, StateAcc) of
  348. {error, Reason} ->
  349. ?ERROR(Reason++"~n", []),
  350. StateAcc;
  351. {ok, StateAcc1} ->
  352. StateAcc1
  353. end
  354. end, State0, ProviderModules)
  355. catch
  356. ?WITH_STACKTRACE(C,T,S)
  357. ?DEBUG("~p: ~p ~p", [C, T, S]),
  358. ?CRASHDUMP("~p: ~p~n~p~n~n~p", [C, T, S, State0]),
  359. throw({error, "Failed creating providers. Run with DEBUG=1 for stacktrace or consult rebar3.crashdump."})
  360. end.
  361. to_list(#state_t{} = State) ->
  362. Fields = record_info(fields, state_t),
  363. Values = tl(tuple_to_list(State)),
  364. lists:zip(Fields, [reformat(I) || I <- Values]).
  365. reformat({K,V}) when is_list(V) ->
  366. {K, [reformat(I) || I <- V]};
  367. reformat({K,V}) ->
  368. try
  369. {K, [reformat(I) || I <- dict:to_list(V)]}
  370. catch
  371. error:{badrecord,dict} ->
  372. {K,V}
  373. end;
  374. reformat(V) ->
  375. try
  376. [reformat(I) || I <- dict:to_list(V)]
  377. catch
  378. error:{badrecord,dict} ->
  379. V
  380. end.
  381. %% ===================================================================
  382. %% Internal functions
  383. %% ===================================================================