diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e1c669..28c5183 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,106 +1,111 @@ cmake_minimum_required(VERSION 3.0) project(kdesrc-build) # Needed for the docs, but optional. If not found, we will simply # defer to native CMake handling. find_package(ECM 5.10.0 NO_MODULE QUIET) if (ECM_FOUND) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(KDEInstallDirs) include(KDECMakeSettings) include(FeatureSummary) include(ECMOptionalAddSubdirectory) find_package(KF5 5.10.0 COMPONENTS DocTools) if(KF5_DocTools_FOUND) ecm_optional_add_subdirectory(doc) else() message(WARNING "Found ECM, but not KF5::DocTools; documentation will not be built") endif() else() message(WARNING "Did not find extra-cmake-modules; documentation will not be built and install paths will be guessed based on CMake settings.") # ECM now sets KDE_INSTALL_DATADIR to PREFIX/share, try to match set(KDE_INSTALL_DATADIR "${CMAKE_INSTALL_PREFIX}/share" CACHE PATH "The parent directory where applications can install their data") set(KDE_INSTALL_BINDIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "The install dir for executables") endif() set(KDESRC_BUILD_INSTALL_MODULES TRUE CACHE BOOL "Controls whether to install the modules that make up the script. Leave enabled unless running straight from source") set(KDESRC_BUILD_MODULE_INSTALL_PREFIX "${KDE_INSTALL_DATADIR}/kdesrc-build/modules" CACHE PATH "Prefix to install the component Perl modules to. (This is only an option to allow for installing to vendor_perl or similar)") if (KDESRC_BUILD_INSTALL_MODULES) message(STATUS "Installing component modules to ${KDESRC_BUILD_MODULE_INSTALL_PREFIX}") install(FILES modules/ksb/Application.pm modules/ksb/BuildContext.pm modules/ksb/BuildException.pm modules/ksb/BuildSystem.pm modules/ksb/Debug.pm modules/ksb/DependencyResolver.pm modules/ksb/FirstRun.pm modules/ksb/IPC.pm modules/ksb/KDEProjectsReader.pm modules/ksb/Module.pm modules/ksb/ModuleResolver.pm modules/ksb/ModuleSet.pm modules/ksb/OptionsBase.pm modules/ksb/OSSupport.pm modules/ksb/PhaseList.pm modules/ksb/RecursiveFH.pm modules/ksb/StatusView.pm modules/ksb/Updater.pm modules/ksb/Util.pm modules/ksb/Version.pm modules/ksb/l10nSystem.pm DESTINATION ${KDESRC_BUILD_MODULE_INSTALL_PREFIX}/ksb) install(FILES modules/ksb/BuildSystem/Autotools.pm modules/ksb/BuildSystem/CMakeBootstrap.pm modules/ksb/BuildSystem/KDE4.pm modules/ksb/BuildSystem/QMake.pm modules/ksb/BuildSystem/Qt4.pm + modules/ksb/BuildSystem/Qt5.pm DESTINATION ${KDESRC_BUILD_MODULE_INSTALL_PREFIX}/ksb/BuildSystem) install(FILES modules/ksb/Module/BranchGroupResolver.pm DESTINATION ${KDESRC_BUILD_MODULE_INSTALL_PREFIX}/ksb/Module) install(FILES modules/ksb/ModuleSet/KDEProjects.pm modules/ksb/ModuleSet/Null.pm + modules/ksb/ModuleSet/Qt.pm DESTINATION ${KDESRC_BUILD_MODULE_INSTALL_PREFIX}/ksb/ModuleSet) install(FILES modules/ksb/IPC/Null.pm modules/ksb/IPC/Pipe.pm DESTINATION ${KDESRC_BUILD_MODULE_INSTALL_PREFIX}/ksb/IPC) install(FILES modules/ksb/Updater/Bzr.pm modules/ksb/Updater/Git.pm modules/ksb/Updater/KDEProject.pm modules/ksb/Updater/KDEProjectMetadata.pm modules/ksb/Updater/Svn.pm + modules/ksb/Updater/Qt5.pm DESTINATION ${KDESRC_BUILD_MODULE_INSTALL_PREFIX}/ksb/Updater) endif() install(PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR}/kdesrc-build DESTINATION ${KDE_INSTALL_BINDIR}) install(PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR}/kdesrc-build-setup DESTINATION ${KDE_INSTALL_BINDIR}) install(PROGRAMS ${CMAKE_SOURCE_DIR}/sample-kde-env-master.sh ${CMAKE_SOURCE_DIR}/sample-xsession.sh + ${CMAKE_SOURCE_DIR}/custom-qt5-libs-build-include + ${CMAKE_SOURCE_DIR}/qt5-build-include ${CMAKE_SOURCE_DIR}/kf5-applications-build-include ${CMAKE_SOURCE_DIR}/kf5-extragear-build-include ${CMAKE_SOURCE_DIR}/kf5-frameworks-build-include ${CMAKE_SOURCE_DIR}/kf5-kdepim-build-include ${CMAKE_SOURCE_DIR}/kf5-qt5-build-include ${CMAKE_SOURCE_DIR}/kf5-workspace-build-include DESTINATION ${KDE_INSTALL_DATADIR}/kdesrc-build) if (ECM_FOUND) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) endif() diff --git a/kdesrc-build-setup b/kdesrc-build-setup index b95a62d..564b92e 100755 --- a/kdesrc-build-setup +++ b/kdesrc-build-setup @@ -1,482 +1,501 @@ #!/usr/bin/env perl # Script to create a configuration file for kdesrc-build. # -# Copyright © 2011, 2016 Michael Pyne. +# Copyright © 2011, 2019 Michael Pyne. # Home page: https://kdesrc-build.kde.org/ # # 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 of the License, 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, write to the Free Software Foundation, Inc., 51 # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA use strict; use 5.018; use IO::Pipe; use File::Copy; use File::Temp qw/tempfile/; use File::Basename; use Cwd qw(abs_path); our $VERSION = 0.03; # Not user-visible yet. sub clearScreen { require POSIX; my $termios = POSIX::Termios->new(); $termios->getattr(1); # Get STDOUT attributes require Term::Cap; my $terminal = Term::Cap->Tgetent({OSPEED => $termios->getospeed}); # Force the clear characters to be output immediately. # Otherwise it might overlap with other output, like error messages. local $| = 1; print $terminal->Tputs('cl', 0); return 0; } sub runDialogExecutable { my (@args) = @_; # Allow for 2 more file descriptors (on top of the normally allowed 0, 1, # 2) to survive the upcoming exec # See "SYSTEM_FD_MAX" in perldoc:perlvar $^F = 4; my $pipe = new IO::Pipe; my $pid; if ($pid = fork()) { # Parent $pipe->reader(); my $output = <$pipe>; waitpid $pid, 0; my $result = ($? >> 8); $pipe->close(); # dialog uses -1 as an exit code, Perl gets just the standard 8 bits # the rest of UNIX uses... if ($? == -1) { clearScreen(); die "Failed to run dialog(1): $@"; } elsif ($result == 255) { clearScreen(); die "Canceled the dialog"; } return $output || $result; } elsif (defined $pid) { # Child $pipe->writer(); my $outputFd = $pipe->fileno(); print "Using fd $outputFd"; exec ('dialog', '--output-fd', $outputFd, '--backtitle', 'kdesrc-build setup', @args); } else { die "Unable to fork? $!"; } } sub getUserInput { my $prompt = shift; my $default = shift; my @args = qw/--inputbox 8 50/; splice @args, 1, 0, $prompt; push @args, $default if $default; return runDialogExecutable(@args); } sub getMenuOption { my ($prompt, @opts) = @_; @opts = @{$opts[0]} if ref $opts[0] eq 'ARRAY'; my @args = qw/--menu 20 70 18/; splice @args, 1, 0, $prompt; while(my ($k, $v) = splice (@opts, 0, 2)) { push @args, $k, $v; } return runDialogExecutable(@args); } sub showInfo { my $message = shift; my @args = qw/--msgbox 20 62/; splice @args, 1, 0, $message; return runDialogExecutable(@args); } sub getYesNoAnswer { my $prompt = shift; my @args = qw/--yesno 8 55/; splice @args, 1, 0, $prompt; return runDialogExecutable(@args) == 0; } sub getDirectory { my $dir = shift; my @args = qw/--dselect 10 70/; splice @args, 1, 0, $dir; return runDialogExecutable(@args); } sub getListOptions { my ($prompt, $opts, $enabled) = @_; die "\$opts not a hash ref" unless (ref $opts eq 'ARRAY'); die "\$enabled not a hash ref" unless (ref $enabled eq 'HASH'); my @args = qw/--checklist 20 70 18/; splice @args, 1, 0, $prompt; splice @args, 0, 0, '--output-separator', ','; while (my ($k, $v) = splice(@{$opts}, 0, 2)) { push (@args, $k, $v, (exists ${$enabled}{$k} ? 'on' : 'off')); } my $output = runDialogExecutable(@args); # Filter out empty results, remove quotes. my @items = split (/,/, $output); s/^"(.*)"$/$1/ foreach @items; @items = grep { length $_ } @items; return @items; } # The 'dialog(1)' program is required, verify it exists before going # further. # We use the --help option since it doesn't send weird terminal characters to the screen # and it's supported on dialog and Debian's dialog replacement called whiptail. system('dialog', '--help') == 0 or do { my $osError = "$!"; say "Unable to run the dialog(1) program, it is required for this setup script."; if ($? == -1) { say "\tThe program wouldn't even run, due to error: $osError"; } else { say "\tProgram ran, but exited with error: ", $? >> 8; } exit 1; }; showInfo(< "$ENV{HOME}/kde/usr (default)", custom => "Custom location, chosen next screen", ]); if ($installDir eq 'custom') { $installDir = getDirectory('/usr/local/kde'); } else { $installDir = "~/kde/usr"; } my $sourceDir = getMenuOption('Where do you want the source code to be saved?', [ home => "$ENV{HOME}/kde/src (default)", custom => "Custom location, chosen next screen", ]); if ($sourceDir eq 'custom') { $sourceDir = getDirectory('/usr/local/kde/src'); } else { $sourceDir = "~/kde/src"; } my $buildDir = getMenuOption('Where do you want temporary build files to be saved? (They might need lots of space)', [ home => "$ENV{HOME}/kde/build (default)", custom => "Custom location, chosen next screen", ]); if ($buildDir eq 'custom') { $buildDir = getDirectory('/usr/local/kde/build'); } else { $buildDir = "~/kde/build"; } my @chosenModules = getListOptions( "Which major module groups do you want to build?", [ + qt5 => 'Qt 5 - Base support libraries (required unless distro supplies)', frameworks => 'KDE Frameworks 5 - Essential libraries/runtime (required)', workspace => 'KDE Plasma 5 Desktop and workspace', base => 'Assorted useful KF5-based applications', pim => 'Personal Information Management software', ], { + qt5 => 1, frameworks => 1, workspace => 1, base => 1, }, ); my $numCpus = getUserInput( 'How many CPU cores do you wish to use for building?', '4'); my $outputFileName = "$ENV{HOME}/.kdesrc-buildrc"; my $output; # Will be output filehandle. while (-e $outputFileName) { (my $printableName = $outputFileName) =~ s/^$ENV{HOME}/~/; my $outputChoice = getMenuOption( "$printableName already exists, what do you want to do?", [ backup => 'Make a backup, then overwrite with the new configuration', custom => 'Write the new configuration to a different file', cancel => 'Cancel setup', ], ); if ($outputChoice eq 'cancel') { showInfo('Setup canceled'); exit 0; } if ($outputChoice eq 'custom') { $outputFileName = getUserInput('Enter desired configuration file name.'); $outputFileName =~ s/^~/$ENV{HOME}/; } if ($outputChoice eq 'backup') { copy($outputFileName, "$outputFileName~") or do { my $error = "$!"; showInfo(<', $outputFileName) or do { my $error = "$!"; showInfo (<= 5.018, 'warnings', 'experimental::smartmatch'; use ksb::Debug; use ksb::Util; use ksb::BuildContext 0.35; use ksb::BuildSystem::QMake; use ksb::BuildException 0.20; use ksb::FirstRun; use ksb::Module; use ksb::ModuleResolver 0.20; use ksb::ModuleSet 0.20; use ksb::ModuleSet::KDEProjects; +use ksb::ModuleSet::Qt; use ksb::OSSupport; use ksb::RecursiveFH; use ksb::DependencyResolver 0.20; use ksb::IPC::Pipe 0.20; use ksb::IPC::Null; use ksb::Updater::Git; use ksb::Version qw(scriptVersion); use List::Util qw(first min); use File::Basename; # basename, dirname use File::Glob ':glob'; use POSIX qw(:sys_wait_h _exit :errno_h); use Getopt::Long qw(GetOptionsFromArray :config gnu_getopt nobundling); use IO::Handle; use IO::Select; ### Package-specific variables (not shared outside this file). use constant { # We use a named remote to make some git commands work that don't accept the # full path. - KDE_PROJECT_ID => 'kde-projects', # git-repository-base for kde_projects.xml + KDE_PROJECT_ID => 'kde-projects', # git-repository-base for sysadmin/repo-metadata + QT_PROJECT_ID => 'qt-projects', # git-repository-base for qt.io Git repo }; ### Package methods sub new { my ($class, @options) = @_; my $self = bless { context => ksb::BuildContext->new(), metadata_module => undef, run_mode => 'build', modules => undef, module_factory => undef, # ref to sub that makes a new Module. # See generateModuleList _base_pid => $$, # See finish() }, $class; # Default to colorized output if sending to TTY ksb::Debug::setColorfulOutput(-t STDOUT); my @moduleList = $self->generateModuleList(@options); $self->{modules} = \@moduleList; if (!@moduleList) { print "No modules to build, exiting.\n"; exit 0; } $self->context()->setupOperatingEnvironment(); # i.e. niceness, ulimits, etc. # After this call, we must run the finish() method # to cleanly complete process execution. if (!pretending() && !$self->context()->takeLock()) { print "$0 is already running!\n"; exit 1; # Don't finish(), it's not our lockfile!! } # Install signal handlers to ensure that the lockfile gets closed. _installSignalHandlers(sub { note ("Signal received, terminating."); @main::atexit_subs = (); # Remove their finish, doin' it manually $self->finish(5); }); return $self; } # Method: _readCommandLineOptionsAndSelectors # # Returns a list of module/module-set selectors, selected module/module-set # options, and global options, based on the command-line arguments passed to # this function. # # This is a package method, should be called as # $app->_readCommandLineOptionsAndSelectors # # Phase: # initialization - Do not call from this function. # # Parameters: # cmdlineOptions - hashref to hold parsed modules options to be applied later. # *Note* this must be done separately, it is not handled by this subroutine. # Global options will be stored in a hashref at $cmdlineOptions->{global}. # Module or module-set options will be stored in a hashref at # $cmdlineOptions->{$moduleName} (it will be necessary to disambiguate # later in the run whether it is a module set or a single module). # # If the global option 'start-program' is set, then the program to start and # its options will be found in a listref pointed to under the # 'start-program' option. # # selectors - listref to hold the list of module or module-set selectors to # build, in the order desired by the user. These will just be strings, the # caller will have to figure out whether the selector is a module or # module-set, and create any needed objects, and then set the recommended # options as listed in cmdlineOptions. # # ctx - to hold the global build state. # # @options - The remainder of the arguments are treated as command line # arguments to process. # # Returns: # Nothing. An exception will be raised on failure, or this function may quit # the program directly (e.g. to handle --help, --usage). sub _readCommandLineOptionsAndSelectors { my $self = shift; my ($cmdlineOptionsRef, $selectorsRef, $ctx, @options) = @_; my $phases = $ctx->phases(); my @savedOptions = @options; # Copied for use in debugging. my $os = ksb::OSSupport->new; my $version = "kdesrc-build " . scriptVersion(); my $author = < Many people have contributed code, bugfixes, and documentation. Please report bugs using the KDE Bugzilla, at https://bugs.kde.org/ DONE # Getopt::Long will store options in %foundOptions, since that is what we # pass in. To allow for custom subroutines to handle an option it is # required that the sub *also* be in %foundOptions... whereupon it will # promptly be overwritten if we're not careful. Instead we let the custom # subs save to %auxOptions, and read those in back over it later. my (%foundOptions, %auxOptions); %foundOptions = ( 'show-info' => sub { say $version; say "OS: ", $os->vendorID(); exit }, version => sub { say $version; exit }, author => sub { say $author; exit }, help => sub { _showHelpMessage(); exit 0 }, install => sub { $self->{run_mode} = 'install'; $phases->phases('install'); }, uninstall => sub { $self->{run_mode} = 'uninstall'; $phases->phases('uninstall'); }, 'no-src' => sub { $phases->filterOutPhase('update'); }, 'no-install' => sub { $phases->filterOutPhase('install'); }, 'no-snapshots' => sub { # The documented form of disable-snapshots $auxOptions{'disable-snapshots'} = 1; }, 'no-tests' => sub { # The "right thing" to do $phases->filterOutPhase('test'); # What actually works at this point. $foundOptions{'run-tests'} = 0; }, 'no-build' => sub { $phases->filterOutPhase('build'); }, # Mostly equivalent to the above 'src-only' => sub { $phases->phases('update'); # We have an auto-switching function that we only want to run # if --src-only was passed to the command line, so we still # need to set a flag for it. $foundOptions{'allow-auto-repo-move'} = 1; }, 'build-only' => sub { $phases->phases('build'); }, 'install-only' => sub { $self->{run_mode} = 'install'; $phases->phases('install'); }, prefix => sub { my ($optName, $arg) = @_; $auxOptions{prefix} = $arg; $foundOptions{kdedir} = $arg; #TODO: Still needed for compat? $foundOptions{reconfigure} = 1; }, query => sub { my (undef, $arg) = @_; my $validMode = qr/^[a-zA-Z0-9_][a-zA-Z0-9_-]*$/; die("Invalid query mode $arg") unless $arg =~ $validMode; # Add useful aliases $arg = 'source-dir' if $arg =~ /^src-?dir$/; $arg = 'build-dir' if $arg =~ /^build-?dir$/; $arg = 'install-dir' if $arg eq 'prefix'; $self->{run_mode} = 'query'; $auxOptions{query} = $arg; $auxOptions{pretend} = 1; # Implied pretend mode }, pretend => sub { # Set pretend mode but also force the build process to run. $auxOptions{pretend} = 1; $foundOptions{'build-when-unchanged'} = 1; }, resume => sub { $auxOptions{resume} = 1; $phases->filterOutPhase('update'); # Implied --no-src $foundOptions{'no-metadata'} = 1; # Implied --no-metadata }, verbose => sub { $foundOptions{'debug-level'} = ksb::Debug::WHISPER }, quiet => sub { $foundOptions{'debug-level'} = ksb::Debug::NOTE }, 'really-quiet' => sub { $foundOptions{'debug-level'} = ksb::Debug::WARNING }, debug => sub { $foundOptions{'debug-level'} = ksb::Debug::DEBUG; debug ("Commandline was: ", join(', ', @savedOptions)); }, # Hack to set module options 'set-module-option-value' => sub { my ($optName, $arg) = @_; my ($module, $option, $value) = split (',', $arg, 3); if ($module && $option) { $cmdlineOptionsRef->{$module} //= { }; $cmdlineOptionsRef->{$module}->{$option} = $value; } }, # Getopt::Long doesn't set these up for us even though we specify an # array. Set them up ourselves. 'start-program' => [ ], 'ignore-modules' => [ ], # Module selectors, the <> is Getopt::Long shortcut for an # unrecognized non-option value (i.e. an actual argument) '<>' => sub { my $arg = shift; push @{$selectorsRef}, $arg; }, ); # Handle any "cmdline-eligible" options not already covered. my $flagHandler = sub { my ($optName, $optValue) = @_; # Assume to set if nothing provided. $optValue = 1 if (!defined $optValue or $optValue eq ''); $optValue = 0 if lc($optValue) eq 'false'; $optValue = 0 if !$optValue; $auxOptions{$optName} = $optValue; }; foreach my $option (keys %ksb::BuildContext::defaultGlobalFlags) { if (!exists $foundOptions{$option}) { $foundOptions{$option} = $flagHandler; # A ref to a sub here! } } # Actually read the options. my $optsSuccess = GetOptionsFromArray(\@options, \%foundOptions, # Options here should not duplicate the flags and options defined below # from ksb::BuildContext! 'version|v', 'author', 'help', 'show-info', 'install', 'uninstall', 'no-src|no-svn', 'no-install', 'no-build', 'no-tests', 'build-when-unchanged|force-build', 'no-metadata', 'verbose', 'quiet|quite|q', 'really-quiet', 'debug', 'reconfigure', 'colorful-output|color!', 'async!', 'src-only|svn-only', 'build-only', 'install-only', 'build-system-only', 'rc-file=s', 'prefix=s', 'niceness|nice:10', 'ignore-modules=s{,}', 'print-modules', 'pretend|dry-run|p', 'refresh-build', 'query=s', 'start-program|run=s{,}', 'revision=i', 'resume-from=s', 'resume-after=s', 'rebuild-failures', 'resume', 'stop-after=s', 'stop-before=s', 'set-module-option-value=s', 'metadata-only', 'include-dependencies', # Special sub used (see above), but have to tell Getopt::Long to look # for negatable boolean flags (map { "$_!" } (keys %ksb::BuildContext::defaultGlobalFlags)), # Default handling fine, still have to ask for strings. (map { "$_:s" } (keys %ksb::BuildContext::defaultGlobalOptions)), '<>', # Required to read non-option args ); if (!$optsSuccess) { croak_runtime("Error reading command-line options."); } # To store the values we found, need to strip out the values that are # subroutines, as those are the ones we created. Alternately, place the # subs inline as an argument to the appropriate option in the # GetOptionsFromArray call above, but that's ugly too. my @readOptionNames = grep { ref($foundOptions{$_}) ne 'CODE' } (keys %foundOptions); # Slice assignment: $left{$key} = $right{$key} foreach $key (@keys), but # with hashref syntax everywhere. @{ $cmdlineOptionsRef->{'global'} }{@readOptionNames} = @foundOptions{@readOptionNames}; @{ $cmdlineOptionsRef->{'global'} }{keys %auxOptions} = values %auxOptions; } # Generates the build context and module list based on the command line options # and module selectors provided, resolves dependencies on those modules if needed, # filters out ignored or skipped modules, and sets up the module factory. # # After this function is called all module set selectors will have been # expanded, and we will have downloaded kde-projects metadata. # # Returns: List of Modules to build. sub generateModuleList { my $self = shift; my @argv = @_; # Note: Don't change the order around unless you're sure of what you're # doing. my $ctx = $self->context(); my $cmdlineOptions = { global => { }, }; my $cmdlineGlobalOptions = $cmdlineOptions->{global}; my $deferredOptions = { }; # 'options' blocks # Process --help, --install, etc. first. my @selectors; $self->_readCommandLineOptionsAndSelectors($cmdlineOptions, \@selectors, $ctx, @argv); # Convert list to hash for lookup my %ignoredSelectors = map { $_, 1 } @{$cmdlineGlobalOptions->{'ignore-modules'}}; my @startProgramAndArgs = @{$cmdlineGlobalOptions->{'start-program'}}; delete @{$cmdlineGlobalOptions}{qw/ignore-modules start-program/}; # rc-file needs special handling. if (exists $cmdlineGlobalOptions->{'rc-file'} && $cmdlineGlobalOptions->{'rc-file'}) { $ctx->setRcFile($cmdlineGlobalOptions->{'rc-file'}); } # disable async if only running a single phase. $cmdlineGlobalOptions->{async} = 0 if (scalar $ctx->phases()->phases() == 1); my $fh = $ctx->loadRcFile(); $ctx->loadPersistentOptions(); if (exists $cmdlineGlobalOptions->{'resume'}) { my $moduleList = $ctx->getPersistentOption('global', 'resume-list'); if (!$moduleList) { error ("b[--resume] specified, but unable to find resume point!"); error ("Perhaps try b[--resume-from] or b[--resume-after]?"); croak_runtime("Invalid --resume flag"); } unshift @selectors, split(/,\s*/, $moduleList); } if (exists $cmdlineGlobalOptions->{'rebuild-failures'}) { my $moduleList = $ctx->getPersistentOption('global', 'last-failed-module-list'); if (!$moduleList) { error ("b[y[--rebuild-failures] was specified, but unable to determine"); error ("which modules have previously failed to build."); croak_runtime("Invalid --rebuild-failures flag"); } unshift @selectors, split(/,\s*/, $moduleList); } # _readConfigurationOptions will add pending global opts to ctx while ensuring # returned modules/sets have any such options stripped out. It will also add # module-specific options to any returned modules/sets. my @optionModulesAndSets = _readConfigurationOptions($ctx, $fh, $deferredOptions); close $fh; # Check if we're supposed to drop into an interactive shell instead. If so, # here's the stop off point. if (@startProgramAndArgs) { $ctx->setupEnvironment(); # Read options from set-env $ctx->commitEnvironmentChanges(); # Apply env options to environment _executeCommandLineProgram(@startProgramAndArgs); # noreturn } # Everything else in cmdlineOptions should be OK to apply directly as a module # or context option. $ctx->setOption(%{$cmdlineGlobalOptions}); # Selecting modules or module sets would requires having the KDE # build metadata (kde-build-metadata and sysadmin/repo-metadata) # available. $ctx->setKDEDependenciesMetadataModuleNeeded(); $ctx->setKDEProjectsMetadataModuleNeeded(); if (!exists $ENV{HARNESS_ACTIVE}) { # Running in a test harness, avoid downloading metadata which will be # ignored in the test or making changes to git config ksb::Updater::Git::verifyGitConfig(); $self->_downloadKDEProjectMetadata(); } # The user might only want metadata to update to allow for a later # --pretend run, check for that here. if (exists $cmdlineGlobalOptions->{'metadata-only'}) { return; } # At this point we have our list of candidate modules / module-sets (as read in # from rc-file). The module sets have not been expanded into modules. # We also might have cmdline "selectors" to determine which modules or # module-sets to choose. First let's select module sets, and expand them. my @globalCmdlineArgs = keys %{$cmdlineGlobalOptions}; my $commandLineModules = scalar @selectors; my $moduleResolver = ksb::ModuleResolver->new($ctx); $moduleResolver->setCmdlineOptions($cmdlineOptions); $moduleResolver->setDeferredOptions($deferredOptions); $moduleResolver->setInputModulesAndOptions(\@optionModulesAndSets); $moduleResolver->setIgnoredSelectors([keys %ignoredSelectors]); $self->_defineNewModuleFactory($moduleResolver); my @modules; if ($commandLineModules) { @modules = $moduleResolver->resolveSelectorsIntoModules(@selectors); } else { # Build everything in the rc-file, in the order specified. @modules = $moduleResolver->expandModuleSets(@optionModulesAndSets); if ($ctx->getOption('kde-languages')) { @modules = _expandl10nModules($ctx, @modules); } } # If modules were on the command line then they are effectively forced to # process unless overridden by command line options as well. If phases # *were* overridden on the command line, then no update pass is required # (all modules already have correct phases) @modules = _updateModulePhases(@modules) unless $commandLineModules; # TODO: Verify this does anything still my $metadataModule = $ctx->getKDEDependenciesMetadataModule(); $ctx->addToIgnoreList($metadataModule->scm()->ignoredModules()); # Remove modules that are explicitly blanked out in their branch-group # i.e. those modules where they *have* a branch-group, and it's set to # be empty (""). my $resolver = $ctx->moduleBranchGroupResolver(); my $branchGroup = $ctx->effectiveBranchGroup(); @modules = grep { my $branch = $_->isKDEProject() ? $resolver->findModuleBranch($_->fullProjectPath(), $branchGroup) : 1; # Just a placeholder truthy value whisper ("Removing ", $_->fullProjectPath(), " due to branch-group") if (defined $branch and !$branch); (!defined $branch or $branch); # This is the actual test } (@modules); @modules = $self->_resolveModuleDependencies(@modules); # Filter --resume-foo options. This might be a second pass, but that should # be OK since there's nothing different going on from the first pass (in # resolveSelectorsIntoModules) in that event. @modules = _applyModuleFilters($ctx, @modules); # Check for ignored modules (post-expansion) @modules = grep { ! exists $ignoredSelectors{$_->name()} } @modules; return @modules; } # Causes kde-projects metadata to be downloaded (unless --pretend, --no-src, or # --no-metadata is in effect, although we'll download even in --pretend if # nothing is available). # # No return value. sub _downloadKDEProjectMetadata { my $self = shift; my $ctx = $self->context(); my $updateStillNeeded = 0; my $wasPretending = pretending(); eval { for my $metadataModule ( $ctx->getKDEDependenciesMetadataModule(), $ctx->getKDEProjectsMetadataModule()) { my $sourceDir = $metadataModule->getSourceDir(); super_mkdir($sourceDir); my $moduleSource = $metadataModule->fullpath('source'); my $updateDesired = !$ctx->getOption('no-metadata') && $ctx->phases()->has('update'); my $updateNeeded = (! -e $moduleSource) || is_dir_empty($moduleSource); my $lastUpdate = $ctx->getPersistentOption('global', 'last-metadata-update') // 0; $updateStillNeeded ||= $updateNeeded; if (!$updateDesired && $updateNeeded && (time - ($lastUpdate)) >= 7200) { warning (" r[b[*] Skipping build metadata update, but it hasn't been updated recently!"); } if ($updateNeeded && pretending()) { warning (" y[b[*] Ignoring y[b[--pretend] option to download required metadata\n" . " y[b[*] --pretend mode will resume after metadata is available."); ksb::Debug::setPretending(0); } if ($updateDesired && (!pretending() || $updateNeeded)) { $metadataModule->scm()->updateInternal(); $ctx->setPersistentOption('global', 'last-metadata-update', time); } ksb::Debug::setPretending($wasPretending); } }; my $err = $@; ksb::Debug::setPretending($wasPretending); if ($err) { die $err if $updateStillNeeded; # Assume previously-updated metadata will work if not updating warning (" b[r[*] Unable to download required metadata for build process"); warning (" b[r[*] Will attempt to press onward..."); warning (" b[r[*] Exception message: $@"); } } # Returns a list of Modules in the proper build order according to the # kde-build-metadata dependency information. # # The kde-build-metadata repository must have already been updated, and the # module factory must be setup. The Modules to reorder must be passed as # arguments. sub _resolveModuleDependencies { my $self = shift; my $ctx = $self->context(); my $metadataModule = $ctx->getKDEDependenciesMetadataModule(); my @modules = @_; @modules = eval { my $dependencyResolver = ksb::DependencyResolver->new($self->{module_factory}); my $branchGroup = $ctx->effectiveBranchGroup(); for my $file ('dependency-data-common', "dependency-data-$branchGroup") { my $dependencyFile = $metadataModule->fullpath('source') . "/$file"; my $dependencies = pretend_open($dependencyFile) or die "Unable to open $dependencyFile: $!"; debug (" -- Reading dependencies from $dependencyFile"); $dependencyResolver->readDependencyData($dependencies); close $dependencies; } my @reorderedModules = $dependencyResolver->resolveDependencies(@modules); return @reorderedModules; }; if ($@) { warning (" r[b[*] Problems encountered trying to sort modules into correct order:"); warning (" r[b[*] $@"); warning (" r[b[*] Will attempt to continue."); } return @modules; } # Runs all update, build, install, etc. phases. Basically this *is* the # script. # The metadata module must already have performed its update by this point. sub runAllModulePhases { my $self = shift; my $ctx = $self->context(); my @modules = $self->modules(); if ($ctx->getOption('print-modules')) { for my $m (@modules) { say ((" " x ($m->getOption('#dependency-level', 'module') // 0)), "$m"); } return 0; # Abort execution early! } # Add to global module list now that we've filtered everything. $ctx->addModule($_) foreach @modules; my $runMode = $self->runMode(); if ($runMode eq 'query') { my $queryMode = $ctx->getOption('query', 'module'); # Default to ->getOption as query method. # $_[0] is short name for first param. my $query = sub { $_[0]->getOption($queryMode) }; $query = sub { $_[0]->fullpath('source') } if $queryMode eq 'source-dir'; $query = sub { $_[0]->fullpath('build') } if $queryMode eq 'build-dir'; $query = sub { $_[0]->installationPath() } if $queryMode eq 'install-dir'; $query = sub { $_[0]->fullProjectPath() } if $queryMode eq 'project-path'; $query = sub { ($_[0]->scm()->_determinePreferredCheckoutSource())[0] // '' } if $queryMode eq 'branch'; if (@modules == 1) { # No leading module name, just the value say $query->($modules[0]); } else { for my $m (@modules) { say "$m: ", $query->($m); } } return 0; } my $result; if ($runMode eq 'build') { # No packages to install, we're in build mode # What we're going to do is fork another child to perform the source # updates while we build. Setup for this first by initializing some # shared memory. my $ipc = 0; my $updateOptsSub = sub { my ($k, $v) = @_; $ctx->setPersistentOption($k, $v); }; if ($ctx->getOption('async')) { $ipc = ksb::IPC::Pipe->new(); $ipc->setPersistentOptionHandler($updateOptsSub); } if (!$ipc) { $ipc = ksb::IPC::Null->new(); $ipc->setPersistentOptionHandler($updateOptsSub); whisper ("Using no IPC mechanism\n"); note ("\n b[<<< Update Process >>>]\n"); $result = _handle_updates ($ipc, $ctx); note (" b[<<< Build Process >>>]\n"); $result = _handle_build ($ipc, $ctx) || $result; } else { $result = _handle_async_build ($ipc, $ctx); $ipc->outputPendingLoggedMessages() if debugging(); } } elsif ($runMode eq 'install') { $result = _handle_install ($ctx); } elsif ($runMode eq 'uninstall') { $result = _handle_uninstall ($ctx); } _cleanup_log_directory($ctx) if $ctx->getOption('purge-old-logs'); _output_failed_module_lists($ctx); # Record all failed modules. Unlike the 'resume-list' option this doesn't # include any successfully-built modules in between failures. my $failedModules = join(',', map { "$_" } $ctx->listFailedModules()); if ($failedModules) { # We don't clear the list of failed modules on success so that # someone can build one or two modules and still use # --rebuild-failures $ctx->setPersistentOption('global', 'last-failed-module-list', $failedModules); } # env driver is just the ~/.config/kde-env-*.sh, session driver is that + ~/.xsession if ($ctx->getOption('install-environment-driver') || $ctx->getOption('install-session-driver')) { _installCustomSessionDriver($ctx); } my $color = 'g[b['; $color = 'r[b[' if $result; info ("${color}", $result ? ":-(" : ":-)") unless pretending(); return $result; } # Method: finish # # Exits the script cleanly, including removing any lock files created. # # Parameters: # ctx - Required; BuildContext to use. # [exit] - Optional; if passed, is used as the exit code, otherwise 0 is used. sub finish { my $self = shift; my $ctx = $self->context(); my $exitcode = shift // 0; if (pretending() || $self->{_base_pid} != $$) { # Abort early if pretending or if we're not the same process # that was started by the user (e.g. async mode, forked pipe-opens exit $exitcode; } $ctx->closeLock(); $ctx->storePersistentOptions(); my $logdir = $ctx->getLogDir(); note ("Your logs are saved in y[$logdir]"); exit $exitcode; } ### Package-internal helper functions. # Reads a "line" from a file. This line is stripped of comments and extraneous # whitespace. Also, backslash-continued multiple lines are merged into a single # line. # # First parameter is the reference to the filehandle to read from. # Returns the text of the line. sub _readNextLogicalLine { my $fileReader = shift; while($_ = $fileReader->readLine()) { # Remove trailing newline chomp; # Replace \ followed by optional space at EOL and try again. if(s/\\\s*$//) { $_ .= $fileReader->readLine(); redo; } s/#.*$//; # Remove comments next if /^\s*$/; # Skip blank lines return $_; } return undef; } # Takes an input line, and extracts it into an option name, and simplified # value. The value has "false" converted to 0, white space simplified (like in # Qt), and tildes (~) in what appear to be path-like entries are converted to # the home directory path. # # First parameter is the build context (used for translating option values). # Second parameter is the line to split. # Return value is (option-name, option-value) sub _splitOptionAndValue { my $ctx = assert_isa(shift, 'ksb::BuildContext'); my $input = shift; my $optionRE = qr/\$\{([a-zA-Z0-9-]+)\}/; # The option is the first word, followed by the # flags on the rest of the line. The interpretation # of the flags is dependent on the option. my ($option, $value) = ($input =~ /^\s* # Find all spaces ([-\w]+) # First match, alphanumeric, -, and _ # (?: ) means non-capturing group, so (.*) is $value # So, skip spaces and pick up the rest of the line. (?:\s+(.*))?$/x); $value = trimmed($value // ''); # Simplify whitespace. $value =~ s/\s+/ /g; # Check for false keyword and convert it to Perl false. $value = 0 if lc($value) eq 'false'; # Replace reference to global option with their value. # The regex basically just matches ${option-name}. my ($sub_var_name) = ($value =~ $optionRE); while ($sub_var_name) { my $sub_var_value = $ctx->getOption($sub_var_name) || ''; if(!$ctx->hasOption($sub_var_name)) { warning (" *\n * WARNING: $sub_var_name is not set at line y[$.]\n *"); ## TODO: filename is missing } debug ("Substituting \${$sub_var_name} with $sub_var_value"); $value =~ s/\$\{$sub_var_name\}/$sub_var_value/g; # Replace other references as well. Keep this RE up to date with # the other one. ($sub_var_name) = ($value =~ $optionRE); } # Replace tildes with home directory. 1 while ($value =~ s"(^|:|=)~/"$1$ENV{'HOME'}/"); return ($option, $value); } # Ensures that the given ModuleSet has at least a valid repository and # use-modules setting based on the given BuildContext. sub _validateModuleSet { my ($ctx, $moduleSet) = @_; my $name = $moduleSet->name() || 'unnamed'; my $rcSources = _getModuleSources($moduleSet); # re-read option from module set since it may be pre-set my $selectedRepo = $moduleSet->getOption('repository'); if (!$selectedRepo) { error (<getOption('git-repository-base'); - if ($selectedRepo ne KDE_PROJECT_ID && + if ($selectedRepo ne KDE_PROJECT_ID && $selectedRepo ne QT_PROJECT_ID && not exists $repoSet->{$selectedRepo}) { my $projectID = KDE_PROJECT_ID; my $moduleSetName = $moduleSet->name(); my $moduleSetId = $moduleSetName ? "module-set ($moduleSetName)" : "module-set"; error (<isa('ksb::BuildContext') ? 'global' : $module->isa('ksb::ModuleSet') ? 'module-set' : $module->isa('ksb::Module') ? 'module' : 'options'; # Just look for an end marker if terminator not provided. $endRE //= qr/^end[\w\s]*$/; _markModuleSource($module, $fileReader->currentFilename() . ":$."); # Read in each option while (($_ = _readNextLogicalLine($fileReader)) && ($_ !~ $endRE)) { my $current_file = $fileReader->currentFilename(); # Sanity check, make sure the section is correctly terminated if(/^(module\b|options\b)/) { error ("Invalid configuration file $current_file at line $.\nAdd an 'end $endWord' before " . "starting a new module.\n"); die make_exception('Config', "Invalid file $current_file"); } my ($option, $value) = _splitOptionAndValue($ctx, $_); eval { $module->setOption($option, $value); }; if (my $err = $@) { if (blessed($err) && $err->isa('ksb::BuildException::Config')) { my $msg = "$current_file:$.: " . $err->message(); my $explanation = $err->optionUsageExplanation(); $msg = $msg . "\n" . $explanation if $explanation; $err->setMessage($msg); } die; # re-throw } } return $module; } # Marks the given OptionsBase subclass (i.e. Module or ModuleSet) as being # read in from the given string (filename:line). An OptionsBase can be # tagged under multiple files. sub _markModuleSource { my ($optionsBase, $configSource) = @_; my $key = '#defined-at'; my $sourcesRef = $optionsBase->hasOption($key) ? $optionsBase->getOption($key) : []; push @$sourcesRef, $configSource; $optionsBase->setOption($key, $sourcesRef); } # Returns rcfile sources for given OptionsBase (comma-separated). sub _getModuleSources { my $optionsBase = shift; my $key = '#defined-at'; my $sourcesRef = $optionsBase->getOption($key) || []; return join(', ', @$sourcesRef); } # Reads in a "moduleset". # # First parameter is the build context. # Second parameter is the filehandle to the config file to read from. # Third parameter is the ksb::ModuleSet to use. # # Returns the ksb::ModuleSet passed in with read-in options set, which may need # to be further expanded (see ksb::ModuleSet::convertToModules). sub _parseModuleSetOptions { my ($ctx, $fileReader, $moduleSet) = @_; $moduleSet = _parseModuleOptions($ctx, $fileReader, $moduleSet, qr/^end\s+module(-?set)?$/); - if ($moduleSet->getOption('repository') eq KDE_PROJECT_ID && - !$moduleSet->isa('ksb::ModuleSet::KDEProjects')) - { - # Perl-specific note! re-blessing the module set into the right 'class' - # You'd probably have to construct an entirely new object and copy the - # members over in other languages. + # Perl-specific note! re-blessing the module set into the right 'class' + # You'd probably have to construct an entirely new object and copy the + # members over in other languages. + if ($moduleSet->getOption('repository') eq KDE_PROJECT_ID) { bless $moduleSet, 'ksb::ModuleSet::KDEProjects'; + } elsif ($moduleSet->getOption('repository') eq QT_PROJECT_ID) { + bless $moduleSet, 'ksb::ModuleSet::Qt'; } return $moduleSet; } # Function: _readConfigurationOptions # # Reads in the settings from the configuration, passed in as an open # filehandle. # # Phase: # initialization - Do not call from this function. # # Parameters: # ctx - The to update based on the configuration read and # any pending command-line options (see cmdlineGlobalOptions). # # filehandle - The I/O object to read from. Must handle _eof_ and _readline_ # methods (e.g. subclass). # # deferredOptions - An out parameter: a hashref holding the options set by any # 'options' blocks read in by this function. Each key (identified by the name # of the 'options' block) will point to a hashref value holding the options to # apply. # # Returns: # @module - Heterogeneous list of and defined in the # configuration file. No module sets will have been expanded out (either # kde-projects or standard sets). # # Throws: # - Config exceptions. sub _readConfigurationOptions { my $ctx = assert_isa(shift, 'ksb::BuildContext'); my $fh = shift; my $deferredOptionsRef = shift; my @module_list; my $rcfile = $ctx->rcFile(); my ($option, %readModules); my $fileReader = ksb::RecursiveFH->new($rcfile); $fileReader->addFile($fh, $rcfile); # Read in global settings while ($_ = $fileReader->readLine()) { s/#.*$//; # Remove comments s/^\s+//; # Remove leading whitespace next unless $_; # Skip blank lines # First command in .kdesrc-buildrc should be a global # options declaration, even if none are defined. if (not /^global\s*$/) { error ("Invalid configuration file: $rcfile."); error ("Expecting global settings section at b[r[line $.]!"); die make_exception('Config', 'Missing global section'); } # Now read in each global option. _parseModuleOptions($ctx, $fileReader, $ctx); last; } my $using_default = 1; my %seenModules; # NOTE! *not* module-sets, *just* modules. my %seenModuleSets; # and vice versa -- named sets only though! my %seenModuleSetItems; # To track option override modules. # Now read in module settings while ($_ = $fileReader->readLine()) { s/#.*$//; # Remove comments s/^\s*//; # Remove leading whitespace next if (/^\s*$/); # Skip blank lines # Get modulename (has dash, dots, slashes, or letters/numbers) my ($type, $modulename) = /^(options|module)\s+([-\/\.\w]+)\s*$/; my $newModule; # 'include' directives can change the current file, so check where we're at $rcfile = $fileReader->currentFilename(); # Module-set? if (not $modulename) { my $moduleSetRE = qr/^module-set\s*([-\/\.\w]+)?\s*$/; ($modulename) = m/$moduleSetRE/; # modulename may be blank -- use the regex directly to match if (not /$moduleSetRE/) { error ("Invalid configuration file $rcfile!"); error ("Expecting a start of module section at r[b[line $.]."); die make_exception('Config', 'Ungrouped/Unknown option'); } if ($modulename && exists $seenModuleSets{$modulename}) { error ("Duplicate module-set $modulename at $rcfile:$."); die make_exception('Config', "Duplicate module-set $modulename defined at $rcfile:$."); } if ($modulename && exists $seenModules{$modulename}) { error ("Name $modulename for module-set at $rcfile:$. is already in use on a module"); die make_exception('Config', "Can't re-use name $modulename for module-set defined at $rcfile:$."); } # A moduleset can give us more than one module to add. $newModule = _parseModuleSetOptions($ctx, $fileReader, ksb::ModuleSet->new($ctx, $modulename || "")); # Save 'use-modules' entries so we can see if later module decls # are overriding/overlaying their options. my @moduleSetItems = $newModule->moduleNamesToFind(); @seenModuleSetItems{@moduleSetItems} = ($newModule) x scalar @moduleSetItems; $seenModuleSets{$modulename} = $newModule if $modulename; } # Duplicate module entry? (Note, this must be checked before the check # below for 'options' sets) elsif (exists $seenModules{$modulename} && $type ne 'options') { error ("Duplicate module declaration b[r[$modulename] on line $. of $rcfile"); die make_exception('Config', "Duplicate module $modulename declared at $rcfile:$."); } # Module/module-set options overrides elsif ($type eq 'options') { my $options = _parseModuleOptions($ctx, $fileReader, ksb::OptionsBase->new()); $deferredOptionsRef->{$modulename} = $options->{options}; next; # Don't add to module list } # Must follow 'options' handling elsif (exists $seenModuleSets{$modulename}) { error ("Name $modulename for module at $rcfile:$. is already in use on a module-set"); die make_exception('Config', "Can't re-use name $modulename for module defined at $rcfile:$."); } else { $newModule = _parseModuleOptions($ctx, $fileReader, ksb::Module->new($ctx, $modulename)); $seenModules{$modulename} = $newModule; } push @module_list, $newModule; $using_default = 0; } while (my ($name, $moduleSet) = each %seenModuleSets) { _validateModuleSet($ctx, $moduleSet); } # If the user doesn't ask to build any modules, build a default set. # The good question is what exactly should be built, but oh well. if ($using_default) { warning (" b[y[*] There do not seem to be any modules to build in your configuration."); return (); } return @module_list; } # Exits out of kdesrc-build, executing the user's preferred shell instead. The # difference is that the environment variables should be as set in kdesrc-build # instead of as read from .bashrc and friends. # # You should pass in the options to run the program with as a list. # # Meant to implement the --run command line option. sub _executeCommandLineProgram { my ($program, @args) = @_; if (!$program) { error ("You need to specify a program with the --run option."); exit 1; # Can't use finish here. } if (($< != $>) && ($> == 0)) { error ("kdesrc-build will not run a program as root unless you really are root."); exit 1; } debug ("Executing b[r[$program] ", join(' ', @args)); exit 0 if pretending(); exec $program, @args or do { # If we get to here, that sucks, but don't continue. error ("Error executing $program: $!"); exit 1; }; } # Function: _split_url # # Subroutine to split a url into a protocol and host sub _split_url { my $url = shift; my ($proto, $host) = ($url =~ m|([^:]*)://([^/]*)/|); return ($proto, $host); } # Function: _check_for_ssh_agent # # Checks if we are supposed to use ssh agent by examining the environment, and # if so checks if ssh-agent has a list of identities. If it doesn't, we run # ssh-add (with no arguments) and inform the user. This can be controlled with # the disable-agent-check parameter. # # Parameters: # 1. Build context sub _check_for_ssh_agent { my $ctx = assert_isa(shift, 'ksb::BuildContext'); # Don't bother with all this if the user isn't even using SSH. return 1 if pretending(); my @svnServers = grep { $_->scmType() eq 'svn' } ($ctx->modulesInPhase('update')); my @gitServers = grep { $_->scmType() eq 'git' } ($ctx->modulesInPhase('update')); my @sshServers = grep { my ($proto, $host) = _split_url($_->getOption('svn-server')); # Check if ssh is explicitly used in the proto, or if the host is the # developer main svn. (defined $proto && $proto =~ /ssh/) || (defined $host && $host =~ /^svn\.kde\.org/); } @svnServers; push @sshServers, grep { # Check for git+ssh:// or git@git.kde.org:/path/etc. my $repo = $_->getOption('repository'); ($repo =~ /^git\+ssh:\/\//) || ($repo =~ /^[a-zA-Z0-9_.]+@.*:\//); } @gitServers; return 1 if (not @sshServers) or $ctx->getOption('disable-agent-check'); whisper ("\tChecking for SSH Agent") if (scalar @sshServers); # We're using ssh to download, see if ssh-agent is running. return 1 unless exists $ENV{'SSH_AGENT_PID'}; my $pid = $ENV{'SSH_AGENT_PID'}; # It's supposed to be running, let's see if there exists the program with # that pid (this check is linux-specific at the moment). if (-d "/proc" and not -e "/proc/$pid") { warning ("r[ *] SSH Agent is enabled, but y[doesn't seem to be running]."); warning ("Since SSH is used to download from Subversion you may want to see why"); warning ("SSH Agent is not working, or correct the environment variable settings."); return 0; } # The agent is running, but does it have any keys? We can't be more specific # with this check because we don't know what key is required. my $noKeys = 0; filter_program_output(sub { $noKeys ||= /no identities/ }, 'ssh-add', '-l'); if ($noKeys) { # Use print so user can't inadvertently keep us quiet about this. print ksb::Debug::colorize (<getOption('ssh-identity-file'); push (@commandLine, $identFile) if $identFile; my $result = system (@commandLine); if ($result) # Run this code for both death-by-signal and nonzero return { my $rcfile = $ctx->rcFile(); print "\nUnable to add SSH identity, aborting.\n"; print "If you don't want kdesrc-build to check in the future,\n"; print ksb::Debug::colorize ("Set the g[disable-agent-check] option to g[true] in your $rcfile.\n\n"); return 0; } } return 1; } # Function: _handle_updates # # Subroutine to update a list of modules. # # Parameters: # 1. IPC module to pass results to. # 2. Build Context, which will be used to determine the module update list. # # The ipc parameter contains an object that is responsible for communicating # the status of building the modules. This function must account for every # module in $ctx's update phase to the ipc object before returning. # # Returns 0 on success, non-zero on error. sub _handle_updates { my ($ipc, $ctx) = @_; my $kdesrc = $ctx->getSourceDir(); my @update_list = $ctx->modulesInPhase('update'); # No reason to print out the text if we're not doing anything. if (!@update_list) { $ipc->sendIPCMessage(ksb::IPC::ALL_UPDATING, "update-list-empty"); $ipc->sendIPCMessage(ksb::IPC::ALL_DONE, "update-list-empty"); return 0; } if (not _check_for_ssh_agent($ctx)) { $ipc->sendIPCMessage(ksb::IPC::ALL_FAILURE, "ssh-failure"); return 1; } if (not -e $kdesrc) { whisper ("KDE source download directory doesn't exist, creating.\n"); if (not super_mkdir ($kdesrc)) { error ("Unable to make directory r[$kdesrc]!"); $ipc->sendIPCMessage(ksb::IPC::ALL_FAILURE, "no-source-dir"); return 1; } } # Once at this point, any errors we get should be limited to a module, # which means we can tell the build thread to start. $ipc->sendIPCMessage(ksb::IPC::ALL_UPDATING, "starting-updates"); my $hadError = 0; foreach my $module (@update_list) { $ipc->setLoggedModule($module->name()); # Note that this must be in this order to avoid accidentally not # running ->update() from short-circuiting if an error is noted. $hadError = !$module->update($ipc, $ctx) || $hadError; } $ipc->sendIPCMessage(ksb::IPC::ALL_DONE, "had_errors: $hadError"); return $hadError; } # Builds the given module. # # Return value is the failure phase, or 0 on success. sub _buildSingleModule { my ($ipc, $ctx, $module, $startTimeRef) = @_; $ctx->resetEnvironment(); $module->setupEnvironment(); my $fail_count = $module->getPersistentOption('failure-count') // 0; my ($resultStatus, $message) = $ipc->waitForModule($module); $ipc->forgetModule($module); if ($resultStatus eq 'failed') { error ("\tUnable to update r[$module], build canceled."); $module->setPersistentOption('failure-count', ++$fail_count); return 'update'; } elsif ($resultStatus eq 'success') { note ("\tSource update complete for g[$module]: $message"); } # Skip actually building a module if the user has selected to skip # builds when the source code was not actually updated. But, don't skip # if we didn't successfully build last time. elsif ($resultStatus eq 'skipped' && !$module->getOption('build-when-unchanged') && $fail_count == 0) { note ("\tSkipping g[$module], its source code has not changed."); return 0; } elsif ($resultStatus eq 'skipped') { note ("\tNo changes to g[$module] source, proceeding to build."); } $$startTimeRef = time; $fail_count = $module->build() ? 0 : $fail_count + 1; $module->setPersistentOption('failure-count', $fail_count); return $fail_count > 0 ? 'build' : 0; } # Function: _handle_build # # Subroutine to handle the build process. # # Parameters: # 1. IPC object to receive results from. # 2. Build Context, which is used to determine list of modules to build. # # If the packages are not already checked-out and/or updated, this # subroutine WILL NOT do so for you. # # This subroutine assumes that the source directory has already been set up. # It will create the build directory if it doesn't already exist. # # If $builddir/$module/.refresh-me exists, the subroutine will # completely rebuild the module (as if --refresh-build were passed for that # module). # # Returns 0 for success, non-zero for failure. sub _handle_build { my ($ipc, $ctx) = @_; my @build_done; my @modules = $ctx->modulesInPhase('build'); my $result = 0; # No reason to print building messages if we're not building. return 0 if scalar @modules == 0; # Check for absolutely essential programs now. if (!_checkForEssentialBuildPrograms($ctx) && !exists $ENV{KDESRC_BUILD_IGNORE_MISSING_PROGRAMS}) { error (" r[b[*] Aborting now to save a lot of wasted time."); error (" y[b[*] export KDESRC_BUILD_IGNORE_MISSING_PROGRAMS=1 and re-run (perhaps with --no-src)"); error (" r[b[*] to continue anyways. If this check was in error please report a bug against"); error (" y[b[*] kdesrc-build at https://bugs.kde.org/"); return 1; } # IPC queue should have a message saying whether or not to bother with the # build. $ipc->waitForStreamStart(); $ctx->unsetPersistentOption('global', 'resume-list'); my $outfile = pretending() ? '/dev/null' : $ctx->getLogDir() . '/build-status'; open (STATUS_FILE, '>', $outfile) or do { error (<statusViewer(); my $i = 1; $statusViewer->numberModulesTotal(scalar @modules); while (my $module = shift @modules) { my $moduleName = $module->name(); my $moduleSet = $module->moduleSet()->name(); my $modOutput = $moduleName; if (debugging(ksb::Debug::WHISPER)) { $modOutput .= " (build system " . $module->buildSystemType() . ")" } $moduleSet = " from g[$moduleSet]" if $moduleSet; note ("Building g[$modOutput]$moduleSet ($i/$num_modules)"); my $start_time = time; my $failedPhase = _buildSingleModule($ipc, $ctx, $module, \$start_time); my $elapsed = prettify_seconds(time - $start_time); if ($failedPhase) { # FAILURE $ctx->markModulePhaseFailed($failedPhase, $module); print STATUS_FILE "$module: Failed on $failedPhase after $elapsed.\n"; if ($result == 0) { # No failures yet, mark this as resume point my $moduleList = join(', ', map { "$_" } ($module, @modules)); $ctx->setPersistentOption('global', 'resume-list', $moduleList); } $result = 1; if ($module->getOption('stop-on-failure')) { note ("\n$module didn't build, stopping here."); return 1; # Error } $statusViewer->numberModulesFailed(1 + $statusViewer->numberModulesFailed); } else { # Success print STATUS_FILE "$module: Succeeded after $elapsed.\n"; push @build_done, $moduleName; # Make it show up as a success $statusViewer->numberModulesSucceeded(1 + $statusViewer->numberModulesSucceeded); } $i++; } continue # Happens at the end of each loop and on next { print "\n"; # Space things out } if ($outfile) { close STATUS_FILE; # Update the symlink in latest to point to this file. my $logdir = $ctx->getSubdirPath('log-dir'); if (-l "$logdir/latest/build-status") { safe_unlink("$logdir/latest/build-status"); } symlink($outfile, "$logdir/latest/build-status"); } info ("<<< g[PACKAGES SUCCESSFULLY BUILT] >>>") if scalar @build_done > 0; my $successes = scalar @build_done; # TODO: l10n my $mods = $successes == 1 ? 'module' : 'modules'; if (not pretending()) { # Print out results, and output to a file my $kdesrc = $ctx->getSourceDir(); open BUILT_LIST, ">$kdesrc/successfully-built"; foreach my $module (@build_done) { info ("$module") if $successes <= 10; print BUILT_LIST "$module\n"; } close BUILT_LIST; info ("Built g[$successes] $mods") if $successes > 10; } else { # Just print out the results if ($successes <= 10) { info ('g[', join ("]\ng[", @build_done), ']'); } else { info ("Built g[$successes] $mods") if $successes > 10; } } info (' '); # Space out nicely return $result; } # Function: _handle_async_build # # This subroutine special-cases the handling of the update and build phases, by # performing them concurrently (where possible), using forked processes. # # Only one thread or process of execution will return from this procedure. Any # other processes will be forced to exit after running their assigned module # phase(s). # # We also redirect ksb::Debug output messages to be sent to a single process # for display on the terminal instead of allowing them all to interrupt each # other. # # Parameters: # 1. IPC Object to use for sending/receiving update/build status. It must be # an object type that supports IPC concurrency (e.g. IPC::Pipe). # 2. Build Context to use, from which the module lists will be determined. # # Returns 0 on success, non-zero on failure. sub _handle_async_build { # The exact method for async is that two children are forked. One child # is a source update process. The other child is a monitor process which will # hold status updates from the update process so that the updates may # happen without waiting for us to be ready to read. my ($ipc, $ctx) = @_; print "\n"; # Space out from metadata messages. my $result = 0; my $monitorPid = fork; if ($monitorPid == 0) { # child my $updaterToMonitorIPC = ksb::IPC::Pipe->new(); my $updaterPid = fork; $SIG{INT} = sub { POSIX::_exit(EINTR); }; if ($updaterPid) { $0 = 'kdesrc-build-updater'; $updaterToMonitorIPC->setSender(); ksb::Debug::setIPC($updaterToMonitorIPC); POSIX::_exit (_handle_updates ($updaterToMonitorIPC, $ctx)); } else { $0 = 'kdesrc-build-monitor'; $ipc->setSender(); $updaterToMonitorIPC->setReceiver(); $ipc->setLoggedModule('#monitor#'); # This /should/ never be used... ksb::Debug::setIPC($ipc); POSIX::_exit (_handle_monitoring ($ipc, $updaterToMonitorIPC)); } } else { # Still the parent, let's do the build. $ipc->setReceiver(); $result = _handle_build ($ipc, $ctx); } $ipc->waitForEnd(); $ipc->close(); # Display a message for updated modules not listed because they were not # built. my $unseenModulesRef = $ipc->unacknowledgedModules(); if (%$unseenModulesRef) { note ("The following modules were updated but not built:"); foreach my $modulename (keys %$unseenModulesRef) { note ("\t$modulename"); } } # It's possible if build fails on first module that git or svn is still # running. Make them stop too. if (waitpid ($monitorPid, WNOHANG) == 0) { kill 'INT', $monitorPid; # Exit code is in $?. waitpid ($monitorPid, 0); $result = 1 if $? != 0; } return $result; } # Function: _handle_install # # Handles the installation process. Simply calls 'make install' in the build # directory, though there is also provision for cleaning the build directory # afterwards, or stopping immediately if there is a build failure (normally # every built module is attempted to be installed). # # Parameters: # 1. Build Context, from which the install list is generated. # # Return value is a shell-style success code (0 == success) sub _handle_install { my $ctx = assert_isa(shift, 'ksb::BuildContext'); my @modules = $ctx->modulesInPhase('install'); @modules = grep { $_->buildSystem()->needsInstalled() } (@modules); my $result = 0; for my $module (@modules) { $ctx->resetEnvironment(); $result = $module->install() || $result; if ($result && $module->getOption('stop-on-failure')) { note ("y[Stopping here]."); return 1; # Error } } return $result; } # Function: _handle_uninstall # # Handles the uninstal process. Simply calls 'make uninstall' in the build # directory, while assuming that Qt or CMake actually handles it. # # The order of the modules is often significant, and it may work better to # uninstall modules in reverse order from how they were installed. However this # code does not automatically reverse the order; modules are uninstalled in the # order determined by the build context. # # This function obeys the 'stop-on-failure' option supported by _handle_install. # # Parameters: # 1. Build Context, from which the uninstall list is generated. # # Return value is a shell-style success code (0 == success) sub _handle_uninstall { my $ctx = assert_isa(shift, 'ksb::BuildContext'); my @modules = $ctx->modulesInPhase('uninstall'); @modules = grep { $_->buildSystem()->needsInstalled() } (@modules); my $result = 0; for my $module (@modules) { $ctx->resetEnvironment(); $result = $module->uninstall() || $result; if ($result && $module->getOption('stop-on-failure')) { note ("y[Stopping here]."); return 1; # Error } } return $result; } # Function: _handle_monitoring # # This is the main subroutine for the monitoring process when using IPC::Pipe. # It reads in all status reports from the source update process and then holds # on to them. When the build process is ready to read information we send what # we have. Otherwise we're waiting on the update process to send us something. # # This convoluted arrangement is required to allow the source update # process to go from start to finish without undue interruption on it waiting # to write out its status to the build process (which is usually busy). # # Parameters: # 1. the IPC object to use to send to build process. # 2. the IPC object to use to receive from update process. # # Returns 0 on success, non-zero on failure. sub _handle_monitoring { my ($ipcToBuild, $ipcFromUpdater) = @_; my @msgs; # Message queue. # We will write to the build process and read from the update process. my $sendFH = $ipcToBuild->{fh} || croak_runtime('??? missing pipe to build proc'); my $recvFH = $ipcFromUpdater->{fh} || croak_runtime('??? missing pipe from monitor'); my $readSelector = IO::Select->new($recvFH); my $writeSelector = IO::Select->new($sendFH); # Start the loop. We will be waiting on either read or write ends. # Whenever select() returns we must check both sets. while ( my ($readReadyRef, $writeReadyRef) = IO::Select->select($readSelector, $writeSelector, undef)) { if (!$readReadyRef && !$writeReadyRef) { # Some kind of error occurred. return 1; } # Check for source updates first. if (@{$readReadyRef}) { undef $@; my $msg = eval { $ipcFromUpdater->receiveMessage(); }; # undef msg indicates EOF, so check for exception obj specifically die $@ if $@; # undef can be returned on EOF as well as error. EOF means the # other side is presumably done. if (! defined $msg) { $readSelector->remove($recvFH); last; # Select no longer needed, just output to build. } else { push @msgs, $msg; # We may not have been waiting for write handle to be ready if # we were blocking on an update from updater thread. $writeSelector->add($sendFH) unless $writeSelector->exists($sendFH); } } # Now check for build updates. if (@{$writeReadyRef}) { # If we're here the update is still going. If we have no messages # to send wait for that first. if (not @msgs) { $writeSelector->remove($sendFH); } else { # Send the message (if we got one). if (!$ipcToBuild->sendMessage(shift @msgs)) { error ("r[mon]: Build process stopped too soon! r[$!]"); return 1; } } } } # Send all remaining messages. while (@msgs) { if (!$ipcToBuild->sendMessage(shift @msgs)) { error ("r[mon]: Build process stopped too soon! r[$!]"); return 1; } } $ipcToBuild->close(); return 0; } # Function: _applyModuleFilters # # Applies any module-specific filtering that is necessary after reading command # line and rc-file options. (This is as opposed to phase filters, which leave # each module as-is but change the phases they operate as part of, this # function could remove a module entirely from the build). # # Used for --resume-{from,after} and --stop-{before,after}, but more could be # added in theory. # This subroutine supports --{resume,stop}-* for both modules and module-sets. # # Parameters: # ctx - in use. # @modules - List of or to apply filters on. # # Returns: # list of or with any inclusion/exclusion filters # applied. Do not assume this list will be a strict subset of the input list, # however the order will not change amongst the input modules. sub _applyModuleFilters { my $ctx = assert_isa(shift, 'ksb::BuildContext'); my @moduleList = @_; if (!$ctx->getOption('resume-from') && !$ctx->getOption('resume-after') && !$ctx->getOption('stop-before') && !$ctx->getOption('stop-after')) { debug ("No command-line filter seems to be present."); return @moduleList; } if ($ctx->getOption('resume-from') && $ctx->getOption('resume-after')) { # This one's an error. error (<getOption('stop-before') && $ctx->getOption('stop-after')) { # This one's an error. error (<getOption('resume-from') || $ctx->getOption('resume-after'); my $startIndex = scalar @moduleList; if ($resumePoint) { debug ("Looking for $resumePoint for --resume-* option"); # || 0 is a hack to force Boolean context. my $filterInclusive = $ctx->getOption('resume-from') || 0; my $found = 0; for (my $i = 0; $i < scalar @moduleList; $i++) { my $module = $moduleList[$i]; $found = $module->name() eq $resumePoint; if ($found) { $startIndex = $filterInclusive ? $i : $i + 1; $startIndex = min($startIndex, scalar @moduleList - 1); last; } } } else { $startIndex = 0; } my $stopPoint = $ctx->getOption('stop-before') || $ctx->getOption('stop-after'); my $stopIndex = 0; if ($stopPoint) { debug ("Looking for $stopPoint for --stop-* option"); # || 0 is a hack to force Boolean context. my $filterInclusive = $ctx->getOption('stop-before') || 0; my $found = 0; for (my $i = $startIndex; $i < scalar @moduleList; $i++) { my $module = $moduleList[$i]; $found = $module->name() eq $stopPoint; if ($found) { $stopIndex = $i - ($filterInclusive ? 1 : 0); last; } } } else { $stopIndex = scalar @moduleList - 1; } if ($startIndex > $stopIndex || scalar @moduleList == 0) { # Lost all modules somehow. croak_runtime("Unknown resume -> stop point $resumePoint -> $stopPoint."); } return @moduleList[$startIndex .. $stopIndex]; } # This defines the factory function needed for lower-level code to properly be # able to create ksb::Module objects from just the module name, while still # having the options be properly set and having the module properly tied into a # context. sub _defineNewModuleFactory { my ($self, $resolver) = @_; my $ctx = $self->context(); $self->{module_factory} = sub { # We used to need a special module-set to ignore virtual deps (they # would throw errors if the name did not exist). But, the resolver # handles that fine as well. return $resolver->resolveModuleIfPresent(shift); }; } # This function converts any 'l10n' references on the command line to return a l10n # module with the proper build system, scm type, etc. # # The languages are selected using global/kde-languages (which should be used # exclusively from the configuration file). sub _expandl10nModules { my ($ctx, @modules) = @_; my $l10n = 'l10n-kde4'; assert_isa($ctx, 'ksb::BuildContext'); # Only filter if 'l10n' is actually present in list. my @matches = grep {$_->name() =~ /^(?:$l10n|l10n)$/} @modules; my @langs = split(' ', $ctx->getOption('kde-languages')); return @modules if (!@matches || !@langs); my $l10nModule; for my $match (@matches) { # Remove all instances of l10n. @modules = grep {$_->name() ne $match->name()} @modules; # Save l10n module if user had it in config. We only save the first # one encountered though. $l10nModule //= $match; } # No l10n module? Just create one. $l10nModule //= ksb::Module->new($ctx, $l10n); whisper ("\tAdding languages ", join(';', @langs), " to build."); $l10nModule->setScmType('l10n'); my $scm = $l10nModule->scm(); # Add all required directories to the l10n module. Its buildsystem should # know to skip scripts and templates. $scm->setLanguageDirs(qw/scripts templates/, @langs); $l10nModule->setBuildSystem($scm); push @modules, $l10nModule; return @modules; } # Updates the built-in phase list for all Modules passed into this function in # accordance with the options set by the user. sub _updateModulePhases { whisper ("Filtering out module phases."); for my $module (@_) { if ($module->getOption('manual-update') || $module->getOption('no-svn') || $module->getOption('no-src')) { $module->phases()->clear(); next; } if ($module->getOption('manual-build')) { $module->phases()->filterOutPhase('build'); $module->phases()->filterOutPhase('test'); $module->phases()->filterOutPhase('install'); } $module->phases()->filterOutPhase('install') unless $module->getOption('install-after-build'); $module->phases()->addPhase('test') if $module->getOption('run-tests'); } return @_; } # This subroutine extract the value from options of the form --option=value, # which can also be expressed as --option value. # # The first parameter is the option that the user passed to the cmd line (e.g. # --prefix=/opt/foo). # The second parameter is a reference to the list of command line options. # # The return value is the value of the option (the list of options might be # shorter by 1, copy it if you don't want it to change), or undef if no value # was provided. sub _extractOptionValue { my ($option, $options_ref) = @_; if ($option =~ /=/) { my @value = split(/=/, $option); shift @value; # We don't need the first one, that the --option part. return if (scalar @value == 0); # If we have more than one element left in @value it's because the # option itself has an = in it, make sure it goes back in the answer. return join('=', @value); } return if scalar @{$options_ref} == 0; return shift @{$options_ref}; } # Like _extractOptionValue, but throws an exception if the value is not # actually present, so you don't have to check for it yourself. If you do get a # return value, it will be defined to something. sub _extractOptionValueRequired { my ($option, $options_ref) = @_; my $returnValue = _extractOptionValue($option, $options_ref); if (not defined $returnValue) { croak_runtime("Option $option needs to be set to some value instead of left blank"); } return $returnValue; } # Function: _cleanup_log_directory # # This function removes log directories from old kdesrc-build runs. All log # directories not referenced by $log_dir/latest somehow are made to go away. # # Parameters: # 1. Build context. # # No return value. sub _cleanup_log_directory { my $ctx = assert_isa(shift, 'ksb::BuildContext'); my $logdir = $ctx->getSubdirPath('log-dir'); return 0 if ! -e "$logdir/latest"; # Could happen for error on first run... # This glob relies on the date being in the specific format YYYY-MM-DD-ID my @dirs = bsd_glob("$logdir/????-??-??-??/", GLOB_NOSORT); my @needed = _reachableModuleLogs("$logdir/latest"); # Convert a list to a hash lookup since Perl lacks a "list-has" my %needed_table; @needed_table{@needed} = (1) x @needed; my $length = scalar @dirs - scalar @needed; if ($length > 15) { # Arbitrary man is arbitrary note ("Removing y[b[$length] out of g[b[$#dirs] old log directories (this may take some time)..."); } elsif ($length > 0) { info ("Removing g[b[$length] out of g[b[$#dirs] old log directories..."); } for my $dir (@dirs) { my ($id) = ($dir =~ m/(\d\d\d\d-\d\d-\d\d-\d\d)/); safe_rmtree($dir) unless $needed_table{$id}; } } # Function: _output_failed_module_list # # Print out an error message, and a list of modules that match that error # message. It will also display the log file name if one can be determined. # The message will be displayed all in uppercase, with PACKAGES prepended, so # all you have to do is give a descriptive message of what this list of # packages failed at doing. # # No message is printed out if the list of failed modules is empty, so this # function can be called unconditionally. # # Parameters: # 1. Build Context # 2. Message to print (e.g. 'failed to foo') # 3. List of ksb::Modules that had failed to foo # # No return value. sub _output_failed_module_list { my ($ctx, $message, @fail_list) = @_; assert_isa($ctx, 'ksb::BuildContext'); $message = uc $message; # Be annoying if (@fail_list) { debug ("Message is $message"); debug ("\tfor ", join(', ', @fail_list)); } if (scalar @fail_list > 0) { my $homedir = $ENV{'HOME'}; my $logfile; warning ("\nr[b[<<< PACKAGES $message >>>]"); for my $module (@fail_list) { $logfile = $module->getOption('#error-log-file'); # async updates may cause us not to have a error log file stored. There's only # one place it should be though, take advantage of side-effect of log_command() # to find it. if (not $logfile) { my $logdir = $module->getLogDir() . "/error.log"; $logfile = $logdir if -e $logdir; } $logfile = "No log file" unless $logfile; $logfile =~ s|$homedir|~|; warning ("r[$module]") if pretending(); warning ("r[$module] - g[$logfile]") if not pretending(); } } } # Function: _output_failed_module_lists # # This subroutine reads the list of failed modules for each phase in the build # context and calls _output_failed_module_list for all the module failures. # # Parameters: # 1. Build context # # Return value: # None sub _output_failed_module_lists { my $ctx = assert_isa(shift, 'ksb::BuildContext'); # This list should correspond to the possible phase names (although # it doesn't yet since the old code didn't, TODO) for my $phase ($ctx->phases()->phases()) { my @failures = $ctx->failedModulesInPhase($phase); _output_failed_module_list($ctx, "failed to $phase", @failures); } # See if any modules fail continuously and warn specifically for them. my @super_fail = grep { ($_->getPersistentOption('failure-count') // 0) > 3 } (@{$ctx->moduleList()}); if (@super_fail) { warning ("\nThe following modules have failed to build 3 or more times in a row:"); warning ("\tr[b[$_]") foreach @super_fail; warning ("\nThere is probably a local error causing this kind of consistent failure, it"); warning ("is recommended to verify no issues on the system.\n"); } } # Function: _installTemplatedFile # # This function takes a given file and a build context, and installs it to a # given location while expanding out template entries within the source file. # # The template language is *extremely* simple: <% foo %> is replaced entirely # with the result of $ctx->getOption(foo, 'no-inherit'). If the result # evaluates false for any reason than an exception is thrown. No quoting of # any sort is used in the result, and there is no way to prevent expansion of # something that resembles the template format. # # Multiple template entries on a line will be replaced. # # The destination file will be created if it does not exist. If the file # already exists then an exception will be thrown. # # Error handling: Any errors will result in an exception being thrown. # # Parameters: # 1. Pathname to the source file (use absolute paths) # 2. Pathname to the destination file (use absolute paths) # 3. Build context to use for looking up template values # # Return value: There is no return value. sub _installTemplatedFile { my ($sourcePath, $destinationPath, $ctx) = @_; assert_isa($ctx, 'ksb::BuildContext'); open (my $input, '<', $sourcePath) or croak_runtime("Unable to open template source $sourcePath: $!"); open (my $output, '>', $destinationPath) or croak_runtime("Unable to open template output $destinationPath: $!"); while (!eof ($input)) { my $line = readline($input); if (!defined ($line)) { croak_runtime("Failed to read from $sourcePath at line $.: $!"); unlink($destinationPath); } # Some lines should only be present in the source as they aid with testing. next if $line =~ /kdesrc-build: filter/; $line =~ s { <% \s* # Template bracket and whitespace ([^\s%]+) # Capture variable name \s*%> # remaining whitespace and closing bracket } { $ctx->getOption($1, 'module') // croak_runtime("Invalid variable $1") }gxe; # Replace all matching expressions, use extended regexp w/ # comments, and replacement is Perl code to execute. (print $output $line) or croak_runtime("Unable to write line to $destinationPath at line $.: $!"); } } # Function: _installCustomFile # # This function installs a source file to a destination path, assuming the # source file is a "templated" source file (see also _installTemplatedFile), and # records a digest of the file actually installed. This function will overwrite # a destination if the destination is identical to the last-installed file. # # Error handling: Any errors will result in an exception being thrown. # # Parameters: # 1. Build context to use for looking up template values, # 2. The full path to the source file. # 3. The full path to the destination file (incl. name) # 4. The key name to use for searching/recording installed MD5 digest. # # Return value: There is no return value. sub _installCustomFile { use File::Copy qw(copy); my $ctx = assert_isa(shift, 'ksb::BuildContext'); my ($sourceFilePath, $destFilePath, $md5KeyName) = @_; my $baseName = basename($sourceFilePath); if (-e $destFilePath) { my $existingMD5 = $ctx->getPersistentOption('/digests', $md5KeyName) // ''; if (fileDigestMD5($destFilePath) ne $existingMD5) { if (!$ctx->getOption('#delete-my-settings')) { error ("\tr[*] Installing \"b[$baseName]\" would overwrite an existing file:"); error ("\tr[*] y[b[$destFilePath]"); error ("\tr[*] If this is acceptable, please delete the existing file and re-run,"); error ("\tr[*] or pass b[--delete-my-settings] and re-run."); return; } elsif (!pretending()) { copy ($destFilePath, "$destFilePath.kdesrc-build-backup"); } } } if (!pretending()) { _installTemplatedFile($sourceFilePath, $destFilePath, $ctx); $ctx->setPersistentOption('/digests', $md5KeyName, fileDigestMD5($destFilePath)); } } # Function: _installCustomSessionDriver # # This function installs the included sample .xsession and environment variable # setup files, and records the md5sum of the installed results. # # If a file already exists, then its md5sum is taken and if the same as what # was previously installed, is overwritten. If not the same, the original file # is left in place and the .xsession is instead installed to # .xsession-kdesrc-build # # Error handling: Any errors will result in an exception being thrown. # # Parameters: # 1. Build context to use for looking up template values, # # Return value: There is no return value. sub _installCustomSessionDriver { use FindBin qw($RealBin); use List::Util qw(first); use File::Copy qw(copy); my $ctx = assert_isa(shift, 'ksb::BuildContext'); my @xdgDataDirs = split(':', $ENV{XDG_DATA_DIRS} || '/usr/local/share/:/usr/share/'); my $xdgDataHome = $ENV{XDG_DATA_HOME} || "$ENV{HOME}/.local/share"; # First we have to find the source my @searchPaths = ($RealBin, map { "$_/apps/kdesrc-build" } ($xdgDataHome, @xdgDataDirs)); s{/+$}{} foreach @searchPaths; # Remove trailing slashes s{//+}{/}g foreach @searchPaths; # Remove duplicate slashes my $envScript = first { -f $_ } ( map { "$_/sample-kde-env-master.sh" } @searchPaths ); my $sessionScript = first { -f $_ } ( map { "$_/sample-xsession.sh" } @searchPaths ); if (!$envScript || !$sessionScript) { warning ("b[*] Unable to find helper files to setup a login session."); warning ("b[*] You will have to setup login yourself, or install kdesrc-build properly."); return; } my $destDir = $ENV{XDG_CONFIG_HOME} || "$ENV{HOME}/.config"; super_mkdir($destDir) unless -d $destDir; _installCustomFile($ctx, $envScript, "$destDir/kde-env-master.sh", 'kde-env-master-digest'); _installCustomFile($ctx, $sessionScript, "$ENV{HOME}/.xsession", 'xsession-digest') if $ctx->getOption('install-session-driver'); if (!pretending()) { if ($ctx->getOption('install-session-driver') && !chmod (0744, "$ENV{HOME}/.xsession")) { error ("\tb[r[*] Error making b[~/.xsession] executable: $!"); error ("\tb[r[*] If this file is not executable you may not be able to login!"); }; } } # Function: _checkForEssentialBuildPrograms # # This subroutine checks for programs which are absolutely essential to the # *build* process and returns false if they are not all present. Right now this # just means qmake and cmake (although this depends on what modules are # actually present in the build context). # # Parameters: # 1. Build context # # Return value: # None sub _checkForEssentialBuildPrograms { my $ctx = assert_isa(shift, 'ksb::BuildContext'); return 1 if pretending(); my @buildModules = $ctx->modulesInPhase('build'); my %requiredPrograms; my %modulesRequiringProgram; foreach my $module ($ctx->modulesInPhase('build')) { my @progs = $module->buildSystem()->requiredPrograms(); # Deliberately used @, since requiredPrograms can return a list. @requiredPrograms{@progs} = 1; foreach my $prog (@progs) { $modulesRequiringProgram{$prog} //= { }; $modulesRequiringProgram{$prog}->{$module->name()} = 1; } } my $wasError = 0; for my $prog (keys %requiredPrograms) { my %requiredPackages = ( qmake => 'Qt', cmake => 'CMake', ); my $programPath = absPathToExecutable($prog); # qmake is not necessarily named 'qmake' if (!$programPath && $prog eq 'qmake') { $programPath = ksb::BuildSystem::QMake::absPathToQMake(); } if (!$programPath) { # Don't complain about Qt if we're building it... if ($prog eq 'qmake' && ( grep { $_->buildSystemType() eq 'Qt' } (@buildModules)) || pretending() ) { next; } $wasError = 1; my $reqPackage = $requiredPackages{$prog} || $prog; my @modulesNeeding = keys %{$modulesRequiringProgram{$prog}}; local $, = ', '; # List separator in output error (<<"EOF"); Unable to find r[b[$prog]. This program is absolutely essential for building the modules: y[@modulesNeeding]. Please ensure the development packages for $reqPackage are installed by using your distribution's package manager. You can also see the https://techbase.kde.org/Getting_Started/Build/Distributions page for information specific to your distribution (although watch for outdated information :( ). EOF } } return !$wasError; } # Function: _reachableModuleLogs # # Returns a list of module directory IDs that must be kept due to being # referenced from the "latest" symlink. # # This function may call itself recursively if needed. # # Parameters: # 1. The log directory under which to search for symlinks, including the "/latest" # part of the path. sub _reachableModuleLogs { my $logdir = shift; my @dirs; # A lexicalized var (my $foo) is required in face of recursiveness. opendir(my $fh, $logdir) or croak_runtime("Can't opendir $logdir: $!"); my $dir = readdir($fh); while(defined $dir) { if (-l "$logdir/$dir") { my $link = readlink("$logdir/$dir"); push @dirs, $link; } elsif ($dir !~ /^\.{1,2}$/) { # Skip . and .. directories (this is a great idea, trust me) push @dirs, _reachableModuleLogs("$logdir/$dir"); } $dir = readdir $fh; } closedir $fh; # Extract numeric IDs from directory names. @dirs = map { m/(\d{4}-\d\d-\d\d-\d\d)/ } (@dirs); # Convert to unique list by abusing hash keys. my %tempHash; @tempHash{@dirs} = (); return keys %tempHash; } # Installs the given subroutine as a signal handler for a set of signals which # could kill the program. # # First parameter is a reference to the sub to act as the handler. sub _installSignalHandlers { my $handlerRef = shift; my @signals = qw/HUP INT QUIT ABRT TERM PIPE/; @SIG{@signals} = ($handlerRef) x scalar @signals; } # Ensures that basic one-time setup to actually *use* installed software is # performed, including .kdesrc-buildrc setup if necessary. # # Returns the appropriate exitcode to pass to the exit function sub performInitialUserSetup { my $self = shift; return ksb::FirstRun::setupUserSystem(); } # Shows a help message and version. Does not exit. sub _showHelpMessage { my $scriptVersion = scriptVersion(); say < and others, and is distributed under the terms of the GNU GPL v2. This script automates the download, build, and install process for KDE software using the latest available source code. Configuration is controlled from "\$PWD/kdesrc-buildrc" or "~/.kdesrc-buildrc". See kdesrc-buildrc-sample for an example. Usage: \$ $0 [--options] [module names] All configured modules are built if none are listed. Important Options: --pretend Don't actually take major actions, instead describe what would be done. --no-src Don't update source code, just build/install. --src-only Only update the source code --refresh-build Start the build from scratch. --rc-file= Read configuration from filename instead of default. --initial-setup Installs Plasma env vars (~/.bashrc), required system pkgs, and a base kdesrc-buildrc. --resume-from= Skips modules until just before or after the given --resume-after= package, then operates as normal. --stop-before= Stops just before or after the given package is --stop-after= reached. --include-dependencies Also builds KDE-based dependencies of given modules. --stop-on-failure Stops the build as soon as a package fails to build. More docs at https://docs.kde.org/trunk5/en/extragear-utils/kdesrc-build/ Supported configuration options: https://go.kde.org/u/ksboptions Supported cmdline options: https://go.kde.org/u/ksbcmdline DONE # Look for indications this is the first run. if (! -e "./kdesrc-buildrc" && ! -e "$ENV{HOME}/.kdesrc-buildrc") { say <{context}; } sub metadataModule { my $self = shift; return $self->{metadata_module}; } sub runMode { my $self = shift; return $self->{run_mode}; } sub modules { my $self = shift; return @{$self->{modules}}; } 1; diff --git a/modules/ksb/BuildSystem/Qt5.pm b/modules/ksb/BuildSystem/Qt5.pm new file mode 100644 index 0000000..f07af60 --- /dev/null +++ b/modules/ksb/BuildSystem/Qt5.pm @@ -0,0 +1,105 @@ +package ksb::BuildSystem::Qt5 0.10; + +# Build system for the Qt5 toolkit + +use strict; +use warnings; +use 5.014; + +use ksb::BuildException; +use ksb::BuildSystem; +use ksb::Debug; +use ksb::Util; + +use parent qw(ksb::BuildSystem); + +# OVERRIDE +sub configuredModuleFileName +{ + return 'Makefile'; +} + +# OVERRIDE +sub name +{ + return 'Qt5'; +} + +# Return value style: boolean +sub configureInternal +{ + my $self = assert_isa(shift, __PACKAGE__); + 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, qw(-confirm-license -opensource -nomake examples -nomake tests); + + # Get the user's CXXFLAGS + my $cxxflags = $module->getOption('cxxflags'); + $module->buildContext()->queueEnvironmentVariable('CXXFLAGS', $cxxflags); + + my $prefix = $module->getOption('prefix'); + my $qtdir = $module->getOption('qtdir'); + + if ($prefix && $qtdir && $prefix ne $qtdir) { + 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')) || + 1 || # TODO: Find a safe way to skip reconfiguration + (! -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/FirstRun.pm b/modules/ksb/FirstRun.pm index 7373cd0..a272867 100644 --- a/modules/ksb/FirstRun.pm +++ b/modules/ksb/FirstRun.pm @@ -1,346 +1,350 @@ package ksb::FirstRun 0.10; use 5.014; use strict; use warnings; use File::Spec qw(splitpath); use ksb::BuildException; use ksb::Debug qw(colorize); use ksb::OSSupport; =head1 NAME ksb::FirstRun =head1 DESCRIPTION Performs initial-install setup, implementing the C<--initial-setup> option. B This module is supposed to be loadable even under minimal Perl environments as fielded in "minimal Docker container" forms of popular distros. =head1 SYNOPSIS my $exitcode = ksb::FirstRun::setupUserSystem(); exit $exitcode; =cut sub setupUserSystem { my $baseDir = shift; my $os = ksb::OSSupport->new; eval { _installSystemPackages($os); _setupBaseConfiguration($baseDir); _setupBashrcFile(); }; if (had_an_exception($@)) { my $msg = $@->{message}; say colorize (" b[r[*] r[$msg]"); return 1; } return 0; } # Internal functions # Reads from the __DATA__ section below and dumps the contents in a hash keyed # by filename (the @@ part between each resource). my %packages; sub _readPackages { return \%packages if %packages; my $cur_file; my $cur_value; my $commit = sub { return unless $cur_file; $packages{$cur_file} = ($cur_value =~ s/ *$//r); $cur_value = ''; }; while(my $line = ) { next if $line =~ /^\s*#/ and $cur_file !~ /sample-rc/; chomp $line; my ($fname) = ($line =~ /^@@ *([^ ]+)$/); if ($fname) { $commit->(); $cur_file = $fname; } else { $cur_value .= "$line\n"; } } $commit->(); return \%packages; } sub _throw { my $msg = shift; die (make_exception('Setup', $msg)); } sub _installSystemPackages { my $os = shift; my $vendor = $os->vendorID; my $osVersion = $os->vendorVersion; print colorize(<> 8 == 0) { say colorize (" b[*] b[g[Looks like things went OK!]"); } else { say colorize (" r[b[*] Ran into an error with the installer!"); } } else { say colorize (" r[b[*] Whoa, I'm not familiar with your distribution, skipping"); } } sub _setupBaseConfiguration { my $baseDir = shift; if (-e "kdesrc-buildrc" || -e "$ENV{HOME}/.kdesrc-buildrc") { print colorize(</dev/null` || 4; - $sampleRc =~ s/%\{num_cpus}/$numCpus/; - $sampleRc =~ s/%\{base_dir}/$baseDir/; + $sampleRc =~ s/%\{num_cpus}/$numCpus/g; + $sampleRc =~ s/%\{base_dir}/$baseDir/g; open my $sampleFh, '>', "$ENV{HOME}/.kdesrc-buildrc" or _throw("Couldn't open new ~/.kdesrc-buildrc: $!"); print $sampleFh $sampleRc or _throw("Couldn't write to ~/.kdesrc-buildrc: $!"); close $sampleFh or _throw("Error closing ~/.kdesrc-buildrc: $!"); } } sub _bashrcIsSetup { return 1; } sub _setupBashrcFile { if (_bashrcIsSetup()) { say colorize(<bestDistroMatch(@supportedDistros); say colorize (" Using installer for b[$bestVendor]"); my $version = $os->vendorVersion(); my @cmd; for my $opt ("$bestVendor/$version", "$bestVendor/unknown") { my $key = "cmd/install/$opt"; next unless exists $pkgsRef->{$key}; @cmd = split(' ', $pkgsRef->{$key}); last; } _throw("No installer for $bestVendor!") unless @cmd; # If not running as root already, add sudo unshift @cmd, 'sudo' if $> != 0; return @cmd; } sub _findBestVendorPackageList { my $os = shift; # Debian handles Ubuntu also my @supportedDistros = map { s{^pkg/([^/]+)/.*$}{$1}; $_ } grep { /^pkg\// } keys %{_readPackages()}; my $bestVendor = $os->bestDistroMatch(@supportedDistros); my $version = $os->vendorVersion(); say colorize (" Installing packages for b[$bestVendor]/b[$version]"); return _packagesForVendor($bestVendor, $version); } sub _packagesForVendor { my ($vendor, $version) = @_; my $packagesRef = _readPackages(); foreach my $opt ("pkg/$vendor/$version", "pkg/$vendor/unknown") { next unless exists $packagesRef->{$opt}; my @packages = split(' ', $packagesRef->{$opt}); return @packages; } return; } 1; __DATA__ @@ pkg/debian/unknown libyaml-libyaml-perl libio-socket-ssl-perl libjson-xs-perl git shared-mime-info cmake build-essential flex bison gperf libssl-dev intltool liburi-perl gettext @@ pkg/opensuse/unknown perl perl-IO-Socket-SSL perl-JSON perl-YAML-LibYAML git shared-mime-info make cmake libqt5-qtbase-common-devel libopenssl-devel intltool polkit-devel libqt5-qtbase-devel libqt5-qtimageformats-devel libqt5-qtmultimedia-devel libqt5-qtdeclarative-devel libqt5-qtx11extras-devel libqt5-qtxmlpatterns-devel libqt5-qtsvg-devel gperf gettext-runtime gettext-tools libxml2-devel libxml2-tools libxslt-devel docbook-xsl-stylesheets docbook_4 perl-URI libXrender-devel xcb-util-keysyms-devel flex bison libQt5Core-private-headers-devel libudev-devel libQt5WebKit5-devel libQt5WebKitWidgets-devel libQt5DesignerComponents5 libqt5-qttools-devel libSM-devel libattr-devel libboost_headers1_66_0-devel libQt5QuickControls2-devel libqt5-qtscript-devel wayland-devel libqt5-qtbase-private-headers-devel lmdb-devel libpng16-compat-devel giflib-devel ModemManager-devel # This pulls in so many other packages! :( NetworkManager-devel qrencode-devel @@ pkg/fedora/unknown perl-IO-Socket-SSL perl-JSON-PP perl-YAML-LibYAML perl-IPC-Cmd git shared-mime-info make cmake openssl-devel intltool gcc gcc-c++ python mesa-libGL-devel dbus-devel gstreamer1-devel polkit-devel gperf gettext gettext-devel libxml2-devel libxml2 libxslt-devel docbook-style-xsl docbook-utils perl-URI libXrender-devel xcb-util-keysyms-devel flex bison libSM-devel libattr-devel boost wayland-devel lmdb-devel libpng-devel giflib-devel ModemManager-devel # This pulls in so many other packages! :( NetworkManager-libnm-devel qrencode-devel @@ pkg/gentoo/unknown dev-util/cmake dev-lang/perl @@ pkg/arch/unknown perl-json perl-yaml-libyaml perl-io-socket-ssl cmake gcc make qt5-base @@ cmd/install/debian/unknown apt-get -q -y --no-install-recommends install @@ cmd/install/opensuse/unknown zypper install -y --no-recommends @@ cmd/install/arch/unknown pacman -Sy --noconfirm @@ cmd/install/fedora/unknown dnf -y install @@ sample-rc # This file controls options to apply when configuring/building modules, and # controls which modules are built in the first place. # List of all options: https://go.kde.org/u/ksboptions global - branch-group kf5-qt5 - kdedir ~/kde-5 # Where to install KF5-based software - # Uncomment this and edit value to choose a different Qt5 -# qtdir /usr # Where to find Qt5 + # Paths + + kdedir ~/kde/usr # Where to install KF5-based software + qtdir ~/kde/qt5 # Where to find Qt5 + + source-dir ~/kde/src # Where sources are downloaded + build-dir ~/kde/build # Where the source build is run + + ignore-kde-structure true # Use flat structure # Will pull in KDE-based dependencies only, to save you the trouble of # listing them all below include-dependencies true - source-dir ~/kde/src - build-dir ~/kde/build - - ignore-kde-structure true - cmake-options -DCMAKE_BUILD_TYPE=RelWithDebInfo make-options -j%{num_cpus} end global # With base options set, the remainder of the file is used to define modules to build, in the # desired order, and set any module-specific options. # # Modules may be grouped into sets, and this is the normal practice. # # You can include other files inline using the "include" command. We do this here # to include files which are updated with kdesrc-build. +# Qt and some Qt-using middleware libraries +include %{base_dir}/qt5-build-include +include %{base_dir}/custom-qt5-libs-build-include + +# KF5 and Plasma :) include %{base_dir}/kf5-qt5-build-include # To change options for modules that have already been defined, use an # 'options' block options kcoreaddons make-options -j4 end options - diff --git a/modules/ksb/Module.pm b/modules/ksb/Module.pm index b013728..7aa8896 100644 --- a/modules/ksb/Module.pm +++ b/modules/ksb/Module.pm @@ -1,972 +1,976 @@ package ksb::Module 0.20; # Class: Module # # Represents a source code module of some sort, which can be updated, built, # and installed. Includes a stringifying overload and can be sorted amongst # other ksb::Modules. use 5.014; use warnings; no if $] >= 5.018, 'warnings', 'experimental::smartmatch'; use parent qw(ksb::OptionsBase); use ksb::IPC; use ksb::Debug; use ksb::Util; use ksb::l10nSystem; use ksb::Updater::Svn; use ksb::Updater::Git; use ksb::Updater::Bzr; use ksb::Updater::KDEProject; use ksb::Updater::KDEProjectMetadata; +use ksb::Updater::Qt5; use ksb::BuildException 0.20; use ksb::BuildSystem 0.30; use ksb::BuildSystem::Autotools; use ksb::BuildSystem::QMake; use ksb::BuildSystem::Qt4; +use ksb::BuildSystem::Qt5; use ksb::BuildSystem::KDE4; use ksb::BuildSystem::CMakeBootstrap; use ksb::ModuleSet::Null; use Storable 'dclone'; use Carp 'confess'; use Scalar::Util 'blessed'; use overload '""' => 'toString', # Add stringify operator. '<=>' => 'compare', ; sub new { my ($class, $ctx, $name) = @_; croak_internal ("Empty ksb::Module constructed") unless $name; my $self = ksb::OptionsBase::new($class); # If building a BuildContext instead of a ksb::Module, then the context # can't have been setup yet... my $contextClass = 'ksb::BuildContext'; if ($class ne $contextClass && (!blessed($ctx) || !$ctx->isa($contextClass))) { croak_internal ("Invalid context $ctx"); } # Clone the passed-in phases so we can be different. my $phases = dclone($ctx->phases()) if $ctx; my %newOptions = ( name => $name, scm_obj => undef, build_obj => undef, phases => $phases, context => $ctx, 'module-set' => undef, ); @{$self}{keys %newOptions} = values %newOptions; return $self; } sub phases { my $self = shift; return $self->{phases}; } sub moduleSet { my ($self) = @_; $self->{'module-set'} //= ksb::ModuleSet::Null->new(); return $self->{'module-set'}; } sub setModuleSet { my ($self, $moduleSet) = @_; assert_isa($moduleSet, 'ksb::ModuleSet'); $self->{'module-set'} = $moduleSet; } # Subroutine to retrieve a subdirectory path with tilde-expansion and # relative path handling. # The parameter is the option key (e.g. build-dir or log-dir) to read and # interpret. sub getSubdirPath { my ($self, $subdirOption) = @_; my $dir = $self->getOption($subdirOption); # If build-dir starts with a slash, it is an absolute path. return $dir if $dir =~ /^\//; # Make sure we got a valid option result. if (!$dir) { confess ("Reading option for $subdirOption gave empty \$dir!"); } # If it starts with a tilde, expand it out. if ($dir =~ /^~/) { $dir =~ s/^~/$ENV{'HOME'}/; } else { # Relative directory, tack it on to the end of $kdesrcdir. my $kdesrcdir = $self->getOption('source-dir'); $dir = "$kdesrcdir/$dir"; } return $dir; } # Method: getInstallPathComponents # # Returns the directory that a module should be installed in. # # NOTE: The return value is a hash. The key 'module' will return the final # module name, the key 'path' will return the full path to the module. The # key 'fullpath' will return their concatenation. # # For example, with $module == 'KDE/kdelibs', and no change in the dest-dir # option, you'd get something like: # # > { # > 'path' => '/home/user/kdesrc/KDE', # > 'module' => 'kdelibs', # > 'fullpath' => '/home/user/kdesrc/KDE/kdelibs' # > } # # If dest-dir were changed to e.g. extragear-multimedia, you'd get: # # > { # > 'path' => '/home/user/kdesrc', # > 'module' => 'extragear-multimedia', # > 'fullpath' => '/home/user/kdesrc/extragear-multimedia' # > } # # Parameters: # pathType - Either 'source' or 'build'. # # Returns: # hash (Not a hashref; See description). sub getInstallPathComponents { my $module = assert_isa(shift, 'ksb::Module'); my $type = shift; my $destdir = $module->destDir(); my $srcbase = $module->getSourceDir(); $srcbase = $module->getSubdirPath('build-dir') if $type eq 'build'; my $combined = "$srcbase/$destdir"; # Remove dup // $combined =~ s/\/+/\//; my @parts = split(/\//, $combined); my %result = (); $result{'module'} = pop @parts; $result{'path'} = join('/', @parts); $result{'fullpath'} = "$result{path}/$result{module}"; my $compatDestDir = $module->destDir($module->name()); my $fullCompatPath = "$srcbase/$compatDestDir"; # We used to have code here to migrate very old directory layouts. It was # removed as of about 2013-09-29. return %result; } # Do note that this returns the *base* path to the source directory, # without the module name or kde_projects stuff appended. If you want that # use subroutine fullpath(). sub getSourceDir { my $self = shift; return $self->getSubdirPath('source-dir'); } sub name { my $self = shift; return $self->{name}; } sub scm { my $self = shift; return $self->{scm_obj} if $self->{scm_obj}; # Look for specific setting of repository and svn-server. If both is # set it's a bug, if one is set, that's the type (because the user says # so...). Don't use getOption($key) as it will try to fallback to # global options. my $svn_status = $self->getOption('svn-server', 'module'); my $repository = $self->getOption('repository', 'module') // ''; my $rcfile = $self->buildContext()->rcFile(); if ($svn_status && $repository) { error (<{scm_obj} = ksb::Updater::Bzr->new($self); } # If it needs a repo it's git. Everything else is svn for now. $self->{scm_obj} //= $repository ? ksb::Updater::Git->new($self) : ksb::Updater::Svn->new($self); return $self->{scm_obj}; } sub setScmType { my ($self, $scmType) = @_; my $newType; given($scmType) { when('git') { $newType = ksb::Updater::Git->new($self); } when('proj') { $newType = ksb::Updater::KDEProject->new($self); } when('metadata') { $newType = ksb::Updater::KDEProjectMetadata->new($self); } when('l10n') { $newType = ksb::l10nSystem->new($self); } when('svn') { $newType = ksb::Updater::Svn->new($self); } when('bzr') { $newType = ksb::Updater::Bzr->new($self); } + when('qt5') { $newType = ksb::Updater::Qt5->new($self); } default { $newType = undef; } } $self->{scm_obj} = $newType; } # Returns a string describing the scm platform of the given module. # Return value: 'git' or 'svn' at this point, as appropriate. sub scmType { my $self = shift; return $self->scm()->name(); } sub currentScmRevision { my $self = shift; return $self->scm()->currentRevisionInternal(); } # Returns a new build system object, given the appropriate name. # This is a sub-optimal way to fix the problem of allowing users to override # the detected build system (we could instead use introspection to figure out # available build systems at runtime). However, KISS... sub buildSystemFromName { my ($self, $name) = @_; my %buildSystemClasses = ( 'generic' => 'ksb::BuildSystem', 'qmake' => 'ksb::BuildSystem::QMake', 'cmake-bootstrap' => 'ksb::BuildSystem::CMakeBootstrap', 'kde' => 'ksb::BuildSystem::KDE4', 'qt' => 'ksb::BuildSystem::Qt4', + 'qt5' => 'ksb::BuildSystem::Qt5', 'autotools' => 'ksb::BuildSystem::Autotools', ); my $class = $buildSystemClasses{lc $name} // undef; return $class->new($self) if ($class); # Past here, no class found croak_runtime("Invalid build system $name requested"); } sub buildSystem { my $self = shift; if ($self->{build_obj} && $self->{build_obj}->name() ne 'generic') { return $self->{build_obj}; } if (my $userBuildSystem = $self->getOption('override-build-system')) { $self->{build_obj} = $self->buildSystemFromName($userBuildSystem); return $self->{build_obj}; } # If not set, let's guess. my $buildType; my $sourceDir = $self->fullpath('source'); if (($self->getOption('repository') =~ /gitorious\.org\/qt\//) || ($self->getOption('repository') =~ /^kde:qt$/) || (-e "$sourceDir/bin/syncqt")) { $buildType = ksb::BuildSystem::Qt4->new($self); } # This test must come before the KDE buildsystem's as cmake's own # bootstrap system also has CMakeLists.txt if (!$buildType && (-e "$sourceDir/CMakeLists.txt") && (-e "$sourceDir/bootstrap")) { $buildType = ksb::BuildSystem::CMakeBootstrap->new($self); } if (!$buildType && (-e "$sourceDir/CMakeLists.txt" || $self->getOption('#xml-full-path'))) { $buildType = ksb::BuildSystem::KDE4->new($self); } # We have to assign to an array to force glob to return all results, # otherwise it acts like a non-reentrant generator whose output depends on # how many times it's been called... if (!$buildType && (my @files = glob ("$sourceDir/*.pro"))) { $buildType = ksb::BuildSystem::QMake->new($self); } # 'configure' is a popular fall-back option even for other build # systems so ensure we check last for autotools. if (!$buildType && (-e "$sourceDir/configure" || -e "$sourceDir/autogen.sh")) { $buildType = ksb::BuildSystem::Autotools->new($self); } # Don't just assume the build system is KDE-based... $buildType //= ksb::BuildSystem->new($self); $self->{build_obj} = $buildType; return $self->{build_obj}; } # Sets the build system **object**, although you can find the build system # type afterwards (see buildSystemType). sub setBuildSystem { my ($self, $obj) = @_; assert_isa($obj, 'ksb::BuildSystem'); $self->{build_obj} = $obj; } # Current possible build system types: # KDE (i.e. cmake), Qt, l10n (KDE language buildsystem), autotools (either # configure or autogen.sh). A final possibility is 'pendingSource' which # simply means that we don't know yet. # # If the build system type is not set ('pendingSource' counts as being # set!) when this function is called then it will be autodetected if # possible, but note that not all possible types will be detected this way. # If in doubt use setBuildSystemType sub buildSystemType { my $self = shift; return $self->buildSystem()->name(); } # Subroutine to build this module. # Returns boolean false on failure, boolean true on success. sub build { my $self = assert_isa(shift, 'ksb::Module'); my $moduleName = $self->name(); my %pathinfo = $self->getInstallPathComponents('build'); my $builddir = $pathinfo{'fullpath'}; my $buildSystem = $self->buildSystem(); if ($buildSystem->name() eq 'generic' && !pretending()) { error ("\tr[b[$self] does not seem to have a build system to use."); return 0; } # Ensure we're in a known directory before we start; some options remove # the old build directory that a previous module might have been using. super_mkdir($pathinfo{'path'}); p_chdir($pathinfo{'path'}); return 0 if !$self->setupBuildSystem(); return 1 if $self->getOption('build-system-only'); if (!$buildSystem->buildInternal()) { return 0; } $self->setPersistentOption('last-build-rev', $self->currentScmRevision()); # TODO: This should be a simple phase to run. if ($self->getOption('run-tests')) { $self->buildSystem()->runTestsuite(); } # TODO: Likewise this should be a phase to run. if ($self->getOption('install-after-build')) { return 0 if !$self->install(); } else { info ("\tSkipping install for y[$self]"); } return 1; } # Subroutine to setup the build system in a directory. # Returns boolean true on success, boolean false (0) on failure. sub setupBuildSystem { my $self = assert_isa(shift, 'ksb::Module'); my $moduleName = $self->name(); my $buildSystem = $self->buildSystem(); if ($buildSystem->name() eq 'generic' && !pretending()) { croak_internal('Build system determination still pending when build attempted.'); } my $refreshReason = $buildSystem->needsRefreshed(); if ($refreshReason ne "") { # The build system needs created, either because it doesn't exist, or # because the user has asked that it be completely rebuilt. info ("\tPreparing build system for y[$self]."); # Check to see if we're actually supposed to go through the # cleaning process. if (!$self->getOption('#cancel-clean') && !$buildSystem->cleanBuildSystem()) { warning ("\tUnable to clean r[$self]!"); return 0; } } if (!$buildSystem->createBuildSystem()) { error ("\tError creating r[$self]'s build system!"); return 0; } # Now we're in the checkout directory # So, switch to the build dir. # builddir is automatically set to the right value for qt p_chdir ($self->fullpath('build')); if (!$buildSystem->configureInternal()) { error ("\tUnable to configure r[$self] with " . $self->buildSystemType()); # Add undocumented ".refresh-me" file to build directory to flag # for --refresh-build for this module on next run. See also the # "needsRefreshed" subroutine. if (open my $fh, '>', '.refresh-me') { say $fh "# Build directory will be re-generated next kdesrc-build run"; say $fh "# due to failing to complete configuration on the last run"; close $fh; }; return 0; } return 1; } # Responsible for installing the module (no update, build, etc.) # Return value: Boolean flag indicating whether module installed successfully or # not. # Exceptions may be thrown for abnormal conditions (e.g. no build dir exists) sub install { my $self = assert_isa(shift, 'ksb::Module'); my $builddir = $self->fullpath('build'); my $buildSysFile = $self->buildSystem()->configuredModuleFileName(); if (!pretending() && ! -e "$builddir/$buildSysFile") { warning ("\tThe build system doesn't exist for r[$self]."); warning ("\tTherefore, we can't install it. y[:-(]."); return 0; } $self->setupEnvironment(); my @makeInstallOpts = split(' ', $self->getOption('make-install-prefix')); # We can optionally uninstall prior to installing # to weed out old unused files. if ($self->getOption('use-clean-install') && $self->getPersistentOption('last-install-rev')) { if (!$self->buildSystem()->uninstallInternal(@makeInstallOpts)) { warning ("\tUnable to uninstall r[$self] before installing the new build."); warning ("\tContinuing anyways..."); } else { $self->unsetPersistentOption('last-install-rev'); } } if (!$self->buildSystem()->installInternal(@makeInstallOpts)) { error ("\tUnable to install r[$self]!"); $self->buildContext()->markModulePhaseFailed('install', $self); return 0; } if (pretending()) { pretend ("\tWould have installed g[$self]"); return 1; } # Past this point we know we've successfully installed, for real. $self->setPersistentOption('last-install-rev', $self->currentScmRevision()); my $remove_setting = $self->getOption('remove-after-install'); # Possibly remove the srcdir and builddir after install for users with # a little bit of HD space. if($remove_setting eq 'all') { # Remove srcdir my $srcdir = $self->fullpath('source'); note ("\tRemoving b[r[$self source]."); safe_rmtree($srcdir); } if($remove_setting eq 'builddir' || $remove_setting eq 'all') { # Remove builddir note ("\tRemoving b[r[$self build directory]."); safe_rmtree($builddir); # We're likely already in the builddir, so chdir back to the root p_chdir('/'); } return 1; } # Handles uninstalling this module (or its sub-directories as given by the checkout-only # option). # # Returns boolean false on failure, boolean true otherwise. sub uninstall { my $self = assert_isa(shift, 'ksb::Module'); my $builddir = $self->fullpath('build'); my $buildSysFile = $self->buildSystem()->configuredModuleFileName(); if (!pretending() && ! -e "$builddir/$buildSysFile") { warning ("\tThe build system doesn't exist for r[$self]."); warning ("\tTherefore, we can't uninstall it."); return 0; } $self->setupEnvironment(); my @makeInstallOpts = split(' ', $self->getOption('make-install-prefix')); if (!$self->buildSystem()->uninstallInternal(@makeInstallOpts)) { error ("\tUnable to uninstall r[$self]!"); $self->buildContext()->markModulePhaseFailed('install', $self); return 0; } if (pretending()) { pretend ("\tWould have uninstalled g[$self]"); return 1; } $self->unsetPersistentOption('last-install-rev'); return 1; } sub buildContext { my $self = shift; return $self->{context}; } # Integrates 'set-env' option to the build context environment sub applyUserEnvironment { my $self = assert_isa(shift, 'ksb::Module'); my $ctx = $self->buildContext(); # Let's see if the user has set env vars to be set. # Note the global set-env must be checked separately anyways, so # we limit inheritance when searching. my $env_hash_ref = $self->getOption('set-env', 'module'); while (my ($key, $value) = each %{$env_hash_ref}) { $ctx->queueEnvironmentVariable($key, $value); } } # Establishes proper build environment in the build context. Should be run # before forking off commands for e.g. updates, builds, installs, etc. sub setupEnvironment { my $self = assert_isa(shift, 'ksb::Module'); my $ctx = $self->buildContext(); my $kdedir = $self->getOption('kdedir'); my $prefix = $self->installationPath(); # Add global set-envs and context $self->buildContext()->applyUserEnvironment(); # Avoid moving /usr up in env vars if ($kdedir ne '/usr') { my @pkg_config_dirs = ("$kdedir/lib/pkgconfig"); $ctx->prependEnvironmentValue('PKG_CONFIG_PATH', @pkg_config_dirs); my @ld_dirs = ("$kdedir/lib", $self->getOption('libpath')); $ctx->prependEnvironmentValue('LD_LIBRARY_PATH', @ld_dirs); my @path = ("$kdedir/bin", $self->getOption('binpath')); $ctx->prependEnvironmentValue('PATH', @path); } # Build system's environment injection my $buildSystem = $self->buildSystem(); $buildSystem->prepareModuleBuildEnvironment($ctx, $self, $prefix); # Read in user environment defines $self->applyUserEnvironment() unless $self == $ctx; } # Returns the path to the log directory used during this run for this # ksb::Module, based on an autogenerated unique id. The id doesn't change # once generated within a single run of the script. sub getLogDir { my ($self) = @_; return $self->buildContext()->getLogDirFor($self); } # Returns a full path that can be open()'d to write a log # file, based on the given basename (with extension). # Updates the 'latest' symlink as well, unlike getLogDir # Use when you know you're going to create a new log sub getLogPath { my ($self, $path) = @_; return $self->buildContext()->getLogPathFor($self, $path); } sub toString { my $self = shift; return $self->name(); } sub compare { my ($self, $other) = @_; return $self->name() cmp $other->name(); } sub update { my ($self, $ipc, $ctx) = @_; my $moduleName = $self->name(); my $module_src_dir = $self->getSourceDir(); my $kdesrc = $ctx->getSourceDir(); if ($kdesrc ne $module_src_dir) { # This module has a different source directory, ensure it exists. if (!super_mkdir($module_src_dir)) { error ("Unable to create separate source directory for r[$self]: $module_src_dir"); $ipc->sendIPCMessage(ksb::IPC::MODULE_FAILURE, $moduleName); next; } } my $fullpath = $self->fullpath('source'); my $count; my $returnValue; eval { $count = $self->scm()->updateInternal($ipc) }; if ($@) { my $reason = ksb::IPC::MODULE_FAILURE; if (had_an_exception()) { if ($@->{'exception_type'} eq 'ConflictPresent') { $reason = ksb::IPC::MODULE_CONFLICT; } else { $ctx->markModulePhaseFailed('build', $self); } $@ = $@->{'message'}; } error ("Error updating r[$self], removing from list of packages to build."); error (" > y[$@]"); $ipc->sendIPCMessage($reason, $moduleName); $self->phases()->filterOutPhase('build'); $returnValue = 0; } else { my $message; if (not defined $count) { $message = ksb::Debug::colorize ("b[y[Unknown changes]."); $ipc->notifyUpdateSuccess($moduleName, $message); } elsif ($count) { $message = "1 file affected." if $count == 1; $message = "$count files affected." if $count != 1; $ipc->notifyUpdateSuccess($moduleName, $message); } else { $message = "0 files affected."; my $refreshReason = $self->buildSystem()->needsRefreshed(); $ipc->sendIPCMessage(ksb::IPC::MODULE_UPTODATE, "$moduleName,$refreshReason"); } # We doing e.g. --src-only, the build phase that normally outputs # number of files updated doesn't get run, so manually mention it # here. if (!$ipc->supportsConcurrency()) { info ("\t$self update complete, $message"); } $returnValue = 1; } info (""); # Print empty line. return $returnValue; } # OVERRIDE # # This calls OptionsBase::setOption and performs any Module-specific # handling. sub setOption { my ($self, %options) = @_; # Ensure we don't accidentally get fed module-set options for (qw(git-repository-base use-modules ignore-modules)) { if (exists $options{$_}) { error (" r[b[*] module b[$self] should be declared as module-set to use b[$_]"); die ksb::BuildException::Config->new($_, "Option $_ can only be used in module-set"); }; } # Special case handling. if (exists $options{'filter-out-phases'}) { for my $phase (split(' ', $options{'filter-out-phases'})) { $self->phases()->filterOutPhase($phase); } delete $options{'filter-out-phases'}; } $self->SUPER::setOption(%options); } # OVERRIDE # # This subroutine returns an option value for a given module. Some globals # can't be overridden by a module's choice (but see 2nd parameter below). # If so, the module's choice will be ignored, and a warning will be issued. # # Option names are case-sensitive! # # Some options (e.g. cmake-options, configure-flags) have the global value # and then the module's own value appended together. To get the actual # module setting you must use the level limit parameter set to 'module'. # # Likewise, some qt module options do not obey the previous proviso since # Qt options are not likely to agree nicely with generic KDE buildsystem # options. # # 1st parameter: Name of option # 2nd parameter: Level limit (optional). If not present, then the value # 'allow-inherit' is used. Options: # - allow-inherit: Module value is used if present (with exceptions), # otherwise global is used. # - module: Only module value is used (if you want only global then use the # buildContext) NOTE: This overrides global "sticky" options as well! sub getOption { my ($self, $key, $levelLimit) = @_; my $ctx = $self->buildContext(); $levelLimit //= 'allow-inherit'; # Some global options would probably make no sense applied to Qt. my @qtCopyOverrides = qw(branch configure-flags tag cxxflags); if (list_has(\@qtCopyOverrides, $key) && $self->buildSystemType() eq 'Qt') { $levelLimit = 'module'; } assert_in($levelLimit, [qw(allow-inherit module)]); # If module-only, check that first. return $self->{options}{$key} if $levelLimit eq 'module'; my $ctxValue = $ctx->getOption($key); # we'll use this a lot from here # Some global options always override module options. return $ctxValue if $ctx->hasStickyOption($key); # Some options append to the global (e.g. conf flags) my @confFlags = qw(cmake-options configure-flags cxxflags); if (list_has(\@confFlags, $key) && $ctxValue) { return trimmed("$ctxValue " . ($self->{options}{$key} || '')); } # Everything else overrides the global option, unless it's simply not # set at all. return $self->{options}{$key} // $ctxValue; } # Gets persistent options set for this module. First parameter is the name # of the option to lookup. Undef is returned if the option is not set, # although even if the option is set, the value returned might be empty. # Note that ksb::BuildContext also has this function, with a slightly # different signature, which OVERRIDEs this function since Perl does not # have parameter-based method overloading. sub getPersistentOption { my ($self, $key) = @_; return $self->buildContext()->getPersistentOption($self->name(), $key); } # Sets a persistent option (i.e. survives between processes) for this module. # First parameter is the name of the persistent option. # Second parameter is its actual value. # See the warning for getPersistentOption above, it also applies for this # method vs. ksb::BuildContext::setPersistentOption sub setPersistentOption { my ($self, $key, $value) = @_; return $self->buildContext()->setPersistentOption($self->name(), $key, $value); } # Unsets a persistent option for this module. # Only parameter is the name of the option to unset. sub unsetPersistentOption { my ($self, $key) = @_; $self->buildContext()->unsetPersistentOption($self->name(), $key); } # Returns the path to the desired directory type (source or build), # including the module destination directory itself. sub fullpath { my ($self, $type) = @_; assert_in($type, [qw/build source/]); my %pathinfo = $self->getInstallPathComponents($type); return $pathinfo{'fullpath'}; } # Returns the "full kde-projects path" for the module. As should be obvious by # the description, this only works for modules with an scm type that is a # Updater::KDEProject (or its subclasses). sub fullProjectPath { my $self = shift; my $path = $self->getOption('#xml-full-path', 'module') || croak_internal("Tried to ask for full path of a module $self that doesn't have one!"); return $path; } # Returns true if this module is (or was derived from) a kde-projects module. sub isKDEProject { my $self = shift; return $self->hasOption('#xml-full-path'); } # Subroutine to return the name of the destination directory for the # checkout and build routines. Based on the dest-dir option. The return # value will be relative to the src/build dir. The user may use the # '$MODULE' or '${MODULE}' sequences, which will be replaced by the name of # the module in question. # # The first parameter is optional, but if provided will be used as the base # path to replace $MODULE entries in dest-dir. sub destDir { my $self = assert_isa(shift, 'ksb::Module'); my $destDir = $self->getOption('dest-dir'); my $basePath = ""; if ($self->getOption('ignore-kde-structure')) { $basePath = $self->name(); } else { $basePath = shift // $self->getOption('#xml-full-path'); $basePath ||= $self->name(); # Default if not provided in repo-metadata } $destDir =~ s/(\$\{MODULE})|(\$MODULE\b)/$basePath/g; return $destDir; } # Subroutine to return the installation path of a given module (the value # that is passed to the CMAKE_INSTALL_PREFIX CMake option). # It is based on the "prefix" and, if it is not set, the "kdedir" option. # The user may use '$MODULE' or '${MODULE}' in the "prefix" option to have # them replaced by the name of the module in question. sub installationPath { my $self = assert_isa(shift, 'ksb::Module'); my $path = $self->getOption('prefix'); if (!$path) { return $self->getOption('kdedir'); } my $moduleName = $self->name(); $path =~ s/(\$\{MODULE})|(\$MODULE\b)/$moduleName/g; return $path; } 1; diff --git a/modules/ksb/ModuleSet/Qt.pm b/modules/ksb/ModuleSet/Qt.pm new file mode 100644 index 0000000..5dfa90d --- /dev/null +++ b/modules/ksb/ModuleSet/Qt.pm @@ -0,0 +1,63 @@ +package ksb::ModuleSet::Qt 0.10; + +# Class: ModuleSet::Qt +# +# This represents a collection of Qt5 source code modules that are collectively +# kept up to date by Qt's init-repository script. This module set is +# essentially uses to make sure that generated ksb::Modules use proper scm() +# and buildSystems() +# +# Use of this module-set is controlled by the 'repository' option being set to +# the magic value 'qt-projects', just as 'kde-projects' is used for KDE. + +use strict; +use warnings; +use 5.014; +use parent qw(ksb::ModuleSet); + +use ksb::BuildContext; +use ksb::BuildException; +use ksb::BuildSystem::Qt5; +use ksb::Debug; +use ksb::Module; +use ksb::Util; + +sub _makeQt5Module +{ + my $self = assert_isa(shift, __PACKAGE__); + my $ctx = assert_isa(shift, 'ksb::BuildContext'); + + my $newModule = ksb::Module->new($ctx, 'qt5'); + + $self->_initializeNewModule($newModule); + + # Repo URL to the Qt5 "supermodule" that contains the documented + # init-repository script. + # See https://wiki.qt.io/Building_Qt_5_from_Git + $newModule->setOption('repository', 'https://code.qt.io/qt/qt5.git'); + $newModule->setScmType('qt5'); + $newModule->setBuildSystem(ksb::BuildSystem::Qt5->new($newModule)); + + # Convert the use-modules/ignore-modules entries into a form appropriate + # for init-repository's module-subset option. + my @modEntries = ($self->modulesToFind(), map { "-$_" } $self->modulesToIgnore()); + $newModule->setOption('use-qt5-modules', join(' ', @modEntries)); + + return $newModule; +} + +# This function should be called after options are read and build metadata is +# available in order to convert this module set to a list of ksb::Module. +# +# In our case, we will return ONLY ONE MODULE. That module will handle "sub +# modules" via the init-repository script so from kdesrc-build's perspective it +# is handled as a single unit. +# +# OVERRIDE from super class +sub convertToModules +{ + my ($self, $ctx) = @_; + return $self->_makeQt5Module($ctx); +} + +1; diff --git a/modules/ksb/Updater/Git.pm b/modules/ksb/Updater/Git.pm index 7d8fb67..97d3816 100644 --- a/modules/ksb/Updater/Git.pm +++ b/modules/ksb/Updater/Git.pm @@ -1,834 +1,844 @@ 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::BuildException; use ksb::Debug; use ksb::IPC::Null; use ksb::Util; use File::Basename; # basename use File::Spec; # tmpdir use POSIX qw(strftime); use List::Util qw(first); use IPC::Cmd qw(run_forked); 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 = $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 =~ s,^refs/tags/,,; # git-clone -b doesn't like refs/tags/ 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 +# Checks that the required source dir is either not already present or is empty. +# Throws an exception if that's not true. +sub _verifySafeToCloneIntoSourceDir { - 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('#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 (<module(); + my $srcdir = $module->fullpath('source'); + + if (-d "$srcdir/.git") { + # Note that this function will throw an exception on failure. + return $self->updateExistingClone(); + } + else { + _verifySafeToCloneIntoSourceDir($module, $srcdir); my $git_repo = $module->getOption('repository'); if (!$git_repo) { croak_internal("Unable to checkout $module, you must specify a repository to use."); } if (!$self->_verifyRefPresent($module, $git_repo)) { croak_runtime( $self->_moduleIsNeeded() ? "$module build was requested, but it has no source code at the requested git branch" : "The required git branch does not exist at the source repository" ); } $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.https://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.https://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; } # Remove old kdesrc-build installed aliases (kde: -> git://anongit.kde.org/) $configOutput = qx'git config --global --get url.git://anongit.kde.org/.insteadOf kde:'; if ($configOutput =~ /^kde:\s*$/) { whisper ("\tRemoving outdated kde: alias"); my $result = safe_system( qw(git config --global --unset-all url.git://anongit.kde.org/.insteadOf kde:) ) >> 8; return 0 if $result != 0; } return 1; } 1; diff --git a/modules/ksb/Updater/Qt5.pm b/modules/ksb/Updater/Qt5.pm new file mode 100644 index 0000000..ef6df13 --- /dev/null +++ b/modules/ksb/Updater/Qt5.pm @@ -0,0 +1,106 @@ +package ksb::Updater::Qt5 0.10; + +# Handles updating Qt 5 source code. Requires git but uses Qt 5's dedicated +# 'init-repository' script to keep the source up to date and coherent. + +use strict; +use warnings; +use 5.014; + +use parent qw(ksb::Updater::Git); + +use ksb::BuildException; +use ksb::Debug; +use ksb::IPC::Null; +use ksb::Util; + +sub name +{ + return 'qt5'; +} + +# Handles calling init-repository to clone or update the appropriate Qt 5 +# submodules. +# +# Returns number of commits updated (or rather, will...) +sub _updateRepository +{ + my $self = assert_isa(shift, __PACKAGE__); + + my $module = $self->module(); + my $srcdir = $module->fullpath('source'); + + if (!pretending() && (! -e "$srcdir/init-repository" || ! -x _)) { + croak_runtime ("The Qt 5 repository update script could not be found, or is not executable!"); + } + + p_chdir($srcdir); + + # See https://wiki.qt.io/Building_Qt_5_from_Git#Getting_the_source_code for + # why we skip web engine by default. As of 2019-01-12 it is only used for + # PIM or optionally within Plasma + my @modules = split(' ', $module->getOption('use-qt5-modules')); + push @modules, qw(default -qtwebengine) + unless @modules; + + my $subset_arg = join(',', @modules); + + # -f forces a re-update if necessary + my @command = ("$srcdir/init-repository", '-f', "--module-subset=$subset_arg"); + note ("\tUsing Qt 5 modules: ", join(', ', @modules)); + + if (0 != log_command($module, 'init-repository', \@command)) { + croak_runtime ("Couldn't update Qt 5 repository submodules!"); + } + + return 1; # TODO: Count commits +} + +# Updates an existing Qt5 super module checkout. +# Throws exceptions on failure, otherwise returns number of commits updated +# OVERRIDE from super class +sub updateExistingClone +{ + my $self = assert_isa(shift, __PACKAGE__); + + # Update init-repository and the shell of the super module itself. + my $count = $self->SUPER::updateExistingClone(); + + # updateRepository has init-repository work to update the source + return $count + $self->_updateRepository(); +} + +# 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. +# OVERRIDE from super class +sub updateCheckout +{ + my $self = assert_isa(shift, __PACKAGE__); + 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 { + $self->_verifySafeToCloneIntoSourceDir($module, $srcdir); + + $self->_clone($module->getOption('repository')); + + note ("\tQt update script is installed, downloading remainder of Qt"); + note ("\tb[y[THIS WILL TAKE SOME TIME]"); + + # With the supermodule cloned, we then need to call into + # init-repository to have it complete the checkout. + return $self->_updateRepository(); # num commits + } + + return 0; # num commits +} + +1; diff --git a/qt5-build-include b/qt5-build-include new file mode 100644 index 0000000..30c3871 --- /dev/null +++ b/qt5-build-include @@ -0,0 +1,20 @@ +module-set qt5-set + repository qt-projects + branch 5.11 # not the most recent but recent enough + + # init-repository supports catch-alls like "default" as well, in which case + # you will want to uncomment ignore-modules below to control which modules to + # leave out + use-modules qtbase qtdeclarative qtgraphicaleffects qtimageformats \ + qtmultimedia qtquickcontrols qtquickcontrols2 qtscript qtsensors qtsvg \ + qttools qtwayland qtwebchannel qtwebsockets qtwebview qtx11extras \ + qtxmlpatterns + + # ignore-modules qtwebengine + + # install path. This *MUST* match your qtdir setting in kdesrc-buildrc! + prefix ${qtdir} + + configure-flags -optimized-tools -reduce-relocations +# make-options -j7 +end module-set