You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1031 lines
34 KiB
1031 lines
34 KiB
package ksb::BuildContext 0.35; |
|
|
|
# Class: BuildContext |
|
# |
|
# This contains the information needed about the build context, e.g. list of |
|
# modules, what phases each module is in, the various options, etc. |
|
|
|
use 5.014; |
|
use warnings; |
|
no if $] >= 5.018, 'warnings', 'experimental::smartmatch'; |
|
|
|
use Carp 'confess'; |
|
use File::Basename; # dirname |
|
use IO::File; |
|
use POSIX qw(strftime); |
|
use Errno qw(:POSIX); |
|
use JSON::PP; |
|
|
|
# We derive from ksb::Module so that BuildContext acts like the 'global' |
|
# ksb::Module, with some extra functionality. |
|
# TODO: Derive from OptionsBase directly and remove getOption override |
|
use parent qw(ksb::Module); |
|
|
|
use ksb::Debug; |
|
use ksb::Util; |
|
use ksb::PhaseList; |
|
use ksb::Module; |
|
use ksb::Module::BranchGroupResolver; |
|
use ksb::Updater::KDEProjectMetadata 0.20; |
|
use ksb::Version qw(scriptVersion); |
|
use ksb::StatusView; |
|
use ksb::KDEXMLReader 0.20; |
|
|
|
use File::Temp qw(tempfile); |
|
use File::Spec; # rel2abs |
|
|
|
my @DefaultPhases = qw/update build install/; |
|
my @rcfiles = ("./kdesrc-buildrc", "$ENV{HOME}/.kdesrc-buildrc"); |
|
my $LOCKFILE_NAME = '.kdesrc-lock'; |
|
|
|
# The # will be replaced by the directory the rc File is stored in. |
|
my $PERSISTENT_FILE_NAME = '#/.kdesrc-build-data'; |
|
|
|
my $SCRIPT_VERSION = scriptVersion(); |
|
|
|
# Should be used for internal state that shouldn't be exposed as a hidden |
|
# cmdline option, or has other cmdline switches (e.g. debug/verbose handling). |
|
my %internalGlobalOptions = ( |
|
"async" => 1, |
|
"build-system-only" => "", |
|
"build-when-unchanged" => 1, # Safe default |
|
"checkout-only" => "", |
|
"colorful-output" => 1, # Use color by default. |
|
"debug-level" => ksb::Debug::INFO, |
|
"filter-out-phases" => '', |
|
"git-desired-protocol" => 'git', # protocol to grab from kde-projects |
|
"git-repository-base" => {}, # Base path template for use multiple times. |
|
"ignore-modules" => '', # See also: use-modules, kde-projects |
|
"include-dependencies" => 0, # Recursively include kde-projects module deps? |
|
"manual-build" => "", |
|
"manual-update" => "", |
|
"niceness" => "10", |
|
"no-svn" => "", # TODO: Rename to no-src |
|
"prefix" => "", # Override installation prefix. |
|
"pretend" => "", |
|
"reconfigure" => "", |
|
"refresh-build" => "", |
|
"repository" => '', # module's git repo |
|
"revision" => '', # Only useful for Subversion modules at cmdline |
|
"set-env" => { }, # Hash of environment vars to set |
|
"ssh-identity-file" => '', # If set, is passed to ssh-add. |
|
"use-modules" => "", |
|
); |
|
|
|
# Holds boolean flags that could be altered from cmdline. |
|
our %defaultGlobalFlags = ( |
|
"delete-my-patches" => 0, # Should only be set from cmdline |
|
"delete-my-settings" => 0, # Should only be set from cmdline |
|
"disable-agent-check" => 0, # If true we don't check on ssh-agent |
|
"disable-snapshots" => 1, # 2016-07-31 Temp. disabled until kde.org fixed to supply snapshots |
|
"ignore-kde-structure" => 0, # Whether to use kde dir structure like extragear/network |
|
"install-after-build" => 1, |
|
"install-environment-driver" => 1, # Setup ~/.config/kde-env-*.sh for login scripts |
|
"install-session-driver" => 0, # Above, + ~/.xsession |
|
"purge-old-logs" => 1, |
|
"run-tests" => 0, # 1 = make test, upload = make Experimental |
|
"stop-on-failure" => 0, |
|
"use-clean-install" => 0, |
|
"use-idle-io-priority" => 0, |
|
"use-stable-kde" => 0, |
|
); |
|
|
|
# Holds other cmdline-accessible options that aren't simply binary flags. |
|
our %defaultGlobalOptions = ( |
|
"binpath" => '', |
|
"branch" => "", |
|
"branch-group" => "", # Overrides branch, uses JSON data. |
|
"build-dir" => "build", |
|
"cmake-options" => "", |
|
"configure-flags" => "", |
|
"custom-build-command" => '', |
|
"cxxflags" => "-pipe", |
|
"dest-dir" => '${MODULE}', # single quotes used on purpose! |
|
"do-not-compile" => "", |
|
"http-proxy" => '', # Proxy server to use for HTTP. |
|
"kdedir" => "$ENV{HOME}/kde", |
|
"kde-languages" => "", |
|
"libpath" => "", |
|
"log-dir" => "log", |
|
"make-install-prefix" => "", # Some people need sudo |
|
"make-options" => "", |
|
"module-base-path" => "", # Used for tags and branches |
|
"override-build-system"=> "", |
|
"override-url" => "", |
|
"persistent-data-file" => "", |
|
"qtdir" => "", |
|
"remove-after-install" => "none", # { none, builddir, all } |
|
"source-dir" => "$ENV{HOME}/kdesrc", |
|
"svn-server" => "svn://anonsvn.kde.org/home/kde", |
|
"tag" => "", |
|
); |
|
|
|
sub new |
|
{ |
|
my ($class, @args) = @_; |
|
|
|
# It is very important to use the ksb::Module:: syntax instead of ksb::Module->, |
|
# otherwise you can't pass $class and have it used as the classname. |
|
my $self = ksb::Module::new($class, undef, 'global'); |
|
my %newOpts = ( |
|
modules => [], |
|
context => $self, # Fix link to buildContext (i.e. $self) |
|
build_options => { |
|
global => { |
|
%internalGlobalOptions, |
|
%defaultGlobalFlags, |
|
%defaultGlobalOptions, |
|
}, |
|
# Module options are stored under here as well, keyed by module->name() |
|
}, |
|
# This one replaces ksb::Module::{phases} |
|
phases => ksb::PhaseList->new(@DefaultPhases), |
|
errors => { |
|
# A map from module *names* (as in modules[] above) to the |
|
# phase name at which they failed. |
|
}, |
|
logPaths=> { |
|
# Holds a hash table of log path bases as expanded by |
|
# getSubdirPath (e.g. [source-dir]/log) to the actual log dir |
|
# *this run*, with the date and unique id added. You must still |
|
# add the module name to use. |
|
}, |
|
rcFiles => [@rcfiles], |
|
rcFile => undef, |
|
env => { }, |
|
ignore_list => [ ], # List of XML paths to ignore completely |
|
kde_projects_metadata => undef, # Enumeration of kde-projects |
|
kde_dependencies_metadata => undef, # Dependency resolution of kde-projects |
|
logical_module_resolver => undef, # For branch-group option |
|
status_view => ksb::StatusView->new(), |
|
projects_db => undef, # See getProjectDataReader |
|
); |
|
|
|
# Merge all new options into our self-hash. |
|
@{$self}{keys %newOpts} = values %newOpts; |
|
$self->{options} = $self->{build_options}{global}; |
|
|
|
assert_isa($self, 'ksb::Module'); |
|
assert_isa($self, 'ksb::BuildContext'); |
|
|
|
return $self; |
|
} |
|
|
|
# Gets the ksb::PhaseList for this context, and optionally sets it first to |
|
# the ksb::PhaseList passed in. |
|
sub phases |
|
{ |
|
my ($self, $phases) = @_; |
|
|
|
if ($phases) { |
|
confess("Invalid type, expected PhaseList") |
|
unless $phases->isa('ksb::PhaseList'); |
|
$self->{phases} = $phases; |
|
} |
|
return $self->{phases}; |
|
} |
|
|
|
sub addModule |
|
{ |
|
my ($self, $module) = @_; |
|
Carp::confess("No module to push") unless $module; |
|
|
|
my $path; |
|
if (list_has($self->{modules}, $module)) { |
|
debug("Skipping duplicate module ", $module->name()); |
|
} |
|
# TODO: Shouldn't this support all modules, not just 'proj' modules? |
|
elsif ($module->scmType() eq 'proj' && |
|
($path = $module->fullProjectPath()) && |
|
# See if the name matches any given in the ignore list. |
|
any(sub { $path =~ /(^|\/)$_($|\/)/ }, $self->{ignore_list})) |
|
{ |
|
debug("Skipping ignored module $module"); |
|
} |
|
else { |
|
debug("Adding $module to module list"); |
|
push @{$self->{modules}}, $module; |
|
} |
|
} |
|
|
|
# Returns a listref of the modules to build |
|
sub moduleList |
|
{ |
|
my $self = shift; |
|
return $self->{modules}; |
|
} |
|
|
|
# Adds a list of modules to ignore processing on completely. |
|
# Parameters should simply be a list of XML repository paths to ignore, |
|
# e.g. 'extragear/utils/kdesrc-build'. Partial paths are acceptable, matches |
|
# are determined by comparing the path provided to the suffix of the full path |
|
# of modules being compared. |
|
# |
|
# Existing items on the ignore list are not removed. |
|
sub addToIgnoreList |
|
{ |
|
my $self = shift; |
|
push @{$self->{ignore_list}}, @_; |
|
|
|
debug ("Set context ignore list to ", join(', ', @_)); |
|
} |
|
|
|
sub setupOperatingEnvironment |
|
{ |
|
my $self = shift; |
|
# Set the process priority |
|
POSIX::nice(int $self->getOption('niceness')); |
|
|
|
# Set the IO priority if available. |
|
if ($self->getOption('use-idle-io-priority')) { |
|
# -p $$ is our PID, -c3 is idle priority |
|
# 0 return value means success |
|
if (safe_system('ionice', '-c3', '-p', $$) != 0) { |
|
warning (" b[y[*] Unable to lower I/O priority, continuing..."); |
|
} |
|
} |
|
|
|
# Get ready for logged output. |
|
ksb::Debug::setLogFile($self->getLogDirFor($self) . '/build-log'); |
|
|
|
# Propagate HTTP proxy through environment unless overridden. |
|
if ((my $proxy = $self->getOption('http-proxy')) && |
|
!defined $ENV{'http_proxy'}) |
|
{ |
|
$self->queueEnvironmentVariable('http_proxy', $proxy); |
|
} |
|
} |
|
|
|
# Clears the list of environment variables to set for log_command runs. |
|
sub resetEnvironment |
|
{ |
|
my $self = assert_isa(shift, 'ksb::BuildContext'); |
|
|
|
$self->{env} = { }; |
|
} |
|
|
|
# Adds an environment variable and value to the list of environment |
|
# variables to apply for the next subprocess execution. |
|
# |
|
# Note that these changes are /not/ reflected in the current environment, |
|
# so if you are doing something that requires that kind of update you |
|
# should do that yourself (but remember to have some way to restore the old |
|
# value if necessary). |
|
# |
|
# In order to keep compatibility with the old 'setenv' sub, no action is |
|
# taken if the value is not equivalent to boolean true. |
|
sub queueEnvironmentVariable |
|
{ |
|
my $self = assert_isa(shift, 'ksb::BuildContext'); |
|
my ($key, $value) = @_; |
|
|
|
return unless $value; |
|
|
|
debug ("\tQueueing g[$key] to be set to y[$value]"); |
|
$self->{env}->{$key} = $value; |
|
} |
|
|
|
# Applies all changes queued by queueEnvironmentVariable to the actual |
|
# environment irretrievably. Use this before exec()'ing another child, for |
|
# instance. |
|
sub commitEnvironmentChanges |
|
{ |
|
my $self = assert_isa(shift, 'ksb::BuildContext'); |
|
|
|
while (my ($key, $value) = each %{$self->{env}}) { |
|
$ENV{$key} = $value; |
|
debug ("\tSetting environment variable g[$key] to g[b[$value]"); |
|
} |
|
} |
|
|
|
# Adds the given library paths to the path already given in an environment |
|
# variable. In addition, detected "system paths" are stripped to ensure |
|
# that we don't inadvertently re-add a system path to be promoted over the |
|
# custom code we're compiling (for instance, when a system Qt is used and |
|
# installed to /usr). |
|
# |
|
# If the environment variable to be modified has already been queued using |
|
# queueEnvironmentVariable, then that (queued) value will be modified and |
|
# will take effect with the next forked subprocess. |
|
# |
|
# Otherwise, the current environment variable value will be used, and then |
|
# queued. Either way the current environment will be unmodified afterward. |
|
# |
|
# First parameter is the name of the environment variable to modify |
|
# All remaining paramters are prepended to the current environment path, in |
|
# the order given. (i.e. param1, param2, param3 -> |
|
# param1:param2:param3:existing) |
|
sub prependEnvironmentValue |
|
{ |
|
my $self = assert_isa(shift, 'ksb::BuildContext'); |
|
my ($envName, @items) = @_; |
|
my @curPaths = split(':', $self->{env}->{$envName} // $ENV{$envName} // ''); |
|
|
|
# Filter out entries to add that are already in the environment from |
|
# the system. |
|
for my $path (grep { list_has(\@curPaths, $_) } (@items) ) { |
|
debug ("\tNot prepending y[$path] to y[$envName] as it appears " . |
|
"to already be defined in y[$envName]."); |
|
} |
|
|
|
@items = grep { not list_has(\@curPaths, $_); } (@items); |
|
|
|
my $envValue = join(':', @items, @curPaths); |
|
|
|
$envValue =~ s/^:*//; |
|
$envValue =~ s/:*$//; # Remove leading/trailing colons |
|
$envValue =~ s/:+/:/; # Remove duplicate colons |
|
|
|
$self->queueEnvironmentVariable($envName, $envValue); |
|
} |
|
|
|
# Tries to take the lock for our current base directory, which currently is |
|
# what passes for preventing people from accidentally running kdesrc-build |
|
# multiple times at once. The lock is based on the base directory instead |
|
# of being global to allow for motivated and/or brave users to properly |
|
# configure kdesrc-build to run simultaneously with different |
|
# configurations. |
|
# |
|
# Return value is a boolean success flag. |
|
sub takeLock |
|
{ |
|
my $self = assert_isa(shift, 'ksb::BuildContext'); |
|
my $baseDir = $self->baseConfigDirectory(); |
|
my $lockfile = "$baseDir/$LOCKFILE_NAME"; |
|
|
|
$! = 0; # Force reset to non-error status |
|
sysopen LOCKFILE, $lockfile, O_WRONLY | O_CREAT | O_EXCL; |
|
my $errorCode = $!; # Save for later testing. |
|
|
|
if ($errorCode == EEXIST) |
|
{ |
|
# Path already exists, read the PID and see if it belongs to a |
|
# running process. |
|
open (my $pidFile, "<", $lockfile) or do |
|
{ |
|
# Lockfile is there but we can't open it?!? Maybe a race |
|
# condition but I have to give up somewhere. |
|
warning (" WARNING: Can't open or create lockfile r[$lockfile]"); |
|
return 1; |
|
}; |
|
|
|
my $pid = <$pidFile>; |
|
close $pidFile; |
|
|
|
if ($pid) |
|
{ |
|
# Recent kdesrc-build; we wrote a PID in there. |
|
chomp $pid; |
|
|
|
# See if something's running with this PID. |
|
if (kill(0, $pid) == 1) |
|
{ |
|
# Something *is* running, likely kdesrc-build. Don't use error, |
|
# it'll scan for $! |
|
print ksb::Debug::colorize(" r[*y[*r[*] kdesrc-build appears to be running. Do you want to:\n"); |
|
print ksb::Debug::colorize(" (b[Q])uit, (b[P])roceed anyways?: "); |
|
|
|
my $choice = <STDIN> // ''; |
|
chomp $choice; |
|
|
|
if (lc $choice ne 'p') |
|
{ |
|
say ksb::Debug::colorize(" y[*] kdesrc-build run canceled."); |
|
return 0; |
|
} |
|
|
|
# We still can't grab the lockfile, let's just hope things |
|
# work out. |
|
note (" y[*] kdesrc-build run in progress by user request."); |
|
return 1; |
|
} |
|
|
|
# If we get here, then the program isn't running (or at least not |
|
# as the current user), so allow the flow of execution to fall |
|
# through below and unlink the lockfile. |
|
} # pid |
|
|
|
# No pid found, optimistically assume the user isn't running |
|
# twice. |
|
warning (" y[WARNING]: stale kdesrc-build lockfile found, deleting."); |
|
unlink $lockfile; |
|
|
|
sysopen (LOCKFILE, $lockfile, O_WRONLY | O_CREAT | O_EXCL) or do { |
|
error (" r[*] Still unable to lock $lockfile, proceeding anyways..."); |
|
return 1; |
|
}; |
|
|
|
# Hope the sysopen worked... fall-through |
|
} |
|
elsif ($errorCode == ENOTTY) |
|
{ |
|
# Stupid bugs... normally sysopen will return ENOTTY, not sure who's to blame between |
|
# glibc and Perl but I know that setting PERLIO=:stdio in the environment "fixes" things. |
|
; # pass |
|
} |
|
elsif ($errorCode != 0) # Some other error occurred. |
|
{ |
|
warning (" r[*]: Error $errorCode while creating lock file (is $baseDir available?)"); |
|
warning (" r[*]: Continuing the script for now..."); |
|
|
|
# Even if we fail it's generally better to allow the script to proceed |
|
# without being a jerk about things, especially as more non-CLI-skilled |
|
# users start using kdesrc-build to build KDE. |
|
return 1; |
|
} |
|
|
|
say LOCKFILE "$$"; |
|
close LOCKFILE; |
|
|
|
return 1; |
|
} |
|
|
|
# Releases the lock obtained by takeLock. |
|
sub closeLock |
|
{ |
|
my $self = assert_isa(shift, 'ksb::BuildContext'); |
|
my $baseDir = $self->baseConfigDirectory(); |
|
my $lockFile = "$baseDir/$LOCKFILE_NAME"; |
|
|
|
unlink ($lockFile) or warning(" y[*] Failed to close lock: $!"); |
|
} |
|
|
|
# This subroutine accepts a Module parameter, and returns the log directory |
|
# for it. You can also pass a BuildContext (including this one) to get the |
|
# default log directory. |
|
# |
|
# As part of setting up what path to use for the log directory, the |
|
# 'latest' symlink will also be setup to point to the returned log |
|
# directory. |
|
sub getLogDirFor |
|
{ |
|
my ($self, $module) = @_; |
|
|
|
my $baseLogPath = $module->getSubdirPath('log-dir'); |
|
my $logDir; |
|
|
|
if (!exists $self->{logPaths}{$baseLogPath}) { |
|
# No log dir made for this base, do so now. |
|
my $id = '01'; |
|
my $date = strftime "%F", localtime; # ISO 8601 date |
|
$id++ while -e "$baseLogPath/$date-$id"; |
|
$self->{logPaths}{$baseLogPath} = "$baseLogPath/$date-$id"; |
|
} |
|
|
|
$logDir = $self->{logPaths}{$baseLogPath}; |
|
return $logDir if pretending(); |
|
|
|
super_mkdir($logDir) unless -e $logDir; |
|
|
|
# No symlink munging or module-name-adding is needed for the default |
|
# log dir. |
|
return $logDir if $module->isa('ksb::BuildContext'); |
|
|
|
# Add a symlink to the latest run for this module. 'latest' itself is |
|
# a directory under the default log directory that holds module |
|
# symlinks, pointing to the last log directory run for that module. We |
|
# do need to be careful of modules that have multiple directory names |
|
# though (like extragear/foo). |
|
|
|
my $latestPath = "$baseLogPath/latest"; |
|
|
|
# Handle stuff like playground/utils or KDE/kdelibs |
|
my ($moduleName, $modulePath) = fileparse($module->name()); |
|
$latestPath .= "/$modulePath" if $module->name() =~ m(/); |
|
|
|
super_mkdir($latestPath); |
|
|
|
my $symlinkTarget = "$logDir/$moduleName"; |
|
my $symlink = "$latestPath/$moduleName"; |
|
|
|
if (-l $symlink and readlink($symlink) ne $symlinkTarget) |
|
{ |
|
unlink($symlink); |
|
symlink($symlinkTarget, $symlink); |
|
} |
|
elsif(not -e $symlink) |
|
{ |
|
# Create symlink initially if we've never done it before. |
|
symlink($symlinkTarget, $symlink); |
|
} |
|
|
|
super_mkdir($symlinkTarget); |
|
return $symlinkTarget; |
|
} |
|
|
|
# Returns rc file in use. Call loadRcFile first. |
|
sub rcFile |
|
{ |
|
my $self = shift; |
|
return $self->{rcFile}; |
|
} |
|
|
|
# Forces the rc file to be read from to be that given by the first |
|
# parameter. |
|
sub setRcFile |
|
{ |
|
my ($self, $file) = @_; |
|
$self->{rcFiles} = [$file]; |
|
$self->{rcFile} = undef; |
|
} |
|
|
|
# Returns an open filehandle to the user's chosen rc file. Use setRcFile |
|
# to choose a file to load before calling this function, otherwise |
|
# loadRcFile will search the default search path. After this function is |
|
# called, rcFile() can be used to determine which file was loaded. |
|
# |
|
# If unable to find or open the rc file an exception is raised. Empty rc |
|
# files are supported however. |
|
sub loadRcFile |
|
{ |
|
my $self = shift; |
|
my @rcFiles = @{$self->{rcFiles}}; |
|
my $fh; |
|
|
|
for my $file (@rcFiles) |
|
{ |
|
if (open ($fh, '<', "$file")) |
|
{ |
|
$self->{rcFile} = File::Spec->rel2abs($file); |
|
return $fh; |
|
} |
|
} |
|
|
|
# No rc found, check if we can use default. |
|
if (scalar @rcFiles == 1) |
|
{ |
|
# This can only happen if the user uses --rc-file, so if we fail to |
|
# load the file, we need to fail to load at all. |
|
my $failedFile = $rcFiles[0]; |
|
|
|
error (<<EOM); |
|
Unable to open config file $failedFile |
|
|
|
Script stopping here since you specified --rc-file on the command line to |
|
load $failedFile manually. If you wish to run the script with no configuration |
|
file, leave the --rc-file option out of the command line. |
|
|
|
If you want to force an empty rc file, use --rc-file /dev/null |
|
|
|
EOM |
|
croak_runtime("Missing $failedFile"); |
|
} |
|
|
|
# If no configuration but no --rc-file option was used, warn the user |
|
# and fail, as there are too many possible modes of using kdesrc-build |
|
# for kdesrc-buildrc-sample to be appropriate. |
|
|
|
error (<<EOM); |
|
b[No configuration file is present.] |
|
|
|
kdesrc-build requires a configuration file to select which KDE software modules |
|
to build, what options to build them with, the path to install to, etc. |
|
|
|
kdesrc-build looks for its configuration in the file `kdesrc-buildrc' in |
|
whatever directory kdesrc-build is run from. |
|
|
|
If no such file exists, kdesrc-build tries to use `~/.kdesrc-buildrc' (note the |
|
leading `.') |
|
|
|
A sample configuration suitable for KDE 4 software is included at the file |
|
`kdesrc-buildrc-sample' which can be copied to the correct location and then |
|
edited. |
|
|
|
KDE Frameworks 5 users can use the `kdesrc-buildrc-kf5-sample' file which can |
|
be copied to the correct location and then edited. |
|
|
|
In either case b[once the configuration file is setup to your liking], you |
|
should run: |
|
b[kdesrc-build --metadata-only] |
|
|
|
to download needed information about the KDE source repositories and then: |
|
b[kdesrc-build --pretend] |
|
|
|
to preview what kdesrc-build will do. |
|
EOM |
|
croak_runtime("No configuration available"); |
|
} |
|
|
|
# Returns the base directory that holds the configuration file. This is |
|
# typically used as the directory base for other necessary kdesrc-build |
|
# execution files, such as the persistent data store and lock file. |
|
# |
|
# The RC file must have been found and loaded first, obviously. |
|
sub baseConfigDirectory |
|
{ |
|
my $self = assert_isa(shift, 'ksb::BuildContext'); |
|
my $rcfile = $self->rcFile() or |
|
croak_internal("Call to baseConfigDirectory before loadRcFile"); |
|
|
|
return dirname($rcfile); |
|
} |
|
|
|
sub modulesInPhase |
|
{ |
|
my ($self, $phase) = @_; |
|
my @list = grep { list_has([$_->phases()->phases()], $phase) } (@{$self->moduleList()}); |
|
return @list; |
|
} |
|
|
|
# Searches for a module with a name that matches the provided parameter, |
|
# and returns its ksb::Module object. Returns undef if no match was found. |
|
# As a special-case, returns the BuildContext itself if the name passed is |
|
# 'global', since the BuildContext also is a (in the "is-a" OOP sense) |
|
# ksb::Module, specifically the 'global' one. |
|
sub lookupModule |
|
{ |
|
my ($self, $moduleName) = @_; |
|
|
|
return $self if $moduleName eq 'global'; |
|
|
|
my @options = grep { $_->name() eq $moduleName } (@{$self->moduleList()}); |
|
return undef unless @options; |
|
|
|
if (scalar @options > 1) { |
|
croak_internal("Detected 2 or more $moduleName ksb::Module objects"); |
|
} |
|
|
|
return $options[0]; |
|
} |
|
|
|
sub markModulePhaseFailed |
|
{ |
|
my ($self, $phase, $module) = @_; |
|
assert_isa($module, 'ksb::Module'); |
|
|
|
$self->{errors}->{$module->name()} = $phase; |
|
} |
|
|
|
# Returns a list (i.e. not a reference to, but a real list) of Modules that failed to |
|
# complete the given phase. |
|
sub failedModulesInPhase |
|
{ |
|
my ($self, $phase) = @_; |
|
|
|
my @failures = grep { |
|
($self->{errors}->{$_->name()} // '') eq $phase |
|
} (@{$self->moduleList()}); |
|
|
|
return @failures; |
|
} |
|
|
|
# Returns a list of modules that had a failure of some sort, in the order the modules |
|
# are listed in our current module list. |
|
sub listFailedModules |
|
{ |
|
my $self = shift; |
|
my @modules = @{$self->moduleList()}; |
|
|
|
# grepping for failures instead of returning error list directly maintains ordering |
|
@modules = grep { |
|
exists $self->{errors}->{$_->name()} |
|
} (@modules); |
|
|
|
return @modules; |
|
} |
|
|
|
# OVERRIDE: Our immediate parent class Module overrides this, but we actually |
|
# want the OptionsBase version to be used instead, until we break the recursive |
|
# use of Module's own getOption calls on our getOption. |
|
sub getOption |
|
{ |
|
&ksb::OptionsBase::getOption; |
|
} |
|
|
|
# OVERRIDE: Overrides OptionsBase::setOption to handle some global-only options. |
|
sub setOption |
|
{ |
|
my ($self, %options) = @_; |
|
|
|
# Special-case handling |
|
my $repoOption = 'git-repository-base'; |
|
if (exists $options{$repoOption}) { |
|
my $value = $options{$repoOption}; |
|
my ($repo, $url) = ($value =~ /^([a-zA-Z0-9_-]+)\s+(.+)$/); |
|
|
|
# This will be a hash reference instead of a scalar |
|
$value = $self->getOption($repoOption) || { }; |
|
|
|
if (!$repo || !$url) { |
|
die ksb::BuildException::Config->new($repoOption, |
|
"Invalid git-repository-base setting: $value"); |
|
} |
|
|
|
$value->{$repo} = $url; |
|
delete $options{$repoOption}; |
|
} |
|
|
|
# Actually set options. |
|
$self->SUPER::setOption(%options); |
|
|
|
# Automatically respond to various global option changes. |
|
while (my ($key, $value) = each %options) { |
|
my $normalizedKey = $key; |
|
$normalizedKey =~ s/^#//; # Remove sticky key modifier. |
|
given ($normalizedKey) { |
|
when ('colorful-output') { ksb::Debug::setColorfulOutput($value); } |
|
when ('debug-level') { ksb::Debug::setDebugLevel($value); } |
|
when ('pretend') { ksb::Debug::setPretending($value); } |
|
} |
|
} |
|
} |
|
|
|
# |
|
# Persistent option handling |
|
# |
|
|
|
# Returns the name of the file to use for persistent data. |
|
# Supports expanding '#' at the beginning of the filename to the directory |
|
# containing the rc-file in use, but only for the default name at this |
|
# point. |
|
sub persistentOptionFileName |
|
{ |
|
my $self = shift; |
|
my $filename = $self->getOption('persistent-data-file'); |
|
|
|
if (!$filename) { |
|
$filename = $PERSISTENT_FILE_NAME; |
|
my $dir = $self->baseConfigDirectory(); |
|
$filename =~ s/^#/$dir/; |
|
} |
|
else { |
|
# Tilde-expand |
|
$filename =~ s/^~\//$ENV{HOME}\//; |
|
} |
|
|
|
return $filename; |
|
} |
|
|
|
# Reads in all persistent options from the file where they are kept |
|
# (.kdesrc-build-data) for use in the program. |
|
# |
|
# The directory used is the same directory that contains the rc file in use. |
|
sub loadPersistentOptions |
|
{ |
|
my $self = assert_isa(shift, 'ksb::BuildContext'); |
|
my $fh = IO::File->new($self->persistentOptionFileName(), '<'); |
|
|
|
return unless $fh; |
|
|
|
# $persistent_data should be a JSON object which we can store directly as a |
|
# hash. |
|
my $persistent_data; |
|
{ |
|
local $/ = undef; # Read in whole file with <> operator. |
|
$persistent_data = <$fh>; |
|
} |
|
|
|
my $persistent_options; |
|
|
|
eval { $persistent_options = decode_json($persistent_data); }; |
|
if ($@) { |
|
# Apparently wasn't JSON, try falling back to old format for compat. |
|
# Previously, this was a Perl code which, when evaluated would give |
|
# us a hash called persistent_options which we can then merge into our |
|
# persistent options. |
|
# TODO: Remove compat code after 2018-06-30 |
|
eval $persistent_data; # Runs Perl code read from file |
|
if ($@) |
|
{ |
|
# Failed. |
|
error ("Failed to read persistent module data: r[b[$@]"); |
|
return; |
|
} |
|
} |
|
|
|
# We need to keep persistent data with the context instead of with the |
|
# applicable modules since otherwise we might forget to write out |
|
# persistent data for modules we didn't build in this run. So, we just |
|
# store it all. |
|
# Layout of this data: |
|
# $self->persistent_options = { |
|
# 'module-name' => { |
|
# option => value, |
|
# # foreach option/value pair |
|
# }, |
|
# # foreach module |
|
# } |
|
$persistent_options = {} if ref $persistent_options ne 'HASH'; |
|
$self->{persistent_options} = $persistent_options; |
|
} |
|
|
|
# Writes out the persistent options to the file .kdesrc-build-data. |
|
# |
|
# The directory used is the same directory that contains the rc file in use. |
|
sub storePersistentOptions |
|
{ |
|
my $self = assert_isa(shift, 'ksb::BuildContext'); |
|
return if pretending(); |
|
|
|
my $fh = IO::File->new($self->persistentOptionFileName(), '>'); |
|
my $json = JSON::PP->new->ascii->pretty; |
|
|
|
if (!$fh) |
|
{ |
|
error ("Unable to save persistent module data: b[r[$!]"); |
|
return; |
|
} |
|
|
|
my $encodedJSON = $json->encode($self->{persistent_options}); |
|
|
|
print $fh $encodedJSON; |
|
undef $fh; # Closes the file |
|
} |
|
|
|
# Returns the value of a "persistent" option (normally read in as part of |
|
# startup), or undef if there is no value stored. |
|
# |
|
# First parameter is the module name to get the option for, or 'global' if |
|
# not for a module. |
|
# Note that unlike setOption/getOption, no inheritance is done at this |
|
# point so if an option is present globally but not for a module you |
|
# must check both if that's what you want. |
|
# Second parameter is the name of the value to retrieve (i.e. the key) |
|
sub getPersistentOption |
|
{ |
|
my ($self, $moduleName, $key) = @_; |
|
my $persistent_opts = $self->{persistent_options}; |
|
|
|
# We must check at each level of indirection to avoid |
|
# "autovivification" |
|
return unless exists $persistent_opts->{$moduleName}; |
|
return unless exists $persistent_opts->{$moduleName}{$key}; |
|
|
|
return $persistent_opts->{$moduleName}{$key}; |
|
} |
|
|
|
# Clears a persistent option if set (for a given module and option-name). |
|
# |
|
# First parameter is the module name to get the option for, or 'global' for |
|
# the global options. |
|
# Second parameter is the name of the value to clear. |
|
# No return value. |
|
sub unsetPersistentOption |
|
{ |
|
my ($self, $moduleName, $key) = @_; |
|
my $persistent_opts = $self->{persistent_options}; |
|
|
|
if (exists $persistent_opts->{$moduleName} && |
|
exists $persistent_opts->{$moduleName}->{$key}) |
|
{ |
|
delete $persistent_opts->{$moduleName}->{$key}; |
|
} |
|
} |
|
|
|
# Sets a "persistent" option which will be read in for a module when |
|
# kdesrc-build starts up and written back out at (normal) program exit. |
|
# |
|
# First parameter is the module name to set the option for, or 'global'. |
|
# Second parameter is the name of the value to set (i.e. key) |
|
# Third parameter is the value to store, which must be a scalar. |
|
sub setPersistentOption |
|
{ |
|
my ($self, $moduleName, $key, $value) = @_; |
|
my $persistent_opts = $self->{persistent_options}; |
|
|
|
# Initialize empty hash ref if nothing defined for this module. |
|
$persistent_opts->{$moduleName} //= { }; |
|
|
|
$persistent_opts->{$moduleName}{$key} = $value; |
|
} |
|
|
|
# Returns the ksb::Module (which has a 'metadata' scm type) that is used for |
|
# kde-build-metadata, so that other modules that need it can call into it if |
|
# necessary. |
|
# |
|
# Also may return undef if the metadata is unavailable or has not yet |
|
# been set by setKDEDependenciesMetadataModule (this method does not |
|
# automatically create the needed module). |
|
sub getKDEDependenciesMetadataModule |
|
{ |
|
my $self = shift; |
|
return $self->{kde_dependencies_metadata}; |
|
} |
|
|
|
# Returns the ksb::Module (which has a 'metadata' scm type) that is used for |
|
# kde-project metadata, so that other modules that need it can call into it if |
|
# necessary. |
|
# |
|
# Also may return undef if the metadata is unavailable or has not yet |
|
# been set by setKDEProjectsMetadataModule (this method does not |
|
# automatically create the needed module). |
|
sub getKDEProjectsMetadataModule |
|
{ |
|
my $self = shift; |
|
return $self->{kde_projects_metadata}; |
|
} |
|
|
|
# Call this method to force this build context to pull in the kde-build-metadata |
|
# module. This is a one-time action, subsequent calls to this method |
|
# are ignored. Use getKDEDependenciesMetadataModule to see if this build context is |
|
# using a metadata module. |
|
# |
|
# This method should be called before setModuleList. |
|
sub setKDEDependenciesMetadataModuleNeeded |
|
{ |
|
my $self = assert_isa(shift, 'ksb::BuildContext'); |
|
|
|
return if defined $self->{kde_dependencies_metadata}; |
|
|
|
my $metadata = ksb::ModuleSet::KDEProjects::getDependenciesModule($self); |
|
|
|
debug ("Introducing dependency metadata into the build"); |
|
assert_isa($metadata->scm(), 'ksb::Updater::KDEProjectMetadata'); |
|
|
|
$self->{kde_dependencies_metadata} = $metadata; |
|
return; |
|
} |
|
|
|
# Call this method to force this build context to pull in the |
|
# sysadmin/repo-metadata module. This is a one-time action, |
|
# subsequent calls to this method are ignored. Use |
|
# getKDEProjectsMetadataModule to see if this build context is using |
|
# a metadata module. |
|
# |
|
# This method should be called before setModuleList. |
|
sub setKDEProjectsMetadataModuleNeeded |
|
{ |
|
my $self = assert_isa(shift, 'ksb::BuildContext'); |
|
|
|
return if defined $self->{kde_projects_metadata}; |
|
|
|
my $metadata = ksb::ModuleSet::KDEProjects::getProjectMetadataModule($self); |
|
|
|
debug ("Introducing project enumeration metadata into the build"); |
|
assert_isa($metadata->scm(), 'ksb::Updater::KDEProjectMetadata'); |
|
|
|
$self->{kde_projects_metadata} = $metadata; |
|
return; |
|
} |
|
|
|
# Returns a KDEXMLReader module, which has already read in the database and |
|
# is ready to be queried. Note that exceptions can be thrown in the process |
|
# of downloading and parsing the database information, so be ready for that. |
|
sub getProjectDataReader |
|
{ |
|
my $self = shift; |
|
|
|
return $self->{projects_db} if $self->{projects_db}; |
|
|
|
my $projectDatabaseModule = $self->getKDEProjectsMetadataModule() or |
|
croak_runtime("kde-projects repository information could not be downloaded: $!"); |
|
|
|
my $protocol = $self->getOption('git-desired-protocol') || 'git'; |
|
if (!list_has(['git', 'http', 'https'], $protocol)) { |
|
error (" b[y[*] Invalid b[git-desired-protocol] $protocol"); |
|
error (" b[y[*] Try setting this option to 'git' if you're not using a proxy"); |
|
croak_runtime ("Invalid git-desired-protocol: $protocol"); |
|
} |
|
|
|
$self->{projects_db} = ksb::KDEXMLReader->new($projectDatabaseModule, $protocol); |
|
return $self->{projects_db}; |
|
} |
|
|
|
# Returns the effective branch group to use for modules. You should not call |
|
# this unless kde-build-metadata is also in use (see |
|
# setKDEDependenciesMetadataModule and moduleBranchGroupResolver). |
|
sub effectiveBranchGroup |
|
{ |
|
my $self = shift; |
|
my $branchGroup = $self->getOption('branch-group', 'module') // ''; |
|
|
|
if (!$branchGroup) { |
|
$branchGroup = $self->getOption('use-stable-kde') |
|
? 'latest-qt4' |
|
: ($self->hasOption('use-stable-kde') # Could also be false if unset |
|
? 'kf5-qt5' # Really set to false |
|
: 'latest-qt4'); # Unset / this is default branch group if no option set |
|
} |
|
|
|
return $branchGroup; |
|
} |
|
|
|
# Returns a ksb::Module::BranchGroupResolver which can be used to efficiently |
|
# determine a git branch to use for a given kde-projects module (when the |
|
# branch-group option is in use), as specified at |
|
# https://community.kde.org/Infrastructure/Project_Metadata. |
|
sub moduleBranchGroupResolver |
|
{ |
|
my $self = shift; |
|
|
|
if (!$self->{logical_module_resolver}) { |
|
my $metadataModule = $self->getKDEDependenciesMetadataModule(); |
|
|
|
croak_internal("Tried to use branch-group, but needed data wasn't loaded!") |
|
unless $metadataModule; |
|
|
|
my $resolver = ksb::Module::BranchGroupResolver->new( |
|
$metadataModule->scm()->logicalModuleGroups()); |
|
$self->{logical_module_resolver} = $resolver; |
|
} |
|
|
|
return $self->{logical_module_resolver}; |
|
} |
|
|
|
sub statusViewer |
|
{ |
|
my $self = shift; |
|
return $self->{status_view}; |
|
} |
|
|
|
1;
|
|
|