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.

380 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. 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).