diff options
Diffstat (limited to 'src/rebar_dir.erl')
| -rw-r--r-- | src/rebar_dir.erl | 196 |
1 files changed, 179 insertions, 17 deletions
diff --git a/src/rebar_dir.erl b/src/rebar_dir.erl index 1ec58d4..17bc48e 100644 --- a/src/rebar_dir.erl +++ b/src/rebar_dir.erl @@ -1,3 +1,4 @@ +%%% @doc utility functions for directory and path handling of all kind. -module(rebar_dir). -export([base_dir/1, @@ -22,19 +23,25 @@ processing_base_dir/2, make_relative_path/2, src_dirs/1, src_dirs/2, + src_dir_opts/2, recursive/2, extra_src_dirs/1, extra_src_dirs/2, all_src_dirs/1, all_src_dirs/3, - retarget_path/2]). + retarget_path/2, + format_source_file_name/2]). -include("rebar.hrl"). +%% @doc returns the directory root for build artifacts +%% for the current profile, such as `_build/default/'. -spec base_dir(rebar_state:t()) -> file:filename_all(). base_dir(State) -> profile_dir(rebar_state:opts(State), rebar_state:current_profiles(State)). +%% @doc returns the directory root for build artifacts for a given set +%% of profiles. -spec profile_dir(rebar_dict(), [atom()]) -> file:filename_all(). profile_dir(Opts, Profiles) -> - {BaseDir, ProfilesStrings} = case [ec_cnv:to_list(P) || P <- Profiles] of + {BaseDir, ProfilesStrings} = case [rebar_utils:to_list(P) || P <- Profiles] of ["global" | _] -> {?MODULE:global_cache_dir(Opts), [""]}; ["bootstrap", "default"] -> {rebar_opts:get(Opts, base_dir, ?DEFAULT_BASE_DIR), ["default"]}; ["default"] -> {rebar_opts:get(Opts, base_dir, ?DEFAULT_BASE_DIR), ["default"]}; @@ -42,28 +49,39 @@ profile_dir(Opts, Profiles) -> %% of profiles to match order passed to `as` ["default"|Rest] -> {rebar_opts:get(Opts, base_dir, ?DEFAULT_BASE_DIR), Rest} end, - ProfilesDir = string:join(ProfilesStrings, "+"), + ProfilesDir = rebar_string:join(ProfilesStrings, "+"), filename:join(BaseDir, ProfilesDir). +%% @doc returns the directory where dependencies should be placed +%% given the current profile. -spec deps_dir(rebar_state:t()) -> file:filename_all(). deps_dir(State) -> filename:join(base_dir(State), rebar_state:get(State, deps_dir, ?DEFAULT_DEPS_DIR)). +%% @doc returns the directory where a dependency should be placed +%% given the current profile, based on its app name. Expects to be passed +%% the result of `deps_dir/1' as a first argument. -spec deps_dir(file:filename_all(), file:filename_all()) -> file:filename_all(). deps_dir(DepsDir, App) -> filename:join(DepsDir, App). +%% @doc returns the absolute path for the project root (by default, +%% the current working directory for the currently running escript). root_dir(State) -> filename:absname(rebar_state:get(State, root_dir, ?DEFAULT_ROOT_DIR)). +%% @doc returns the expected location of the `_checkouts' directory. -spec checkouts_dir(rebar_state:t()) -> file:filename_all(). checkouts_dir(State) -> filename:join(root_dir(State), rebar_state:get(State, checkouts_dir, ?DEFAULT_CHECKOUTS_DIR)). +%% @doc returns the expected location of a given app in the checkouts +%% directory for the project. -spec checkouts_dir(rebar_state:t(), file:filename_all()) -> file:filename_all(). checkouts_dir(State, App) -> filename:join(checkouts_dir(State), App). +%% @doc Returns the directory where plugins are located. -spec plugins_dir(rebar_state:t()) -> file:filename_all(). plugins_dir(State) -> case lists:member(global, rebar_state:current_profiles(State)) of @@ -73,33 +91,50 @@ plugins_dir(State) -> filename:join(base_dir(State), rebar_state:get(State, plugins_dir, ?DEFAULT_PLUGINS_DIR)) end. +%% @doc returns the list of relative path where the project applications can +%% be located. -spec lib_dirs(rebar_state:t()) -> file:filename_all(). lib_dirs(State) -> rebar_state:get(State, project_app_dirs, ?DEFAULT_PROJECT_APP_DIRS). +%% @doc returns the user's home directory. +-spec home_dir() -> file:filename_all(). home_dir() -> {ok, [[Home]]} = init:get_argument(home), Home. +%% @doc returns the directory where the global configuration files for rebar3 +%% may be stored. +-spec global_config_dir(rebar_state:t()) -> file:filename_all(). global_config_dir(State) -> Home = home_dir(), rebar_state:get(State, global_rebar_dir, filename:join([Home, ".config", "rebar3"])). +%% @doc returns the path of the global rebar.config file +-spec global_config(rebar_state:t()) -> file:filename_all(). global_config(State) -> filename:join(global_config_dir(State), "rebar.config"). +%% @doc returns the default path of the global rebar.config file +-spec global_config() -> file:filename_all(). global_config() -> Home = home_dir(), filename:join([Home, ".config", "rebar3", "rebar.config"]). +%% @doc returns the location for the global cache directory -spec global_cache_dir(rebar_dict()) -> file:filename_all(). global_cache_dir(Opts) -> Home = home_dir(), rebar_opts:get(Opts, global_rebar_dir, filename:join([Home, ".cache", "rebar3"])). +%% @doc appends the cache directory to the path passed to this function. +-spec local_cache_dir(file:filename_all()) -> file:filename_all(). local_cache_dir(Dir) -> filename:join(Dir, ".rebar3"). +%% @doc returns the current working directory, with some specific +%% conversions and handling done to be cross-platform compatible. +-spec get_cwd() -> file:filename_all(). get_cwd() -> {ok, Dir} = file:get_cwd(), %% On windows cwd may return capital letter for drive, @@ -108,20 +143,33 @@ get_cwd() -> %% cwd as soon as it possible. filename:join([Dir]). +%% @doc returns the file location for the global template +%% configuration variables file. +-spec template_globals(rebar_state:t()) -> file:filename_all(). template_globals(State) -> filename:join([global_config_dir(State), "templates", "globals"]). +%% @doc returns the location for the global template directory +-spec template_dir(rebar_state:t()) -> file:filename_all(). template_dir(State) -> filename:join([global_config_dir(State), "templates"]). +%% @doc checks if the current working directory is the base directory +%% for the project. +-spec processing_base_dir(rebar_state:t()) -> boolean(). processing_base_dir(State) -> Cwd = get_cwd(), processing_base_dir(State, Cwd). +%% @doc checks if the passed in directory is the base directory for +%% the project. +-spec processing_base_dir(rebar_state:t(), file:filename()) -> boolean(). processing_base_dir(State, Dir) -> AbsDir = filename:absname(Dir), AbsDir =:= rebar_state:get(State, base_dir). +%% @doc make a path absolute +-spec make_absolute_path(file:filename()) -> file:filename(). make_absolute_path(Path) -> case filename:pathtype(Path) of absolute -> @@ -135,73 +183,151 @@ make_absolute_path(Path) -> filename:join([Dir, Path]) end. +%% @doc normalizing a path removes all of the `..' and the +%% `.' segments it may contain. +-spec make_normalized_path(file:filename()) -> file:filename(). make_normalized_path(Path) -> AbsPath = make_absolute_path(Path), Components = filename:split(AbsPath), make_normalized_path(Components, []). +%% @private drops path fragments for normalization +-spec make_normalized_path([string()], [string()]) -> file:filename(). make_normalized_path([], NormalizedPath) -> filename:join(lists:reverse(NormalizedPath)); make_normalized_path([H|T], NormalizedPath) -> case H of + "." when NormalizedPath == [], T == [] -> make_normalized_path(T, ["."]); "." -> make_normalized_path(T, NormalizedPath); - ".." -> make_normalized_path(T, tl(NormalizedPath)); + ".." when NormalizedPath == [] -> make_normalized_path(T, [".."]); + ".." when hd(NormalizedPath) =/= ".." -> make_normalized_path(T, tl(NormalizedPath)); _ -> make_normalized_path(T, [H|NormalizedPath]) end. +%% @doc take a source and a target path, and relativize the target path +%% onto the source. +%% +%% Example: +%% ``` +%% 1> rebar_dir:make_relative_path("a/b/c/d/file", "a/b/file"). +%% "c/d/file" +%% 2> rebar_dir:make_relative_path("a/b/file", "a/b/c/d/file"). +%% "../../file" +%% ''' +-spec make_relative_path(file:filename(), file:filename()) -> file:filename(). make_relative_path(Source, Target) -> AbsSource = make_normalized_path(Source), AbsTarget = make_normalized_path(Target), do_make_relative_path(filename:split(AbsSource), filename:split(AbsTarget)). +%% @private based on fragments of paths, replace the number of common +%% segments by `../' bits, and add the rest of the source alone after it +-spec do_make_relative_path([string()], [string()]) -> file:filename(). do_make_relative_path([H|T1], [H|T2]) -> do_make_relative_path(T1, T2); do_make_relative_path(Source, Target) -> Base = lists:duplicate(max(length(Target) - 1, 0), ".."), filename:join(Base ++ Source). +%%% @doc +%%% `src_dirs' and `extra_src_dirs' can be configured with options +%%% like this: +%%% ``` +%%% {src_dirs,[{"foo",[{recursive,false}]}]} +%%% {extra_src_dirs,[{"bar",[recursive]}]} (equivalent to {recursive,true}) +%%% ''' +%%% `src_dirs/1,2' and `extra_src_dirs/1,2' return only the list of +%%% directories for the `src_dirs' and `extra_src_dirs' options +%%% respectively, while `src_dirs_opts/2' returns the options list for +%%% the given directory, no matter if it is configured as `src_dirs' or +%%% `extra_src_dirs'. -spec src_dirs(rebar_dict()) -> list(file:filename_all()). src_dirs(Opts) -> src_dirs(Opts, []). +%% @doc same as `src_dirs/1', but allows to pass in a list of default options. -spec src_dirs(rebar_dict(), list(file:filename_all())) -> list(file:filename_all()). src_dirs(Opts, Default) -> - ErlOpts = rebar_opts:erl_opts(Opts), - Vs = proplists:get_all_values(src_dirs, ErlOpts), - case lists:append([rebar_opts:get(Opts, src_dirs, []) | Vs]) of - [] -> Default; - Dirs -> lists:usort(Dirs) - end. + src_dirs(src_dirs, Opts, Default). +%% @doc same as `src_dirs/1', but for the `extra_src_dirs' options -spec extra_src_dirs(rebar_dict()) -> list(file:filename_all()). extra_src_dirs(Opts) -> extra_src_dirs(Opts, []). +%% @doc same as `src_dirs/2', but for the `extra_src_dirs' options -spec extra_src_dirs(rebar_dict(), list(file:filename_all())) -> list(file:filename_all()). extra_src_dirs(Opts, Default) -> + src_dirs(extra_src_dirs, Opts, Default). + +%% @private agnostic version of src_dirs and extra_src_dirs. +src_dirs(Type, Opts, Default) -> + lists:usort([ + case D0 of + {D,_} -> normalize_relative_path(D); + _ -> normalize_relative_path(D0) + end || D0 <- raw_src_dirs(Type,Opts,Default)]). + +%% @private extracts the un-formatted src_dirs or extra_src_dirs +%% options as configured. +raw_src_dirs(Type, Opts, Default) -> ErlOpts = rebar_opts:erl_opts(Opts), - Vs = proplists:get_all_values(extra_src_dirs, ErlOpts), - case lists:append([rebar_opts:get(Opts, extra_src_dirs, []) | Vs]) of + Vs = proplists:get_all_values(Type, ErlOpts), + case lists:append([rebar_opts:get(Opts, Type, []) | Vs]) of [] -> Default; - Dirs -> lists:usort(Dirs) + Dirs -> Dirs end. +%% @private normalizes relative paths so that ./a/b/c/ => a/b/c +normalize_relative_path(Path) -> + make_normalized_path(filename:split(Path), []). + +%% @doc returns all the source directories (`src_dirs' and +%% `extra_src_dirs'). -spec all_src_dirs(rebar_dict()) -> list(file:filename_all()). all_src_dirs(Opts) -> all_src_dirs(Opts, [], []). +%% @doc returns all the source directories (`src_dirs' and +%% `extra_src_dirs') while being able to configure defaults for both. -spec all_src_dirs(rebar_dict(), list(file:filename_all()), list(file:filename_all())) -> list(file:filename_all()). all_src_dirs(Opts, SrcDefault, ExtraDefault) -> lists:usort(src_dirs(Opts, SrcDefault) ++ extra_src_dirs(Opts, ExtraDefault)). -%% given a path if that path is an ancestor of an app dir return the path relative to that -%% apps outdir. if the path is not an ancestor to any app dirs but is an ancestor of the -%% project root return the path relative to the project base_dir. if it is not an ancestor +%%% @doc +%%% Return the list of options for the given src directory +%%% If the same option is given multiple times for a directory in the +%%% config, the priority order is: first occurence of `src_dirs' +%%% followed by first occurence of `extra_src_dirs'. +-spec src_dir_opts(rebar_dict(), file:filename_all()) -> [{atom(),term()}]. +src_dir_opts(Opts, Dir) -> + RawSrcDirs = raw_src_dirs(src_dirs, Opts, []), + RawExtraSrcDirs = raw_src_dirs(extra_src_dirs, Opts, []), + AllOpts = [Opt || {D, Opt} <- RawSrcDirs++RawExtraSrcDirs, D==Dir], + lists:ukeysort(1, proplists:unfold(lists:append(AllOpts))). + +%%% @doc +%%% Return the value of the 'recursive' option for the given directory. +%%% If not given, the value of 'recursive' in the 'erlc_compiler' +%%% options is used, and finally the default is 'true'. +-spec recursive(rebar_dict(), file:filename_all()) -> boolean(). +recursive(Opts, Dir) -> + DirOpts = src_dir_opts(Opts, Dir), + Default = proplists:get_value(recursive, + rebar_opts:get(Opts, erlc_compiler, []), + true), + R = proplists:get_value(recursive, DirOpts, Default), + R. + +%% @doc given a path if that path is an ancestor of an app dir, return the path relative to that +%% apps outdir. If the path is not an ancestor to any app dirs but is an ancestor of the +%% project root, return the path relative to the project base_dir. If it is not an ancestor %% of either return it unmodified -spec retarget_path(rebar_state:t(), string()) -> string(). - retarget_path(State, Path) -> ProjectApps = rebar_state:project_apps(State), retarget_path(State, Path, ProjectApps). +%% @private worker for retarget_path/2 +%% @end %% not relative to any apps in project, check to see it's relative to %% project root retarget_path(State, Path, []) -> @@ -217,3 +343,39 @@ retarget_path(State, Path, [App|Rest]) -> {ok, NewPath} -> filename:join([rebar_app_info:out_dir(App), NewPath]); {error, badparent} -> retarget_path(State, Path, Rest) end. + +format_source_file_name(Path, Opts) -> + Type = case rebar_opts:get(Opts, compiler_source_format, + ?DEFAULT_COMPILER_SOURCE_FORMAT) of + V when V == absolute; V == relative; V == build -> + V; + Other -> + warn_source_format_once(Other) + end, + case Type of + absolute -> resolve_linked_source(Path); + build -> Path; + relative -> + Cwd = rebar_dir:get_cwd(), + rebar_dir:make_relative_path(resolve_linked_source(Path), Cwd) + end. + +%% @private displays a warning for the compiler source format option +%% only once +-spec warn_source_format_once(term()) -> ok. +warn_source_format_once(Format) -> + Warn = application:get_env(rebar, warn_source_format) =/= {ok, false}, + application:set_env(rebar, warn_source_format, false), + case Warn of + false -> + ok; + true -> + ?WARN("Invalid argument ~p for compiler_source_format - " + "assuming ~ts~n", [Format, ?DEFAULT_COMPILER_SOURCE_FORMAT]) + end. + +%% @private takes a filename and canonicalizes its path if it is a link. +-spec resolve_linked_source(file:filename()) -> file:filename(). +resolve_linked_source(Src) -> + {Dir, Base} = rebar_file_utils:split_dirname(Src), + filename:join(rebar_file_utils:resolve_link(Dir), Base). |
