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

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