25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

285 lines
13 KiB

10 년 전
9 년 전
9 년 전
9 년 전
9 년 전
9 년 전
10 년 전
10 년 전
  1. -module(rebar_app_discover).
  2. -export([do/2,
  3. format_error/1,
  4. find_unbuilt_apps/1,
  5. find_apps/1,
  6. find_apps/2,
  7. find_app/2,
  8. find_app/3]).
  9. -include("rebar.hrl").
  10. -include_lib("providers/include/providers.hrl").
  11. do(State, LibDirs) ->
  12. BaseDir = rebar_state:dir(State),
  13. Dirs = [filename:join(BaseDir, LibDir) || LibDir <- LibDirs],
  14. Apps = find_apps(Dirs, all),
  15. ProjectDeps = rebar_state:deps_names(State),
  16. DepsDir = rebar_dir:deps_dir(State),
  17. CurrentProfiles = rebar_state:current_profiles(State),
  18. %% There may be a top level src which is an app and there may not
  19. %% Find it here if there is, otherwise define the deps parent as root
  20. TopLevelApp = define_root_app(Apps, State),
  21. %% Handle top level deps
  22. State1 = lists:foldl(fun(Profile, StateAcc) ->
  23. ProfileDeps = rebar_state:get(StateAcc, {deps, Profile}, []),
  24. ProfileDeps2 = rebar_utils:tup_dedup(ProfileDeps),
  25. StateAcc1 = rebar_state:set(StateAcc, {deps, Profile}, ProfileDeps2),
  26. ParsedDeps = parse_profile_deps(Profile
  27. ,TopLevelApp
  28. ,ProfileDeps2
  29. ,rebar_state:opts(StateAcc1)
  30. ,StateAcc1),
  31. rebar_state:set(StateAcc1, {parsed_deps, Profile}, ParsedDeps)
  32. end, State, lists:reverse(CurrentProfiles)),
  33. %% Handle sub project apps deps
  34. %% Sort apps so we get the same merged deps config everytime
  35. SortedApps = rebar_utils:sort_deps(Apps),
  36. lists:foldl(fun(AppInfo, StateAcc) ->
  37. Name = rebar_app_info:name(AppInfo),
  38. case enable(State, AppInfo) of
  39. true ->
  40. {AppInfo1, StateAcc1} = merge_deps(AppInfo, StateAcc),
  41. OutDir = filename:join(DepsDir, Name),
  42. AppInfo2 = rebar_app_info:out_dir(AppInfo1, OutDir),
  43. ProjectDeps1 = lists:delete(Name, ProjectDeps),
  44. rebar_state:project_apps(StateAcc1
  45. ,rebar_app_info:deps(AppInfo2, ProjectDeps1));
  46. false ->
  47. ?INFO("Ignoring ~s", [Name]),
  48. StateAcc
  49. end
  50. end, State1, SortedApps).
  51. define_root_app(Apps, State) ->
  52. RootDir = rebar_dir:root_dir(State),
  53. case ec_lists:find(fun(X) ->
  54. ec_file:real_dir_path(rebar_app_info:dir(X)) =:=
  55. ec_file:real_dir_path(RootDir)
  56. end, Apps) of
  57. {ok, App} ->
  58. rebar_app_info:name(App);
  59. error ->
  60. root
  61. end.
  62. format_error({module_list, File}) ->
  63. io_lib:format("Error reading module list from ~p~n", [File]);
  64. format_error({missing_module, Module}) ->
  65. io_lib:format("Module defined in app file missing: ~p~n", [Module]).
  66. merge_deps(AppInfo, State) ->
  67. %% These steps make sure that hooks and artifacts are run in the context of
  68. %% the application they are defined at. If an umbrella structure is used and
  69. %% they are deifned at the top level they will instead run in the context of
  70. %% the State and at the top level, not as part of an application.
  71. Default = reset_hooks(rebar_state:default(State)),
  72. {C, State1} = project_app_config(AppInfo, State),
  73. AppInfo0 = rebar_app_info:update_opts(AppInfo, Default, C),
  74. CurrentProfiles = rebar_state:current_profiles(State1),
  75. Name = rebar_app_info:name(AppInfo0),
  76. %% We reset the opts here to default so no profiles are applied multiple times
  77. AppInfo1 = rebar_app_info:apply_overrides(rebar_state:get(State1, overrides, []), AppInfo0),
  78. AppInfo2 = rebar_app_info:apply_profiles(AppInfo1, CurrentProfiles),
  79. %% Will throw an exception if checks fail
  80. rebar_app_info:verify_otp_vsn(AppInfo2),
  81. State2 = lists:foldl(fun(Profile, StateAcc) ->
  82. handle_profile(Profile, Name, AppInfo2, StateAcc)
  83. end, State1, lists:reverse(CurrentProfiles)),
  84. {AppInfo2, State2}.
  85. handle_profile(Profile, Name, AppInfo, State) ->
  86. TopParsedDeps = rebar_state:get(State, {parsed_deps, Profile}, {[], []}),
  87. TopLevelProfileDeps = rebar_state:get(State, {deps, Profile}, []),
  88. AppProfileDeps = rebar_app_info:get(AppInfo, {deps, Profile}, []),
  89. AppProfileDeps2 = rebar_utils:tup_dedup(AppProfileDeps),
  90. ProfileDeps2 = rebar_utils:tup_dedup(rebar_utils:tup_umerge(TopLevelProfileDeps
  91. ,AppProfileDeps2)),
  92. State1 = rebar_state:set(State, {deps, Profile}, ProfileDeps2),
  93. %% Only deps not also specified in the top level config need
  94. %% to be included in the parsed deps
  95. NewDeps = ProfileDeps2 -- TopLevelProfileDeps,
  96. ParsedDeps = parse_profile_deps(Profile, Name, NewDeps, rebar_app_info:opts(AppInfo), State1),
  97. State2 = rebar_state:set(State1, {deps, Profile}, ProfileDeps2),
  98. rebar_state:set(State2, {parsed_deps, Profile}, TopParsedDeps++ParsedDeps).
  99. parse_profile_deps(Profile, Name, Deps, Opts, State) ->
  100. DepsDir = rebar_prv_install_deps:profile_dep_dir(State, Profile),
  101. Locks = rebar_state:get(State, {locks, Profile}, []),
  102. rebar_app_utils:parse_deps(Name
  103. ,DepsDir
  104. ,Deps
  105. ,rebar_state:opts(State, Opts)
  106. ,Locks
  107. ,1).
  108. project_app_config(AppInfo, State) ->
  109. C = rebar_config:consult(rebar_app_info:dir(AppInfo)),
  110. Dir = rebar_app_info:dir(AppInfo),
  111. Opts = maybe_reset_hooks(Dir, rebar_state:opts(State), State),
  112. {C, rebar_state:opts(State, Opts)}.
  113. %% Here we check if the app is at the root of the project.
  114. %% If it is, then drop the hooks from the config so they aren't run twice
  115. maybe_reset_hooks(Dir, Opts, State) ->
  116. case ec_file:real_dir_path(rebar_dir:root_dir(State)) of
  117. Dir ->
  118. reset_hooks(Opts);
  119. _ ->
  120. Opts
  121. end.
  122. reset_hooks(Opts) ->
  123. lists:foldl(fun(Key, OptsAcc) ->
  124. rebar_opts:set(OptsAcc, Key, [])
  125. end, Opts, [post_hooks, pre_hooks, provider_hooks, artifacts]).
  126. -spec all_app_dirs(list(file:name())) -> list(file:name()).
  127. all_app_dirs(LibDirs) ->
  128. lists:flatmap(fun(LibDir) ->
  129. app_dirs(LibDir)
  130. end, LibDirs).
  131. app_dirs(LibDir) ->
  132. Path1 = filename:join([LibDir,
  133. "src",
  134. "*.app.src"]),
  135. Path2 = filename:join([LibDir,
  136. "src",
  137. "*.app.src.script"]),
  138. Path3 = filename:join([LibDir,
  139. "ebin",
  140. "*.app"]),
  141. lists:usort(lists:foldl(fun(Path, Acc) ->
  142. Files = filelib:wildcard(ec_cnv:to_list(Path)),
  143. [app_dir(File) || File <- Files] ++ Acc
  144. end, [], [Path1, Path2, Path3])).
  145. find_unbuilt_apps(LibDirs) ->
  146. find_apps(LibDirs, invalid).
  147. -spec find_apps([file:filename_all()]) -> [rebar_app_info:t()].
  148. find_apps(LibDirs) ->
  149. find_apps(LibDirs, valid).
  150. -spec find_apps([file:filename_all()], valid | invalid | all) -> [rebar_app_info:t()].
  151. find_apps(LibDirs, Validate) ->
  152. rebar_utils:filtermap(fun(AppDir) ->
  153. find_app(AppDir, Validate)
  154. end, all_app_dirs(LibDirs)).
  155. -spec find_app(file:filename_all(), valid | invalid | all) -> {true, rebar_app_info:t()} | false.
  156. find_app(AppDir, Validate) ->
  157. find_app(rebar_app_info:new(), AppDir, Validate).
  158. find_app(AppInfo, AppDir, Validate) ->
  159. AppFile = filelib:wildcard(filename:join([AppDir, "ebin", "*.app"])),
  160. AppSrcFile = filelib:wildcard(filename:join([AppDir, "src", "*.app.src"])),
  161. AppSrcScriptFile = filelib:wildcard(filename:join([AppDir, "src", "*.app.src.script"])),
  162. try_handle_app_file(AppInfo, AppFile, AppDir, AppSrcFile, AppSrcScriptFile, Validate).
  163. app_dir(AppFile) ->
  164. filename:join(rebar_utils:droplast(filename:split(filename:dirname(AppFile)))).
  165. -spec create_app_info(rebar_app_info:t(), file:name(), file:name()) -> rebar_app_info:t().
  166. create_app_info(AppInfo, AppDir, AppFile) ->
  167. [{application, AppName, AppDetails}] = rebar_config:consult_app_file(AppFile),
  168. AppVsn = proplists:get_value(vsn, AppDetails),
  169. Applications = proplists:get_value(applications, AppDetails, []),
  170. IncludedApplications = proplists:get_value(included_applications, AppDetails, []),
  171. AppInfo1 = rebar_app_info:name(
  172. rebar_app_info:original_vsn(
  173. rebar_app_info:dir(AppInfo, AppDir), AppVsn), AppName),
  174. AppInfo2 = rebar_app_info:applications(
  175. rebar_app_info:app_details(AppInfo1, AppDetails),
  176. IncludedApplications++Applications),
  177. Valid = case rebar_app_utils:validate_application_info(AppInfo2) =:= true
  178. andalso rebar_app_info:has_all_artifacts(AppInfo2) =:= true of
  179. true ->
  180. true;
  181. _ ->
  182. false
  183. end,
  184. rebar_app_info:dir(rebar_app_info:valid(AppInfo2, Valid), AppDir).
  185. %% Read in and parse the .app file if it is availabe. Do the same for
  186. %% the .app.src file if it exists.
  187. try_handle_app_file(AppInfo, [], AppDir, [], AppSrcScriptFile, Validate) ->
  188. try_handle_app_src_file(AppInfo, [], AppDir, AppSrcScriptFile, Validate);
  189. try_handle_app_file(AppInfo, [], AppDir, AppSrcFile, _, Validate) ->
  190. try_handle_app_src_file(AppInfo, [], AppDir, AppSrcFile, Validate);
  191. try_handle_app_file(AppInfo0, [File], AppDir, AppSrcFile, _, Validate) ->
  192. try create_app_info(AppInfo0, AppDir, File) of
  193. AppInfo ->
  194. AppInfo1 = rebar_app_info:app_file(AppInfo, File),
  195. AppInfo2 = case AppSrcFile of
  196. [F] ->
  197. rebar_app_info:app_file_src(AppInfo1, F);
  198. [] ->
  199. %% Set to undefined in case AppInfo previous had a .app.src
  200. rebar_app_info:app_file_src(AppInfo1, undefined);
  201. Other when is_list(Other) ->
  202. throw({error, {multiple_app_files, Other}})
  203. end,
  204. case Validate of
  205. valid ->
  206. case rebar_app_utils:validate_application_info(AppInfo2) of
  207. true ->
  208. {true, AppInfo2};
  209. _ ->
  210. false
  211. end;
  212. invalid ->
  213. case rebar_app_utils:validate_application_info(AppInfo2) of
  214. true ->
  215. false;
  216. _ ->
  217. {true, AppInfo2}
  218. end;
  219. all ->
  220. {true, AppInfo2}
  221. end
  222. catch
  223. throw:{error, {Module, Reason}} ->
  224. ?DEBUG("Falling back to app.src file because .app failed: ~s", [Module:format_error(Reason)]),
  225. try_handle_app_src_file(AppInfo0, File, AppDir, AppSrcFile, Validate)
  226. end;
  227. try_handle_app_file(_AppInfo, Other, _AppDir, _AppSrcFile, _, _Validate) ->
  228. throw({error, {multiple_app_files, Other}}).
  229. %% Read in the .app.src file if we aren't looking for a valid (already built) app
  230. try_handle_app_src_file(_AppInfo, _, _AppDir, [], _Validate) ->
  231. false;
  232. try_handle_app_src_file(_AppInfo, _, _AppDir, _AppSrcFile, valid) ->
  233. false;
  234. try_handle_app_src_file(AppInfo, _, AppDir, [File], Validate) when Validate =:= invalid
  235. ; Validate =:= all ->
  236. AppInfo1 = create_app_info(AppInfo, AppDir, File),
  237. case filename:extension(File) of
  238. ".script" ->
  239. {true, rebar_app_info:app_file_src_script(AppInfo1, File)};
  240. _ ->
  241. {true, rebar_app_info:app_file_src(AppInfo1, File)}
  242. end;
  243. try_handle_app_src_file(_AppInfo, _, _AppDir, Other, _Validate) ->
  244. throw({error, {multiple_app_files, Other}}).
  245. enable(State, AppInfo) ->
  246. not lists:member(to_atom(rebar_app_info:name(AppInfo)),
  247. rebar_state:get(State, excluded_apps, [])).
  248. to_atom(Bin) ->
  249. list_to_atom(binary_to_list(Bin)).