您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

305 行
11 KiB

15 年前
15 年前
15 年前
15 年前
15 年前
15 年前
15 年前
  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. ok;
  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. [filename:nativename(Target), filename:nativename(Source)]),
  93. [{use_stdout, false}, return_on_error]),
  94. case win32_ok(Res) of
  95. true -> ok;
  96. false ->
  97. {error, lists:flatten(
  98. io_lib:format("Failed to symlink ~s to ~s~n",
  99. [Source, Target]))}
  100. end.
  101. %% @doc Remove files and directories.
  102. %% Target is a single filename, directoryname or wildcard expression.
  103. -spec rm_rf(string()) -> 'ok'.
  104. rm_rf(Target) ->
  105. case os:type() of
  106. {unix, _} ->
  107. EscTarget = escape_path(Target),
  108. {ok, []} = rebar_utils:sh(?FMT("rm -rf ~s", [EscTarget]),
  109. [{use_stdout, false}, abort_on_error]),
  110. ok;
  111. {win32, _} ->
  112. Filelist = filelib:wildcard(Target),
  113. Dirs = [F || F <- Filelist, filelib:is_dir(F)],
  114. Files = Filelist -- Dirs,
  115. ok = delete_each(Files),
  116. ok = delete_each_dir_win32(Dirs),
  117. ok
  118. end.
  119. -spec cp_r(list(string()), file:filename()) -> 'ok'.
  120. cp_r([], _Dest) ->
  121. ok;
  122. cp_r(Sources, Dest) ->
  123. case os:type() of
  124. {unix, _} ->
  125. EscSources = [escape_path(Src) || Src <- Sources],
  126. SourceStr = string:join(EscSources, " "),
  127. {ok, []} = rebar_utils:sh(?FMT("cp -R ~s \"~s\"",
  128. [SourceStr, Dest]),
  129. [{use_stdout, false}, abort_on_error]),
  130. ok;
  131. {win32, _} ->
  132. lists:foreach(fun(Src) -> ok = cp_r_win32(Src,Dest) end, Sources),
  133. ok
  134. end.
  135. -spec mv(string(), file:filename()) -> 'ok'.
  136. mv(Source, Dest) ->
  137. case os:type() of
  138. {unix, _} ->
  139. EscSource = escape_path(Source),
  140. EscDest = escape_path(Dest),
  141. {ok, []} = rebar_utils:sh(?FMT("mv ~s ~s", [EscSource, EscDest]),
  142. [{use_stdout, false}, abort_on_error]),
  143. ok;
  144. {win32, _} ->
  145. Res = rebar_utils:sh(
  146. ?FMT("robocopy /move /s \"~s\" \"~s\" 1> nul",
  147. [filename:nativename(Source),
  148. filename:nativename(Dest)]),
  149. [{use_stdout, false}, return_on_error]),
  150. case win32_ok(Res) of
  151. true -> ok;
  152. false ->
  153. {error, lists:flatten(
  154. io_lib:format("Failed to move ~s to ~s~n",
  155. [Source, Dest]))}
  156. end
  157. end.
  158. win32_ok({ok, _}) -> true;
  159. win32_ok({error, {Rc, _}}) when Rc<9; Rc=:=16 -> true;
  160. win32_ok(_) -> false.
  161. delete_each([]) ->
  162. ok;
  163. delete_each([File | Rest]) ->
  164. case file:delete(File) of
  165. ok ->
  166. delete_each(Rest);
  167. {error, enoent} ->
  168. delete_each(Rest);
  169. {error, Reason} ->
  170. ?ERROR("Failed to delete file ~s: ~p\n", [File, Reason]),
  171. ?FAIL
  172. end.
  173. write_file_if_contents_differ(Filename, Bytes) ->
  174. ToWrite = iolist_to_binary(Bytes),
  175. case file:read_file(Filename) of
  176. {ok, ToWrite} ->
  177. ok;
  178. {ok, _} ->
  179. file:write_file(Filename, ToWrite);
  180. {error, _} ->
  181. file:write_file(Filename, ToWrite)
  182. end.
  183. %% returns an os appropriate tmpdir given a path
  184. -spec system_tmpdir() -> file:filename().
  185. -spec system_tmpdir(PathComponents) -> file:filename() when
  186. PathComponents :: [file:name()].
  187. system_tmpdir() -> system_tmpdir([]).
  188. system_tmpdir(PathComponents) ->
  189. Tmp = case erlang:system_info(system_architecture) of
  190. "win32" ->
  191. "./tmp";
  192. _SysArch ->
  193. "/tmp"
  194. end,
  195. filename:join([Tmp|PathComponents]).
  196. %% recursively removes a directory and then recreates the same
  197. %% directory but empty
  198. -spec reset_dir(Path) -> ok | {error, Reason} when
  199. Path :: file:name(),
  200. Reason :: file:posix().
  201. reset_dir(Path) ->
  202. %% delete the directory if it exists
  203. _ = ec_file:remove(Path, [recursive]),
  204. %% recreate the directory
  205. filelib:ensure_dir(filename:join([Path, "dummy.beam"])).
  206. %% Linux touch but using erlang functions to work in bot *nix os and
  207. %% windows
  208. -spec touch(Path) -> ok | {error, Reason} when
  209. Path :: file:name(),
  210. Reason :: file:posix().
  211. touch(Path) ->
  212. {ok, A} = file:read_file_info(Path),
  213. ok = file:write_file_info(Path, A#file_info{mtime = calendar:local_time(),
  214. atime = calendar:local_time()}).
  215. %% ===================================================================
  216. %% Internal functions
  217. %% ===================================================================
  218. delete_each_dir_win32([]) -> ok;
  219. delete_each_dir_win32([Dir | Rest]) ->
  220. {ok, []} = rebar_utils:sh(?FMT("rd /q /s \"~s\"",
  221. [filename:nativename(Dir)]),
  222. [{use_stdout, false}, return_on_error]),
  223. delete_each_dir_win32(Rest).
  224. xcopy_win32(Source,Dest)->
  225. %% "xcopy \"~s\" \"~s\" /q /y /e 2> nul", Chanegd to robocopy to
  226. %% handle long names. May have issues with older windows.
  227. Res = rebar_utils:sh(
  228. ?FMT("robocopy \"~s\" \"~s\" /e /is /purge 2> nul",
  229. [filename:nativename(Source), filename:nativename(Dest)]),
  230. [{use_stdout, false}, return_on_error]),
  231. case win32_ok(Res) of
  232. true -> ok;
  233. false ->
  234. {error, lists:flatten(
  235. io_lib:format("Failed to copy ~s to ~s~n",
  236. [Source, Dest]))}
  237. end.
  238. cp_r_win32({true, SourceDir}, {true, DestDir}) ->
  239. %% from directory to directory
  240. ok = case file:make_dir(DestDir) of
  241. {error, eexist} -> ok;
  242. Other -> Other
  243. end,
  244. ok = xcopy_win32(SourceDir, DestDir);
  245. cp_r_win32({false, Source} = S,{true, DestDir}) ->
  246. %% from file to directory
  247. cp_r_win32(S, {false, filename:join(DestDir, filename:basename(Source))});
  248. cp_r_win32({false, Source},{false, Dest}) ->
  249. %% from file to file
  250. {ok,_} = file:copy(Source, Dest),
  251. ok;
  252. cp_r_win32({true, SourceDir}, {false, DestDir}) ->
  253. case filelib:is_regular(DestDir) of
  254. true ->
  255. %% From directory to file? This shouldn't happen
  256. {error, lists:flatten(
  257. io_lib:format("Cannot copy dir (~p) to file (~p)\n",
  258. [SourceDir, DestDir]))};
  259. false ->
  260. %% Specifying a target directory that doesn't currently exist.
  261. %% So let's attempt to create this directory
  262. case filelib:ensure_dir(filename:join(DestDir, "dummy")) of
  263. ok ->
  264. ok = xcopy_win32(SourceDir, DestDir);
  265. {error, Reason} ->
  266. {error, lists:flatten(
  267. io_lib:format("Unable to create dir ~p: ~p\n",
  268. [DestDir, Reason]))}
  269. end
  270. end;
  271. cp_r_win32(Source,Dest) ->
  272. Dst = {filelib:is_dir(Dest), Dest},
  273. lists:foreach(fun(Src) ->
  274. ok = cp_r_win32({filelib:is_dir(Src), Src}, Dst)
  275. end, filelib:wildcard(Source)),
  276. ok.
  277. escape_path(Str) ->
  278. re:replace(Str, "([ ()?])", "\\\\&", [global, {return, list}]).