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.

612 lines
23 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  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.