25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

320 lines
12 KiB

15 년 전
15 년 전
15 년 전
13 년 전
15 년 전
15 년 전
13 년 전
15 년 전
15 년 전
13 년 전
14 년 전
14 년 전
14 년 전
  1. %% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
  2. %% ex: ts=4 sw=4 et
  3. %% -------------------------------------------------------------------
  4. %%
  5. %% rebar: Erlang Build Tools
  6. %%
  7. %% Copyright (c) 2009 Dave Smith (dizzyd@dizzyd.com)
  8. %%
  9. %% Permission is hereby granted, free of charge, to any person obtaining a copy
  10. %% of this software and associated documentation files (the "Software"), to deal
  11. %% in the Software without restriction, including without limitation the rights
  12. %% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. %% copies of the Software, and to permit persons to whom the Software is
  14. %% furnished to do so, subject to the following conditions:
  15. %%
  16. %% The above copyright notice and this permission notice shall be included in
  17. %% all copies or substantial portions of the Software.
  18. %%
  19. %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. %% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. %% THE SOFTWARE.
  26. %% -------------------------------------------------------------------
  27. -module(rebar_file_utils).
  28. -export([try_consult/1,
  29. format_error/1,
  30. symlink_or_copy/2,
  31. rm_rf/1,
  32. cp_r/2,
  33. mv/2,
  34. delete_each/1,
  35. write_file_if_contents_differ/2,
  36. system_tmpdir/0,
  37. system_tmpdir/1,
  38. reset_dir/1,
  39. touch/1]).
  40. -include("rebar.hrl").
  41. -include_lib("providers/include/providers.hrl").
  42. -include_lib("kernel/include/file.hrl").
  43. %% ===================================================================
  44. %% Public API
  45. %% ===================================================================
  46. try_consult(File) ->
  47. case file:consult(File) of
  48. {ok, Terms} ->
  49. Terms;
  50. {error, enoent} ->
  51. [];
  52. {error, Reason} ->
  53. throw(?PRV_ERROR({bad_term_file, File, Reason}))
  54. end.
  55. format_error({bad_term_file, AppFile, Reason}) ->
  56. io_lib:format("Error reading file ~s: ~s", [AppFile, file:format_error(Reason)]).
  57. symlink_or_copy(Source, Target) ->
  58. Link = case os:type() of
  59. {win32, _} ->
  60. Source;
  61. _ ->
  62. rebar_dir:make_relative_path(Source, Target)
  63. end,
  64. case file:make_symlink(Link, Target) of
  65. ok ->
  66. ok;
  67. {error, eexist} ->
  68. exists;
  69. {error, _} ->
  70. case os:type() of
  71. {win32, _} ->
  72. S = unicode:characters_to_list(Source),
  73. T = unicode:characters_to_list(Target),
  74. case filelib:is_dir(S) of
  75. true ->
  76. win32_symlink(S, T);
  77. false ->
  78. cp_r([S], T)
  79. end;
  80. _ ->
  81. case filelib:is_dir(Target) of
  82. true ->
  83. ok;
  84. false ->
  85. cp_r([Source], Target)
  86. end
  87. end
  88. end.
  89. win32_symlink(Source, Target) ->
  90. Res = rebar_utils:sh(
  91. ?FMT("cmd /c mklink /j \"~s\" \"~s\"",
  92. [rebar_utils:escape_double_quotes(filename:nativename(Target)),
  93. rebar_utils:escape_double_quotes(filename:nativename(Source))]),
  94. [{use_stdout, false}, return_on_error]),
  95. case win32_ok(Res) of
  96. true -> ok;
  97. false ->
  98. {error, lists:flatten(
  99. io_lib:format("Failed to symlink ~s to ~s~n",
  100. [Source, Target]))}
  101. end.
  102. %% @doc Remove files and directories.
  103. %% Target is a single filename, directoryname or wildcard expression.
  104. -spec rm_rf(string()) -> 'ok'.
  105. rm_rf(Target) ->
  106. case os:type() of
  107. {unix, _} ->
  108. EscTarget = rebar_utils:escape_chars(Target),
  109. {ok, []} = rebar_utils:sh(?FMT("rm -rf ~s", [EscTarget]),
  110. [{use_stdout, false}, abort_on_error]),
  111. ok;
  112. {win32, _} ->
  113. Filelist = filelib:wildcard(Target),
  114. Dirs = [F || F <- Filelist, filelib:is_dir(F)],
  115. Files = Filelist -- Dirs,
  116. ok = delete_each(Files),
  117. ok = delete_each_dir_win32(Dirs),
  118. ok
  119. end.
  120. -spec cp_r(list(string()), file:filename()) -> 'ok'.
  121. cp_r([], _Dest) ->
  122. ok;
  123. cp_r(Sources, Dest) ->
  124. case os:type() of
  125. {unix, _} ->
  126. EscSources = [rebar_utils:escape_chars(Src) || Src <- Sources],
  127. SourceStr = string:join(EscSources, " "),
  128. {ok, []} = rebar_utils:sh(?FMT("cp -R ~s \"~s\"",
  129. [SourceStr, rebar_utils:escape_double_quotes(Dest)]),
  130. [{use_stdout, false}, abort_on_error]),
  131. ok;
  132. {win32, _} ->
  133. lists:foreach(fun(Src) -> ok = cp_r_win32(Src,Dest) end, Sources),
  134. ok
  135. end.
  136. -spec mv(string(), file:filename()) -> 'ok'.
  137. mv(Source, Dest) ->
  138. case os:type() of
  139. {unix, _} ->
  140. EscSource = rebar_utils:escape_chars(Source),
  141. EscDest = rebar_utils:escape_chars(Dest),
  142. {ok, []} = rebar_utils:sh(?FMT("mv ~s ~s", [EscSource, EscDest]),
  143. [{use_stdout, false}, abort_on_error]),
  144. ok;
  145. {win32, _} ->
  146. Cmd = case filelib:is_dir(Source) of
  147. true ->
  148. ?FMT("robocopy /move /s \"~s\" \"~s\" 1> nul",
  149. [rebar_utils:escape_double_quotes(filename:nativename(Source)),
  150. rebar_utils:escape_double_quotes(filename:nativename(Dest))]);
  151. false ->
  152. ?FMT("robocopy /move /s \"~s\" \"~s\" \"~s\" 1> nul",
  153. [rebar_utils:escape_double_quotes(filename:nativename(filename:dirname(Source))),
  154. rebar_utils:escape_double_quotes(filename:nativename(Dest)),
  155. rebar_utils:escape_double_quotes(filename:basename(Source))])
  156. end,
  157. Res = rebar_utils:sh(Cmd,
  158. [{use_stdout, false}, return_on_error]),
  159. case win32_ok(Res) of
  160. true -> ok;
  161. false ->
  162. {error, lists:flatten(
  163. io_lib:format("Failed to move ~s to ~s~n",
  164. [Source, Dest]))}
  165. end
  166. end.
  167. win32_ok({ok, _}) -> true;
  168. win32_ok({error, {Rc, _}}) when Rc<9; Rc=:=16 -> true;
  169. win32_ok(_) -> false.
  170. delete_each([]) ->
  171. ok;
  172. delete_each([File | Rest]) ->
  173. case file:delete(File) of
  174. ok ->
  175. delete_each(Rest);
  176. {error, enoent} ->
  177. delete_each(Rest);
  178. {error, Reason} ->
  179. ?ERROR("Failed to delete file ~s: ~p\n", [File, Reason]),
  180. ?FAIL
  181. end.
  182. write_file_if_contents_differ(Filename, Bytes) ->
  183. ToWrite = iolist_to_binary(Bytes),
  184. case file:read_file(Filename) of
  185. {ok, ToWrite} ->
  186. ok;
  187. {ok, _} ->
  188. file:write_file(Filename, ToWrite, [raw]);
  189. {error, _} ->
  190. file:write_file(Filename, ToWrite, [raw])
  191. end.
  192. %% returns an os appropriate tmpdir given a path
  193. -spec system_tmpdir() -> file:filename().
  194. -spec system_tmpdir(PathComponents) -> file:filename() when
  195. PathComponents :: [file:name()].
  196. system_tmpdir() -> system_tmpdir([]).
  197. system_tmpdir(PathComponents) ->
  198. Tmp = case erlang:system_info(system_architecture) of
  199. "win32" ->
  200. "./tmp";
  201. _SysArch ->
  202. "/tmp"
  203. end,
  204. filename:join([Tmp|PathComponents]).
  205. %% recursively removes a directory and then recreates the same
  206. %% directory but empty
  207. -spec reset_dir(Path) -> ok | {error, Reason} when
  208. Path :: file:name(),
  209. Reason :: file:posix().
  210. reset_dir(Path) ->
  211. %% delete the directory if it exists
  212. _ = ec_file:remove(Path, [recursive]),
  213. %% recreate the directory
  214. filelib:ensure_dir(filename:join([Path, "dummy.beam"])).
  215. %% Linux touch but using erlang functions to work in bot *nix os and
  216. %% windows
  217. -spec touch(Path) -> ok | {error, Reason} when
  218. Path :: file:name(),
  219. Reason :: file:posix().
  220. touch(Path) ->
  221. {ok, A} = file:read_file_info(Path),
  222. ok = file:write_file_info(Path, A#file_info{mtime = calendar:local_time(),
  223. atime = calendar:local_time()}).
  224. %% ===================================================================
  225. %% Internal functions
  226. %% ===================================================================
  227. delete_each_dir_win32([]) -> ok;
  228. delete_each_dir_win32([Dir | Rest]) ->
  229. {ok, []} = rebar_utils:sh(?FMT("rd /q /s \"~s\"",
  230. [rebar_utils:escape_double_quotes(filename:nativename(Dir))]),
  231. [{use_stdout, false}, return_on_error]),
  232. delete_each_dir_win32(Rest).
  233. xcopy_win32(Source,Dest)->
  234. %% "xcopy \"~s\" \"~s\" /q /y /e 2> nul", Chanegd to robocopy to
  235. %% handle long names. May have issues with older windows.
  236. Cmd = case filelib:is_dir(Source) of
  237. true ->
  238. ?FMT("robocopy \"~s\" \"~s\" /e /is 1> nul",
  239. [rebar_utils:escape_double_quotes(filename:nativename(Source)),
  240. rebar_utils:escape_double_quotes(filename:nativename(Dest))]);
  241. false ->
  242. ?FMT("robocopy \"~s\" \"~s\" \"~s\" /e /is 1> nul",
  243. [rebar_utils:escape_double_quotes(filename:nativename(filename:dirname(Source))),
  244. rebar_utils:escape_double_quotes(filename:nativename(Dest)),
  245. rebar_utils:escape_double_quotes(filename:basename(Source))])
  246. end,
  247. Res = rebar_utils:sh(Cmd,
  248. [{use_stdout, false}, return_on_error]),
  249. case win32_ok(Res) of
  250. true -> ok;
  251. false ->
  252. {error, lists:flatten(
  253. io_lib:format("Failed to copy ~s to ~s~n",
  254. [Source, Dest]))}
  255. end.
  256. cp_r_win32({true, SourceDir}, {true, DestDir}) ->
  257. %% from directory to directory
  258. ok = case file:make_dir(DestDir) of
  259. {error, eexist} -> ok;
  260. Other -> Other
  261. end,
  262. ok = xcopy_win32(SourceDir, DestDir);
  263. cp_r_win32({false, Source} = S,{true, DestDir}) ->
  264. %% from file to directory
  265. cp_r_win32(S, {false, filename:join(DestDir, filename:basename(Source))});
  266. cp_r_win32({false, Source},{false, Dest}) ->
  267. %% from file to file
  268. {ok,_} = file:copy(Source, Dest),
  269. ok;
  270. cp_r_win32({true, SourceDir}, {false, DestDir}) ->
  271. case filelib:is_regular(DestDir) of
  272. true ->
  273. %% From directory to file? This shouldn't happen
  274. {error, lists:flatten(
  275. io_lib:format("Cannot copy dir (~p) to file (~p)\n",
  276. [SourceDir, DestDir]))};
  277. false ->
  278. %% Specifying a target directory that doesn't currently exist.
  279. %% So let's attempt to create this directory
  280. case filelib:ensure_dir(filename:join(DestDir, "dummy")) of
  281. ok ->
  282. ok = xcopy_win32(SourceDir, DestDir);
  283. {error, Reason} ->
  284. {error, lists:flatten(
  285. io_lib:format("Unable to create dir ~p: ~p\n",
  286. [DestDir, Reason]))}
  287. end
  288. end;
  289. cp_r_win32(Source,Dest) ->
  290. Dst = {filelib:is_dir(Dest), Dest},
  291. lists:foreach(fun(Src) ->
  292. ok = cp_r_win32({filelib:is_dir(Src), Src}, Dst)
  293. end, filelib:wildcard(Source)),
  294. ok.