You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

381 lines
15 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  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).