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

380 行
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. 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 = 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. "." -> make_normalized_path(T, NormalizedPath);
  176. ".." -> make_normalized_path(T, tl(NormalizedPath));
  177. _ -> make_normalized_path(T, [H|NormalizedPath])
  178. end.
  179. %% @doc take a source and a target path, and relativize the target path
  180. %% onto the source.
  181. %%
  182. %% Example:
  183. %% ```
  184. %% 1> rebar_dir:make_relative_path("a/b/c/d/file", "a/b/file").
  185. %% "c/d/file"
  186. %% 2> rebar_dir:make_relative_path("a/b/file", "a/b/c/d/file").
  187. %% "../../file"
  188. %% '''
  189. -spec make_relative_path(file:filename(), file:filename()) -> file:filename().
  190. make_relative_path(Source, Target) ->
  191. AbsSource = make_normalized_path(Source),
  192. AbsTarget = make_normalized_path(Target),
  193. do_make_relative_path(filename:split(AbsSource), filename:split(AbsTarget)).
  194. %% @private based on fragments of paths, replace the number of common
  195. %% segments by `../' bits, and add the rest of the source alone after it
  196. -spec do_make_relative_path([string()], [string()]) -> file:filename().
  197. do_make_relative_path([H|T1], [H|T2]) ->
  198. do_make_relative_path(T1, T2);
  199. do_make_relative_path(Source, Target) ->
  200. Base = lists:duplicate(max(length(Target) - 1, 0), ".."),
  201. filename:join(Base ++ Source).
  202. %%% @doc
  203. %%% `src_dirs' and `extra_src_dirs' can be configured with options
  204. %%% like this:
  205. %%% ```
  206. %%% {src_dirs,[{"foo",[{recursive,false}]}]}
  207. %%% {extra_src_dirs,[{"bar",[recursive]}]} (equivalent to {recursive,true})
  208. %%% '''
  209. %%% `src_dirs/1,2' and `extra_src_dirs/1,2' return only the list of
  210. %%% directories for the `src_dirs' and `extra_src_dirs' options
  211. %%% respectively, while `src_dirs_opts/2' returns the options list for
  212. %%% the given directory, no matter if it is configured as `src_dirs' or
  213. %%% `extra_src_dirs'.
  214. -spec src_dirs(rebar_dict()) -> list(file:filename_all()).
  215. src_dirs(Opts) -> src_dirs(Opts, []).
  216. %% @doc same as `src_dirs/1', but allows to pass in a list of default options.
  217. -spec src_dirs(rebar_dict(), list(file:filename_all())) -> list(file:filename_all()).
  218. src_dirs(Opts, Default) ->
  219. src_dirs(src_dirs, Opts, Default).
  220. %% @doc same as `src_dirs/1', but for the `extra_src_dirs' options
  221. -spec extra_src_dirs(rebar_dict()) -> list(file:filename_all()).
  222. extra_src_dirs(Opts) -> extra_src_dirs(Opts, []).
  223. %% @doc same as `src_dirs/2', but for the `extra_src_dirs' options
  224. -spec extra_src_dirs(rebar_dict(), list(file:filename_all())) -> list(file:filename_all()).
  225. extra_src_dirs(Opts, Default) ->
  226. src_dirs(extra_src_dirs, Opts, Default).
  227. %% @private agnostic version of src_dirs and extra_src_dirs.
  228. src_dirs(Type, Opts, Default) ->
  229. lists:usort([
  230. case D0 of
  231. {D,_} -> normalize_relative_path(D);
  232. _ -> normalize_relative_path(D0)
  233. end || D0 <- raw_src_dirs(Type,Opts,Default)]).
  234. %% @private extracts the un-formatted src_dirs or extra_src_dirs
  235. %% options as configured.
  236. raw_src_dirs(Type, Opts, Default) ->
  237. ErlOpts = rebar_opts:erl_opts(Opts),
  238. Vs = proplists:get_all_values(Type, ErlOpts),
  239. case lists:append([rebar_opts:get(Opts, Type, []) | Vs]) of
  240. [] -> Default;
  241. Dirs -> Dirs
  242. end.
  243. %% @private normalizes relative paths so that ./a/b/c/ => a/b/c
  244. normalize_relative_path(Path) ->
  245. make_normalized_path(filename:split(Path), []).
  246. %% @doc returns all the source directories (`src_dirs' and
  247. %% `extra_src_dirs').
  248. -spec all_src_dirs(rebar_dict()) -> list(file:filename_all()).
  249. all_src_dirs(Opts) -> all_src_dirs(Opts, [], []).
  250. %% @doc returns all the source directories (`src_dirs' and
  251. %% `extra_src_dirs') while being able to configure defaults for both.
  252. -spec all_src_dirs(rebar_dict(), list(file:filename_all()), list(file:filename_all())) ->
  253. list(file:filename_all()).
  254. all_src_dirs(Opts, SrcDefault, ExtraDefault) ->
  255. lists:usort(src_dirs(Opts, SrcDefault) ++ extra_src_dirs(Opts, ExtraDefault)).
  256. %%% @doc
  257. %%% Return the list of options for the given src directory
  258. %%% If the same option is given multiple times for a directory in the
  259. %%% config, the priority order is: first occurence of `src_dirs'
  260. %%% followed by first occurence of `extra_src_dirs'.
  261. -spec src_dir_opts(rebar_dict(), file:filename_all()) -> [{atom(),term()}].
  262. src_dir_opts(Opts, Dir) ->
  263. RawSrcDirs = raw_src_dirs(src_dirs, Opts, []),
  264. RawExtraSrcDirs = raw_src_dirs(extra_src_dirs, Opts, []),
  265. AllOpts = [Opt || {D,Opt} <- RawSrcDirs++RawExtraSrcDirs,
  266. D==Dir],
  267. lists:ukeysort(1,proplists:unfold(lists:append(AllOpts))).
  268. %%% @doc
  269. %%% Return the value of the 'recursive' option for the given directory.
  270. %%% If not given, the value of 'recursive' in the 'erlc_compiler'
  271. %%% options is used, and finally the default is 'true'.
  272. -spec recursive(rebar_dict(), file:filename_all()) -> boolean().
  273. recursive(Opts, Dir) ->
  274. DirOpts = src_dir_opts(Opts, Dir),
  275. Default = proplists:get_value(recursive,
  276. rebar_opts:get(Opts, erlc_compiler, []),
  277. true),
  278. R = proplists:get_value(recursive, DirOpts, Default),
  279. R.
  280. %% @doc given a path if that path is an ancestor of an app dir, return the path relative to that
  281. %% apps outdir. If the path is not an ancestor to any app dirs but is an ancestor of the
  282. %% project root, return the path relative to the project base_dir. If it is not an ancestor
  283. %% of either return it unmodified
  284. -spec retarget_path(rebar_state:t(), string()) -> string().
  285. retarget_path(State, Path) ->
  286. ProjectApps = rebar_state:project_apps(State),
  287. retarget_path(State, Path, ProjectApps).
  288. %% @private worker for retarget_path/2
  289. %% @end
  290. %% not relative to any apps in project, check to see it's relative to
  291. %% project root
  292. retarget_path(State, Path, []) ->
  293. case rebar_file_utils:path_from_ancestor(rebar_file_utils:canonical_path(Path), rebar_state:dir(State)) of
  294. {ok, NewPath} -> filename:join([base_dir(State), NewPath]);
  295. %% not relative to project root, don't modify
  296. {error, badparent} -> Path
  297. end;
  298. %% relative to current app, retarget to the same dir relative to
  299. %% the app's out_dir
  300. retarget_path(State, Path, [App|Rest]) ->
  301. case rebar_file_utils:path_from_ancestor(rebar_file_utils:canonical_path(Path), rebar_app_info:dir(App)) of
  302. {ok, NewPath} -> filename:join([rebar_app_info:out_dir(App), NewPath]);
  303. {error, badparent} -> retarget_path(State, Path, Rest)
  304. end.
  305. format_source_file_name(Path, Opts) ->
  306. Type = case rebar_opts:get(Opts, compiler_source_format,
  307. ?DEFAULT_COMPILER_SOURCE_FORMAT) of
  308. V when V == absolute; V == relative; V == build ->
  309. V;
  310. Other ->
  311. warn_source_format_once(Other)
  312. end,
  313. case Type of
  314. absolute -> resolve_linked_source(Path);
  315. build -> Path;
  316. relative ->
  317. Cwd = rebar_dir:get_cwd(),
  318. rebar_dir:make_relative_path(resolve_linked_source(Path), Cwd)
  319. end.
  320. %% @private displays a warning for the compiler source format option
  321. %% only once
  322. -spec warn_source_format_once(term()) -> ok.
  323. warn_source_format_once(Format) ->
  324. Warn = application:get_env(rebar, warn_source_format) =/= {ok, false},
  325. application:set_env(rebar, warn_source_format, false),
  326. case Warn of
  327. false ->
  328. ok;
  329. true ->
  330. ?WARN("Invalid argument ~p for compiler_source_format - "
  331. "assuming ~ts~n", [Format, ?DEFAULT_COMPILER_SOURCE_FORMAT])
  332. end.
  333. %% @private takes a filename and canonicalizes its path if it is a link.
  334. -spec resolve_linked_source(file:filename()) -> file:filename().
  335. resolve_linked_source(Src) ->
  336. {Dir, Base} = rebar_file_utils:split_dirname(Src),
  337. filename:join(rebar_file_utils:resolve_link(Dir), Base).