@ -179,6 +179,8 @@ bootstrap_rebar3() ->
-define(FMT(Str, Args), lists:flatten(io_lib:format(Str, Args))).
-define(FMT(Str, Args), lists:flatten(io_lib:format(Str, Args))).
%%/rebar.hrl
%%/rebar.hrl
%%rebar_file_utils
%%rebar_file_utils
-include_lib("kernel/include/file.hrl").
symlink_or_copy(Source, Target) ->
symlink_or_copy(Source, Target) ->
Link = case os:type() of
Link = case os:type() of
{win32, _} ->
{win32, _} ->
@ -190,55 +192,63 @@ symlink_or_copy(Source, Target) ->
ok ->
ok ->
ok;
ok;
{error, eexist} ->
{error, eexist} ->
ok ;
exists ;
{error, _} ->
{error, _} ->
cp_r([Source], Target)
case os:type() of
{win32, _} ->
S = unicode:characters_to_list(Source),
T = unicode:characters_to_list(Target),
case filelib:is_dir(S) of
true ->
win32_symlink_or_copy(S, T);
false ->
cp_r([S], T)
end;
_ ->
case filelib:is_dir(Target) of
true ->
ok;
false ->
cp_r([Source], Target)
end
end
end.
end.
make_relative_path(Source, Target) ->
do_make_relative_path(filename:split(Source), filename:split(Target)).
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).
-spec cp_r(list(string()), file:filename()) -> 'ok'.
cp_r([], _Dest) ->
cp_r([], _Dest) ->
ok;
ok;
cp_r(Sources, Dest) ->
cp_r(Sources, Dest) ->
case os:type() of
case os:type() of
{unix, _} ->
{unix, _} ->
EscSources = [escape_path(Src) || Src <- Sources],
EscSources = [escape_chars(Src) || Src <- Sources],
SourceStr = join(EscSources, " "),
SourceStr = join(EscSources, " "),
os:cmd(?FMT("cp -R ~s \"~s\"", [SourceStr, Dest])),
{ok, []} = sh(?FMT("cp -Rp ~ts \"~ts\"",
[SourceStr, escape_double_quotes(Dest)]),
[{use_stdout, false}, abort_on_error]),
ok;
ok;
{win32, _} ->
{win32, _} ->
lists:foreach(fun(Src) -> ok = cp_r_win32(Src,Dest) end, Sources),
lists:foreach(fun(Src) -> ok = cp_r_win32(Src,Dest) end, Sources),
ok
ok
end.
end.
xcopy_win32(Source,Dest)->
R = os:cmd(?FMT("xcopy \"~s\" \"~s\" /q /y /e 2> nul",
[filename:nativename(Source), filename:nativename(Dest)])),
case length(R) > 0 of
%% when xcopy fails, stdout is empty and and error message is printed
%% to stderr (which is redirected to nul)
%% @private Compatibility function for windows
win32_symlink_or_copy(Source, Target) ->
Res = sh(?FMT("cmd /c mklink /j \"~ts\" \"~ts\"",
[escape_double_quotes(filename:nativename(Target)),
escape_double_quotes(filename:nativename(Source))]),
[{use_stdout, false}, return_on_error]),
case win32_mklink_ok(Res, Target) of
true -> ok;
true -> ok;
false ->
{error, lists:flatten(
io_lib:format("Failed to xcopy from ~s to ~s~n",
[Source, Dest]))}
false -> cp_r_win32(Source, drop_last_dir_from_path(Target))
end.
end.
cp_r_win32({true, SourceDir}, {true, DestDir}) ->
cp_r_win32({true, SourceDir}, {true, DestDir}) ->
%% from directory to directory
%% from directory to directory
SourceBase = filename:basename(SourceDir),
ok = case file:make_dir(filename:join(DestDir, SourceBase)) of
ok = case file:make_dir(DestDir) of
{error, eexist} -> ok;
{error, eexist} -> ok;
Other -> Other
Other -> Other
end,
end,
ok = xcopy_win32(SourceDir, filename:join( DestDir, SourceBase) );
ok = xcopy_win32(SourceDir, DestDir);
cp_r_win32({false, Source} = S,{true, DestDir}) ->
cp_r_win32({false, Source} = S,{true, DestDir}) ->
%% from file to directory
%% from file to directory
cp_r_win32(S, {false, filename:join(DestDir, filename:basename(Source))});
cp_r_win32(S, {false, filename:join(DestDir, filename:basename(Source))});
@ -272,10 +282,231 @@ cp_r_win32(Source,Dest) ->
end, filelib:wildcard(Source)),
end, filelib:wildcard(Source)),
ok.
ok.
escape_path(Str) ->
re:replace(Str, "([ ()?])", "\\\\&", [global, {return, list}]).
%% drops the last 'node' of the filename, presumably the last dir such as 'src'
%% this is because cp_r_win32/2 automatically adds the dir name, to appease
%% robocopy and be more uniform with POSIX
drop_last_dir_from_path([]) ->
[];
drop_last_dir_from_path(Path) ->
case lists:droplast(filename:split(Path)) of
[] -> [];
Dirs -> filename:join(Dirs)
end.
%% @private specifically pattern match against the output
%% of the windows 'mklink' shell call; different values from
%% what win32_ok/1 handles
win32_mklink_ok({ok, _}, _) ->
true;
win32_mklink_ok({error,{1,"Local NTFS volumes are required to complete the operation.\n"}}, _) ->
false;
win32_mklink_ok({error,{1,"Cannot create a file when that file already exists.\n"}}, Target) ->
% File or dir is already in place; find if it is already a symlink (true) or
% if it is a directory (copy-required; false)
is_symlink(Target);
win32_mklink_ok(_, _) ->
false.
xcopy_win32(Source,Dest)->
%% "xcopy \"~ts\" \"~ts\" /q /y /e 2> nul", Changed to robocopy to
%% handle long names. May have issues with older windows.
Cmd = case filelib:is_dir(Source) of
true ->
%% For robocopy, copying /a/b/c/ to /d/e/f/ recursively does not
%% create /d/e/f/c/*, but rather copies all files to /d/e/f/*.
%% The usage we make here expects the former, not the later, so we
%% must manually add the last fragment of a directory to the `Dest`
%% in order to properly replicate POSIX platforms
NewDest = filename:join([Dest, filename:basename(Source)]),
?FMT("robocopy \"~ts\" \"~ts\" /e 1> nul",
[escape_double_quotes(filename:nativename(Source)),
escape_double_quotes(filename:nativename(NewDest))]);
false ->
?FMT("robocopy \"~ts\" \"~ts\" \"~ts\" /e 1> nul",
[escape_double_quotes(filename:nativename(filename:dirname(Source))),
escape_double_quotes(filename:nativename(Dest)),
escape_double_quotes(filename:basename(Source))])
end,
Res = sh(Cmd, [{use_stdout, false}, return_on_error]),
case win32_ok(Res) of
true -> ok;
false ->
{error, lists:flatten(
io_lib:format("Failed to copy ~ts to ~ts~n",
[Source, Dest]))}
end.
is_symlink(Filename) ->
{ok, Info} = file:read_link_info(Filename),
Info#file_info.type == symlink.
win32_ok({ok, _}) -> true;
win32_ok({error, {Rc, _}}) when Rc<9; Rc=:=16 -> true;
win32_ok(_) -> false.
%%/rebar_file_utils
%%/rebar_file_utils
%%rebar_utils
%% escape\ as\ a\ shell\?
escape_chars(Str) when is_atom(Str) ->
escape_chars(atom_to_list(Str));
escape_chars(Str) ->
re:replace(Str, "([ ()?`!$&;\"\'])", "\\\\&",
[global, {return, list}, unicode]).
%% "escape inside these"
escape_double_quotes(Str) ->
re:replace(Str, "([\"\\\\`!$&*;])", "\\\\&",
[global, {return, list}, unicode]).
sh(Command0, Options0) ->
DefaultOptions = [{use_stdout, false}],
Options = [expand_sh_flag(V)
|| V <- proplists:compact(Options0 ++ DefaultOptions)],
ErrorHandler = proplists:get_value(error_handler, Options),
OutputHandler = proplists:get_value(output_handler, Options),
Command = lists:flatten(patch_on_windows(Command0, proplists:get_value(env, Options, []))),
PortSettings = proplists:get_all_values(port_settings, Options) ++
[exit_status, {line, 16384}, use_stdio, stderr_to_stdout, hide, eof],
Port = open_port({spawn, Command}, PortSettings),
try
case sh_loop(Port, OutputHandler, []) of
{ok, _Output} = Ok ->
Ok;
{error, {_Rc, _Output}=Err} ->
ErrorHandler(Command, Err)
end
after
port_close(Port)
end.
sh_loop(Port, Fun, Acc) ->
receive
{Port, {data, {eol, Line}}} ->
sh_loop(Port, Fun, Fun(Line ++ "\n", Acc));
{Port, {data, {noeol, Line}}} ->
sh_loop(Port, Fun, Fun(Line, Acc));
{Port, eof} ->
Data = lists:flatten(lists:reverse(Acc)),
receive
{Port, {exit_status, 0}} ->
{ok, Data};
{Port, {exit_status, Rc}} ->
{error, {Rc, Data}}
end
end.
expand_sh_flag(return_on_error) ->
{error_handler,
fun(_Command, Err) ->
{error, Err}
end};
expand_sh_flag(abort_on_error) ->
{error_handler,
fun log_and_abort/2};
expand_sh_flag({use_stdout, false}) ->
{output_handler,
fun(Line, Acc) ->
[Line | Acc]
end};
expand_sh_flag({cd, _CdArg} = Cd) ->
{port_settings, Cd};
expand_sh_flag({env, _EnvArg} = Env) ->
{port_settings, Env}.
%% We do the shell variable substitution ourselves on Windows and hope that the
%% command doesn't use any other shell magic.
patch_on_windows(Cmd, Env) ->
case os:type() of
{win32,nt} ->
Cmd1 = "cmd /q /c "
++ lists:foldl(fun({Key, Value}, Acc) ->
expand_env_variable(Acc, Key, Value)
end, Cmd, Env),
%% Remove left-over vars
re:replace(Cmd1, "\\\$\\w+|\\\${\\w+}", "",
[global, {return, list}, unicode]);
_ ->
Cmd
end.
%% @doc Given env. variable `FOO' we want to expand all references to
%% it in `InStr'. References can have two forms: `$FOO' and `${FOO}'
%% The end of form `$FOO' is delimited with whitespace or EOL
-spec expand_env_variable(string(), string(), term()) -> string().
expand_env_variable(InStr, VarName, RawVarValue) ->
case chr(InStr, $$) of
0 ->
%% No variables to expand
InStr;
_ ->
ReOpts = [global, unicode, {return, list}],
VarValue = re:replace(RawVarValue, "\\\\", "\\\\\\\\", ReOpts),
%% Use a regex to match/replace:
%% Given variable "FOO": match $FOO\s | $FOOeol | ${FOO}
RegEx = io_lib:format("\\\$(~ts(\\W|$)|{~ts})", [VarName, VarName]),
re:replace(InStr, RegEx, [VarValue, "\\2"], ReOpts)
end.
-spec log_and_abort(string(), {integer(), string()}) -> no_return().
log_and_abort(Command, {Rc, Output}) ->
io:format("sh(~ts)~n"
"failed with return code ~w and the following output:~n"
"~ts", [Command, Rc, Output]),
throw(bootstrap_abort).
%%/rebar_utils
%%rebar_dir
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).
make_normalized_path(Path) ->
AbsPath = make_absolute_path(Path),
Components = filename:split(AbsPath),
make_normalized_path(Components, []).
make_absolute_path(Path) ->
case filename:pathtype(Path) of
absolute ->
Path;
relative ->
{ok, Dir} = file:get_cwd(),
filename:join([Dir, Path]);
volumerelative ->
Volume = hd(filename:split(Path)),
{ok, Dir} = file:get_cwd(Volume),
filename:join([Dir, Path])
end.
-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);
".." when NormalizedPath == [] -> make_normalized_path(T, [".."]);
".." when hd(NormalizedPath) =/= ".." -> make_normalized_path(T, tl(NormalizedPath));
_ -> make_normalized_path(T, [H|NormalizedPath])
end.
%%/rebar_dir
setup_env() ->
setup_env() ->
%% We don't need or want relx providers loaded yet
%% We don't need or want relx providers loaded yet
application:load(rebar),
application:load(rebar),
@ -412,3 +643,9 @@ join([], Sep) when is_list(Sep) ->
[];
[];
join([H|T], Sep) ->
join([H|T], Sep) ->
H ++ lists:append([Sep ++ X || X <- T]).
H ++ lists:append([Sep ++ X || X <- T]).
%% Same for chr; no non-deprecated equivalent in OTP20+
chr(S, C) when is_integer(C) -> chr(S, C, 1).
chr([C|_Cs], C, I) -> I;
chr([_|Cs], C, I) -> chr(Cs, C, I+1);
chr([], _C, _I) -> 0.