Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.

382 righe
16 KiB

10 anni fa
10 anni fa
10 anni fa
10 anni fa
10 anni fa
10 anni fa
10 anni fa
10 anni fa
10 anni fa
10 anni fa
10 anni fa
10 anni fa
10 anni fa
10 anni fa
10 anni fa
10 anni fa
10 anni fa
10 anni fa
10 anni fa
10 anni fa
10 anni fa
10 anni fa
10 anni fa
  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).