#!/usr/bin/perl -w # # Project Builder main application # # $Id$ # # Copyright B. Cornec 2007 # Provided under the GPL v2 # Syntax: see at end use strict 'vars'; use Getopt::Std; use Data::Dumper; use English; use AppConfig qw(:argcount :expand); use File::Basename; use File::Copy; use Time::localtime qw(localtime); use POSIX qw(strftime); use vars qw (%defpkgdir %extpkgdir %version %confparam %filteredfiles %pbroot $debug $LOG $projectbuilderver $projectbuilderrev); $debug = 0; # Debug level $LOG = *STDOUT; # Where to log use lib qw (lib); use ProjectBuilder::common qw (env_init); use ProjectBuilder::distro qw (distro_init); use ProjectBuilder::cms; use ProjectBuilder::changelog qw (changelog); use ProjectBuilder::Version qw (version_init); my %opts; # CLI Options my $action; # action to realize my $test = "FALSE"; my $option = ""; my @pkgs; my $pbtag; # Global TAG variable my $pbver; # Global VERSION variable my $pbrev; # Global REVISION variable my @date=(localtime->sec(), localtime->min(), localtime->hour(), localtime->mday(), localtime->mon(), localtime->year(), localtime->wday(), localtime->yday(), localtime->isdst()); my $pbdate = strftime("%Y-%m-%d", @date); getopts('hl:p:qtv',\%opts); version_init(); if (defined $opts{'h'}) { syntax(); exit(0); } if (defined $opts{'v'}) { $debug++; } if (defined $opts{'q'}) { $debug=-1; } if (defined $opts{'l'}) { open(LOG,"> $opts{'l'}") || die "Unable to log to $opts{'l'}: $!"; $LOG = *LOG; $debug = 0 if ($debug == -1); } # Handles test option if (defined $opts{'t'}) { $test = "TRUE"; # Works only for SVN $option = "-r BASE"; } # Get Action $action = shift @ARGV; die syntax() if (not defined $action); # Handles project name if any if (defined $opts{'p'}) { $ENV{'PBPROJ'} = env_init($opts{'p'}); } else { $ENV{'PBPROJ'} = env_init(); } print $LOG "Project $ENV{'PBPROJ'}\n" if ($debug >= 0); print $LOG "Action: $action\n" if ($debug >= 0); # Act depending on action if ($action =~ /^cms2build$/) { my $ptr = get_pkg(); @pkgs = @$ptr; cms_init(); foreach my $pbpkg (@pkgs) { if (-f "$ENV{'PBROOT'}/$pbpkg/VERSION") { open(V,"$ENV{'PBROOT'}/$pbpkg/VERSION") || die "Unable to open $ENV{'PBROOT'}/$pbpkg/VERSION"; $pbver = ; chomp($pbver); close(V); } else { $pbver = $ENV{'PBVER'}; } if (-f "$ENV{'PBROOT'}/$pbpkg/TAG") { open(T,"$ENV{'PBROOT'}/$pbpkg/TAG") || die "Unable to open $ENV{'PBROOT'}/$pbpkg/TAG"; $pbtag = ; chomp($pbtag); close(T); } else { $pbtag = $ENV{'PBTAG'}; } $pbrev = $ENV{'PBREVISION'}; print $LOG "\n" if ($debug >= 0); print $LOG "Management of $pbpkg $pbver-$pbtag (rev $pbrev)\n" if ($debug >= 0); die "Unable to get env var PBDESTDIR" if (not defined $ENV{'PBDESTDIR'}); # Clean up dest if necessary. The export will recreate it my $dest = "$ENV{'PBDESTDIR'}/$pbpkg-$pbver"; pbrm_rf($dest) if (-d $dest); # Export CMS tree for the concerned package to dest # And generate some additional files $OUTPUT_AUTOFLUSH=1; # computes in which dir we have to work my $dir = $defpkgdir{$pbpkg}; $dir = $extpkgdir{$pbpkg} if (not defined $dir); pbsystem("$ENV{'PBCMSEXP'} $option $ENV{'PBROOT'}/$dir $dest 1>/dev/null", "Exporting $ENV{'PBROOT'}/$dir"); # Creates a REVISION file open(R,"> $dest/REVISION") || die "Unable to create $dest/REVISION"; print R "$pbrev\n"; close(R); # Extract cms log history and store it pbsystem("$ENV{'PBCMSLOG'} $option $ENV{'PBROOT'}/$dir > $dest/$ENV{'PBCMSLOGFILE'}", "Extracting log info"); my %build; open(D,"$ENV{'PBCONF'}/DISTROS") || die "Unable to find $ENV{'PBCONF'}/DISTROS\n"; while () { my $d = $_; my ($ndir,$ver) = split(/_/,$d); chomp($ver); my ($ddir, $dver, $dfam, $dtype, $dsuf) = distro_init($ndir,$ver); print $LOG "DEBUG: distro tuple: ".Dumper($ddir, $dver, $dfam, $dtype, $dsuf)."\n" if ($debug >= 1); print $LOG "DEBUG Filtering PBDATE => $pbdate, PBTAG => $pbtag, PBVER => $pbver\n" if ($debug >= 1); # Filter build files from the less precise up to the most with overloading # Filter all files found, keeping the name, and generating in dest # Find all build files first relatively to PBROOT my %bfiles; print $LOG "DEBUG dir: $ENV{'PBCONF'}/$pbpkg\n" if ($debug >= 1); $build{"$ddir-$dver"} = "yes"; if (-d "$ENV{'PBCONF'}/$pbpkg/$dtype") { opendir(BDIR,"$ENV{'PBCONF'}/$pbpkg/$dtype") || die "Unable to open dir $ENV{'PBCONF'}/$pbpkg/$dtype: $!"; foreach my $f (readdir(BDIR)) { next if ($f =~ /^\./); $bfiles{$f} = "$ENV{'PBCONF'}/$pbpkg/$dtype/$f"; $bfiles{$f} =~ s~$ENV{'PBROOT'}~~; } closedir(BDIR); } elsif (-d "$ENV{'PBCONF'}/$pbpkg/$dfam") { opendir(BDIR,"$ENV{'PBCONF'}/$pbpkg/$dfam") || die "Unable to open dir $ENV{'PBCONF'}/$pbpkg/$dfam: $!"; foreach my $f (readdir(BDIR)) { next if ($f =~ /^\./); $bfiles{$f} = "$ENV{'PBCONF'}/$pbpkg/$dfam/$f"; $bfiles{$f} =~ s~$ENV{'PBROOT'}~~; } closedir(BDIR); } elsif (-d "$ENV{'PBCONF'}/$pbpkg/$ddir") { opendir(BDIR,"$ENV{'PBCONF'}/$pbpkg/$ddir") || die "Unable to open dir $ENV{'PBCONF'}/$pbpkg/$ddir: $!"; foreach my $f (readdir(BDIR)) { next if ($f =~ /^\./); $bfiles{$f} = "$ENV{'PBCONF'}/$pbpkg/$ddir/$f"; $bfiles{$f} =~ s~$ENV{'PBROOT'}~~; } closedir(BDIR); } elsif (-d "$ENV{'PBCONF'}/$pbpkg/$ddir-$dver") { opendir(BDIR,"$ENV{'PBCONF'}/$pbpkg/$ddir-$dver") || die "Unable to open dir $ENV{'PBCONF'}/$pbpkg/$ddir-$dver: $!"; foreach my $f (readdir(BDIR)) { next if ($f =~ /^\./); $bfiles{$f} = "$ENV{'PBCONF'}/$pbpkg/$ddir-$dver/$f"; $bfiles{$f} =~ s~$ENV{'PBROOT'}~~; } closedir(BDIR); } else { $build{"$ddir-$dver"} = "no"; next; } print $LOG "DEBUG bfiles: ".Dumper(\%bfiles)."\n" if ($debug >= 1); # Get all filters to apply # They're cumulative from less specific to most specific # suffix is .pbf my @ffiles; my ($ffile0, $ffile1, $ffile2, $ffile3); if (-d "$ENV{'PBCONF'}/$pbpkg/pbfilter") { $ffile0 = "$ENV{'PBCONF'}/$pbpkg/pbfilter/$dtype.pbf" if (-f "$ENV{'PBCONF'}/$pbpkg/pbfilter/$dtype.pbf"); $ffile1 = "$ENV{'PBCONF'}/$pbpkg/pbfilter/$dfam.pbf" if (-f "$ENV{'PBCONF'}/$pbpkg/pbfilter/$dfam.pbf"); $ffile2 = "$ENV{'PBCONF'}/$pbpkg/pbfilter/$ddir.pbf" if (-f "$ENV{'PBCONF'}/$pbpkg/pbfilter/$ddir.pbf"); $ffile3 = "$ENV{'PBCONF'}/$pbpkg/pbfilter/$ddir-$dver.pbf" if (-f "$ENV{'PBCONF'}/$pbpkg/pbfilter/$ddir-$dver.pbf"); push @ffiles,$ffile0 if (defined $ffile0); push @ffiles,$ffile1 if (defined $ffile1); push @ffiles,$ffile2 if (defined $ffile2); push @ffiles,$ffile3 if (defined $ffile3); } my $config = AppConfig->new({ # Auto Create variables mentioned in Conf file CREATE => 1, DEBUG => 0, GLOBAL => { # Each conf item is a hash ARGCOUNT => AppConfig::ARGCOUNT_HASH } }); my $ptr; if (@ffiles) { print $LOG "DEBUG ffiles: ".Dumper(\@ffiles)."\n" if ($debug >= 1); $config->file(@ffiles); $ptr = $config->get("filter"); print $LOG "DEBUG f:".Dumper($ptr)."\n" if ($debug >= 1); } else { $ptr = { }; } # Apply now all the filters on all the files concerned # destination dir depends on the type of file if (defined $ptr) { foreach my $f (values %bfiles) { filter_file("$ENV{'PBROOT'}/$f",$ptr,"$dest/pbconf/$ddir-$dver/".basename($f),$pbpkg,$dtype,$dsuf); } if (defined $filteredfiles{$dir}) { foreach my $f (split(/,/,$filteredfiles{$dir})) { filter_file("$ENV{'PBROOT'}/$dir/$f",$ptr,"$dest/$f",$pbpkg,$dtype,$dsuf); } } } } if ($debug >= 0) { my @found; my @notfound; foreach my $b (keys %build) { push @found,$b if ($build{$b} =~ /yes/); push @notfound,$b if ($build{$b} =~ /no/); } print $LOG "Build files generated for ".join(',',@found)."\n"; print $LOG "No Build files found for ".join(',',@notfound)."\n"; } close(D); # Prepare the dest directory for archive if (-x "$ENV{'PBCONF'}/$pbpkg/pbinit") { pbsystem("cd $dest ; $ENV{'PBCONF'}/$pbpkg/pbinit","Executing init script $ENV{'PBCONF'}/$pbpkg/pbinit"); } # Archive dest dir chdir "$ENV{'PBDESTDIR'}"; # Possibility to look at PBSRC to guess more the filename pbsystem("tar cfphz $pbpkg-$pbver.tar.gz $pbpkg-$pbver","Creating $pbpkg tar files compressed"); print $LOG "Under $ENV{'PBDESTDIR'}/$pbpkg-$pbver.tar.gz\n" if ($debug >= 0); # Keep track of what is generated for build2pkg default open(LAST,"> $ENV{'PBDESTDIR'}/LAST") || die "Unable to create $ENV{'PBDESTDIR'}/LAST"; print LAST "$pbver-$pbtag\n"; close(LAST); } } elsif ($action =~ /^build2pkg$/) { # Check whether we have a specific version to build my $vertag = shift @ARGV; if (not defined $vertag) { open(LAST,"$ENV{'PBDESTDIR'}/LAST") || die "Unable to open $ENV{'PBDESTDIR'}/LAST\nYou may want to precise as parameter version-tag"; $vertag = ; chomp($vertag); close(LAST); } ($pbver,$pbtag) = split(/-/,$vertag); # Get list of packages to build my $ptr = get_pkg(); @pkgs = @$ptr; # Get the running distro to build on my ($ddir, $dver, $dfam, $dtype, $dsuf) = distro_init(); print $LOG "DEBUG: distro tuple: ".join(',',($ddir, $dver, $dfam, $dtype, $dsuf))."\n" if ($debug >= 1); chdir "$ENV{'PBBUILDDIR'}"; foreach my $pbpkg (@pkgs) { my $src="$ENV{'PBDESTDIR'}/$pbpkg-$pbver.tar.gz"; print $LOG "Handling source file $src\n" if ($debug >= 0); if ($dtype eq "rpm") { # rpm has its own standard build directory my $tmp=`rpmquery --eval '%{_topdir}' 2> /dev/null`; chomp($tmp); $ENV{'PBBUILDDIR'}=$tmp; print $LOG "Working under $ENV{'PBBUILDDIR'}\n" if ($debug >= 0); foreach my $d ('RPMS','SRPMS','SPECS','SOURCES','BUILD') { if (! -d "$ENV{'PBBUILDDIR'}/$d") { pbmkdir_p("$ENV{'PBBUILDDIR'}/$d") || die "Please ensure that you can write into $ENV{'PBBUILDDIR'} to create $d\nSolution: setup _topdir in your ~/.rpmmacros or\nchown the $ENV{'PBBUILDDIR'} directory to your uid"; } } # We need to first extract the spec file symlink "$src","$ENV{'PBBUILDDIR'}/SOURCES/".basename($src) || die "Unable to symlink $src in $ENV{'PBBUILDDIR'}/SOURCES"; my @specfile; @specfile = extract_build_files($src,"$pbpkg-$pbver/pbconf/$ddir-$dver/","$ENV{'PBBUILDDIR'}/SPECS"); print $LOG "specfile: ".Dumper(\@specfile)."\n" if ($debug >= 1); # set LANGUAGE to check for correct log messages $ENV{'LANGUAGE'}="C"; #system("ls -R $ENV{'PBBUILDDIR'}") if ($debug >= 1); foreach my $f (@specfile) { if ($f =~ /\.spec$/) { pbsystem("rpmbuild -ba $f","Building package with $f"); last; } } } elsif ($dtype eq "tgz") { pbmkdir_p("$ENV{'PBBUILDDIR'}/install") if (! -d "$ENV{'PBBUILDDIR'}/install"); } elsif ($dtype eq "ebuild") { pbmkdir_p("$ENV{'PBBUILDDIR'}/portage") if (! -d "$ENV{'PBBUILDDIR'}/portage"); } else { } } } else { print $LOG "'$action' is not available\n"; syntax(); } # Function which applies filter on files sub filter_file { my $f=shift; my $ptr=shift; my %filter=%$ptr; my $destfile=shift; my $pbpkg=shift; my $dtype=shift; my $dsuf=shift; print $LOG "DEBUG: From $f to $destfile\n" if ($debug >= 1); pbmkdir_p(dirname($destfile)) if (! -d dirname($destfile)); open(DEST,"> $destfile") || die "Unable to create $destfile"; open(FILE,"$f") || die "Unable to open $f: $!"; while () { my $line = $_; foreach my $s (keys %filter) { # Process single variables print $LOG "DEBUG filter{$s}: $filter{$s}\n" if ($debug > 1); my $tmp = $filter{$s}; next if (not defined $tmp); # Expand variables if any single one found if ($tmp =~ /\$/) { eval { $tmp =~ s/(\$\w+)/$1/eeg }; # special case for ChangeLog } elsif (($tmp =~ /^yes$/) && ($s =~ /^PBLOG$/) && ($line =~ /^PBLOG$/)) { my $p = $defpkgdir{$pbpkg}; $p = $extpkgdir{$pbpkg} if (not defined $p); changelog($dtype, $pbpkg, $pbtag, $dsuf, $p, \*DEST); $tmp = ""; } $line =~ s|$s|$tmp|; } print DEST $line; } close(FILE); close(DEST); } sub get_pkg { my @pkgs; # Get packages list if (not defined $ARGV[0]) { @pkgs = keys %defpkgdir; } elsif ($ARGV[0] =~ /^all$/) { @pkgs = keys %defpkgdir; if (defined %extpkgdir) { my $k = keys %extpkgdir; if (defined $k) { push(@pkgs, keys %extpkgdir); } } } else { @pkgs = @ARGV; } print $LOG "Packages: ".join(',',@pkgs)."\n" if ($debug >= 0); return(\@pkgs); } sub extract_build_files { my $src=shift; my $dir=shift; my $ddir=shift; my @files; pbsystem("tar xfpz $src $dir >/dev/null","Extracting build files"); opendir(DIR,"$dir") || die "Unable to open directory $dir"; foreach my $f (readdir(DIR)) { next if ($f =~ /^\./); move("$dir/$f","$ddir") || die "Unable to move $dir/$f to $ddir"; print $LOG "mv $dir/$f $ddir\n" if ($debug >= 1); push @files,"$ddir/$f"; } closedir(DIR); # Not enough but still a first cleanup pbrm_rf("$dir"); return(@files); } sub syntax { print "pb (aka project-builder) Version $projectbuilderver-$projectbuilderrev\n"; print "\n"; print "Syntax: pb [-vhqt][-p project] [...]\n"; print "\n"; print "-h : This help file\n"; print "-q : Quiet mode\n"; print "-t : Test mode (not done yet)\n"; print "-v : Verbose mode\n"; print "\n"; print "-p project : Name of the project you're working on\n"; print " (or use the env variable PBPROJ) \n"; print "\n"; print " can be:\n"; print "\n"; print "\tcms2build: Create a tar file of the project under your CMS\n"; print "\t CMS supported are SVN and CVS\n"; print "\t parameters are packages to build\n"; print "\t if not using default list\n"; print "\n"; print "\tbuild2pkg: Create packages for your running distribution \n"; print "\t first parameter is version-tag to build\n"; print "\t if not using default version-tag\n"; print "\t following parameters are packages to build\n"; print "\t if not using default list\n"; print "\n"; print "\n"; }