diff options
Diffstat (limited to 'lib/Autom4te/FileUtils.pm')
-rw-r--r-- | lib/Autom4te/FileUtils.pm | 452 |
1 files changed, 452 insertions, 0 deletions
diff --git a/lib/Autom4te/FileUtils.pm b/lib/Autom4te/FileUtils.pm new file mode 100644 index 0000000..30bbdb9 --- /dev/null +++ b/lib/Autom4te/FileUtils.pm @@ -0,0 +1,452 @@ +# Copyright (C) 2003-2012 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +############################################################### +# The main copy of this file is in Automake's git repository. # +# Updates should be sent to automake-patches@gnu.org. # +############################################################### + +package Autom4te::FileUtils; + +=head1 NAME + +Autom4te::FileUtils - handling files + +=head1 SYNOPSIS + + use Autom4te::FileUtils + +=head1 DESCRIPTION + +This perl module provides various general purpose file handling functions. + +=cut + +use 5.006; +use strict; +use Exporter; +use File::stat; +use IO::File; +use Autom4te::Channels; +use Autom4te::ChannelDefs; + +use vars qw (@ISA @EXPORT); + +@ISA = qw (Exporter); +@EXPORT = qw (&open_quote &contents + &find_file &mtime + &update_file &up_to_date_p + &xsystem &xsystem_hint &xqx + &dir_has_case_matching_file &reset_dir_cache + &set_dir_cache_file); + + +=item C<open_quote ($file_name)> + +Quote C<$file_name> for open. + +=cut + +# $FILE_NAME +# open_quote ($FILE_NAME) +# ----------------------- +# If the string $S is a well-behaved file name, simply return it. +# If it starts with white space, prepend './', if it ends with +# white space, add '\0'. Return the new string. +sub open_quote($) +{ + my ($s) = @_; + if ($s =~ m/^\s/) + { + $s = "./$s"; + } + if ($s =~ m/\s$/) + { + $s = "$s\0"; + } + return $s; +} + +=item C<find_file ($file_name, @include)> + +Return the first path for a C<$file_name> in the C<include>s. + +We match exactly the behavior of GNU M4: first look in the current +directory (which includes the case of absolute file names), and then, +if the file name is not absolute, look in C<@include>. + +If the file is flagged as optional (ends with C<?>), then return undef +if absent, otherwise exit with error. + +=cut + +# $FILE_NAME +# find_file ($FILE_NAME, @INCLUDE) +# -------------------------------- +sub find_file ($@) +{ + use File::Spec; + + my ($file_name, @include) = @_; + my $optional = 0; + + $optional = 1 + if $file_name =~ s/\?$//; + + return File::Spec->canonpath ($file_name) + if -e $file_name; + + if (!File::Spec->file_name_is_absolute ($file_name)) + { + foreach my $path (@include) + { + return File::Spec->canonpath (File::Spec->catfile ($path, $file_name)) + if -e File::Spec->catfile ($path, $file_name) + } + } + + fatal "$file_name: no such file or directory" + unless $optional; + return undef; +} + +=item C<mtime ($file)> + +Return the mtime of C<$file>. Missing files, or C<-> standing for +C<STDIN> or C<STDOUT> are "obsolete", i.e., as old as possible. + +=cut + +# $MTIME +# MTIME ($FILE) +# ------------- +sub mtime ($) +{ + my ($file) = @_; + + return 0 + if $file eq '-' || ! -f $file; + + my $stat = stat ($file) + or fatal "cannot stat $file: $!"; + + return $stat->mtime; +} + + +=item C<update_file ($from, $to, [$force])> + +Rename C<$from> as C<$to>, preserving C<$to> timestamp if it has not +changed, unless C<$force> is true (defaults to false). Recognize +C<$to> = C<-> standing for C<STDIN>. C<$from> is always +removed/renamed. + +=cut + +# &update_file ($FROM, $TO; $FORCE) +# --------------------------------- +sub update_file ($$;$) +{ + my ($from, $to, $force) = @_; + $force = 0 + unless defined $force; + my $SIMPLE_BACKUP_SUFFIX = $ENV{'SIMPLE_BACKUP_SUFFIX'} || '~'; + use File::Compare; + use File::Copy; + + if ($to eq '-') + { + my $in = new IO::File ("< " . open_quote ($from)); + my $out = new IO::File (">-"); + while ($_ = $in->getline) + { + print $out $_; + } + $in->close; + unlink ($from) || fatal "cannot remove $from: $!"; + return; + } + + if (!$force && -f "$to" && compare ("$from", "$to") == 0) + { + # File didn't change, so don't update its mod time. + msg 'note', "'$to' is unchanged"; + unlink ($from) + or fatal "cannot remove $from: $!"; + return + } + + if (-f "$to") + { + # Back up and install the new one. + move ("$to", "$to$SIMPLE_BACKUP_SUFFIX") + or fatal "cannot backup $to: $!"; + move ("$from", "$to") + or fatal "cannot rename $from as $to: $!"; + msg 'note', "'$to' is updated"; + } + else + { + move ("$from", "$to") + or fatal "cannot rename $from as $to: $!"; + msg 'note', "'$to' is created"; + } +} + + +=item C<up_to_date_p ($file, @dep)> + +Is C<$file> more recent than C<@dep>? + +=cut + +# $BOOLEAN +# &up_to_date_p ($FILE, @DEP) +# --------------------------- +sub up_to_date_p ($@) +{ + my ($file, @dep) = @_; + my $mtime = mtime ($file); + + foreach my $dep (@dep) + { + if ($mtime < mtime ($dep)) + { + verb "up_to_date ($file): outdated: $dep"; + return 0; + } + } + + verb "up_to_date ($file): up to date"; + return 1; +} + + +=item C<handle_exec_errors ($command, [$expected_exit_code = 0], [$hint])> + +Display an error message for C<$command>, based on the content of +C<$?> and C<$!>. Be quiet if the command exited normally +with C<$expected_exit_code>. If C<$hint> is given, display that as well +if the command failed to run at all. + +=cut + +sub handle_exec_errors ($;$$) +{ + my ($command, $expected, $hint) = @_; + $expected = 0 unless defined $expected; + if (defined $hint) + { + $hint = "\n" . $hint; + } + else + { + $hint = ''; + } + + $command = (split (' ', $command))[0]; + if ($!) + { + fatal "failed to run $command: $!" . $hint; + } + else + { + use POSIX qw (WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG); + + if (WIFEXITED ($?)) + { + my $status = WEXITSTATUS ($?); + # Propagate exit codes. + fatal ('', + "$command failed with exit status: $status", + exit_code => $status) + unless $status == $expected; + } + elsif (WIFSIGNALED ($?)) + { + my $signal = WTERMSIG ($?); + fatal "$command terminated by signal: $signal"; + } + else + { + fatal "$command exited abnormally"; + } + } +} + +=item C<xqx ($command)> + +Same as C<qx> (but in scalar context), but fails on errors. + +=cut + +# xqx ($COMMAND) +# -------------- +sub xqx ($) +{ + my ($command) = @_; + + verb "running: $command"; + + $! = 0; + my $res = `$command`; + handle_exec_errors $command + if $?; + + return $res; +} + + +=item C<xsystem (@argv)> + +Same as C<system>, but fails on errors, and reports the C<@argv> +in verbose mode. + +=cut + +sub xsystem (@) +{ + my (@command) = @_; + + verb "running: @command"; + + $! = 0; + handle_exec_errors "@command" + if system @command; +} + + +=item C<xsystem_hint ($msg, @argv)> + +Same as C<xsystem>, but allows to pass a hint that will be displayed +in case the command failed to run at all. + +=cut + +sub xsystem_hint (@) +{ + my ($hint, @command) = @_; + + verb "running: @command"; + + $! = 0; + handle_exec_errors "@command", 0, $hint + if system @command; +} + + +=item C<contents ($file_name)> + +Return the contents of C<$file_name>. + +=cut + +# contents ($FILE_NAME) +# --------------------- +sub contents ($) +{ + my ($file) = @_; + verb "reading $file"; + local $/; # Turn on slurp-mode. + my $f = new Autom4te::XFile "< " . open_quote ($file); + my $contents = $f->getline; + $f->close; + return $contents; +} + + +=item C<dir_has_case_matching_file ($DIRNAME, $FILE_NAME)> + +Return true iff $DIR contains a file name that matches $FILE_NAME case +insensitively. + +We need to be cautious on case-insensitive case-preserving file +systems (e.g. Mac OS X's HFS+). On such systems C<-f 'Foo'> and C<-f +'foO'> answer the same thing. Hence if a package distributes its own +F<CHANGELOG> file, but has no F<ChangeLog> file, automake would still +try to distribute F<ChangeLog> (because it thinks it exists) in +addition to F<CHANGELOG>, although it is impossible for these two +files to be in the same directory (the two file names designate the +same file). + +=cut + +use vars '%_directory_cache'; +sub dir_has_case_matching_file ($$) +{ + # Note that print File::Spec->case_tolerant returns 0 even on MacOS + # X (with Perl v5.8.1-RC3 at least), so do not try to shortcut this + # function using that. + + my ($dirname, $file_name) = @_; + return 0 unless -f "$dirname/$file_name"; + + # The file appears to exist, however it might be a mirage if the + # system is case insensitive. Let's browse the directory and check + # whether the file is really in. We maintain a cache of directories + # so Automake doesn't spend all its time reading the same directory + # again and again. + if (!exists $_directory_cache{$dirname}) + { + error "failed to open directory '$dirname'" + unless opendir (DIR, $dirname); + $_directory_cache{$dirname} = { map { $_ => 1 } readdir (DIR) }; + closedir (DIR); + } + return exists $_directory_cache{$dirname}{$file_name}; +} + +=item C<reset_dir_cache ($dirname)> + +Clear C<dir_has_case_matching_file>'s cache for C<$dirname>. + +=cut + +sub reset_dir_cache ($) +{ + delete $_directory_cache{$_[0]}; +} + +=item C<set_dir_cache_file ($dirname, $file_name)> + +State that C<$dirname> contains C<$file_name> now. + +=cut + +sub set_dir_cache_file ($$) +{ + my ($dirname, $file_name) = @_; + $_directory_cache{$dirname}{$file_name} = 1 + if exists $_directory_cache{$dirname}; +} + +1; # for require + +### Setup "GNU" style for perl-mode and cperl-mode. +## Local Variables: +## perl-indent-level: 2 +## perl-continued-statement-offset: 2 +## perl-continued-brace-offset: 0 +## perl-brace-offset: 0 +## perl-brace-imaginary-offset: 0 +## perl-label-offset: -2 +## cperl-indent-level: 2 +## cperl-brace-offset: 0 +## cperl-continued-brace-offset: 0 +## cperl-label-offset: -2 +## cperl-extra-newline-before-brace: t +## cperl-merge-trailing-else: nil +## cperl-continued-statement-offset: 2 +## End: |