您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

612 行
23 KiB

  1. -module(rebar_app_info).
  2. -export([new/0,
  3. new/1,
  4. new/2,
  5. new/3,
  6. new/4,
  7. new/5,
  8. update_opts/3,
  9. update_opts_deps/2,
  10. discover/1,
  11. name/1,
  12. name/2,
  13. app_file_src/1,
  14. app_file_src/2,
  15. app_file_src_script/1,
  16. app_file_src_script/2,
  17. app_file/1,
  18. app_file/2,
  19. app_details/1,
  20. app_details/2,
  21. parent/1,
  22. parent/2,
  23. original_vsn/1,
  24. original_vsn/2,
  25. ebin_dir/1,
  26. priv_dir/1,
  27. applications/1,
  28. applications/2,
  29. profiles/1,
  30. profiles/2,
  31. deps/1,
  32. deps/2,
  33. dep_level/1,
  34. dep_level/2,
  35. dir/1,
  36. dir/2,
  37. out_dir/1,
  38. out_dir/2,
  39. default/1,
  40. default/2,
  41. opts/1,
  42. opts/2,
  43. get/2,
  44. get/3,
  45. set/3,
  46. source/1,
  47. source/2,
  48. is_lock/1,
  49. is_lock/2,
  50. is_checkout/1,
  51. is_checkout/2,
  52. valid/1,
  53. valid/2,
  54. is_available/1,
  55. is_available/2,
  56. verify_otp_vsn/1,
  57. has_all_artifacts/1,
  58. apply_overrides/2,
  59. add_to_profile/3,
  60. apply_profiles/2,
  61. deduplicate/1,
  62. do_deduplicate/2]).
  63. -include("rebar.hrl").
  64. -include_lib("providers/include/providers.hrl").
  65. -export_type([t/0]).
  66. -record(app_info_t, {name :: binary() | undefined,
  67. app_file_src :: file:filename_all() | undefined,
  68. app_file_src_script:: file:filename_all() | undefined,
  69. app_file :: file:filename_all() | undefined,
  70. original_vsn :: binary() | undefined,
  71. parent=root :: binary() | root,
  72. app_details=[] :: list(),
  73. applications=[] :: list(),
  74. deps=[] :: list(),
  75. profiles=[default] :: [atom()],
  76. default=dict:new() :: rebar_dict(),
  77. opts=dict:new() :: rebar_dict(),
  78. dep_level=0 :: integer(),
  79. dir :: file:name(),
  80. out_dir :: file:name(),
  81. source :: string() | tuple() | checkout | undefined,
  82. is_lock=false :: boolean(),
  83. is_checkout=false :: boolean(),
  84. valid :: boolean() | undefined,
  85. is_available=false :: boolean()}).
  86. %%============================================================================
  87. %% types
  88. %%============================================================================
  89. -type t() :: #app_info_t{}.
  90. %%============================================================================
  91. %% API
  92. %% ============================================================================
  93. %% @doc Build a new, empty, app info value. This is not of a lot of use and you
  94. %% probably wont be doing this much.
  95. -spec new() -> t().
  96. new() ->
  97. #app_info_t{}.
  98. %% @doc Build a new app info value with only the app name set.
  99. -spec new(atom() | binary() | string()) ->
  100. {ok, t()}.
  101. new(AppName) ->
  102. {ok, #app_info_t{name=rebar_utils:to_binary(AppName)}}.
  103. %% @doc Build a new app info value with only the name and version set.
  104. -spec new(atom() | binary() | string(), binary() | string()) ->
  105. {ok, t()}.
  106. new(AppName, Vsn) ->
  107. {ok, #app_info_t{name=rebar_utils:to_binary(AppName),
  108. original_vsn=rebar_utils:to_binary(Vsn)}}.
  109. %% @doc build a complete version of the app info with all fields set.
  110. -spec new(atom() | binary() | string(), binary() | string(), file:name()) ->
  111. {ok, t()}.
  112. new(AppName, Vsn, Dir) ->
  113. {ok, #app_info_t{name=rebar_utils:to_binary(AppName),
  114. original_vsn=rebar_utils:to_binary(Vsn),
  115. dir=rebar_utils:to_list(Dir),
  116. out_dir=rebar_utils:to_list(Dir)}}.
  117. %% @doc build a complete version of the app info with all fields set.
  118. -spec new(atom() | binary() | string(), binary() | string(), file:name(), list()) ->
  119. {ok, t()}.
  120. new(AppName, Vsn, Dir, Deps) ->
  121. {ok, #app_info_t{name=rebar_utils:to_binary(AppName),
  122. original_vsn=rebar_utils:to_binary(Vsn),
  123. dir=rebar_utils:to_list(Dir),
  124. out_dir=rebar_utils:to_list(Dir),
  125. deps=Deps}}.
  126. %% @doc build a complete version of the app info with all fields set.
  127. -spec new(atom() | binary(), atom() | binary() | string(), binary() | string(), file:name(), list()) ->
  128. {ok, t()}.
  129. new(Parent, AppName, Vsn, Dir, Deps) ->
  130. {ok, #app_info_t{name=rebar_utils:to_binary(AppName),
  131. parent=Parent,
  132. original_vsn=rebar_utils:to_binary(Vsn),
  133. dir=rebar_utils:to_list(Dir),
  134. out_dir=rebar_utils:to_list(Dir),
  135. deps=Deps}}.
  136. %% @doc update the opts based on the contents of a config
  137. %% file for the app
  138. -spec update_opts(t(), rebar_dict(), [any()]) -> t().
  139. update_opts(AppInfo, Opts, Config) ->
  140. LockDeps = case source(AppInfo) of
  141. Tuple when is_tuple(Tuple) andalso element(1, Tuple) =:= pkg ->
  142. %% Deps are set separate for packages
  143. %% instead of making it seem we have no deps
  144. %% don't set anything here.
  145. [];
  146. _ ->
  147. deps_from_config(dir(AppInfo), Config)
  148. end,
  149. Plugins = proplists:get_value(plugins, Config, []),
  150. Terms = LockDeps++[{{plugins, default}, Plugins} | Config],
  151. true = rebar_config:verify_config_format(Terms),
  152. LocalOpts = dict:from_list(Terms),
  153. NewOpts = rebar_opts:merge_opts(LocalOpts, Opts),
  154. AppInfo#app_info_t{opts=NewOpts,
  155. default=NewOpts}.
  156. %% @doc update the opts based on new deps, usually from an app's hex registry metadata
  157. -spec update_opts_deps(t(), [any()]) -> t().
  158. update_opts_deps(AppInfo=#app_info_t{opts=Opts}, Deps) ->
  159. LocalOpts = dict:from_list([{{locks, default}, Deps}, {{deps, default}, Deps}]),
  160. NewOpts = rebar_opts:merge_opts(LocalOpts, Opts),
  161. AppInfo#app_info_t{opts=NewOpts,
  162. default=NewOpts,
  163. deps=Deps}.
  164. %% @private extract the deps for an app in `Dir' based on its config file data
  165. -spec deps_from_config(file:filename(), [any()]) -> [{tuple(), any()}, ...].
  166. deps_from_config(Dir, Config) ->
  167. case rebar_config:consult_lock_file(filename:join(Dir, ?LOCK_FILE)) of
  168. [] ->
  169. [{{deps, default}, proplists:get_value(deps, Config, [])}];
  170. D ->
  171. %% We want the top level deps only from the lock file.
  172. %% This ensures deterministic overrides for configs.
  173. Deps = [X || X <- D, element(3, X) =:= 0],
  174. [{{locks, default}, D}, {{deps, default}, Deps}]
  175. end.
  176. %% @doc discover a complete version of the app info with all fields set.
  177. -spec discover(file:filename_all()) -> {ok, t()} | not_found.
  178. discover(Dir) ->
  179. case rebar_app_discover:find_app(Dir, all) of
  180. {true, AppInfo} ->
  181. {ok, AppInfo};
  182. false ->
  183. not_found
  184. end.
  185. %% @doc get the name of the app.
  186. -spec name(t()) -> binary().
  187. name(#app_info_t{name=Name}) ->
  188. Name.
  189. %% @doc set the name of the app.
  190. -spec name(t(), atom() | binary() | string()) -> t().
  191. name(AppInfo=#app_info_t{}, AppName) ->
  192. AppInfo#app_info_t{name=rebar_utils:to_binary(AppName)}.
  193. %% @doc get the dictionary of options for the app.
  194. -spec opts(t()) -> rebar_dict().
  195. opts(#app_info_t{opts=Opts}) ->
  196. Opts.
  197. %% @doc set the dictionary of options for the app.
  198. -spec opts(t(), rebar_dict()) -> t().
  199. opts(AppInfo, Opts) ->
  200. AppInfo#app_info_t{opts=Opts}.
  201. %% @doc get the dictionary of options under the default profile.
  202. %% Represents a root set prior to applying other profiles.
  203. -spec default(t()) -> rebar_dict().
  204. default(#app_info_t{default=Default}) ->
  205. Default.
  206. %% @doc set the dictionary of options under the default profile.
  207. %% Useful when re-applying profile.
  208. -spec default(t(), rebar_dict()) -> t().
  209. default(AppInfo, Default) ->
  210. AppInfo#app_info_t{default=Default}.
  211. %% @doc look up a value in the dictionary of options; fails if
  212. %% the key for it does not exist.
  213. -spec get(t(), term()) -> term().
  214. get(AppInfo, Key) ->
  215. {ok, Value} = dict:find(Key, AppInfo#app_info_t.opts),
  216. Value.
  217. %% @doc look up a value in the dictionary of options; returns
  218. %% a `Default' value otherwise.
  219. -spec get(t(), term(), term()) -> term().
  220. get(AppInfo, Key, Default) ->
  221. case dict:find(Key, AppInfo#app_info_t.opts) of
  222. {ok, Value} ->
  223. Value;
  224. error ->
  225. Default
  226. end.
  227. %% @doc sets a given value in the dictionary of options for the app.
  228. -spec set(t(), any(), any()) -> t().
  229. set(AppInfo=#app_info_t{opts=Opts}, Key, Value) ->
  230. AppInfo#app_info_t{opts = dict:store(Key, Value, Opts)}.
  231. %% @doc finds the .app.src file for an app, if any.
  232. -spec app_file_src(t()) -> file:filename_all() | undefined.
  233. app_file_src(#app_info_t{app_file_src=undefined, dir=Dir, name=Name, opts=Opts}) ->
  234. CandidatePaths = [filename:join([rebar_utils:to_list(Dir), Src, rebar_utils:to_list(Name)++".app.src"])
  235. || Src <- rebar_opts:get(Opts, src_dirs, ["src"])],
  236. case lists:dropwhile(fun(Path) -> not filelib:is_file(Path) end, CandidatePaths) of
  237. [] -> undefined;
  238. [AppFileSrc|_] -> AppFileSrc
  239. end;
  240. app_file_src(#app_info_t{app_file_src=AppFileSrc}) ->
  241. rebar_utils:to_list(AppFileSrc).
  242. %% @doc sets the .app.src file for an app. An app without such a file
  243. %% can explicitly be set with `undefined'.
  244. -spec app_file_src(t(), file:filename_all() | undefined) -> t().
  245. app_file_src(AppInfo=#app_info_t{}, undefined) ->
  246. AppInfo#app_info_t{app_file_src=undefined};
  247. app_file_src(AppInfo=#app_info_t{}, AppFileSrc) ->
  248. AppInfo#app_info_t{app_file_src=rebar_utils:to_list(AppFileSrc)}.
  249. %% @doc finds the .app.src.script file for an app, if any.
  250. -spec app_file_src_script(t()) -> file:filename_all() | undefined.
  251. app_file_src_script(#app_info_t{app_file_src_script=undefined, dir=Dir, name=Name}) ->
  252. AppFileSrcScript = filename:join([rebar_utils:to_list(Dir), "src", rebar_utils:to_list(Name)++".app.src.script"]),
  253. case filelib:is_file(AppFileSrcScript) of
  254. true ->
  255. AppFileSrcScript;
  256. false ->
  257. undefined
  258. end;
  259. app_file_src_script(#app_info_t{app_file_src_script=AppFileSrcScript}) ->
  260. rebar_utils:to_list(AppFileSrcScript).
  261. %% @doc sets the .app.src.script file for an app. An app without such a file
  262. %% can explicitly be set with `undefined'.
  263. -spec app_file_src_script(t(), file:filename_all()) -> t().
  264. app_file_src_script(AppInfo=#app_info_t{}, undefined) ->
  265. AppInfo#app_info_t{app_file_src_script=undefined};
  266. app_file_src_script(AppInfo=#app_info_t{}, AppFileSrcScript) ->
  267. AppInfo#app_info_t{app_file_src_script=rebar_utils:to_list(AppFileSrcScript)}.
  268. %% @doc finds the .app file for an app, if any.
  269. -spec app_file(t()) -> file:filename_all() | undefined.
  270. app_file(#app_info_t{app_file=undefined, out_dir=Dir, name=Name}) ->
  271. AppFile = filename:join([rebar_utils:to_list(Dir), "ebin", rebar_utils:to_list(Name)++".app"]),
  272. case filelib:is_file(AppFile) of
  273. true ->
  274. AppFile;
  275. false ->
  276. undefined
  277. end;
  278. app_file(#app_info_t{app_file=AppFile}) ->
  279. AppFile.
  280. %% @doc sets the .app file for an app.
  281. -spec app_file(t(), file:filename_all() | undefined) -> t().
  282. app_file(AppInfo=#app_info_t{}, AppFile) ->
  283. AppInfo#app_info_t{app_file=AppFile}.
  284. %% @doc returns the information stored in the app's app file,
  285. %% or if none, from the .app.src file.
  286. -spec app_details(t()) -> list().
  287. app_details(AppInfo=#app_info_t{app_details=[]}) ->
  288. case app_file(AppInfo) of
  289. undefined ->
  290. case rebar_config:consult_app_file(app_file_src(AppInfo)) of
  291. [] -> [];
  292. [{application, _Name, AppDetails}] -> AppDetails
  293. end;
  294. AppFile ->
  295. try rebar_file_utils:try_consult(AppFile) of
  296. [] -> [];
  297. [{application, _Name, AppDetails}] -> AppDetails
  298. catch
  299. throw:{error, {Module, Reason}} ->
  300. ?DEBUG("Warning, falling back to .app.src because of: ~ts",
  301. [Module:format_error(Reason)]),
  302. case rebar_config:consult_app_file(app_file_src(AppInfo)) of
  303. [] -> [];
  304. [{application, _Name, AppDetails}] -> AppDetails
  305. end
  306. end
  307. end;
  308. app_details(#app_info_t{app_details=AppDetails}) ->
  309. AppDetails.
  310. %% @doc stores the information that would be returned from the
  311. %% app file, when reading from `app_details/1'.
  312. -spec app_details(t(), list()) -> t().
  313. app_details(AppInfo=#app_info_t{}, AppDetails) ->
  314. AppInfo#app_info_t{app_details=AppDetails}.
  315. %% @doc returns the app's parent in the dep tree.
  316. -spec parent(t()) -> root | binary().
  317. parent(#app_info_t{parent=Parent}) ->
  318. Parent.
  319. %% @doc sets the app's parent.
  320. -spec parent(t(), binary() | root) -> t().
  321. parent(AppInfo=#app_info_t{}, Parent) ->
  322. AppInfo#app_info_t{parent=Parent}.
  323. %% @doc returns the original version of the app (unevaluated if
  324. %% asking for a semver)
  325. -spec original_vsn(t()) -> binary().
  326. original_vsn(#app_info_t{original_vsn=Vsn}) ->
  327. Vsn.
  328. %% @doc stores the original version of the app (unevaluated if
  329. %% asking for a semver)
  330. -spec original_vsn(t(), binary() | string()) -> t().
  331. original_vsn(AppInfo=#app_info_t{}, Vsn) ->
  332. AppInfo#app_info_t{original_vsn=rebar_utils:to_binary(Vsn)}.
  333. %% @doc returns the list of applications the app depends on.
  334. -spec applications(t()) -> list().
  335. applications(#app_info_t{applications=Applications}) ->
  336. Applications.
  337. %% @doc sets the list of applications the app depends on.
  338. %% Should be obtained from the app file.
  339. -spec applications(t(), list()) -> t().
  340. applications(AppInfo=#app_info_t{}, Applications) ->
  341. AppInfo#app_info_t{applications=Applications}.
  342. %% @doc returns the list of active profiles
  343. -spec profiles(t()) -> list().
  344. profiles(#app_info_t{profiles=Profiles}) ->
  345. Profiles.
  346. %% @doc sets the list of active profiles
  347. -spec profiles(t(), list()) -> t().
  348. profiles(AppInfo=#app_info_t{}, Profiles) ->
  349. AppInfo#app_info_t{profiles=Profiles}.
  350. %% @doc returns the list of dependencies
  351. -spec deps(t()) -> list().
  352. deps(#app_info_t{deps=Deps}) ->
  353. Deps.
  354. %% @doc sets the list of dependencies.
  355. -spec deps(t(), list()) -> t().
  356. deps(AppInfo=#app_info_t{}, Deps) ->
  357. AppInfo#app_info_t{deps=Deps}.
  358. %% @doc returns the level the app has in the lock files or in the
  359. %% dep tree.
  360. -spec dep_level(t()) -> non_neg_integer().
  361. dep_level(#app_info_t{dep_level=Level}) ->
  362. Level.
  363. %% @doc sets the level the app has in the lock files or in the
  364. %% dep tree.
  365. -spec dep_level(t(), non_neg_integer()) -> t().
  366. dep_level(AppInfo=#app_info_t{}, Level) ->
  367. AppInfo#app_info_t{dep_level=Level}.
  368. %% @doc returns the directory that contains the app.
  369. -spec dir(t()) -> file:name().
  370. dir(#app_info_t{dir=Dir}) ->
  371. Dir.
  372. %% @doc sets the directory that contains the app.
  373. -spec dir(t(), file:name()) -> t().
  374. dir(AppInfo=#app_info_t{out_dir=undefined}, Dir) ->
  375. AppInfo#app_info_t{dir=rebar_utils:to_list(Dir),
  376. out_dir=rebar_utils:to_list(Dir)};
  377. dir(AppInfo=#app_info_t{}, Dir) ->
  378. AppInfo#app_info_t{dir=rebar_utils:to_list(Dir)}.
  379. %% @doc returns the directory where build artifacts for the app
  380. %% should go
  381. -spec out_dir(t()) -> file:name().
  382. out_dir(#app_info_t{out_dir=OutDir}) ->
  383. OutDir.
  384. %% @doc sets the directory where build artifacts for the app
  385. %% should go
  386. -spec out_dir(t(), file:name()) -> t().
  387. out_dir(AppInfo=#app_info_t{}, OutDir) ->
  388. AppInfo#app_info_t{out_dir=rebar_utils:to_list(OutDir)}.
  389. %% @doc gets the directory where ebin files for the app should go
  390. -spec ebin_dir(t()) -> file:name().
  391. ebin_dir(#app_info_t{out_dir=OutDir}) ->
  392. rebar_utils:to_list(filename:join(OutDir, "ebin")).
  393. %% @doc gets the directory where private files for the app should go
  394. -spec priv_dir(t()) -> file:name().
  395. priv_dir(#app_info_t{out_dir=OutDir}) ->
  396. rebar_utils:to_list(filename:join(OutDir, "priv")).
  397. %% @doc finds the source specification for the app
  398. -spec source(t()) -> string() | tuple().
  399. source(#app_info_t{source=Source}) ->
  400. Source.
  401. %% @doc sets the source specification for the app
  402. -spec source(t(), string() | tuple() | checkout) -> t().
  403. source(AppInfo=#app_info_t{}, Source) ->
  404. AppInfo#app_info_t{source=Source}.
  405. %% @doc returns the lock status for the app
  406. -spec is_lock(t()) -> boolean().
  407. is_lock(#app_info_t{is_lock=IsLock}) ->
  408. IsLock.
  409. %% @doc sets the lock status for the app
  410. -spec is_lock(t(), boolean()) -> t().
  411. is_lock(AppInfo=#app_info_t{}, IsLock) ->
  412. AppInfo#app_info_t{is_lock=IsLock}.
  413. %% @doc returns whether the app is a checkout app or not
  414. -spec is_checkout(t()) -> boolean().
  415. is_checkout(#app_info_t{is_checkout=IsCheckout}) ->
  416. IsCheckout.
  417. %% @doc sets whether the app is a checkout app or not
  418. -spec is_checkout(t(), boolean()) -> t().
  419. is_checkout(AppInfo=#app_info_t{}, IsCheckout) ->
  420. AppInfo#app_info_t{is_checkout=IsCheckout}.
  421. %% @doc returns whether the app source exists in the deps dir
  422. -spec is_available(t()) -> boolean().
  423. is_available(#app_info_t{is_available=IsAvailable}) ->
  424. IsAvailable.
  425. %% @doc sets whether the app's source is available
  426. %% only set if the app's source is found in the expected dep directory
  427. -spec is_available(t(), boolean()) -> t().
  428. is_available(AppInfo=#app_info_t{}, IsAvailable) ->
  429. AppInfo#app_info_t{is_available=IsAvailable}.
  430. %% @doc returns whether the app is valid (built) or not
  431. -spec valid(t()) -> boolean().
  432. valid(AppInfo=#app_info_t{valid=undefined}) ->
  433. case rebar_app_utils:validate_application_info(AppInfo) =:= true
  434. andalso has_all_artifacts(AppInfo) =:= true of
  435. true ->
  436. true;
  437. _ ->
  438. false
  439. end;
  440. valid(#app_info_t{valid=Valid}) ->
  441. Valid.
  442. %% @doc sets whether the app is valid (built) or not. If left unset,
  443. %% rebar3 will do the detection of the status itself.
  444. -spec valid(t(), boolean()) -> t().
  445. valid(AppInfo=#app_info_t{}, Valid) ->
  446. AppInfo#app_info_t{valid=Valid}.
  447. %% @doc checks whether the app can be built with the current
  448. %% Erlang/OTP version. If the check fails, the function raises
  449. %% an exception and displays an error.
  450. -spec verify_otp_vsn(t()) -> ok | no_return().
  451. verify_otp_vsn(AppInfo) ->
  452. rebar_utils:check_min_otp_version(rebar_app_info:get(AppInfo, minimum_otp_vsn, undefined)),
  453. rebar_utils:check_blacklisted_otp_versions(rebar_app_info:get(AppInfo, blacklisted_otp_vsns, [])).
  454. %% @doc checks whether all the build artifacts for an app to be considered
  455. %% valid are present.
  456. -spec has_all_artifacts(#app_info_t{}) -> true | {false, file:filename()}.
  457. has_all_artifacts(AppInfo) ->
  458. Artifacts = rebar_app_info:get(AppInfo, artifacts, []),
  459. OutDir = out_dir(AppInfo),
  460. Context = [{base_dir, rebar_app_info:get(AppInfo, base_dir, ?DEFAULT_BASE_DIR)}
  461. ,{profile_dir, rebar_dir:profile_dir(opts(AppInfo), profiles(AppInfo))}
  462. ,{out_dir, OutDir}],
  463. all(OutDir, Context, Artifacts).
  464. %% @private checks that all files/artifacts in the directory are found.
  465. %% Template evaluation must happen and a bbmustache context needs to
  466. %% be provided.
  467. -spec all(file:filename(), term(), [string()]) -> true | {false, string()}.
  468. all(_, _, []) ->
  469. true;
  470. all(Dir, Context, [File|Artifacts]) ->
  471. FilePath = filename:join(Dir, rebar_templater:render(File, Context)),
  472. case filelib:is_regular(FilePath) of
  473. false ->
  474. ?DEBUG("Missing artifact ~ts", [FilePath]),
  475. {false, File};
  476. true ->
  477. all(Dir, Context, Artifacts)
  478. end.
  479. %%%%%
  480. %% @doc given a set of override rules, modify the app info accordingly
  481. -spec apply_overrides(list(), t()) -> t().
  482. apply_overrides(Overrides, AppInfo) ->
  483. Name = binary_to_atom(rebar_app_info:name(AppInfo), utf8),
  484. Opts = rebar_opts:apply_overrides(opts(AppInfo), Name, Overrides),
  485. AppInfo#app_info_t{default=Opts, opts=Opts}.
  486. %% @doc adds a new profile with its own config to the app data
  487. -spec add_to_profile(t(), atom(), [{_,_}]) -> t().
  488. add_to_profile(AppInfo, Profile, KVs) when is_atom(Profile), is_list(KVs) ->
  489. Opts = rebar_opts:add_to_profile(opts(AppInfo), Profile, KVs),
  490. AppInfo#app_info_t{opts=Opts}.
  491. %% @doc applies and merges the profile configuration in the specified order
  492. %% of profiles (or for a single profile) and returns an app info record
  493. %% with the resulting configuration
  494. -spec apply_profiles(t(), atom() | [atom(),...]) -> t().
  495. apply_profiles(AppInfo, Profile) when not is_list(Profile) ->
  496. apply_profiles(AppInfo, [Profile]);
  497. apply_profiles(AppInfo, [default]) ->
  498. AppInfo;
  499. apply_profiles(AppInfo=#app_info_t{default = Defaults, profiles=CurrentProfiles}, Profiles) ->
  500. AppliedProfiles = case Profiles of
  501. %% Head of list global profile is special, only for use by rebar3
  502. %% It does not clash if a user does `rebar3 as global...` but when
  503. %% it is the head we must make sure not to prepend `default`
  504. [global | _] ->
  505. Profiles;
  506. _ ->
  507. deduplicate(CurrentProfiles ++ Profiles)
  508. end,
  509. ConfigProfiles = rebar_app_info:get(AppInfo, profiles, []),
  510. NewOpts =
  511. lists:foldl(fun(default, OptsAcc) ->
  512. OptsAcc;
  513. (Profile, OptsAcc) ->
  514. case proplists:get_value(Profile, ConfigProfiles, []) of
  515. OptsList when is_list(OptsList) ->
  516. ProfileOpts = dict:from_list(OptsList),
  517. rebar_opts:merge_opts(Profile, ProfileOpts, OptsAcc);
  518. Other ->
  519. throw(?PRV_ERROR({profile_not_list, Profile, Other}))
  520. end
  521. end, Defaults, AppliedProfiles),
  522. AppInfo#app_info_t{profiles = AppliedProfiles, opts=NewOpts}.
  523. %% @private drops duplicated profile definitions
  524. -spec deduplicate(list()) -> list().
  525. deduplicate(Profiles) ->
  526. do_deduplicate(lists:reverse(Profiles), []).
  527. %% @private drops duplicated profile definitions
  528. -spec do_deduplicate(list(), list()) -> list().
  529. do_deduplicate([], Acc) ->
  530. Acc;
  531. do_deduplicate([Head | Rest], Acc) ->
  532. case lists:member(Head, Acc) of
  533. true -> do_deduplicate(Rest, Acc);
  534. false -> do_deduplicate(Rest, [Head | Acc])
  535. end.