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

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