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

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