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

381 行
15 KiB

  1. %%% @doc utility functions for directory and path handling of all kind.
  2. -module(rebar_dir).
  3. -export([base_dir/1,
  4. profile_dir/2,
  5. profile_dir_name/1,
  6. deps_dir/1,
  7. deps_dir/2,
  8. root_dir/1,
  9. checkouts_dir/1,
  10. checkouts_dir/2,
  11. checkouts_out_dir/1,
  12. checkouts_out_dir/2,
  13. plugins_dir/1,
  14. lib_dirs/1,
  15. home_dir/0,
  16. global_config_dir/1,
  17. global_config/1,
  18. global_config/0,
  19. global_cache_dir/1,
  20. local_cache_dir/1,
  21. get_cwd/0,
  22. template_globals/1,
  23. template_dir/1,
  24. processing_base_dir/1,
  25. processing_base_dir/2,
  26. make_relative_path/2,
  27. src_dirs/1, src_dirs/2,
  28. src_dir_opts/2, recursive/2,
  29. extra_src_dirs/1, extra_src_dirs/2,
  30. all_src_dirs/1, all_src_dirs/3,
  31. retarget_path/2,
  32. format_source_file_name/1,
  33. format_source_file_name/2]).
  34. -include("rebar.hrl").
  35. %% @doc returns the directory root for build artifacts
  36. %% for the current profile, such as `_build/default/'.
  37. -spec base_dir(rebar_state:t()) -> file:filename_all().
  38. base_dir(State) ->
  39. profile_dir(rebar_state:opts(State), rebar_state:current_profiles(State)).
  40. %% @doc returns the directory root for build artifacts for a given set
  41. %% of profiles.
  42. -spec profile_dir(rebar_dict(), [atom(), ...]) -> file:filename_all().
  43. profile_dir(Opts, Profiles) ->
  44. BasePath =
  45. case Profiles of
  46. [global | _] -> ?MODULE:global_cache_dir(Opts);
  47. [_|_] -> rebar_opts:get(Opts, base_dir, ?DEFAULT_BASE_DIR)
  48. end,
  49. DirName = profile_dir_name_(Profiles),
  50. filename:join(BasePath, DirName).
  51. %% @doc returns the directory name for build artifacts for a given set
  52. %% of profiles.
  53. -spec profile_dir_name(rebar_state:t()) -> file:filename_all().
  54. profile_dir_name(State) ->
  55. profile_dir_name_(rebar_state:current_profiles(State)).
  56. -spec profile_dir_name_([atom(), ...]) -> file:filename_all().
  57. profile_dir_name_(Profiles)
  58. when is_list(Profiles) ->
  59. case [rebar_utils:to_list(P) || P <- Profiles] of
  60. ["global" | _] -> "";
  61. ["bootstrap", "default"] -> "default";
  62. ["default"] -> "default";
  63. %% drop `default' from the profile dir if it's implicit and reverse order
  64. %% of profiles to match order passed to `as`
  65. ["default"|NonDefaultNames] -> rebar_string:join(NonDefaultNames, "+")
  66. end.
  67. %% @doc returns the directory where dependencies should be placed
  68. %% given the current profile.
  69. -spec deps_dir(rebar_state:t()) -> file:filename_all().
  70. deps_dir(State) ->
  71. filename:join(base_dir(State), rebar_state:get(State, deps_dir, ?DEFAULT_DEPS_DIR)).
  72. %% @doc returns the directory where a dependency should be placed
  73. %% given the current profile, based on its app name. Expects to be passed
  74. %% the result of `deps_dir/1' as a first argument.
  75. -spec deps_dir(file:filename_all(), file:filename_all()) -> file:filename_all().
  76. deps_dir(DepsDir, App) ->
  77. filename:join(DepsDir, App).
  78. %% @doc returns the absolute path for the project root (by default,
  79. %% the current working directory for the currently running escript).
  80. root_dir(State) ->
  81. filename:absname(rebar_state:get(State, root_dir, ?DEFAULT_ROOT_DIR)).
  82. %% @doc returns the expected location of the `_checkouts' directory.
  83. -spec checkouts_dir(rebar_state:t()) -> file:filename_all().
  84. checkouts_dir(State) ->
  85. rebar_file_utils:canonical_path(filename:join(root_dir(State), rebar_state:get(State, checkouts_dir, ?DEFAULT_CHECKOUTS_DIR))).
  86. %% @doc returns the expected location of a given app in the checkouts
  87. %% directory for the project.
  88. -spec checkouts_dir(rebar_state:t(), file:filename_all()) -> file:filename_all().
  89. checkouts_dir(State, App) ->
  90. filename:join(checkouts_dir(State), App).
  91. %% @doc returns the location of the directory checkouts are built to
  92. -spec checkouts_out_dir(rebar_state:t()) -> file:filename_all().
  93. checkouts_out_dir(State) ->
  94. filename:join(base_dir(State), rebar_state:get(State, checkouts_out_dir, ?DEFAULT_CHECKOUTS_OUT_DIR)).
  95. %% @doc returns the expected location of a given app in the checkouts
  96. %% directory for the project.
  97. -spec checkouts_out_dir(rebar_state:t(), file:filename_all()) -> file:filename_all().
  98. checkouts_out_dir(State, App) ->
  99. filename:join(checkouts_out_dir(State), App).
  100. %% @doc Returns the directory where plugins are located.
  101. -spec plugins_dir(rebar_state:t()) -> file:filename_all().
  102. plugins_dir(State) ->
  103. case lists:member(global, rebar_state:current_profiles(State)) of
  104. true ->
  105. filename:join([base_dir(State), global_config_dir(State), rebar_state:get(State, plugins_dir, ?DEFAULT_PLUGINS_DIR)]);
  106. false ->
  107. filename:join(base_dir(State), rebar_state:get(State, plugins_dir, ?DEFAULT_PLUGINS_DIR))
  108. end.
  109. %% @doc returns the list of relative path where the project applications can
  110. %% be located.
  111. -spec lib_dirs(rebar_state:t()) -> file:filename_all().
  112. lib_dirs(State) ->
  113. rebar_state:get(State, project_app_dirs, ?DEFAULT_PROJECT_APP_DIRS).
  114. %% @doc returns the user's home directory.
  115. -spec home_dir() -> file:filename_all().
  116. home_dir() ->
  117. {ok, [[Home]]} = init:get_argument(home),
  118. Home.
  119. %% @doc returns the directory where the global configuration files for rebar3
  120. %% may be stored.
  121. -spec global_config_dir(rebar_state:t()) -> file:filename_all().
  122. global_config_dir(State) ->
  123. filename:join([rebar_config_dir(State), ".config", "rebar3"]).
  124. rebar_config_dir(State) ->
  125. case os:getenv("REBAR_GLOBAL_CONFIG_DIR") of
  126. false ->
  127. rebar_state:get(State, global_rebar_dir, home_dir());
  128. ConfDir ->
  129. ConfDir
  130. end.
  131. %% @doc returns the path of the global rebar.config file
  132. -spec global_config(rebar_state:t()) -> file:filename_all().
  133. global_config(State) ->
  134. filename:join(global_config_dir(State), "rebar.config").
  135. %% @doc returns the default path of the global rebar.config file
  136. -spec global_config() -> file:filename_all().
  137. global_config() ->
  138. Home = home_dir(),
  139. filename:join([Home, ".config", "rebar3", "rebar.config"]).
  140. %% @doc returns the location for the global cache directory
  141. -spec global_cache_dir(rebar_dict()) -> file:filename_all().
  142. global_cache_dir(Opts) ->
  143. Home = home_dir(),
  144. rebar_opts:get(Opts, global_rebar_dir, filename:join([Home, ".cache", "rebar3"])).
  145. %% @doc appends the cache directory to the path passed to this function.
  146. -spec local_cache_dir(file:filename_all()) -> file:filename_all().
  147. local_cache_dir(Dir) ->
  148. filename:join(Dir, ".rebar3").
  149. %% @doc returns the current working directory, with some specific
  150. %% conversions and handling done to be cross-platform compatible.
  151. -spec get_cwd() -> file:filename_all().
  152. get_cwd() ->
  153. {ok, Dir} = file:get_cwd(),
  154. %% On windows cwd may return capital letter for drive,
  155. %% for example C:/foobar. But as said in http://www.erlang.org/doc/man/filename.html#join-1
  156. %% filename:join/1,2 anyway will convert drive-letter to lowercase, so we have to "internalize"
  157. %% cwd as soon as it possible.
  158. filename:join([Dir]).
  159. %% @doc returns the file location for the global template
  160. %% configuration variables file.
  161. -spec template_globals(rebar_state:t()) -> file:filename_all().
  162. template_globals(State) ->
  163. filename:join([global_config_dir(State), "templates", "globals"]).
  164. %% @doc returns the location for the global template directory
  165. -spec template_dir(rebar_state:t()) -> file:filename_all().
  166. template_dir(State) ->
  167. filename:join([global_config_dir(State), "templates"]).
  168. %% @doc checks if the current working directory is the base directory
  169. %% for the project.
  170. -spec processing_base_dir(rebar_state:t()) -> boolean().
  171. processing_base_dir(State) ->
  172. Cwd = get_cwd(),
  173. processing_base_dir(State, Cwd).
  174. %% @doc checks if the passed in directory is the base directory for
  175. %% the project.
  176. -spec processing_base_dir(rebar_state:t(), file:filename()) -> boolean().
  177. processing_base_dir(State, Dir) ->
  178. AbsDir = filename:absname(Dir),
  179. AbsDir =:= rebar_state:get(State, base_dir).
  180. %% @doc take a source and a target path, and relativize the target path
  181. %% onto the source.
  182. %%
  183. %% Example:
  184. %% ```
  185. %% 1> rebar_dir:make_relative_path("a/b/c/d/file", "a/b/file").
  186. %% "c/d/file"
  187. %% 2> rebar_dir:make_relative_path("a/b/file", "a/b/c/d/file").
  188. %% "../../file"
  189. %% '''
  190. -spec make_relative_path(file:filename(), file:filename()) -> file:filename().
  191. make_relative_path(Source, Target) ->
  192. AbsSource = rebar_file_utils:normalized_path(Source),
  193. AbsTarget = rebar_file_utils:normalized_path(Target),
  194. do_make_relative_path(filename:split(AbsSource), filename:split(AbsTarget)).
  195. %% @private based on fragments of paths, replace the number of common
  196. %% segments by `../' bits, and add the rest of the source alone after it
  197. -spec do_make_relative_path([string()], [string()]) -> file:filename().
  198. do_make_relative_path([H|T1], [H|T2]) ->
  199. do_make_relative_path(T1, T2);
  200. do_make_relative_path(Source, Target) ->
  201. Base = lists:duplicate(max(length(Target) - 1, 0), ".."),
  202. filename:join(Base ++ Source).
  203. %%% @doc
  204. %%% `src_dirs' and `extra_src_dirs' can be configured with options
  205. %%% like this:
  206. %%% ```
  207. %%% {src_dirs,[{"foo",[{recursive,false}]}]}
  208. %%% {extra_src_dirs,[{"bar",[recursive]}]} (equivalent to {recursive,true})
  209. %%% '''
  210. %%% `src_dirs/1,2' and `extra_src_dirs/1,2' return only the list of
  211. %%% directories for the `src_dirs' and `extra_src_dirs' options
  212. %%% respectively, while `src_dirs_opts/2' returns the options list for
  213. %%% the given directory, no matter if it is configured as `src_dirs' or
  214. %%% `extra_src_dirs'.
  215. -spec src_dirs(rebar_dict()) -> list(file:filename_all()).
  216. src_dirs(Opts) -> src_dirs(Opts, []).
  217. %% @doc same as `src_dirs/1', but allows to pass in a list of default options.
  218. -spec src_dirs(rebar_dict(), list(file:filename_all())) -> list(file:filename_all()).
  219. src_dirs(Opts, Default) ->
  220. src_dirs(src_dirs, Opts, Default).
  221. %% @doc same as `src_dirs/1', but for the `extra_src_dirs' options
  222. -spec extra_src_dirs(rebar_dict()) -> list(file:filename_all()).
  223. extra_src_dirs(Opts) -> extra_src_dirs(Opts, []).
  224. %% @doc same as `src_dirs/2', but for the `extra_src_dirs' options
  225. -spec extra_src_dirs(rebar_dict(), list(file:filename_all())) -> list(file:filename_all()).
  226. extra_src_dirs(Opts, Default) ->
  227. src_dirs(extra_src_dirs, Opts, Default).
  228. %% @private agnostic version of src_dirs and extra_src_dirs.
  229. src_dirs(Type, Opts, Default) ->
  230. lists:usort([
  231. case D0 of
  232. {D,_} -> rebar_file_utils:normalize_relative_path(D);
  233. _ -> rebar_file_utils:normalize_relative_path(D0)
  234. end || D0 <- raw_src_dirs(Type,Opts,Default)]).
  235. %% @private extracts the un-formatted src_dirs or extra_src_dirs
  236. %% options as configured.
  237. raw_src_dirs(Type, Opts, Default) ->
  238. ErlOpts = rebar_opts:erl_opts(Opts),
  239. Vs = proplists:get_all_values(Type, ErlOpts),
  240. case lists:append([rebar_opts:get(Opts, Type, []) | Vs]) of
  241. [] -> Default;
  242. Dirs -> Dirs
  243. end.
  244. %% @doc returns all the source directories (`src_dirs' and
  245. %% `extra_src_dirs').
  246. -spec all_src_dirs(rebar_dict()) -> list(file:filename_all()).
  247. all_src_dirs(Opts) -> all_src_dirs(Opts, [], []).
  248. %% @doc returns all the source directories (`src_dirs' and
  249. %% `extra_src_dirs') while being able to configure defaults for both.
  250. -spec all_src_dirs(rebar_dict(), list(file:filename_all()), list(file:filename_all())) ->
  251. list(file:filename_all()).
  252. all_src_dirs(Opts, SrcDefault, ExtraDefault) ->
  253. lists:usort(src_dirs(Opts, SrcDefault) ++ extra_src_dirs(Opts, ExtraDefault)).
  254. %%% @doc
  255. %%% Return the list of options for the given src directory
  256. %%% If the same option is given multiple times for a directory in the
  257. %%% config, the priority order is: first occurence of `src_dirs'
  258. %%% followed by first occurence of `extra_src_dirs'.
  259. -spec src_dir_opts(rebar_dict(), file:filename_all()) -> [{atom(),term()}].
  260. src_dir_opts(Opts, Dir) ->
  261. RawSrcDirs = raw_src_dirs(src_dirs, Opts, []),
  262. RawExtraSrcDirs = raw_src_dirs(extra_src_dirs, Opts, []),
  263. AllOpts = [Opt || {D, Opt} <- RawSrcDirs++RawExtraSrcDirs, D==Dir],
  264. lists:ukeysort(1, proplists:unfold(lists:append(AllOpts))).
  265. %%% @doc
  266. %%% Return the value of the 'recursive' option for the given directory.
  267. %%% If not given, the value of 'recursive' in the 'erlc_compiler'
  268. %%% options is used, and finally the default is 'true'.
  269. -spec recursive(rebar_dict(), file:filename_all()) -> boolean().
  270. recursive(Opts, Dir) ->
  271. DirOpts = src_dir_opts(Opts, Dir),
  272. Default = proplists:get_value(recursive,
  273. rebar_opts:get(Opts, erlc_compiler, []),
  274. true),
  275. R = proplists:get_value(recursive, DirOpts, Default),
  276. R.
  277. %% @doc given a path if that path is an ancestor of an app dir, return the path relative to that
  278. %% apps outdir. If the path is not an ancestor to any app dirs but is an ancestor of the
  279. %% project root, return the path relative to the project base_dir. If it is not an ancestor
  280. %% of either return it unmodified
  281. -spec retarget_path(rebar_state:t(), string()) -> string().
  282. retarget_path(State, Path) ->
  283. ProjectApps = rebar_state:project_apps(State),
  284. retarget_path(State, Path, ProjectApps).
  285. %% @private worker for retarget_path/2
  286. %% @end
  287. %% not relative to any apps in project, check to see it's relative to
  288. %% project root
  289. retarget_path(State, Path, []) ->
  290. case rebar_file_utils:path_from_ancestor(rebar_file_utils:canonical_path(Path), rebar_state:dir(State)) of
  291. {ok, NewPath} -> filename:join([base_dir(State), NewPath]);
  292. %% not relative to project root, don't modify
  293. {error, badparent} -> Path
  294. end;
  295. %% relative to current app, retarget to the same dir relative to
  296. %% the app's out_dir
  297. retarget_path(State, Path, [App|Rest]) ->
  298. case rebar_file_utils:path_from_ancestor(rebar_file_utils:canonical_path(Path), rebar_app_info:dir(App)) of
  299. {ok, NewPath} -> filename:join([rebar_app_info:out_dir(App), NewPath]);
  300. {error, badparent} -> retarget_path(State, Path, Rest)
  301. end.
  302. format_source_file_name(Path) ->
  303. format_source_file_name(Path, dict:new()).
  304. format_source_file_name(Path, Opts) ->
  305. Type = case rebar_opts:get(Opts, compiler_source_format,
  306. ?DEFAULT_COMPILER_SOURCE_FORMAT) of
  307. V when V == absolute; V == relative; V == build ->
  308. V;
  309. Other ->
  310. warn_source_format_once(Other)
  311. end,
  312. case Type of
  313. absolute -> resolve_linked_source(Path);
  314. build -> Path;
  315. relative ->
  316. Cwd = rebar_dir:get_cwd(),
  317. rebar_dir:make_relative_path(resolve_linked_source(Path), Cwd)
  318. end.
  319. %% @private displays a warning for the compiler source format option
  320. %% only once
  321. -spec warn_source_format_once(term()) -> ok.
  322. warn_source_format_once(Format) ->
  323. Warn = application:get_env(rebar, warn_source_format) =/= {ok, false},
  324. application:set_env(rebar, warn_source_format, false),
  325. case Warn of
  326. false ->
  327. ok;
  328. true ->
  329. ?WARN("Invalid argument ~p for compiler_source_format - "
  330. "assuming ~ts~n", [Format, ?DEFAULT_COMPILER_SOURCE_FORMAT])
  331. end.
  332. %% @private takes a filename and canonicalizes its path if it is a link.
  333. -spec resolve_linked_source(file:filename()) -> file:filename().
  334. resolve_linked_source(Src) ->
  335. {Dir, Base} = rebar_file_utils:split_dirname(Src),
  336. filename:join(rebar_file_utils:resolve_link(Dir), Base).