#! /usr/bin/perl # # $Id$ # Module to generate all the initial makefiles etc. # Copyright (C) 2002 Jan Kratochvil # # 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; exactly version 2 of June 1991 is required # # 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, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA package AutoGen; use vars qw($VERSION); $VERSION=do { my @r=(q$Revision$=~/\d+/g); sprintf "%d.".("%03d"x$#r),@r; }; use strict; use warnings; BEGIN { my @missing; for (split "\n",<<'HERE') { use Carp qw(cluck confess); use Getopt::Long; # &GetOptions, $Getopt::Long::* use File::Basename; # &basename use File::Grep qw(fgrep); use File::HomeDir; # &home use File::Remove qw(remove); use File::NCopy qw(copy); use File::chdir; # $CWD use File::Touch; # &touch use POSIX qw(WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG WIFSTOPPED WSTOPSIG); HERE eval "$_\n; 1;" or push @missing,(/^\s*use\s+([^\s;]+)/)[0]; } die "You are missing some modules - install them by:\n" ."\tperl -MCPAN -e 'install qw(".join(" ",@missing).")'\n" if @missing; } our %Options; sub _help { return ' Beware: '.basename($0).' is a tool only for maintainers! Supported parameters: --rpm Build RPM packages locally (needs /usr/src/(redhat|packages)/ access) --rpmtest Build RPM like "rpm" but w/o gpg/pgp signing --clean Standard cleanup method --fullclean Like clean but even the .cvsignore files are removed --copy Behave exactly like in default mode but copy all instead of symlinks -h|--help Print this help message. '.($Options{"help"} || ""); } sub _readfile { my($filename)=@_; local $/=undef(); local *F; open F,$filename or confess "Open \"$filename\": $!"; my $r=; close F or cluck "Close \"$filename\": $!"; return $r; } sub _writefile { my($filename,@content)=@_; local *F; open F,">".$filename or confess "rewrite \"$filename\": $!"; print F @content; close F or cluck "close \"$filename\": $!"; } my %_rpmeval_cache; sub _rpmeval { my(@names)=@_; my @r=map({ my $name=$_; my $nameref=\$_rpmeval_cache{$name}; $$nameref=_readfile('rpm --eval %'.$name.'|') if !defined $$nameref; chomp $$nameref; $$nameref; } @names); return @r if wantarray(); confess "Scalar return for ".scalar(@r)." values" if 1!=@r; return $r[0]; } sub _system { my(@args)=@_; system(@args) and confess join(" ",@args).": $?=".join(",", (!WIFEXITED($?) ? () : ("EXITSTATUS(".WEXITSTATUS($?).")")), (!WIFSIGNALED($?) ? () : ("TERMSIG(" .WTERMSIG($?) .")")), (!WIFSTOPPED($?) ? () : ("STOPSIG(" .WSTOPSIG($?) .")")), ); } # Assumed wildcard pattern expansion to exactly one item if !$nocheck sub _copy { my(@files)=@_; my $nocheck=shift @files if $files[0] eq "nocheck"; my $dest=pop @files; @files==copy @files,$dest or $nocheck or confess "$!"; } # Assumed wildcard pattern expansion to exactly one item if !$nocheck sub _remove { my(@files)=@_; my $nocheck=shift @files if $files[0] eq "nocheck"; my $flag=shift @files if ref $files[0]; @files==remove((!$flag ? () : $flag),@files) or $nocheck or confess "$!"; } # FIXME: File::NCopy exists but File::NMove doesn't sub _move { my(@files)=@_; my $dest=pop @files; _copy @files,$dest; _remove @files; } # $args{ # "sign"=>bool, # }, sub _rpmbuild { my($class,%args)=@_; my $name=$Options{"name"}; _remove "nocheck",\1, _rpmeval("_tmppath" )."/$name-*-root", _rpmeval("_builddir")."/$name-*"; my($specsrc)=map((-e $_ ? $_ : "$name.spec.in"),"$name.spec.m4.in"); my $spec=_readfile $specsrc; $spec=~s/\\\n/ /gs; $class->run(%Options, "ARGV"=>["--copy"], "configure_args"=>[split /\s+/,($spec=~/^%configure\s+(.*)$/m)[0]], ); _system "make dist $name.spec"; _copy "$name-*.tar.gz",_rpmeval("_sourcedir"); _system(join(" ","rpm", "-ba", (!$args{"sign"} ? () : "--sign"), "$name.spec", )); _system "make dist-tarZ" if $Options{"dist-tarZ"}; _remove _rpmeval{"_sourcedir"}."/$name-*.tar.gz"; _move _rpmeval{"_srcrpmdir"}."/$name-*.src.rpm","."; _move "nocheck",_rpmeval{"_rpmdir"}."/"._rpmeval{"_arch"}."/$name-*."._rpmeval{"_arch"}.".rpm","."; _system "ls -l $name-*"; # NOTREACHED } # WARNING: doesn't respect %Options change! my @_cleanfiles_cache; sub _cleanfiles { # maintainer-clean hack is not safe, please list all files for 'rm'. # When the filename doesn't contain '/', it is applied to ALL directories. # Please note that files exactly in root dir MUST have ./ in the front # (to not to be considered as ALL-directories files). if (!@_cleanfiles_cache) { @_cleanfiles_cache=map({ local $_=$_; # Prevent: Modification of a read-only value attempted s/\Q\E/$Options{"name"}/ego; s#/+#/#g; # "*xyzzy" basename -> "*xyzzy",".*xyzzy" for proper cleaning (!m#^((?:.*/)?)([*][^/]*)$# ? ($_) : ("$1$2","$1.$2")); } ( ".#*", # Possible attempt to put comments in qw() list qw( *~ *.orig *.rej core Makefile Makefile.in TAGS tags ID .deps .libs *.[oa] *.l[oa] *.l[oa]T .cvsignore ./errs* ./intl ./configure ./configure.scan ./config.guess ./config.status ./config.sub ./config.log ./config.cache ./config.h ./config.h.in ./confdefs.h ./conftest* ./autoh[0-9]* ./confcache ./config.rpath ./depcomp ./stamp-h ./stamp-h.in ./stamp-h1 ./install-sh ./aclocal.m4 ./autom4te-*.cache ./m4 ./missing ./mkinstalldirs ./libtool ./ltconfig ./ltmain.sh ./ChangeLog ./ABOUT-NLS ./-[0-9]* ./-devel-[0-9]* ./.spec ./.m4 ./.spec.m4 ./macros/macros.dep ./po/Makefile.in.in ./po/POTFILES* ./po/cat-id-tbl.c ./po/cat-id-tbl.tmp ./po/*.gmo ./po/*.mo ./po/stamp-cat-id ./po/.pot ./po/ChangeLog ./po/Makevars ./po/Makevars.template ./po/Rules-quot ./po/*.sed ./po/*.sin ./po/*.header ), map((!$_ ? () : do { my $dir=$_; map("$dir/$_",qw( *.stamp sgml* tmpl* html* *.txt *.txt.bak *.args *.hierarchy *.signals )); }),$Options{"gtk-doc-dir"}), map((!$_ ? () : do { my $dir=$_; map("$dir/$_",qw( *.html *.info* *.txt *.tex *.sgml )); }),$Options{"docbook-lite-dir"}), map((!$_ ? () : @$_),$Options{"clean"}), )); # sanity check for (@_cleanfiles_cache) { confess "dir-specific 'clean'-pattern must start with './': $_" if m#^(?!\Q./\E).*/#; }; } return @_cleanfiles_cache; } sub _cleanfilesfordir { my($dir)=@_; return map({ if (m#^\Q$dir\E/([^/]+)$#) { # this-dir: "./this-dir/file-name.c" ($1); } elsif (m#^[^/]+$#) { # all-dirs: "file-name.c"; the same as "./*/file-name.c" ($&); } elsif (do { # all-subdirs: "./parent-of-this-dir/*/file-name.c" m#/[*]/([^/]+)$#; ($_=$1) && $dir=~m#^\Q$`\E(?:/|$)#; }) { ($_); } else { (); } } _cleanfiles()); } sub _cvsdirs { my(@startdirs)=@_; my @r=(); my @todo=(@startdirs); while (defined(my $dir=shift @todo)) { local *ENTRIES; my $entries_filename="$dir/CVS/Entries"; open ENTRIES,$entries_filename or (cluck "open \"$entries_filename\": $!" and next); push @r,$dir; local $/="\n"; my %local=(); local $_; while () { chomp; next if !m#^D/([^/]+)/#; $local{$1}=1; } close ENTRIES or cluck "close \"$entries_filename\": $!"; if (-e (my $entries_log_filename=$dir."/CVS/Entries.Log")) { local *ENTRIES_LOG; if (open ENTRIES_LOG,$entries_log_filename or cluck "open \"$entries_log_filename\": $!") { local $_; while () { chomp; if (m#^A D/([^/]*)/#) { $local{$1}=1; } elsif (m#^R D/([^/]*)/#) { delete $local{$1}; } else { cluck "$entries_log_filename: Unrecognized line $.: $_"; } } close ENTRIES_LOG or cluck "close \"$entries_log_filename\": $!"; } } push @todo,map(("$dir/$_"),keys(%local)); } return @r; } sub _expandclass { my($patt)=@_; return $patt if $patt!~/\Q[\E(.*?)\Q]\E/; my($pre,$post)=($`,$'); # FIXME: local($`,$') doesn't work - why? return map({ _expandclass("$pre$_$post"); } split("",$1)); } sub run { my($class,%options)=@_; local %Options=%options; do { require $_ if -e; } for (home()."/.".$Options{"name"}.".autogen.pl"); do { $$_=1 if !defined($$_) && fgrep { /^\s*AUTOMAKE_OPTIONS\s*=[^#]*\bdist-tarZ\b/m; } "Makefile.am"; } for (\$Options{"dist-tarZ"}); Getopt::Long::Configure('noignorecase','prefix_pattern=(--|-|\+|)'); local @ARGV=@{$Options{"ARGV"}}; print _help() and confess if !GetOptions( "rpm" ,sub { $class->_rpmbuild("sign"=>1); return; }, "rpmtest" ,sub { $class->_rpmbuild("sign"=>0); return; }, "dist" ,\$Options{"ARGV_dist"}, "copy!" ,\$Options{"ARGV_copy"}, "fullclean",\$Options{"ARGV_fullclean"}, "clean" ,\$Options{"ARGV_clean"}, "h|help" ,sub { print _help(); exit 0; }, $Options{"GetOptions_args"}, ) || @ARGV; for my $subdir (map((!$_ ? () : @$_),$Options{"subdirs"})) { local $CWD=$subdir; _system "./autogen.pl",@{$Options{"ARGV"}},"--dist"; # use "--dist" just as fallback! } for my $dir (_cvsdirs(".")) { my @cleanfilesfordir=_cleanfilesfordir $dir; _writefile $dir."/.cvsignore",map("$_\n",@cleanfilesfordir) if !$Options{"ARGV_fullclean"}; _remove "nocheck",\1,map({ _expandclass("$dir/$_"); } grep({ $Options{"ARGV_fullclean"} or $_ ne ".cvsignore"; } @cleanfilesfordir)); } return if $Options{"ARGV_clean"} || $Options{"ARGV_fullclean"}; $Options{"aclocal_args"}=[qw(-I macros),map((!$_ ? () : @$_),$Options{"aclocal_args"})]; my $configure_in=_readfile("configure.in"); do { $$_=1 if !defined($$_) && $configure_in=~/^AM_GNU_GETTEXT\b/m; } for (\$Options{"want-gettextize"}); do { $$_=1 if !defined($$_) && $configure_in=~/^AM_PROG_LIBTOOL\b/m; } for (\$Options{"want-libtoolize"}); do { $$_=1 if !defined($$_) && $configure_in=~/^A[CM]_CONFIG_HEADER\b/m; } for (\$Options{"want-autoheader"}); my @copy_arg=(!$Options{"ARGV_copy"} ? () : "--copy"); do { &$_ if $_; } for ($Options{"prep"}); touch "po/POTFILES.in" if -d "po"; if ($Options{"want-gettextize"}) { # don't use multi-arg system() here as it would reject "printflush("gettextize recovery rename \"$_~\"->\"$_\"... "); rename "$_~","$_" or confess "$!"; STDERR->printflush("ok\n"); } if (!-e "po/Makevars") { my $Makevars_template="po/Makevars.template"; my $makevars=_readfile $Makevars_template; $makevars=~s/^(COPYRIGHT_HOLDER)\b.*$/"$1=".$Options{"COPYRIGHT_HOLDER"}/meg or confess "COPYRIGHT_HOLDER not found in $Makevars_template"; _writefile "po/Makevars",$makevars; } # Prevent updating of contents during touch of any source file; # change the .po contents only when some data get updated for my $Makefile_in_in ("po/Makefile.in.in") { my $file=_readfile $Makefile_in_in; $file=~s%(\$\Q(MSGMERGE_UPDATE)\E) (\$\$\Q{lang}.po \E\$\Q(DOMAIN).pot\E)$% $1.q< --backup=simple --suffix="~" >.$2.q<;> .q< if test `diff -u $${lang}.po~ $${lang}.po> .q< | sed> .q< -e '1,/^@@.*@@$$/d'> .q< -e '/^[+-]"POT-Creation-Date:/d'> .q< -e '/^[^+-]/d'> .q< | wc -l` -eq 0;then> .q< touch --reference=$${lang}.po $${lang}.po~;> .q< mv -f $${lang}.po~ $${lang}.po;> .q< else> .q< rm -f $${lang}.po~;> .q< fi> %me or confess; unlink $Makefile_in_in or confess "$!"; _writefile $Makefile_in_in,$file; } } _system "aclocal",map((!$_ ? () : @$_),$Options{"aclocal_args"}); _system qw(libtoolize),@copy_arg if $Options{"want-libtoolize"}; _system qw(autoheader) if $Options{"want-autoheader"}; # "ChangeLog" is reqd by automake(1) # Don't remove it afterwards as it may still be needed during automatic automake Makefile rebuilds File::Touch->new("atime_only"=>1)->touch("ChangeLog"); _system qw(automake --add-missing),@copy_arg; _system qw(autoconf); # Why it is left there after RedHat autoconf-2.53-8 ? _remove "nocheck",\1,"autom4te-*.cache"; return if $Options{"ARGV_dist"}; # shared/static switching cannot be based on maintainer-mode in configure _system(qw(./configure --enable-maintainer-mode), ($Options{"want-libtoolize"} && qw(--enable-shared --disable-static)), map((!$_ ? () : @$_),$Options{"configure_args"}), ); } 1; __END__ Tested with: RedHat autoconf-2.53-8 RedHat automake-1.6.3-1 RedHat gettext-0.11.4-3 RedHat libtool-1.4.2-12 RedHat perl-5.8.0-48