FileUtils.pm 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. # Copyright (C) 2003-2017 Free Software Foundation, Inc.
  2. # This program is free software; you can redistribute it and/or modify
  3. # it under the terms of the GNU General Public License as published by
  4. # the Free Software Foundation; either version 2, or (at your option)
  5. # any later version.
  6. # This program is distributed in the hope that it will be useful,
  7. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  9. # GNU General Public License for more details.
  10. # You should have received a copy of the GNU General Public License
  11. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  12. ###############################################################
  13. # The main copy of this file is in Automake's git repository. #
  14. # Updates should be sent to automake-patches@gnu.org. #
  15. ###############################################################
  16. package Automake::FileUtils;
  17. =head1 NAME
  18. Automake::FileUtils - handling files
  19. =head1 SYNOPSIS
  20. use Automake::FileUtils
  21. =head1 DESCRIPTION
  22. This perl module provides various general purpose file handling functions.
  23. =cut
  24. use 5.006;
  25. use strict;
  26. use Exporter;
  27. use File::stat;
  28. use IO::File;
  29. use Automake::Channels;
  30. use Automake::ChannelDefs;
  31. use vars qw (@ISA @EXPORT);
  32. @ISA = qw (Exporter);
  33. @EXPORT = qw (&contents
  34. &find_file &mtime
  35. &update_file &up_to_date_p
  36. &xsystem &xsystem_hint &xqx
  37. &dir_has_case_matching_file &reset_dir_cache
  38. &set_dir_cache_file);
  39. =item C<find_file ($file_name, @include)>
  40. Return the first path for a C<$file_name> in the C<include>s.
  41. We match exactly the behavior of GNU M4: first look in the current
  42. directory (which includes the case of absolute file names), and then,
  43. if the file name is not absolute, look in C<@include>.
  44. If the file is flagged as optional (ends with C<?>), then return undef
  45. if absent, otherwise exit with error.
  46. =cut
  47. # $FILE_NAME
  48. # find_file ($FILE_NAME, @INCLUDE)
  49. # --------------------------------
  50. sub find_file ($@)
  51. {
  52. use File::Spec;
  53. my ($file_name, @include) = @_;
  54. my $optional = 0;
  55. $optional = 1
  56. if $file_name =~ s/\?$//;
  57. return File::Spec->canonpath ($file_name)
  58. if -e $file_name;
  59. if (!File::Spec->file_name_is_absolute ($file_name))
  60. {
  61. foreach my $path (@include)
  62. {
  63. return File::Spec->canonpath (File::Spec->catfile ($path, $file_name))
  64. if -e File::Spec->catfile ($path, $file_name)
  65. }
  66. }
  67. fatal "$file_name: no such file or directory"
  68. unless $optional;
  69. return undef;
  70. }
  71. =item C<mtime ($file)>
  72. Return the mtime of C<$file>. Missing files, or C<-> standing for
  73. C<STDIN> or C<STDOUT> are "obsolete", i.e., as old as possible.
  74. =cut
  75. # $MTIME
  76. # MTIME ($FILE)
  77. # -------------
  78. sub mtime ($)
  79. {
  80. my ($file) = @_;
  81. return 0
  82. if $file eq '-' || ! -f $file;
  83. my $stat = stat ($file)
  84. or fatal "cannot stat $file: $!";
  85. return $stat->mtime;
  86. }
  87. =item C<update_file ($from, $to, [$force])>
  88. Rename C<$from> as C<$to>, preserving C<$to> timestamp if it has not
  89. changed, unless C<$force> is true (defaults to false). Recognize
  90. C<$to> = C<-> standing for C<STDIN>. C<$from> is always
  91. removed/renamed.
  92. =cut
  93. # &update_file ($FROM, $TO; $FORCE)
  94. # ---------------------------------
  95. sub update_file ($$;$)
  96. {
  97. my ($from, $to, $force) = @_;
  98. $force = 0
  99. unless defined $force;
  100. my $SIMPLE_BACKUP_SUFFIX = $ENV{'SIMPLE_BACKUP_SUFFIX'} || '~';
  101. use File::Compare;
  102. use File::Copy;
  103. if ($to eq '-')
  104. {
  105. my $in = new IO::File $from, "<";
  106. my $out = new IO::File (">-");
  107. while ($_ = $in->getline)
  108. {
  109. print $out $_;
  110. }
  111. $in->close;
  112. unlink ($from) || fatal "cannot remove $from: $!";
  113. return;
  114. }
  115. if (!$force && -f "$to" && compare ("$from", "$to") == 0)
  116. {
  117. # File didn't change, so don't update its mod time.
  118. msg 'note', "'$to' is unchanged";
  119. unlink ($from)
  120. or fatal "cannot remove $from: $!";
  121. return
  122. }
  123. if (-f "$to")
  124. {
  125. # Back up and install the new one.
  126. move ("$to", "$to$SIMPLE_BACKUP_SUFFIX")
  127. or fatal "cannot backup $to: $!";
  128. move ("$from", "$to")
  129. or fatal "cannot rename $from as $to: $!";
  130. msg 'note', "'$to' is updated";
  131. }
  132. else
  133. {
  134. move ("$from", "$to")
  135. or fatal "cannot rename $from as $to: $!";
  136. msg 'note', "'$to' is created";
  137. }
  138. }
  139. =item C<up_to_date_p ($file, @dep)>
  140. Is C<$file> more recent than C<@dep>?
  141. =cut
  142. # $BOOLEAN
  143. # &up_to_date_p ($FILE, @DEP)
  144. # ---------------------------
  145. sub up_to_date_p ($@)
  146. {
  147. my ($file, @dep) = @_;
  148. my $mtime = mtime ($file);
  149. foreach my $dep (@dep)
  150. {
  151. if ($mtime < mtime ($dep))
  152. {
  153. verb "up_to_date ($file): outdated: $dep";
  154. return 0;
  155. }
  156. }
  157. verb "up_to_date ($file): up to date";
  158. return 1;
  159. }
  160. =item C<handle_exec_errors ($command, [$expected_exit_code = 0], [$hint])>
  161. Display an error message for C<$command>, based on the content of
  162. C<$?> and C<$!>. Be quiet if the command exited normally
  163. with C<$expected_exit_code>. If C<$hint> is given, display that as well
  164. if the command failed to run at all.
  165. =cut
  166. sub handle_exec_errors ($;$$)
  167. {
  168. my ($command, $expected, $hint) = @_;
  169. $expected = 0 unless defined $expected;
  170. if (defined $hint)
  171. {
  172. $hint = "\n" . $hint;
  173. }
  174. else
  175. {
  176. $hint = '';
  177. }
  178. $command = (split (' ', $command))[0];
  179. if ($!)
  180. {
  181. fatal "failed to run $command: $!" . $hint;
  182. }
  183. else
  184. {
  185. use POSIX qw (WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG);
  186. if (WIFEXITED ($?))
  187. {
  188. my $status = WEXITSTATUS ($?);
  189. # Propagate exit codes.
  190. fatal ('',
  191. "$command failed with exit status: $status",
  192. exit_code => $status)
  193. unless $status == $expected;
  194. }
  195. elsif (WIFSIGNALED ($?))
  196. {
  197. my $signal = WTERMSIG ($?);
  198. fatal "$command terminated by signal: $signal";
  199. }
  200. else
  201. {
  202. fatal "$command exited abnormally";
  203. }
  204. }
  205. }
  206. =item C<xqx ($command)>
  207. Same as C<qx> (but in scalar context), but fails on errors.
  208. =cut
  209. # xqx ($COMMAND)
  210. # --------------
  211. sub xqx ($)
  212. {
  213. my ($command) = @_;
  214. verb "running: $command";
  215. $! = 0;
  216. my $res = `$command`;
  217. handle_exec_errors $command
  218. if $?;
  219. return $res;
  220. }
  221. =item C<xsystem (@argv)>
  222. Same as C<system>, but fails on errors, and reports the C<@argv>
  223. in verbose mode.
  224. =cut
  225. sub xsystem (@)
  226. {
  227. my (@command) = @_;
  228. verb "running: @command";
  229. $! = 0;
  230. handle_exec_errors "@command"
  231. if system @command;
  232. }
  233. =item C<xsystem_hint ($msg, @argv)>
  234. Same as C<xsystem>, but allows to pass a hint that will be displayed
  235. in case the command failed to run at all.
  236. =cut
  237. sub xsystem_hint (@)
  238. {
  239. my ($hint, @command) = @_;
  240. verb "running: @command";
  241. $! = 0;
  242. handle_exec_errors "@command", 0, $hint
  243. if system @command;
  244. }
  245. =item C<contents ($file_name)>
  246. Return the contents of C<$file_name>.
  247. =cut
  248. # contents ($FILE_NAME)
  249. # ---------------------
  250. sub contents ($)
  251. {
  252. my ($file) = @_;
  253. verb "reading $file";
  254. local $/; # Turn on slurp-mode.
  255. my $f = new Automake::XFile $file, "<";
  256. my $contents = $f->getline;
  257. $f->close;
  258. return $contents;
  259. }
  260. =item C<dir_has_case_matching_file ($DIRNAME, $FILE_NAME)>
  261. Return true iff $DIR contains a file name that matches $FILE_NAME case
  262. insensitively.
  263. We need to be cautious on case-insensitive case-preserving file
  264. systems (e.g. Mac OS X's HFS+). On such systems C<-f 'Foo'> and C<-f
  265. 'foO'> answer the same thing. Hence if a package distributes its own
  266. F<CHANGELOG> file, but has no F<ChangeLog> file, automake would still
  267. try to distribute F<ChangeLog> (because it thinks it exists) in
  268. addition to F<CHANGELOG>, although it is impossible for these two
  269. files to be in the same directory (the two file names designate the
  270. same file).
  271. =cut
  272. use vars '%_directory_cache';
  273. sub dir_has_case_matching_file ($$)
  274. {
  275. # Note that print File::Spec->case_tolerant returns 0 even on MacOS
  276. # X (with Perl v5.8.1-RC3 at least), so do not try to shortcut this
  277. # function using that.
  278. my ($dirname, $file_name) = @_;
  279. return 0 unless -f "$dirname/$file_name";
  280. # The file appears to exist, however it might be a mirage if the
  281. # system is case insensitive. Let's browse the directory and check
  282. # whether the file is really in. We maintain a cache of directories
  283. # so Automake doesn't spend all its time reading the same directory
  284. # again and again.
  285. if (!exists $_directory_cache{$dirname})
  286. {
  287. error "failed to open directory '$dirname'"
  288. unless opendir (DIR, $dirname);
  289. $_directory_cache{$dirname} = { map { $_ => 1 } readdir (DIR) };
  290. closedir (DIR);
  291. }
  292. return exists $_directory_cache{$dirname}{$file_name};
  293. }
  294. =item C<reset_dir_cache ($dirname)>
  295. Clear C<dir_has_case_matching_file>'s cache for C<$dirname>.
  296. =cut
  297. sub reset_dir_cache ($)
  298. {
  299. delete $_directory_cache{$_[0]};
  300. }
  301. =item C<set_dir_cache_file ($dirname, $file_name)>
  302. State that C<$dirname> contains C<$file_name> now.
  303. =cut
  304. sub set_dir_cache_file ($$)
  305. {
  306. my ($dirname, $file_name) = @_;
  307. $_directory_cache{$dirname}{$file_name} = 1
  308. if exists $_directory_cache{$dirname};
  309. }
  310. 1; # for require
  311. ### Setup "GNU" style for perl-mode and cperl-mode.
  312. ## Local Variables:
  313. ## perl-indent-level: 2
  314. ## perl-continued-statement-offset: 2
  315. ## perl-continued-brace-offset: 0
  316. ## perl-brace-offset: 0
  317. ## perl-brace-imaginary-offset: 0
  318. ## perl-label-offset: -2
  319. ## cperl-indent-level: 2
  320. ## cperl-brace-offset: 0
  321. ## cperl-continued-brace-offset: 0
  322. ## cperl-label-offset: -2
  323. ## cperl-extra-newline-before-brace: t
  324. ## cperl-merge-trailing-else: nil
  325. ## cperl-continued-statement-offset: 2
  326. ## End: