diff --git a/modules/ksb/BuildSystem/Autotools.pm b/modules/ksb/BuildSystem/Autotools.pm index e70da92..ad4f9fb 100644 --- a/modules/ksb/BuildSystem/Autotools.pm +++ b/modules/ksb/BuildSystem/Autotools.pm @@ -1,72 +1,69 @@ -package ksb::BuildSystem::Autotools; +package ksb::BuildSystem::Autotools 0.10; # This is a module used to support configuring with autotools. use strict; use warnings; use 5.014; -our $VERSION = '0.10'; - -use List::Util qw(first); +use parent qw(ksb::BuildSystem); use ksb::Debug; use ksb::Util; -use ksb::BuildSystem; -our @ISA = ('ksb::BuildSystem'); +use List::Util qw(first); sub name { return 'autotools'; } # Return value style: boolean sub configureInternal { my $self = assert_isa(shift, 'ksb::BuildSystem::Autotools'); my $module = $self->module(); my $sourcedir = $module->fullpath('source'); my $installdir = $module->installationPath(); # 'module'-limited option grabbing can return undef, so use // # to convert to empty string in that case. my @bootstrapOptions = split_quoted_on_whitespace( $module->getOption('configure-flags', 'module') // ''); my $configureCommand = first { -e "$sourcedir/$_" } qw(configure autogen.sh); my $configureInFile = first { -e "$sourcedir/$_" } qw(configure.in configure.ac); # If we have a configure.in or configure.ac but configureCommand is autogen.sh # we assume that configure is created by autogen.sh as usual in some GNU Projects. # So we run autogen.sh first to create the configure command and # recheck for that. if ($configureInFile && $configureCommand eq 'autogen.sh') { p_chdir($sourcedir); my $err = log_command($module, 'autogen', ["$sourcedir/$configureCommand"]); return 0 if $err != 0; # We don't want a Makefile in the srcdir, so run make-distclean if that happened # ... and hope that is enough to fix it if (-e "$sourcedir/Makefile") { $err = log_command($module, 'distclean', [qw(make distclean)]); return 0 if $err != 0; } # Now recheck $configureCommand = first { -e "$sourcedir/$_" } qw(configure autogen.sh); } croak_internal("No configure command available") unless $configureCommand; p_chdir($module->fullpath('build')); return log_command($module, 'configure', [ "$sourcedir/$configureCommand", "--prefix=$installdir", @bootstrapOptions ]) == 0; } 1; diff --git a/modules/ksb/BuildSystem/CMakeBootstrap.pm b/modules/ksb/BuildSystem/CMakeBootstrap.pm index 4bcf91b..33fedf0 100644 --- a/modules/ksb/BuildSystem/CMakeBootstrap.pm +++ b/modules/ksb/BuildSystem/CMakeBootstrap.pm @@ -1,49 +1,46 @@ -package ksb::BuildSystem::CMakeBootstrap; +package ksb::BuildSystem::CMakeBootstrap 0.10; # This is a module used to do only one thing: Bootstrap CMake onto a system # that doesn't have it, or has only an older version of it. use strict; use warnings; use 5.014; -our $VERSION = '0.10'; +use parent qw(ksb::BuildSystem); use ksb::Debug; use ksb::Util; -use ksb::BuildSystem; - -our @ISA = ('ksb::BuildSystem'); sub name { return 'cmake-bootstrap'; } sub requiredPrograms { return qw{c++}; } # Return value style: boolean sub configureInternal { my $self = assert_isa(shift, 'ksb::BuildSystem::CMakeBootstrap'); my $module = $self->module(); my $sourcedir = $module->fullpath('source'); my $installdir = $module->installationPath(); # 'module'-limited option grabbing can return undef, so use // # to convert to empty string in that case. my @bootstrapOptions = split_quoted_on_whitespace( $module->getOption('configure-flags', 'module') // ''); p_chdir($module->fullpath('build')); return log_command($module, 'cmake-bootstrap', [ "$sourcedir/bootstrap", "--prefix=$installdir", @bootstrapOptions ]) == 0; } 1; diff --git a/modules/ksb/BuildSystem/KDE4.pm b/modules/ksb/BuildSystem/KDE4.pm index 80e087e..7c8bdfd 100644 --- a/modules/ksb/BuildSystem/KDE4.pm +++ b/modules/ksb/BuildSystem/KDE4.pm @@ -1,225 +1,224 @@ package ksb::BuildSystem::KDE4 0.20; # Class responsible for building KDE4 CMake-based modules. use strict; use warnings; use 5.014; +use parent qw(ksb::BuildSystem); + +use ksb::BuildContext 0.30; use ksb::Debug; use ksb::Util; -use ksb::BuildContext 0.30; -use ksb::BuildSystem 0.30; - -our @ISA = ('ksb::BuildSystem'); sub needsInstalled { my $self = shift; return 0 if $self->name() eq 'kde-common'; # Vestigial return 1; } sub name { return 'KDE'; } sub isProgressOutputSupported { return 1; } # Called by the module being built before it runs its build/install process. Should # setup any needed environment variables, build context settings, etc., in preparation # for the build and install phases. sub prepareModuleBuildEnvironment { my ($self, $ctx, $module, $prefix) = @_; $ctx->prependEnvironmentValue('CMAKE_PREFIX_PATH', $prefix); $ctx->prependEnvironmentValue('XDG_DATA_DIRS', "$prefix/share"); my $qtdir = $module->getOption('qtdir'); if ($qtdir && $qtdir ne $prefix) { # Ensure we can find Qt5's own CMake modules $ctx->prependEnvironmentValue('CMAKE_MODULE_PATH', "$qtdir/lib/cmake"); } } sub requiredPrograms { return qw{cmake qmake}; } sub configuredModuleFileName { my $self = shift; return 'cmake_install.cmake'; } sub runTestsuite { my $self = assert_isa(shift, 'ksb::BuildSystem::KDE4'); my $module = $self->module(); # Note that we do not run safe_make, which should really be called # safe_compile at this point. # Step 1: Ensure the tests are built, oh wait we already did that when we ran # CMake :) my $make_target = 'test'; if ($module->getOption('run-tests') eq 'upload') { $make_target = 'Experimental'; } info ("\tRunning test suite..."); # Step 2: Run the tests. my $numTests = -1; my $countCallback = sub { if ($_ && /([0-9]+) tests failed out of/) { $numTests = $1; } }; my $result = log_command($module, 'test-results', [ 'make', $make_target ], { callback => $countCallback, no_translate => 1}); if ($result != 0) { my $logDir = $module->getLogDir(); if ($numTests > 0) { warning ("\t$numTests tests failed for y[$module], consult $logDir/test-results.log for info"); } else { warning ("\tSome tests failed for y[$module], consult $logDir/test-results.log for info"); } return 0; } else { info ("\tAll tests ran successfully."); } return 1; } # Re-implementing the one in BuildSystem since in CMake we want to call # make install/fast, so it only installs rather than building + installing sub installInternal { my $self = shift; my $module = $self->module(); my $target = 'install/fast'; my @cmdPrefix = @_; $target = 'install' if $module->getOption('custom-build-command'); return $self->safe_make ({ target => $target, logfile => 'install', message => 'Installing..', 'prefix-options' => [@cmdPrefix], subdirs => [ split(' ', $module->getOption("checkout-only")) ], }) == 0; } sub configureInternal { my $self = assert_isa(shift, 'ksb::BuildSystem::KDE4'); my $module = $self->module(); # Use cmake to create the build directory (sh script return value # semantics). if (_safe_run_cmake ($module)) { error ("\tUnable to configure r[$module] with CMake!"); return 0; } return 1; } ### Internal package functions. # Subroutine to run CMake to create the build directory for a module. # CMake is not actually run if pretend mode is enabled. # # First parameter is the module to run cmake on. # Return value is the shell return value as returned by log_command(). i.e. # 0 for success, non-zero for failure. sub _safe_run_cmake { my $module = assert_isa(shift, 'ksb::Module'); my $srcdir = $module->fullpath('source'); my @commands = split_quoted_on_whitespace ($module->getOption('cmake-options')); # grep out empty fields @commands = grep {!/^\s*$/} @commands; # Add -DBUILD_foo=OFF options for the directories in do-not-compile. # This will only work if the CMakeLists.txt file uses macro_optional_add_subdirectory() my @masked_directories = split(' ', $module->getOption('do-not-compile')); push @commands, "-DBUILD_$_=OFF" foreach @masked_directories; # Get the user's CXXFLAGS, use them if specified and not already given # on the command line. my $cxxflags = $module->getOption('cxxflags'); if ($cxxflags and not grep { /^-DCMAKE_CXX_FLAGS(:\w+)?=/ } @commands) { push @commands, "-DCMAKE_CXX_FLAGS:STRING=$cxxflags"; } my $prefix = $module->installationPath(); push @commands, "-DCMAKE_INSTALL_PREFIX=$prefix"; if ($module->getOption('run-tests') && !grep { /^\s*-DKDE4_BUILD_TESTS(:BOOL)?=(ON|TRUE|1)\s*$/ } (@commands) ) { whisper ("Enabling tests"); push @commands, "-DKDE4_BUILD_TESTS:BOOL=ON"; # Also enable phonon tests. if ($module =~ /^phonon$/) { push @commands, "-DPHONON_BUILD_TESTS:BOOL=ON"; } } if ($module->getOption('run-tests') eq 'upload') { whisper ("Enabling upload of test results"); push @commands, "-DBUILD_experimental:BOOL=ON"; } unshift @commands, 'cmake', $srcdir; # Add to beginning of list. my $old_options = $module->getPersistentOption('last-cmake-options') || ''; my $builddir = $module->fullpath('build'); if (($old_options ne get_list_digest(@commands)) || $module->getOption('reconfigure') || ! -e "$builddir/CMakeCache.txt" # File should exist only on successful cmake run ) { info ("\tRunning g[cmake]..."); # Remove any stray CMakeCache.txt safe_unlink ("$srcdir/CMakeCache.txt") if -e "$srcdir/CMakeCache.txt"; safe_unlink ("$builddir/CMakeCache.txt") if -e "$builddir/CMakeCache.txt"; $module->setPersistentOption('last-cmake-options', get_list_digest(@commands)); return log_command($module, "cmake", \@commands); } # Skip cmake run return 0; } 1; diff --git a/modules/ksb/BuildSystem/QMake.pm b/modules/ksb/BuildSystem/QMake.pm index 8bbb0f1..53c5626 100644 --- a/modules/ksb/BuildSystem/QMake.pm +++ b/modules/ksb/BuildSystem/QMake.pm @@ -1,85 +1,82 @@ -package ksb::BuildSystem::QMake; +package ksb::BuildSystem::QMake 0.10; # A build system used to build modules that use qmake use strict; use warnings; use 5.014; -our $VERSION = '0.10'; - -use List::Util qw(first); +use parent qw(ksb::BuildSystem); use ksb::Debug; use ksb::Util; -use ksb::BuildSystem; -our @ISA = ('ksb::BuildSystem'); +use List::Util qw(first); sub name { return 'qmake'; } sub requiredPrograms { return qw{qmake}; } # I've never had problems with modern QMake-using modules being built in a # specific build directory, until I tried using QMake to build Qt5 modules # (past qtbase). Many seem fail with builddir != srcdir sub needsBuilddirHack { my $self = shift; my $module = $self->module(); # Assume code.qt.io modules all need hack for now return ($module->getOption('repository') =~ /qt\.io/); } # Returns the absolute path to 'qmake'. Note the actual executable name may # not necessarily be 'qmake' as some distributions rename it to allow for # co-installability with Qt 3 (and 5...) # If no suitable qmake can be found, undef is returned. # This is a "static class method" i.e. use ksb::BuildSystem::QMake::absPathToQMake() sub absPathToQMake { my @possibilities = qw/qmake qmake4 qmake-qt4 qmake-mac qmake-qt5/; return first { absPathToExecutable($_) } @possibilities; } # Return value style: boolean sub configureInternal { my $self = assert_isa(shift, 'ksb::BuildSystem::QMake'); my $module = $self->module(); my $builddir = $module->fullpath('build'); my $sourcedir = $self->needsBuilddirHack() ? $builddir : $module->fullpath('source'); my @qmakeOpts = split(' ', $module->getOption('qmake-options')); my @projectFiles = glob("$sourcedir/*.pro"); @projectFiles = ("$module.pro") if (!@projectFiles && pretending()); if (!@projectFiles || !$projectFiles[0]) { croak_internal("No *.pro files could be found for $module"); } if (@projectFiles > 1) { error (" b[r[*] Too many possible *.pro files for $module"); return 0; } p_chdir($builddir); my $qmake = absPathToQMake(); return 0 unless $qmake; info ("\tRunning g[qmake]..."); return log_command($module, 'qmake', [ $qmake, @qmakeOpts, $projectFiles[0] ]) == 0; } 1; diff --git a/modules/ksb/BuildSystem/Qt4.pm b/modules/ksb/BuildSystem/Qt4.pm index feaed54..26c4a98 100644 --- a/modules/ksb/BuildSystem/Qt4.pm +++ b/modules/ksb/BuildSystem/Qt4.pm @@ -1,101 +1,98 @@ -package ksb::BuildSystem::Qt4; +package ksb::BuildSystem::Qt4 0.10; # Build system for the Qt4 toolkit use strict; use warnings; use 5.014; -our $VERSION = '0.10'; +use parent qw(ksb::BuildSystem); use ksb::Debug; use ksb::Util; -use ksb::BuildSystem; - -our @ISA = ('ksb::BuildSystem'); sub needsInstalled { my $self = assert_isa(shift, 'ksb::BuildSystem::Qt4'); my $module = $self->module(); return $module->getOption('qtdir') ne $module->fullpath('build'); } sub name { return 'Qt'; } sub needsBuilddirHack { return 1; } # Return value style: boolean sub configureInternal { my $self = assert_isa(shift, 'ksb::BuildSystem::Qt4'); my $module = $self->module(); my $srcdir = $module->fullpath('source'); my $script = "$srcdir/configure"; if (! -e $script && !pretending()) { error ("\tMissing configure script for r[b[$module]"); return 0; } my @commands = split (/\s+/, $module->getOption('configure-flags')); push @commands, '-confirm-license', '-opensource'; # Get the user's CXXFLAGS my $cxxflags = $module->getOption('cxxflags'); $module->buildContext()->queueEnvironmentVariable('CXXFLAGS', $cxxflags); my $prefix = $module->getOption('qtdir'); if (!$prefix) { error ("\tThe b[qtdir] option must be set to determine where to install r[b[$module]"); return 0; } # Some users have added -prefix manually to their flags, they # probably shouldn't anymore. :) if (scalar grep /^-prefix(=.*)?$/, @commands) { warning (<fullpath('build'); my $old_flags = $module->getPersistentOption('last-configure-flags') || ''; my $cur_flags = get_list_digest(@commands); if(($cur_flags ne $old_flags) || ($module->getOption('reconfigure')) || (! -e "$builddir/Makefile") ) { note ("\tb[r[LGPL license selected for Qt]. See $srcdir/LICENSE.LGPL"); info ("\tRunning g[configure]..."); $module->setPersistentOption('last-configure-flags', $cur_flags); return log_command($module, "configure", \@commands) == 0; } # Skip execution of configure. return 1; } 1; diff --git a/modules/ksb/IPC/Null.pm b/modules/ksb/IPC/Null.pm index 41dec3c..007082d 100644 --- a/modules/ksb/IPC/Null.pm +++ b/modules/ksb/IPC/Null.pm @@ -1,42 +1,39 @@ -package ksb::IPC::Null; +package ksb::IPC::Null 0.10; # Dummy IPC module in case SysVIPC doesn't work or async mode is not needed. use strict; use warnings; use 5.014; -our $VERSION = '0.10'; - -use ksb::IPC; -our @ISA = qw(ksb::IPC); +use parent qw(ksb::IPC); sub new { my $class = shift; my $self = $class->SUPER::new; $self->{'msgList'} = []; # List of messages. return bless $self, $class; # OOP in Perl is so completely retarded } sub sendMessage { my $self = shift; my $msg = shift; push @{$self->{'msgList'}}, $msg; return 1; } sub receiveMessage { my $self = shift; return undef unless scalar @{$self->{'msgList'}} > 0; return shift @{$self->{'msgList'}}; } 1; diff --git a/modules/ksb/IPC/Pipe.pm b/modules/ksb/IPC/Pipe.pm index 5a8fc1e..12e7fe6 100644 --- a/modules/ksb/IPC/Pipe.pm +++ b/modules/ksb/IPC/Pipe.pm @@ -1,129 +1,126 @@ -package ksb::IPC::Pipe; +package ksb::IPC::Pipe 0.20; # IPC class that uses pipes in addition to forking for IPC. use strict; use warnings; use 5.014; -our $VERSION = '0.20'; - -use ksb::IPC; -our @ISA = qw(ksb::IPC); +use parent qw(ksb::IPC); use ksb::Util qw(croak_internal croak_runtime); use IO::Handle; use IO::Pipe; use Errno qw(EINTR); sub new { my $class = shift; my $self = $class->SUPER::new; # Define file handles. $self->{fh} = IO::Pipe->new(); return bless $self, $class; } # Call this to let the object know it will be the update process. sub setSender { my $self = shift; $self->{fh}->writer(); # Disable buffering and any possibility of IO 'interpretation' of the bytes $self->{fh}->autoflush(1); binmode($self->{fh}) } sub setReceiver { my $self = shift; $self->{fh}->reader(); # Disable buffering and any possibility of IO 'interpretation' of the bytes $self->{fh}->autoflush(1); binmode($self->{fh}) } # Reimplementation of ksb::IPC::supportsConcurrency sub supportsConcurrency { return 1; } # Required reimplementation of ksb::IPC::sendMessage # First parameter is the (encoded) message to send. sub sendMessage { my ($self, $msg) = @_; # Since streaming does not provide message boundaries, we will insert # ourselves, by sending a 2-byte unsigned length, then the message. my $encodedMsg = pack ("S a*", length($msg), $msg); my $result = $self->{fh}->syswrite($encodedMsg); if (!$result || length($encodedMsg) != $result) { croak_runtime("Unable to write full msg to pipe: $!"); } return 1; } sub _readNumberOfBytes { my ($self, $length) = @_; my $fh = $self->{fh}; my $readLength = 0; my $result; while ($readLength < $length) { $! = 0; # Reset errno my $curLength = $fh->sysread ($result, ($length - $readLength), $readLength); # EINTR is OK, but check early so we don't trip 0-length check next if (!defined $curLength && $!{EINTR}); return if (defined $curLength && $curLength == 0); croak_internal("Error reading $length bytes from pipe: $!") if !$curLength; croak_internal("sysread read too much: $curLength vs $length") if ($curLength > $length); $readLength += $curLength; } return $result; } # Required reimplementation of ksb::IPC::receiveMessage sub receiveMessage { my $self = shift; # Read unsigned short with msg length, then the message my $msgLength = $self->_readNumberOfBytes(2); return if !$msgLength; $msgLength = unpack ("S", $msgLength); # Decode to Perl type if (!$msgLength) { croak_internal ("Failed to read $msgLength bytes as needed by earlier message!"); } return $self->_readNumberOfBytes($msgLength); } sub close { my $self = shift; $self->{fh}->close(); } 1; diff --git a/modules/ksb/ModuleSet/Null.pm b/modules/ksb/ModuleSet/Null.pm index 5db5444..edefa4f 100644 --- a/modules/ksb/ModuleSet/Null.pm +++ b/modules/ksb/ModuleSet/Null.pm @@ -1,33 +1,32 @@ -package ksb::ModuleSet::Null; +package ksb::ModuleSet::Null 0.10; # Class: ModuleSet::Null # # Used automatically by to represent the abscence of a without # requiring definedness checks. use strict; use warnings; use 5.014; -our $VERSION = '0.10'; -our @ISA = qw(ksb::ModuleSet); +use parent qw(ksb::ModuleSet); use ksb::Util; sub new { my $class = shift; return bless {}, $class; } sub name { return ''; } sub convertToModules { croak_internal("kdesrc-build should not have made it to this call. :-("); } 1; diff --git a/modules/ksb/Updater/Bzr.pm b/modules/ksb/Updater/Bzr.pm index 39bd41c..8fd8f40 100644 --- a/modules/ksb/Updater/Bzr.pm +++ b/modules/ksb/Updater/Bzr.pm @@ -1,96 +1,92 @@ -package ksb::Updater::Bzr; +package ksb::Updater::Bzr 0.10; # Support the bazaar source control manager for libdbusmenu-qt use strict; use warnings; use 5.014; -our $VERSION = '0.10'; +use parent qw(ksb::Updater); use ksb::Debug; use ksb::Util; -use ksb::Updater; - -# Our superclass -our @ISA = qw(ksb::Updater); # scm-specific update procedure. # May change the current directory as necessary. # Should return a count of files changed (or commits, or something similar) sub updateInternal { my $self = assert_isa(shift, 'ksb::Updater::Bzr'); my $module = assert_isa($self->module(), 'ksb::Module'); # Full path to source directory on-disk. my $srcdir = $module->fullpath('source'); my $bzrRepoName = $module->getOption('repository'); # Or whatever regex is appropriate to strip the bzr URI protocol. $bzrRepoName =~ s/^bzr:\/\///; if (! -e "$srcdir/.bzr") { # Cmdline assumes bzr will create the $srcdir directory and then # check the source out into that directory. my @cmd = ('bzr', 'branch', $bzrRepoName, $srcdir); # Exceptions are used for failure conditions if (log_command($module, 'bzr-branch', \@cmd) != 0) { die make_exception('Internal', "Unable to checkout $module!"); } # TODO: Filtering the output by passing a subroutine to log_command # should give us the number of revisions, or we can just somehow # count files. my $newRevisionCount = 0; return $newRevisionCount; } else { # Update existing checkout. The source is currently in $srcdir p_chdir($srcdir); if (log_command($module, 'bzr-pull', ['bzr', 'pull']) != 0) { die make_exception('Internal', "Unable to update $module!"); } # I haven't looked at bzr up output yet to determine how to find # number of affected files or number of revisions skipped. my $changeCount = 0; return $changeCount; } return 0; } sub name { return 'bzr'; } # This is used to track things like the last successfully installed # revision of a given module. sub currentRevisionInternal { my $self = assert_isa(shift, 'ksb::Updater::Bzr'); my $module = $self->module(); my $result; # filter_program_output can throw exceptions eval { p_chdir($module->fullpath('source')); ($result, undef) = filter_program_output(undef, 'bzr', 'revno'); chomp $result if $result; }; if ($@) { error ("Unable to run r[b[bzr], is bazaar installed?"); error (" -- Error was: r[$@]"); return undef; } return $result; } 1; diff --git a/modules/ksb/Updater/Git.pm b/modules/ksb/Updater/Git.pm index cb03df5..0f0fc16 100644 --- a/modules/ksb/Updater/Git.pm +++ b/modules/ksb/Updater/Git.pm @@ -1,826 +1,824 @@ -package ksb::Updater::Git; +package ksb::Updater::Git 0.15; # Module which is responsible for updating git-based source code modules. Can # have some features overridden by subclassing (see ksb::Updater::KDEProject # for an example). +use strict; +use warnings; use 5.014; +use parent qw(ksb::Updater); + use ksb::Debug; +use ksb::IPC::Null; use ksb::Util; -use ksb::Updater; - -our $VERSION = '0.10'; - -our @ISA = qw(ksb::Updater); use File::Basename; # basename use File::Spec; # tmpdir use POSIX qw(strftime); use List::Util qw(first); use IPC::Cmd qw(run_forked); -use ksb::IPC::Null; - use constant { DEFAULT_GIT_REMOTE => 'origin', }; # scm-specific update procedure. # May change the current directory as necessary. sub updateInternal { my $self = assert_isa(shift, 'ksb::Updater::Git'); my $ipc = shift; $self->{ipc} = $ipc // ksb::IPC::Null->new(); return $self->updateCheckout(); delete $self->{ipc}; } sub name { return 'git'; } sub currentRevisionInternal { my $self = assert_isa(shift, 'ksb::Updater::Git'); return $self->commit_id('HEAD'); } # Returns the current sha1 of the given git "commit-ish". sub commit_id { my $self = assert_isa(shift, 'ksb::Updater::Git'); my $commit = shift or croak_internal("Must specify git-commit to retrieve id for"); my $module = $self->module(); my $gitdir = $module->fullpath('source') . '/.git'; # Note that the --git-dir must come before the git command itself. my ($id, undef) = filter_program_output( undef, # No filter qw/git --git-dir/, $gitdir, 'rev-parse', $commit, ); chomp $id if $id; return $id; } sub _verifyRefPresent { my ($self, $module, $repo) = @_; my ($commitId, $commitType) = $self->_determinePreferredCheckoutSource($module); return 1 if pretending(); my $ref = (($commitType eq 'branch') ? 'refs/heads/' : ($commitType eq 'tag') ? 'refs/tags/' : '') . $commitId; my $hashref = run_forked("git ls-remote --exit-code $repo $ref", { timeout => 10, discard_output => 1, terminate_on_parent_sudden_death => 1}); my $result = $hashref->{exit_code}; return 0 if ($result == 2); # Connection successful, but ref not found return 1 if ($result == 0); # Ref is present croak_runtime("git had error exit $result when verifying $ref present in repository at $repo"); } # Perform a git clone to checkout the latest branch of a given git module # # First parameter is the repository (typically URL) to use. # Throws an exception if it fails. sub _clone { my $self = assert_isa(shift, 'ksb::Updater::Git'); my $git_repo = shift; my $module = $self->module(); my $srcdir = $module->fullpath('source'); my @args = ('--', $git_repo, $srcdir); my $ipc = $self->{ipc} // croak_internal ('Missing IPC object'); note ("Cloning g[$module]"); p_chdir($module->getSourceDir()); my ($commitId, $commitType) = $self->_determinePreferredCheckoutSource($module); $commitId = "refs/tags/$commitId" if $commitType eq 'tag'; unshift @args, '-b', $commitId; # Checkout branch right away if (0 != log_command($module, 'git-clone', ['git', 'clone', @args])) { croak_runtime("Failed to make initial clone of $module"); } $ipc->notifyPersistentOptionChange( $module->name(), 'git-cloned-repository', $git_repo); p_chdir($srcdir); # Setup user configuration if (my $name = $module->getOption('git-user')) { my ($username, $email) = ($name =~ /^([^<]+) +<([^>]+)>$/); if (!$username || !$email) { croak_runtime("Invalid username or email for git-user option: $name". " (should be in format 'User Name '"); } whisper ("\tAdding git identity $name for new git module $module"); my $result = (safe_system(qw(git config --local user.name), $username) >> 8) == 0; $result = (safe_system(qw(git config --local user.email), $email) >> 8 == 0) || $result; if (!$result) { warning ("Unable to set user.name and user.email git config for y[b[$module]!"); } } return; } # Either performs the initial checkout or updates the current git checkout # for git-using modules, as appropriate. # # If errors are encountered, an exception is raised. # # Returns the number of *commits* affected. sub updateCheckout { my $self = assert_isa(shift, 'ksb::Updater::Git'); my $module = $self->module(); my $srcdir = $module->fullpath('source'); if (-d "$srcdir/.git") { # Note that this function will throw an exception on failure. return $self->updateExistingClone(); } else { # Check if an existing source directory is there somehow. if (-e "$srcdir" && !is_dir_empty($srcdir)) { if ($module->getOption('#delete-my-patches')) { warning ("\tRemoving conflicting source directory " . "as allowed by --delete-my-patches"); warning ("\tRemoving b[$srcdir]"); safe_rmtree($srcdir) or croak_internal("Unable to delete $srcdir!"); } else { error (<getOption('repository'); if (!$git_repo) { croak_internal("Unable to checkout $module, you must specify a repository to use."); } if (!$self->_verifyRefPresent($module, $git_repo)) { if (!$self->_moduleIsNeeded()) { note ("Skipping g[$module], this module was not in the containing module-set at this branch"); return 0; } croak_runtime("The desired git reference is not available for $module"); } $self->_clone($git_repo); return 1 if pretending(); return count_command_output('git', '--git-dir', "$srcdir/.git", 'ls-files'); } return 0; } # Intended to be reimplemented sub _moduleIsNeeded { return 1; } # Selects a git remote for the user's selected repository (preferring a # defined remote if available, using 'origin' otherwise). # # Assumes the current directory is already set to the source directory. # # Throws an exception on error. # # Return value: Remote name that should be used for further updates. # # See also the 'repository' module option. sub _setupBestRemote { my $self = assert_isa(shift, 'ksb::Updater::Git'); my $module = $self->module(); my $cur_repo = $module->getOption('repository'); my $ipc = $self->{ipc} // croak_internal ('Missing IPC object'); # Search for an existing remote name first. If none, add our alias. my @remoteNames = $self->bestRemoteName($cur_repo); if (!@remoteNames) { # The desired repo doesn't have a named remote, this should be # because the user switched it in the rc-file. We control the # 'origin' remote to fix this. if ($self->hasRemote(DEFAULT_GIT_REMOTE)) { if (log_command($module, 'git-update-remote', ['git', 'remote', 'set-url', DEFAULT_GIT_REMOTE, $cur_repo]) != 0) { croak_runtime("Unable to update the fetch URL for existing remote alias for $module"); } } elsif (log_command($module, 'git-remote-setup', ['git', 'remote', 'add', DEFAULT_GIT_REMOTE, $cur_repo]) != 0) { croak_runtime("Unable to add a git remote named " . DEFAULT_GIT_REMOTE . " for $cur_repo"); } push @remoteNames, DEFAULT_GIT_REMOTE; } # Make a notice if the repository we're using has moved. my $old_repo = $module->getPersistentOption('git-cloned-repository'); if ($old_repo and ($cur_repo ne $old_repo)) { note (" y[b[*]\ty[$module]'s selected repository has changed"); note (" y[b[*]\tfrom y[$old_repo]"); note (" y[b[*]\tto b[$cur_repo]"); note (" y[b[*]\tThe git remote named b[", DEFAULT_GIT_REMOTE, "] has been updated"); # Update what we think is the current repository on-disk. $ipc->notifyPersistentOptionChange( $module->name(), 'git-cloned-repository', $cur_repo); } return $remoteNames[0]; } # Completes the steps needed to update a git checkout to be checked-out to # a given remote-tracking branch. Any existing local branch with the given # branch set as upstream will be used if one exists, otherwise one will be # created. The given branch will be rebased into the local branch. # # No checkout is done, this should be performed first. # Assumes we're already in the needed source dir. # Assumes we're in a clean working directory (use git-stash to achieve # if necessary). # # First parameter is the remote to use. # Second parameter is the branch to update to. # Returns boolean success flag. # Exception may be thrown if unable to create a local branch. sub _updateToRemoteHead { my $self = shift; my ($remoteName, $branch) = @_; my $module = $self->module(); # The 'branch' option requests a given head in the user's selected # repository. Normally the remote head is mapped to a local branch, # which can have a different name. So, first we make sure the remote # head is actually available, and if it is we compare its SHA1 with # local branches to find a matching SHA1. Any local branches that are # found must also be remote-tracking. If this is all true we just # re-use that branch, otherwise we create our own remote-tracking # branch. my $branchName = $self->getRemoteBranchName($remoteName, $branch); if (!$branchName) { my $newName = $self->makeBranchname($remoteName, $branch); whisper ("\tUpdating g[$module] with new remote-tracking branch y[$newName]"); if (0 != log_command($module, 'git-checkout-branch', ['git', 'checkout', '-b', $newName, "$remoteName/$branch"])) { croak_runtime("Unable to perform a git checkout of $remoteName/$branch to a local branch of $newName"); } } else { whisper ("\tUpdating g[$module] using existing branch g[$branchName]"); if (0 != log_command($module, 'git-checkout-update', ['git', 'checkout', $branchName])) { croak_runtime("Unable to perform a git checkout to existing branch $branchName"); } # On the right branch, merge in changes. return 0 == log_command($module, 'git-rebase', ['git', 'rebase', "$remoteName/$branch"]); } return 1; } # Completes the steps needed to update a git checkout to be checked-out to # a given commit. The local checkout is left in a detached HEAD state, # even if there is a local branch which happens to be pointed to the # desired commit. Based the given commit is used directly, no rebase/merge # is performed. # # No checkout is done, this should be performed first. # Assumes we're already in the needed source dir. # Assumes we're in a clean working directory (use git-stash to achieve # if necessary). # # First parameter is the commit to update to. This can be in pretty # much any format that git itself will respect (e.g. tag, sha1, etc.). # It is recommended to use refs/$foo/$bar syntax for specificity. # Returns boolean success flag. sub _updateToDetachedHead { my ($self, $commit) = @_; my $module = $self->module(); info ("\tDetaching head to b[$commit]"); return 0 == log_command($module, 'git-checkout-commit', ['git', 'checkout', $commit]); } # Updates an already existing git checkout by running git pull. # # Throws an exception on error. # # Return parameter is the number of affected *commits*. sub updateExistingClone { my $self = assert_isa(shift, 'ksb::Updater::Git'); my $module = $self->module(); my $cur_repo = $module->getOption('repository'); my $result; p_chdir($module->fullpath('source')); # Try to save the user if they are doing a merge or rebase if (-e '.git/MERGE_HEAD' || -e '.git/rebase-merge' || -e '.git/rebase-apply') { croak_runtime ("Aborting git update for $module, you appear to have a rebase or merge in progress!"); } my $remoteName = $self->_setupBestRemote(); # Download updated objects. This also updates remote heads so do this # before we start comparing branches and such. if (0 != log_command($module, 'git-fetch', ['git', 'fetch', '--tags', $remoteName])) { croak_runtime ("Unable to perform git fetch for $remoteName ($cur_repo)"); } # Now we need to figure out if we should update a branch, or simply # checkout a specific tag/SHA1/etc. my ($commitId, $commitType) = $self->_determinePreferredCheckoutSource($module); note ("Updating g[$module] (to $commitType b[$commitId])"); my $start_commit = $self->commit_id('HEAD'); my $updateSub; if ($commitType eq 'branch') { $updateSub = sub { $self->_updateToRemoteHead($remoteName, $commitId) }; } else { $updateSub = sub { $self->_updateToDetachedHead($commitId); } } # With all remote branches fetched, and the checkout of our desired # branch completed, we can now use our update sub to complete the # changes. $self->stashAndUpdate($updateSub); return count_command_output('git', 'rev-list', "$start_commit..HEAD"); } # Goes through all the various combination of git checkout selection options in # various orders of priority. # # Returns a *list* containing: (the resultant symbolic ref/or SHA1,'branch' or # 'tag' (to determine if something like git-pull would be suitable or whether # you have a detached HEAD)). Since the sym-ref is returned first that should # be what you get in a scalar context, if that's all you want. sub _determinePreferredCheckoutSource { my ($self, $module) = @_; $module //= $self->module(); my @priorityOrderedSources = ( # option-name type getOption-inheritance-flag [qw(commit tag module)], [qw(revision tag module)], [qw(tag tag module)], [qw(branch branch module)], [qw(branch-group branch module)], [qw(use-stable-kde branch module)], # commit/rev/tag don't make sense for git as globals [qw(branch branch allow-inherit)], [qw(branch-group branch allow-inherit)], [qw(use-stable-kde branch allow-inherit)], ); # For modules that are not actually a 'proj' module we skip branch-group # and use-stable-kde entirely to allow for global/module branch selection # options to be selected... kind of complicated, but more DWIMy if (!$module->scm()->isa('ksb::Updater::KDEProject')) { @priorityOrderedSources = grep { $_->[0] ne 'branch-group' && $_->[0] ne 'use-stable-kde' } @priorityOrderedSources; } my $checkoutSource; # Sorry about the !!, easiest way to be clear that bool context is intended my $sourceTypeRef = first { !!($checkoutSource = ($module->getOption($_->[0], $_->[2]) // '')) } @priorityOrderedSources; if (!$sourceTypeRef) { return qw(master branch); } # One fixup is needed for use-stable-kde, to pull the actual branch name # from the right spot. Although if no branch name is set we use master, # without trying to search again. if ($sourceTypeRef->[0] eq 'use-stable-kde') { $checkoutSource = $module->getOption('#branch:stable', 'module') || 'master'; } # Likewise branch-group requires special handling. checkoutSource is # currently the branch-group to be resolved. if ($sourceTypeRef->[0] eq 'branch-group') { assert_isa($self, 'ksb::Updater::KDEProject'); $checkoutSource = $self->_resolveBranchGroup($checkoutSource); if (!$checkoutSource) { my $branchGroup = $module->getOption('branch-group'); whisper ("No specific branch set for $module and $branchGroup, using master!"); $checkoutSource = 'master'; } } if ($sourceTypeRef->[0] eq 'tag' && $checkoutSource !~ m{^refs/tags/}) { $checkoutSource = "refs/tags/$checkoutSource"; } return ($checkoutSource, $sourceTypeRef->[1]); } # Splits a URI up into its component parts. Taken from # http://search.cpan.org/~ether/URI-1.67/lib/URI.pm # Copyright Gisle Aas under the following terms: # "This program is free software; you can redistribute it and/or modify it # under the same terms as Perl itself." sub _splitUri { my($scheme, $authority, $path, $query, $fragment) = $_[0] =~ m|(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?|; return ($scheme, $authority, $path, $query, $fragment); } # This stashes existing changes if necessary, and then runs a provided # update routine in order to advance the given module to the desired head. # Finally, if changes were stashed, they are applied and the stash stack is # popped. # # It is assumed that the required remote has been setup already, that we # are on the right branch, and that we are already in the correct # directory. # # First parameter is a reference to the subroutine to run. This subroutine # should need no parameters and return a boolean success indicator. It may # throw exceptions. # # Throws an exception on error. # # No return value. sub stashAndUpdate { my $self = assert_isa(shift, 'ksb::Updater::Git'); my $updateSub = shift; my $module = $self->module(); my $date = strftime ("%F-%R", gmtime()); # ISO Date, hh:mm time # To find out if we should stash, we just use git diff --quiet, twice to # account for the index and the working dir. # Note: Don't use safe_system, as the error code is stripped to the exit code my $status = pretending() ? 0 : system('git', 'diff', '--quiet'); if ($status == -1 || $status & 127) { croak_runtime("$module doesn't appear to be a git module."); } my $needsStash = 0; if ($status) { # There are local changes. $needsStash = 1; } else { $status = pretending() ? 0 : system('git', 'diff', '--cached', '--quiet'); if ($status == -1 || $status & 127) { croak_runtime("$module doesn't appear to be a git module."); } else { $needsStash = ($status != 0); } } if ($needsStash) { info ("\tLocal changes detected, stashing them away..."); $status = log_command($module, 'git-stash-save', [ qw(git stash save --quiet), "kdesrc-build auto-stash at $date", ]); if ($status != 0) { croak_runtime("Unable to stash local changes for $module, aborting update."); } } if (!$updateSub->()) { error ("\tUnable to update the source code for r[b[$module]"); return; } # Update is performed and successful, re-apply the stashed changes if ($needsStash) { info ("\tModule updated, reapplying your local changes."); $status = log_command($module, 'git-stash-pop', [ qw(git stash pop --index --quiet) ]); if ($status != 0) { error (<module(); my $chosenName; # Use "$branch" directly if not already used, otherwise try to prefix # with the remote name. for my $possibleBranch ($branch, "$remoteName-$branch", "ksdc-$remoteName-$branch") { my $result = system('git', 'show-ref', '--quiet', '--verify', '--', "refs/heads/$possibleBranch") >> 8; return $possibleBranch if $result == 1; } croak_runtime("Unable to find good branch name for $module branch name $branch"); } # Returns the number of lines in the output of the given command. The command # and all required arguments should be passed as a normal list, and the current # directory should already be set as appropriate. # # Return value is the number of lines of output. # Exceptions are raised if the command could not be run. sub count_command_output { # Don't call with $self->, all args are passed to filter_program_output my @args = @_; my $count = 0; filter_program_output(sub { $count++ if $_ }, @args); return $count; } # A simple wrapper that is used to split the output of 'git config --null' # correctly. All parameters are then passed to filter_program_output (so look # there for help on usage). sub slurp_git_config_output { # Don't call with $self->, all args are passed to filter_program_output local $/ = "\000"; # Split on null # This gets rid of the trailing nulls for single-line output. (chomp uses # $/ instead of hardcoding newline chomp(my @output = filter_program_output(undef, @_)); # No filter return @output; } # Returns true if the git module in the current directory has a remote of the # name given by the first parameter. sub hasRemote { my ($self, $remote) = @_; my $hasRemote = 0; eval { filter_program_output(sub { $hasRemote ||= ($_ && /^$remote/) }, 'git', 'remote'); }; return $hasRemote; } # Subroutine to add the 'kde:' alias to the user's git config if it's not # already set. # # Call this as a static class function, not as an object method # (i.e. ksb::Updater::Git::verifyGitConfig, not $foo->verifyGitConfig) # # Returns false on failure of any sort, true otherwise. sub verifyGitConfig { my $configOutput = qx'git config --global --get url.git://anongit.kde.org/.insteadOf kde:'; # 0 means no error, 1 means no such section exists -- which is OK if ((my $errNum = $? >> 8) >= 2) { my $error = "Code $errNum"; my %errors = ( 3 => 'Invalid config file (~/.gitconfig)', 4 => 'Could not write to ~/.gitconfig', 2 => 'No section was provided to git-config', 1 => 'Invalid section or key', 5 => 'Tried to set option that had no (or multiple) values', 6 => 'Invalid regexp with git-config', 128 => 'HOME environment variable is not set (?)', ); $error = $errors{$errNum} if exists $errors{$errNum}; error (" r[*] Unable to run b[git] command:\n\t$error"); return 0; } # If we make it here, I'm just going to assume git works from here on out # on this simple task. if ($configOutput !~ /^kde:\s*$/) { whisper ("\tAdding git download kde: alias"); my $result = safe_system( qw(git config --global --add url.git://anongit.kde.org/.insteadOf kde:) ) >> 8; return 0 if $result != 0; } $configOutput = qx'git config --global --get url.git@git.kde.org:.pushInsteadOf kde:'; if ($configOutput !~ /^kde:\s*$/) { whisper ("\tAdding git upload kde: alias"); my $result = safe_system( qw(git config --global --add url.git@git.kde.org:.pushInsteadOf kde:) ) >> 8; return 0 if $result != 0; } return 1; } 1; diff --git a/modules/ksb/Updater/KDEProjectMetadata.pm b/modules/ksb/Updater/KDEProjectMetadata.pm index 77985e9..c0489fc 100644 --- a/modules/ksb/Updater/KDEProjectMetadata.pm +++ b/modules/ksb/Updater/KDEProjectMetadata.pm @@ -1,71 +1,68 @@ -package ksb::Updater::KDEProjectMetadata; +package ksb::Updater::KDEProjectMetadata 0.20; # Updater used only to specifically update the "kde-build-metadata" module # used for storing dependency information, among other things. use strict; use warnings; use 5.014; -our $VERSION = '0.20'; +use parent qw(ksb::Updater::KDEProject); use ksb::Util; use ksb::Debug; -use ksb::Updater::KDEProject; - -our @ISA = qw(ksb::Updater::KDEProject); use JSON::PP; sub name { return 'metadata'; } # Returns a list of the full kde-project paths for each module to ignore. sub ignoredModules { my $self = assert_isa(shift, 'ksb::Updater::KDEProjectMetadata'); my $path = $self->module()->fullpath('source') . "/build-script-ignore"; # Now that we in theory have up-to-date source code, read in the # ignore file and propagate that information to our context object. my $fh = pretend_open($path) or croak_internal("Unable to read ignore data from $path: $!"); my $ctx = $self->module()->buildContext(); my @ignoreModules = map { chomp $_; $_ } # 3 Remove newlines grep { !/^\s*$/ } # 2 Filter empty lines map { s/#.*$//; $_ } # 1 Remove comments (<$fh>); return @ignoreModules; } # If JSON support is present, and the metadata has already been downloaded # (e.g. with ->updateInternal), returns a hashref to the logical module group # data contained within the kde-build-metadata, decoded from its JSON format. # See https://community.kde.org/Infrastructure/Project_Metadata sub logicalModuleGroups { my $self = shift; my $path = $self->module()->fullpath('source') . "/logical-module-structure"; my $fh = pretend_open($path) or croak_internal("Unable to read logical module structure: $!"); my ($json_hashref, $e) = do { local $/; # The 'local $/' disables line-by-line reading; slurps the whole file undef $@; my $json = eval { decode_json(<$fh>) }; close $fh; ($json, $@); # Implicit return }; croak_runtime ("Unable to load module group data! :(\n\t$e") if $e; return $json_hashref; } 1; diff --git a/modules/ksb/Updater/Svn.pm b/modules/ksb/Updater/Svn.pm index 68942fe..29230a5 100644 --- a/modules/ksb/Updater/Svn.pm +++ b/modules/ksb/Updater/Svn.pm @@ -1,675 +1,672 @@ -package ksb::Updater::Svn; +package ksb::Updater::Svn 0.10; # Module responsible for source code updates on Subversion modules. Used as a # superclass for our l10n update/build system as well. use strict; use warnings; use 5.014; -our $VERSION = '0.10'; +use parent qw(ksb::Updater); use ksb::Debug; use ksb::Util; -use ksb::Updater; - -our @ISA = qw(ksb::Updater); use IPC::Open3 qw(open3); # Returns true if a module has a base component to their name (e.g. KDE/, # extragear/, or playground). Note that modules that aren't in trunk/KDE # don't necessary meet this criteria (e.g. kdereview is a module itself). sub _has_base_module { my $moduleName = shift; return $moduleName =~ /^(extragear|playground|KDE)(\/[^\/]+)?$/; } # Subroutine to return the branch prefix. i.e. the part before the branch # name and module name. # # The first parameter is the module name in question. # The second parameter should be 'branches' if we're dealing with a branch # or 'tags' if we're dealing with a tag. # # Ex: 'kdelibs' => 'branches/KDE' # 'kdevelop' => 'branches/kdevelop' sub _branch_prefix { my $moduleName = shift; my $type = shift; # These modules seem to have their own subdir in /tags. my @tag_components = qw/arts koffice amarok kst qt taglib/; # The map call adds the kde prefix to the module names because I don't feel # like typing them all in. my @kde_module_list = ((map {'kde' . $_} qw/-base-artwork -wallpapers accessibility addons admin artwork base bindings edu games graphics libs network pim pimlibs plasma-addons sdk toys utils webdev/)); # If the user already has the module in the form KDE/foo, it's already # done. return "$type/KDE" if $moduleName =~ /^KDE\//; # KDE proper modules seem to use this pattern. return "$type/KDE" if list_has(\@kde_module_list, $moduleName); # KDE extragear / playground modules use this pattern return "$type" if _has_base_module($moduleName); # If we doing a tag just return 'tags' because the next part is the actual # tag name, which is added by the caller, unless the module has its own # subdirectory in /tags. return "$type" if $type eq 'tags' and not list_has(\@tag_components, $moduleName); # Everything else. return "$type/$moduleName"; } # This subroutine is responsible for stripping the KDE/ part from the # beginning of modules that were entered by the user like "KDE/kdelibs" # instead of the normal "kdelibs". That way you can search for kdelibs # without having to strip KDE/ everywhere. sub _moduleBaseName { my $moduleName = shift; $moduleName =~ s/^KDE\///; return $moduleName; } # Subroutine to return a module URL for a module using the 'branch' option. # First parameter is the module in question. # Second parameter is the type ('tags' or 'branches') sub _handle_branch_tag_option { my $module = assert_isa(shift, 'ksb::Module'); my $type = shift; my $branch = _branch_prefix($module->name(), $type); my $svn_server = $module->getOption('svn-server'); my $branchname = $module->getOption($type eq 'branches' ? 'branch' : 'tag'); # Remove trailing slashes. $svn_server =~ s/\/*$//; # Remove KDE/ prefix for module name. my $moduleName = _moduleBaseName($module->name()); # KDE modules have a different module naming scheme than the rest it seems. return "$svn_server/$branch/$branchname/$moduleName" if $branch =~ /\/KDE\/?$/; # Non-trunk translations happen in a single branch. Assume all non-trunk # global branches are intended for the stable translations. if ($moduleName =~ /^l10n-kde4\/?/ && $branch ne 'trunk') { return "$svn_server/branches/stable/$moduleName"; } # Otherwise don't append the module name by default since it makes more # sense to branch this way in many situations (i.e. kdesupport tags, phonon) return "$svn_server/$branch/$branchname"; } # Subroutine to return the appropriate SVN URL for a given module, based on # the user settings. For example, 'kdelibs' -> # https://svn.kde.org/home/kde/trunk/KDE/kdelibs # # This operates under a double hierarchy: # 1. If any module-specific option is present, it wins. # 2. If only global options are present, the order override-url, tag, # branch, module-base-path, is preferred. sub svn_module_url { my $self = assert_isa(shift, 'ksb::Updater::Svn'); my $module = $self->module(); my $svn_server = $module->getOption('svn-server'); my $modulePath; foreach my $levelLimit ('module', 'allow-inherit') { $modulePath = $module->getOption('module-base-path', $levelLimit); # Allow user to override normal processing of the module in a few ways, # to make it easier to still be able to use kdesrc-build even when I # can't be there to manually update every little special case. if($module->getOption('override-url', $levelLimit)) { return $module->getOption('override-url', $levelLimit); } if($module->getOption('tag', $levelLimit)) { return _handle_branch_tag_option($module, 'tags'); } my $branch = $module->getOption('branch', $levelLimit); if($branch and $branch ne 'trunk') { return _handle_branch_tag_option($module, 'branches'); } my $moduleName = _moduleBaseName($module->name()); # The following modules are in /trunk, not /trunk/KDE. There are others, # but these are the important ones. my @non_trunk_modules = qw(extragear kdesupport koffice icecream kde-common playground KDE kdereview www l10n-kde4); my $module_root = $moduleName; $module_root =~ s/\/.*//; # Remove everything after the first slash if (not $modulePath and $levelLimit eq 'allow-inherit') { $modulePath = "trunk/KDE/$moduleName"; $modulePath = "trunk/$moduleName" if list_has(\@non_trunk_modules, $module_root); $modulePath =~ s/^\/*//; # Eliminate / at beginning of string. $modulePath =~ s/\/*$//; # Likewise at the end. } last if $modulePath; } # Remove trailing slashes. $svn_server =~ s/\/*$//; # Note that the module name is no longer appended if module-base-path is used (i.e. # $branch variable was set. This is a change as of version 1.8. return "$svn_server/$modulePath"; } # Subroutine to determine whether or not the given module has the correct # URL. If not, a warning is printed out. # First parameter: module to check. # Return: Nothing. sub _verifyCorrectServerURL { my $self = assert_isa(shift, 'ksb::Updater::Svn'); my $module = $self->module(); my $module_expected_url = $self->svn_module_url(); my $module_actual_url = $self->svnInfo('URL'); if (!$module_actual_url) { croak_runtime ("Unable to determine working copy's svn URL for $module"); } $module_expected_url =~ s{/+$}{}; # Remove trailing slashes $module_actual_url =~ s{/+$}{}; # Remove trailing slashes if ($module_actual_url ne $module_expected_url) { # Check if the --src-only flag was passed. my $module = $self->module(); if ($module->buildContext()->getOption('#allow-auto-repo-move')) { note ("g[$module] is checked out from a different location than expected."); note ("Attempting to correct to $module_expected_url"); my ($expected_host, $expected_path) = ($module_expected_url =~ m{://([^/]+)/(.*)$}); my ($actual_host, $actual_path) = ($module_actual_url =~ m{://([^/]+)/(.*)$}); # If the path didn't change but the host info did try --relocate # otherwise try regular svn switch. if (($expected_path eq $actual_path) && ($expected_host ne $actual_host)) { log_command($module, 'svn-switch', [ 'svn', 'switch', '--relocate', $module_actual_url, $module_expected_url]); } else { log_command($module, 'svn-switch', [ 'svn', 'switch', $module_expected_url]); } return; } warning (<module(); # svn 1.7 has a different working copy format that must be manually # converted. This will mess up everything else so make this our first # check. p_chdir($module->fullpath('source')); # gensym makes a symbol that can be made a filehandle by open3 use Symbol qw(gensym); # Can't use filter_program_output as that doesn't capture STDERR on # purpose. We, on the other hand, just want STDERR. my $stderrReader = gensym(); my $pid = open3(undef, undef, $stderrReader, 'svn', '--non-interactive', 'status'); my @errorLines = grep { /:\s*E155036:/ } (<$stderrReader>); waitpid ($pid, 0); if (@errorLines) { warning (<_verifyCorrectServerURL(); } # Subroutine used to handle the checkout-only option. It handles updating # subdirectories of an already-checked-out module. # # This function can throw an exception in the event of a update failure. # # First parameter is the module. # All remaining parameters are subdirectories to check out. # # Returns the number of files changed by the update, or undef if unable to # be determined. sub update_module_subdirectories { my $self = assert_isa(shift, 'ksb::Updater::Svn'); my $module = $self->module(); my $numChanged = 0; # If we have elements in @path, download them now for my $dir (@_) { info ("\tUpdating g[$dir]"); my $logname = $dir; $logname =~ tr{/}{-}; my $count = $self->run_svn("svn-up-$logname", [ 'svn', 'up', $dir ]); $numChanged = undef unless defined $count; $numChanged += $count if defined $numChanged; } return $numChanged; } # Checkout a module that has not been checked out before, along with any # subdirectories the user desires. # # This function will throw an exception in the event of a failure to update. # # The first parameter is the module to checkout (including extragear and # playground modules). # All remaining parameters are subdirectories of the module to checkout. # # Returns number of files affected, or undef. sub checkout_module_path { my $self = assert_isa(shift, 'ksb::Updater::Svn'); my $module = $self->module(); my @path = @_; my %pathinfo = $module->getInstallPathComponents('source'); my @args; if (not -e $pathinfo{'path'} and not super_mkdir($pathinfo{'path'})) { croak_runtime ("Unable to create path r[$pathinfo{path}]!"); } p_chdir ($pathinfo{'path'}); my $svn_url = $self->svn_module_url(); my $modulename = $pathinfo{'module'}; # i.e. kdelibs for KDE/kdelibs as $module push @args, ('svn', 'co', '--non-interactive'); push @args, '-N' if scalar @path; # Tells svn to only update the base dir push @args, $svn_url; push @args, $modulename; note ("Checking out g[$module]"); my $count = $self->run_svn('svn-co', \@args); p_chdir ($pathinfo{'module'}) if scalar @path; my $count2 = $self->update_module_subdirectories(@path); return $count + $count2 if defined $count and defined $count2; return undef; } # Update a module that has already been checked out, along with any # subdirectories the user desires. # # This function will throw an exception in the event of an update failure. # # The first parameter is the module to checkout (including extragear and # playground modules). # All remaining parameters are subdirectories of the module to checkout. sub update_module_path { my ($self, @path) = @_; assert_isa($self, 'ksb::Updater::Svn'); my $module = $self->module(); my $fullpath = $module->fullpath('source'); my @args; p_chdir ($fullpath); push @args, ('svn', 'up', '--non-interactive'); push @args, '-N' if scalar @path; note ("Updating g[$module]"); my $count = eval { $self->run_svn('svn-up', \@args); }; # Update failed, try svn cleanup. if (had_an_exception() && $@->{exception_type} ne 'ConflictPresent') { info ("\tUpdate failed, trying a cleanup."); my $result = safe_system('svn', 'cleanup'); $result == 0 or croak_runtime ("Unable to update $module, " . "svn cleanup failed with exit code $result"); info ("\tCleanup complete."); # Now try again (allow exception to bubble up this time). $count = $self->run_svn('svn-up-2', \@args); } my $count2 = $self->update_module_subdirectories(@path); return $count + $count2 if defined $count and defined $count2; return undef; } # Run the svn command. This is a special subroutine so that we can munge # the generated output to see what files have been added, and adjust the # build according. # # This function will throw an exception in the event of a build failure. # # First parameter is the ksb::Module object we're building. # Second parameter is the filename to use for the log file. # Third parameter is a reference to a list, which is the command ('svn') # and all of its arguments. # Return value is the number of files update (may be undef if unable to tell) sub run_svn { my ($self, $logfilename, $arg_ref) = @_; assert_isa($self, 'ksb::Updater::Svn'); my $module = $self->module(); my $revision = $module->getOption('revision'); if ($revision && $revision ne '0') { my @tmp = @{$arg_ref}; # Insert after first two entries, deleting 0 entries from the # list. splice @tmp, 2, 0, '-r', $revision; $arg_ref = \@tmp; } my $count = 0; my $conflict = 0; my $callback = sub { return unless $_; # The check for capitalized letters in the second column is because # svn can use the first six columns for updates (the characters will # all be uppercase), which makes it hard to tell apart from normal # sentences (like "At Revision foo" $count++ if /^[UPDARGMC][ A-Z]/; $conflict = 1 if /^C[ A-Z]/; }; # Do svn update. my $result = log_command($module, $logfilename, $arg_ref, { callback => $callback }); return 0 if pretending(); croak_runtime("Error updating $module!") unless $result == 0; if ($conflict) { warning ("Source code conflict exists in r[$module], this module will not"); warning ("build until it is resolved."); die make_exception('ConflictPresent', "Source conflicts exist in $module"); } return $count; } # Subroutine to check for subversion conflicts in a module. Basically just # runs svn st and looks for "^C". # # First parameter is the module to check for conflicts on. # Returns 0 if a conflict exists, non-zero otherwise. sub module_has_conflict { my $module = assert_isa(shift, 'ksb::Module'); my $srcdir = $module->fullpath('source'); if ($module->getOption('no-svn')) { whisper ("\tSource code conflict check skipped."); return 1; } else { info ("\tChecking for source conflicts... "); } my $pid = open my $svnProcess, "-|"; if (!$pid) { error ("\tUnable to open check source conflict status: b[r[$!]"); return 0; # false allows the build to proceed anyways. }; if (0 == $pid) { close STDERR; # No broken pipe warnings disable_locale_message_translation(); exec {'svn'} (qw/svn --non-interactive st/, $srcdir) or croak_runtime("Cannot execute 'svn' program: $!"); # Not reached } while (<$svnProcess>) { if (/^C/) { error (<isa('ksb::Module') should be true. sub updateInternal { my $self = assert_isa(shift, 'ksb::Updater::Svn'); my $module = $self->module(); my $fullpath = $module->fullpath('source'); my @options = split(' ', $module->getOption('checkout-only')); if (-e "$fullpath/.svn") { $self->check_module_validity(); my $updateCount = $self->update_module_path(@options); my $log_filter = sub { return unless defined $_; print $_ if /^C/; print $_ if /Checking for/; return; }; # Use log_command as the check so that an error file gets created. if (0 != log_command($module, 'conflict-check', ['kdesrc-build', 'ksb::Updater::Svn::module_has_conflict', $module], { callback => $log_filter, no_translate => 1 }) ) { croak_runtime (" * Conflicts present in module $module"); } return $updateCount; } else { return $self->checkout_module_path(@options); } } sub name { return 'svn'; } sub currentRevisionInternal { my $self = assert_isa(shift, 'ksb::Updater::Svn'); return $self->svnInfo('Revision'); } # Returns a requested parameter from 'svn info'. # # First parameter is a string with the name of the parameter to retrieve (e.g. URL). # Each line of output from svn info is searched for the requested string. # Returns the string value of the parameter or undef if an error occurred. sub svnInfo { my $self = assert_isa(shift, 'ksb::Updater::Svn'); my $module = $self->module(); my $param = shift; my $srcdir = $module->fullpath('source'); my $result; # Predeclare to outscope upcoming eval if (pretending() && ! -e $srcdir) { return 'Unknown'; } # Search each line of output, ignore stderr. # eval since filter_program_output uses exceptions. eval { # Need to chdir into the srcdir, in case srcdir is a symlink. # svn info /path/to/symlink barfs otherwise. p_chdir ($srcdir); my @lines = filter_program_output( sub { /^$param:/ }, 'svn', 'info', '--non-interactive', '.' ); croak_runtime ("No svn info output!") unless @lines; chomp ($result = $lines[0]); $result =~ s/^$param:\s*//; }; if($@) { error ("Unable to run r[b[svn], is the Subversion program installed?"); error (" -- Error was: r[$@]"); return undef; } return $result; } 1; diff --git a/modules/ksb/l10nSystem.pm b/modules/ksb/l10nSystem.pm index 3725c10..4f7e86e 100644 --- a/modules/ksb/l10nSystem.pm +++ b/modules/ksb/l10nSystem.pm @@ -1,227 +1,223 @@ -package ksb::l10nSystem; +package ksb::l10nSystem 0.10; # This class is an implementation of both the source and build interfaces needed to # support building KDE l10n modules. use strict; use warnings; use 5.014; -our $VERSION = '0.10'; +use parent qw(ksb::Updater::Svn ksb::BuildSystem); use ksb::Debug; use ksb::Util; -use ksb::Updater::Svn; -use ksb::BuildSystem; - -our @ISA = ('ksb::Updater::Svn', 'ksb::BuildSystem'); sub new { my ($class, $module) = @_; # Ensure associated module updates from the proper svn path. # TODO: Support different localization branches? $module->setOption('module-base-path', 'trunk/l10n-kde4'); my $refreshMessage = "an update happened"; return bless { module => $module, needsRefreshed => $refreshMessage }, $class; } sub module { my $self = shift; return $self->{module}; } sub configuredModuleFileName { # Not quite correct (we should be looking at each individual language # but it at least keeps the process going. return 'teamnames'; } # Sets the directories that are to be checked out/built/etc. # There should be one l10nSystem for the entire l10n build (i.e. add # all required support dirs and languages). sub setLanguageDirs { my ($self, @languageDirs) = @_; $self->{l10n_dirs} = \@languageDirs; } # Returns true if the given subdirectory (reference from the module's root source directory) # can be built or not. Should be reimplemented by subclasses as appropriate. sub isSubdirBuildable { my ($self, $subdir) = @_; return ($subdir ne 'scripts' && $subdir ne 'templates'); } sub prepareModuleBuildEnvironment { my ($ctx, $module, $prefix) = @_; $ctx->prependEnvironmentValue('CMAKE_PREFIX_PATH', $prefix); } # scm-specific update procedure. # May change the current directory as necessary. sub updateInternal { my $self = assert_isa(shift, 'ksb::Updater'); my $module = $self->module(); my $fullpath = $module->fullpath('source'); my @dirs = @{$self->{l10n_dirs}}; if (-e "$fullpath/.svn") { $self->check_module_validity(); my $count = $self->update_module_path(@dirs); $self->{needsRefreshed} = '' if $count == 0; return $count; } else { return $self->checkout_module_path(@dirs); } } sub name { return 'l10n'; } # Returns a list of just the languages to install. sub languages { my $self = assert_isa(shift, 'ksb::l10nSystem'); my @langs = @{$self->{l10n_dirs}}; return grep { $self->isSubdirBuildable($_); } (@langs); } # Buildsystem support section sub needsRefreshed { my $self = shift; # Should be a 'reason' string except if no update happened. return $self->{needsRefreshed}; } sub buildInternal { my $self = assert_isa(shift, 'ksb::l10nSystem'); my $builddir = $self->module()->fullpath('build'); my @langs = $self->languages(); my $result = 0; $result = ($self->safe_make({ target => undef, message => "Building localization for language...", logbase => "build", subdirs => \@langs, }) == 0) || $result; return $result; } sub configureInternal { my $self = assert_isa(shift, 'ksb::l10nSystem'); my $builddir = $self->module()->fullpath('build'); my @langs = $self->languages(); my $result = 0; for my $lang (@langs) { my $prefix = $self->module()->installationPath(); p_chdir("$builddir/$lang"); info ("\tConfiguring to build language $lang"); $result = (log_command($self->module(), "cmake-$lang", ['cmake', '-DCMAKE_INSTALL_PREFIX=' . $prefix]) == 0) || $result; } return $result; } sub installInternal { my $self = assert_isa(shift, 'ksb::l10nSystem'); my $builddir = $self->module()->fullpath('build'); my @langs = $self->languages(); return ($self->safe_make({ target => 'install', message => "Installing language...", logbase => "install", subdirs => \@langs, }) == 0); } # Subroutine to link a source directory into an alternate directory in # order to fake srcdir != builddir for modules that don't natively support # it. The first parameter is the module to prepare. # # The return value is true (non-zero) if it succeeded, and 0 (false) if it # failed. # # On return from the subroutine the current directory will be in the build # directory, since that's the only directory you should touch from then on. sub prepareFakeBuilddir { my $self = assert_isa(shift, 'ksb::l10nSystem'); my $module = $self->module(); my $builddir = $module->fullpath('build'); my $srcdir = $module->fullpath('source'); # List reference, not a real list. The initial kdesrc-build does *NOT* # fork another kdesrc-build using exec, see sub log_command() for more # info. my $args = [ 'kdesrc-build', 'main::safe_lndir', $srcdir, $builddir ]; info ("\tSetting up alternate build directory for l10n"); return (0 == log_command ($module, 'create-builddir', $args)); } # Subroutine to create the build system for a module. This involves making # sure the directory exists and then running any preparatory steps (like # for l10n modules). This subroutine assumes that the module is already # downloaded. # # Return convention: boolean (inherited) sub createBuildSystem { my $self = assert_isa(shift, 'ksb::l10nSystem'); my $module = $self->module(); my $builddir = $module->fullpath('build'); # l10n doesn't support srcdir != builddir, fake it. whisper ("\tFaking builddir for g[$module]"); if (!$self->prepareFakeBuilddir()) { error ("Error creating r[$module] build system!"); return 0; } p_chdir ($builddir); my @langs = @{$self->{l10n_dirs}}; @langs = grep { $self->isSubdirBuildable($_) } (@langs); foreach my $lang (@langs) { my $cmd_ref = [ './scripts/autogen.sh', $lang ]; if (log_command ($module, "build-system-$lang", $cmd_ref)) { error ("\tUnable to create build system for r[$module]"); } } $module->setOption('#reconfigure', 1); # Force reconfigure of the module return 1; } 1;