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.

598 lines
22 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
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. 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.