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.
 
 
 
 

1445 lines
48 KiB

package ksb::Application;
# Class: Application
#
# Contains the application-layer logic (i.e. creating a build context, reading
# options, parsing command-line, etc.)
use strict;
use warnings;
use v5.10;
no if $] >= 5.018, 'warnings', 'experimental::smartmatch';
our $VERSION = '0.10';
use ksb::Debug;
use ksb::Util;
use ksb::BuildContext;
use ksb::Module;
use ksb::RecursiveFH;
use ksb::Version qw(scriptVersion);
use List::Util qw(first min);
### Package-specific variables (not shared outside this file).
my $SCRIPT_VERSION = scriptVersion();
# This is a hash since Perl doesn't have a "in" keyword.
my %ignore_list; # List of packages to refuse to include in the build list.
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
};
### Package methods
sub new
{
my ($class, @options) = @_;
my $self = bless {
context => ksb::BuildContext->new(),
metadata_module => undef,
run_mode => 'build',
modules => undef,
}, $class;
# Default to colorized output if sending to TTY
ksb::Debug::setColorfulOutput(-t STDOUT);
my @moduleList = $self->generateModuleList(@options);
$self->{modules} = \@moduleList;
$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!!
}
return $self;
}
# Runs the pre-initialization phase and takes the kdesrc-build lock. Once this
# function successfully completes it is required for the main process to call
# finish() to remove the lock.
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 $pendingOptions = { };
debug ("+++ Reached pre-init phase");
# Process --help, --install, etc. first.
my @modules = $self->_processCmdlineArguments($ctx, $pendingOptions, @argv);
debug ("--- Arguments read: ", scalar @modules, " command line modules");
# Output time once we know if pretending or not.
my $time = localtime;
info ("Script started processing at g[$time]") unless pretending();
# Change name and type of command line entries beginning with + to force
# them to be XML project modules.
foreach (@modules) {
if (substr($_->{name}, 0, 1) eq '+') {
debug ("Forcing ", $_->name(), " to be an XML module");
$_->setScmType('proj');
substr($_->{name}, 0, 1) = ''; # Remove first char
}
}
my $fh = $ctx->loadRcFile();
# If we're still here, read the options
my @optionModulesAndSets = _readConfigurationOptions($ctx, $fh);
close $fh;
debug ("--- Config file read: ", scalar @optionModulesAndSets, " modules/module sets.");
debug (" --- " , (scalar grep { $_->isa('ksb::ModuleSet') } @optionModulesAndSets) , " are module sets.");
# Modify the options read from the rc-file to have the pending changes from
# the command line.
foreach my $pendingModule (keys %{$pendingOptions}) {
my $options = ${$pendingOptions}{$pendingModule};
my ($module) = grep {
$_->isa('ksb::Module') && $pendingModule eq $_->name()
} (@optionModulesAndSets);
if (!$module) {
warning ("Tried to set options for unknown module b[y[$pendingModule]");
next;
}
while (my ($key, $value) = each %{$options}) {
$module->setOption($key, $value);
}
}
# Check if we're supposed to drop into an interactive shell instead. If so,
# here's the stop off point.
if (my $prog = $ctx->getOption('#start-program'))
{
# @modules is the command line arguments to pass in this case.
_executeCommandLineProgram($prog, @modules);
}
my $commandLineModules = scalar @modules;
my $metadataModule;
# Allow named module-sets to be given on the command line.
if ($commandLineModules) {
# Copy ksb::Module and ksb::ModuleSet objects from the ones created by
# _readConfigurationOptions since their module-type will actually be set.
_spliceOptionModules(\@modules, \@optionModulesAndSets);
debug ("--- spliced rc-file modules/sets into command line modules");
debug (" --- now ", scalar @modules, " modules present");
# After this splicing, the only bare ksb::Modules with a 'proj'
# scm type should be the ones from the command line that we got from
# _processCmdlineArguments.
# Modify l10n module inline, if present.
for (@modules) {
if ($_->name() eq 'l10n' && $_->isa('ksb::Module')) {
$_->setScmType('l10n')
}
}
# Filter --resume-foo first so entire module-sets can be skipped.
# Wrap in eval to catch runtime errors
eval { @modules = _applyModuleFilters($ctx, @modules); };
debug ("--- Applied command-line --resume-foo pass, ", scalar @modules, " remain.");
debug (" --- " , (scalar grep { $_->isa("ksb::ModuleSet") } @modules) , " are module sets.");
($metadataModule, @modules) = _expandModuleSets($ctx, @modules);
debug ("--- Expanded command-line module sets into ", scalar @modules, " modules.");
debug (" --- Metadata module needed? ", $metadataModule ? "Yes" : "No");
# If we have any 'guessed' modules they came from the command line. We
# want them to use ksb::Modules that came from
# _readConfigurationOptions() instead of ones from _processCmdlineArguments(),
# if any are available. But we don't expand module-sets from
# _readConfigurationOptions unconditionally, only ones where the name
# matched between the command line and rc-file, so the previous
# _spliceOptionModules might not have found them. (e.g. if you named a
# module on the command line that isn't named directly in the rc file
# but would eventually be found implicitly). To make this work we have
# to expand out our rc-file Modules and try splicing again.
if (first { $_->getOption('#guessed-kde-project', 'module') } @modules) {
my (undef, @expandedOptionModules) = _expandModuleSets($ctx, @optionModulesAndSets);
_spliceOptionModules(\@modules, \@expandedOptionModules);
debug ("--- re-spliced rc-file modules/sets into command line modules");
debug (" --- now ", scalar @modules, " modules present");
}
# At this point @modules has no more module sets, kde-projects or
# otherwise.
ksb::Module->setModuleSource('cmdline');
}
else {
# Build everything in the rc-file, in the order specified.
($metadataModule, @modules) = _expandModuleSets($ctx, @optionModulesAndSets);
debug ("--- Expanded rc-file module sets into ", scalar @modules, " modules.");
debug (" --- Metadata module needed? ", $metadataModule ? "Yes" : "No");
if ($ctx->getOption('kde-languages')) {
my $l10nModule = ksb::Module->new($ctx, 'l10n');
$l10nModule->setScmType('l10n');
$l10nModule->setBuildSystem($l10nModule->scm());
debug ("--- Added l10n module to end of build");
push @modules, $l10nModule;
}
ksb::Module->setModuleSource('config');
}
# 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 that
# event.
@modules = _applyModuleFilters($ctx, @modules);
debug ("--- Applied all-modules --resume-foo pass, ", scalar @modules, " remain.");
debug (" --- ", (scalar grep { $_->isa("ksb::ModuleSet") } @modules), " are module sets (should be 0!).");
# Apply kde-languages, by appending needed l10n modules to the end of the
# build.
@modules = _expandl10nModules($ctx, @modules);
debug ("--- Expanded l10n modules. ", scalar @modules, " remain.");
# Remove ignored modules and/or module-sets
@modules = grep {
not exists $ignore_list{$_->name()} && not exists $ignore_list{$_->moduleSet()->name()}
} (@modules);
debug ("--- Handled ignore lists. ", scalar @modules, " remain.");
# 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;
debug ("--- Updated module phases. ", scalar @modules, " remain.");
# Save our metadata module, if used.
$self->{metadata_module} = $metadataModule;
return @modules;
}
### 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 dependant 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 //= '';
# Simplify whitespace.
$value =~ s/\s+$//;
$value =~ s/^\s+//;
$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_value)) {
warning (" *\n * WARNING: $sub_var_name is not set at line y[$.]\n *");
}
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);
}
# Reads in the options from the config file and adds them to the option store.
# The first parameter is a BuildContext object to use for creating the returned
# ksb::Module under.
# The second parameter is a reference to the file handle to read from.
# The third parameter is the module name. It can be either an
# already-constructed ksb::Module object (in which case it is used directly and any
# options read for the module are applied directly to the object), or it can be
# a string containing the module name (in which case a new ksb::Module object will
# be created). For global options the module name should be 'global', or just
# pass in the BuildContext for this param as well.
#
# The return value is the ksb::Module with options set as given in the configuration
# file for that module. If global options were being read then a BuildContext
# is returned (but that is-a ksb::Module anyways).
sub _parseModuleOptions
{
my ($ctx, $fileReader, $moduleOrName) = @_;
assert_isa($ctx, 'ksb::BuildContext');
my $rcfile = $ctx->rcFile();
my $module;
# Figure out what objects to store options into. If given, just use
# that, otherwise use context or a new ksb::Module depending on the name.
if (ref $moduleOrName) {
$module = $moduleOrName;
assert_isa($module, 'ksb::Module');
}
elsif ($moduleOrName eq 'global') {
$module = $ctx;
}
else {
$module = ksb::Module->new($ctx, $moduleOrName);
}
my $endWord = $module->isa('ksb::BuildContext') ? 'global' : 'module';
my $endRE = qr/^end\s+$endWord/;
# Read in each option
while ($_ = _readNextLogicalLine($fileReader))
{
last if m/$endRE/;
# Sanity check, make sure the section is correctly terminated
if(/^(module\s|module$)/)
{
error ("Invalid configuration file $rcfile at line $.\nAdd an 'end $endWord' before " .
"starting a new module.\n");
die make_exception('Config', "Invalid $rcfile");
}
my ($option, $value) = _splitOptionAndValue($ctx, $_);
# Handle special options.
if ($module->isa('ksb::BuildContext') && $option eq 'git-repository-base') {
# This will be a hash reference instead of a scalar
my ($repo, $url) = ($value =~ /^([a-zA-Z0-9_-]+)\s+(.+)$/);
$value = $ctx->getOption($option) || { };
if (!$repo || !$url) {
error (<<"EOF");
The y[git-repository-base] option at y[b[$rcfile:$.]
requires a repository name and URL.
e.g. git-repository base y[b[kde] g[b[git://anongit.kde.org/]
Use this in a "module-set" group:
e.g.
module-set kdesupport-set
repository y[b[kde]
use-modules automoc akonadi soprano attica
end module-set
EOF
die make_exception('Config', "Invalid git-repository-base");
}
$value->{$repo} = $url;
}
# Read ~~ as "is in this list:"
elsif ($option ~~ [qw(git-repository-base use-modules ignore-modules)]) {
error (" r[b[*] module b[$module] (near line $.) should be declared as module-set to use b[$option]");
die make_exception('Config', "Option $option can only be used in module-set");
}
elsif ($option eq 'filter-out-phases') {
for my $phase (split(' ', $value)) {
$module->phases()->filterOutPhase($phase);
}
next; # Don't fallthrough to set the option
}
$module->setOption($option, $value);
}
return $module;
}
# 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 name of the moduleset, which is really the name
# of the base repository to use (this can be left empty).
#
# Returns a ksb::ModuleSet describing the module-set encountered, which may
# need to be further expanded (see ksb::ModuleSet::convertToModules).
sub _parseModuleSetOptions
{
my $ctx = assert_isa(shift, 'ksb::BuildContext');
my $fileReader = shift;
my $moduleSetName = shift || '';
my $rcfile = $ctx->rcFile();
my $startLine = $.; # For later error messages
my $internalModuleSetName =
$moduleSetName || "<module-set at line $startLine>";
my $moduleSet = ksb::ModuleSet->new($ctx, $internalModuleSetName);
my %optionSet; # We read all options, and apply them to all modules
while($_ = _readNextLogicalLine($fileReader)) {
last if /^end\s+module(-?set)?$/;
my ($option, $value) = _splitOptionAndValue($ctx, $_);
if ($option eq 'use-modules') {
my @modules = split(' ', $value);
if (not @modules) {
error ("No modules were selected for the current module-set");
error ("in the y[use-modules] on line $. of $rcfile");
die make_exception('Config', 'Invalid use-modules');
}
$moduleSet->setModulesToFind(@modules);
}
elsif ($option eq 'ignore-modules') {
my @modulesToIgnore = split(' ', $value);
if (not @modulesToIgnore) {
error ("No modules were selected for the current module-set");
error ("in the y[ignore-modules] on line $. of $rcfile");
die make_exception('Config', 'Invalid ignore-modules');
}
$moduleSet->setModulesToIgnore(@modulesToIgnore);
}
elsif ($option eq 'set-env') {
ksb::Module::processSetEnvOption(\%optionSet, $option, $value);
}
else {
$optionSet{$option} = $value;
}
}
$moduleSet->setOptions(\%optionSet);
# Check before we use this module set whether the user did something silly.
my $repoSet = $ctx->getOption('git-repository-base');
if (!exists $optionSet{'repository'}) {
error (<<EOF);
There was no repository selected for the module-set declared on line $startLine
of $rcfile.
A repository is needed to determine where to download the source code from.
Most will want to use the b[g[kde-projects] repository. See also
http://kdesrc-build.kde.org/documentation/kde-modules-and-selection.html#module-sets
EOF
die make_exception('Config', 'Missing repository option');
}
if (($optionSet{'repository'} ne KDE_PROJECT_ID) &&
not exists $repoSet->{$optionSet{'repository'}})
{
my $projectID = KDE_PROJECT_ID;
my $moduleSetId = $moduleSetName ? "module-set ($moduleSetName)"
: "module-set";
error (<<EOF);
There is no repository assigned to y[b[$optionSet{repository}] when assigning a
$moduleSetId on line $startLine of $rcfile.
These repositories are defined by g[b[git-repository-base] in the global
section of $rcfile.
Make sure you spelled your repository name right!
If you are trying to pull the module information from the KDE
http://projects.kde.org/ website, please use b[$projectID] for the value of
the b[repository] option.
EOF
die make_exception('Config', 'Unknown repository base');
}
if ($optionSet{'repository'} eq KDE_PROJECT_ID) {
# 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.
bless $moduleSet, 'ksb::ModuleSet::KDEProjects';
}
return $moduleSet;
}
# Function: _readConfigurationOptions
#
# Reads in the settings from the configuration, passed in as an open
# filehandle.
#
# Phase:
# initialization - Do not call <finish> from this function.
#
# Parameters:
# ctx - The <BuildContext> to update based on the configuration read.
# filehandle - The I/O object to read from. Must handle _eof_ and _readline_
# methods (e.g. <IO::Handle> subclass).
#
# Returns:
# @module - Heterogenous list of <Modules> and <ModuleSets> 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 @module_list;
my $rcfile = $ctx->rcFile();
my ($option, $modulename, %readModules);
my $fileReader = ksb::RecursiveFH->new();
$fileReader->addFilehandle($fh);
# Read in global settings
while ($_ = $fileReader->readLine())
{
s/#.*$//; # Remove comments
s/^\s*//; # Remove leading whitespace
next if (/^\s*$/); # 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, 'global');
last;
}
my $using_default = 1;
# 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)
($modulename) = /^module\s+([-\/\.\w]+)\s*$/;
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');
}
# A moduleset can give us more than one module to add.
push @module_list, _parseModuleSetOptions($ctx, $fileReader, $modulename);
}
else {
# Overwrite options set for existing modules.
if (my @modules = grep { $_->name() eq $modulename } @module_list) {
# We check for definedness as a module-set can exist but be
# unnamed.
if ($modules[0]->moduleSet()->isa('ksb::ModuleSet::Null')) {
warning ("Multiple module declarations for $modules[0]");
}
_parseModuleOptions($ctx, $fileReader, $modules[0]); # Don't re-add
}
else {
push @module_list, _parseModuleOptions($ctx, $fileReader, $modulename);
}
}
# Don't build default modules if user has their own wishes.
$using_default = 0;
}
# 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: _spliceOptionModules
#
# Replaces any modules in a given list that have a name matching that of a
# "option module" with that option module inline. Modules that have no "option
# module" match are unchanged.
#
# Parameters:
# @$modules - Listref of modules to potentially splice in replacements of.
# @$optionModules - Listref to list of the "option" modules (and module-sets),
# which should be of the same level of kde-project expansion as @$modules. A
# module-set might be spliced in to replace a named module.
#
# Returns:
# Nothing.
sub _spliceOptionModules
{
my ($modulesRef, $optionModulesRef) = @_;
for (my $i = 0; $i < scalar @{$modulesRef}; $i++) {
my $module = ${$modulesRef}[$i];
my ($optionModule) = grep {
$_->name() eq $module->name()
} @{$optionModulesRef};
splice @$modulesRef, $i, 1, $optionModule if defined $optionModule;
}
}
# 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 - <BuildContext> in use.
# @modules - List of <Modules> or <ModuleSets> to apply filters on.
#
# Returns:
# list of <Modules> or <ModuleSets> 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 (<<EOF);
You specified both r[b[--resume-from] and r[b[--resume-after] but you can only
use one.
EOF
croak_runtime("Both --resume-after and --resume-from specified.");
}
if ($ctx->getOption('stop-before') && $ctx->getOption('stop-after'))
{
# This one's an error.
error (<<EOF);
You specified both r[b[--stop-before] and r[b[--stop-after] but you can only
use one.
EOF
croak_runtime("Both --stop-before and --stop-from specified.");
}
return unless @moduleList; # Empty input?
my $resumePoint = $ctx->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];
}
# Function: _expandModuleSets
#
# Replaces <ModuleSets> in an input list from the command line that name
# module-sets listed in the configuration file, and returns the new list.
#
# <Modules> are ignored if found in the input list, and transferred to the
# output list in the same relative order.
#
# This function may result in kde-projects metadata being downloaded and
# processed.
#
# Parameters:
# $ctx - <BuildContext> in use for this script execution.
# @modules - list of <Modules>, <ModuleSets> to be expanded.
#
# Returns:
# $metadataModule - a <Module> to use if needed for kde-projects support, can be
# undef if not actually required this run.
# @modules - List of <Modules> with any module-sets expanded into <Modules>.
sub _expandModuleSets
{
my ($ctx, @buildModuleList) = @_;
my $filter = sub {
my $moduleOrSetName = $_->name();
# 'proj' module types can only come from command line -- we assume the
# user is trying to build a module from the kde-projects repo without
# first putting into rc-file.
if ($_->isa('ksb::Module') && $_->scmType() ne 'proj') {
return $_;
}
if ($_->isa('ksb::ModuleSet')) {
return $_->convertToModules($ctx);
}
my $moduleSet = ksb::ModuleSet::KDEProjects->new($ctx, '<command line>');
$moduleSet->setModulesToFind($_->name());
$moduleSet->{options}->{'#guessed-kde-project'} = 1;
debug ("--- Trying to find a home for $_");
return $moduleSet->convertToModules($ctx);
};
my @moduleResults = map { &$filter } (@buildModuleList);
my $metadataModule;
if (first { $_->scmType() eq 'proj' } @moduleResults) {
debug ("Introducing metadata module into the build");
$metadataModule = ksb::ModuleSet::KDEProjects::getMetadataModule($ctx);
assert_isa($metadataModule, 'ksb::Module');
}
return ($metadataModule, @moduleResults);
}
# 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;
}
# Method: _processCmdlineArguments
#
# Processes the command line arguments, which are used to modify the given
# <BuildContext> and possibly return a list of <Modules>.
#
# This is a package method, should be called as $app->_processCmdlineArguments
#
# Phase:
# initialization - Do not call <finish> from this function.
#
# Parameters:
# ctx - BuildContext in use.
# pendingOptions - hashref to hold parsed modules options to be applied later.
# *Note* this must be done separately, it is not handled by this subroutine.
# @options - The remainder of the arguments are treated as command line
# arguments to process.
#
# Returns:
# - List of <ksb::Modules> that represent modules specifically entered on the
# command-line, _or_
# - List of options to pass to a command named by the --run command line
# option. (This is true if and only if the _ctx_ ends up with the
# _#start-program_ option set).
sub _processCmdlineArguments
{
my $self = shift;
my $ctx = assert_isa(shift, 'ksb::BuildContext');
my $pendingOptions = shift;
my $phases = $ctx->phases();
my @savedOptions = @_; # Used for --debug
my @options = @_;
my $arg;
my $version = "kdesrc-build $SCRIPT_VERSION";
my $author = <<DONE;
$version was written (mostly) by:
Michael Pyne <mpyne\@kde.org>
Many people have contributed code, bugfixes, and documentation.
Please report bugs using the KDE Bugzilla, at http://bugs.kde.org/
DONE
my @enteredModules;
while ($_ = shift @options)
{
SWITCH: {
/^(--version)$/ && do { print "$version\n"; exit; };
/^--author$/ && do { print $author; exit; };
/^(-h)|(--?help)$/ && do {
print <<DONE;
$version
http://kdesrc-build.kde.org/
This script automates the download, build, and install process for KDE software
using the latest available source code.
You should first setup a configuration file (~/.kdesrc-buildrc). You can do
this by running the kdesrc-build-setup program, which should be included with
this one. You can also copy the kdesrc-buildrc-sample file (which should be
included) to ~/.kdesrc-buildrc.
Basic synopsis, after setting up .kdesrc-buildrc:
\$ $0 [--options] [module names]
The module names can be either the name of an individual module (as set in your
configuration with a module declaration, or a use-modules declaration), or of a
module set (as set with a module-set declaration).
If you don\'t specify any particular module names, then every module you have
listed in your configuration will be built, in the order listed.
Copyright (c) 2003 - 2013 $author
The script is distributed under the terms of the GNU General Public License
v2, and includes ABSOLUTELY NO WARRANTY!!!
Options:
--no-src Skip contacting the source server.
--no-build Skip the build process.
--no-install Don't automatically install after build.
--pretend Don't actually take major actions, instead describe
what would be done.
--src-only Only update the source code (Identical to --no-build
at this point).
--build-only Build only, don't perform updates or install.
--rc-file=<filename> Read configuration from filename instead of default.
--resume-from=<pkg> Skips modules until just before the given package,
then operates as normal.
--resume-after=<pkg> Skips modules up to and including the given package,
then operates as normal.
--stop-before=<pkg> Skips the given package and all later packages.
--stop-after=<pkg> Skips all packages after the given package.
--reconfigure Run CMake/configure again, but don't clean the build
directory.
--build-system-only Create the build infrastructure, but don't actually
perform the build.
--<option>= Any unrecognized options are added to the global
configuration, overriding any value that may exist.
--<module>,<option>= Likewise, this allows you to override any module
specific option from the command line.
--pretend (or -p) Don't actually contact the source server, run make,
or create/delete files and directories. Instead,
output what the script would have done.
--refresh-build Start the build from scratch.
--help You\'re reading it. :-)
--version Output the program version.
You can get more help by going online to http://kdesrc-build.kde.org/ to view
the online documentation. If you have installed kdesrc-build you may also be
able to view the documentation using KHelpCenter or Konqueror at the URL
help:/kdesrc-build
DONE
# We haven't done any locking... no need to finish()
exit 0;
};
/^--install$/ && do {
$self->{run_mode} = 'install';
$phases->phases('install');
last SWITCH;
};
/^--uninstall$/ && do {
$self->{run_mode} = 'uninstall';
$phases->phases('uninstall');
last SWITCH;
};
/^--no-snapshots$/ && do {
$ctx->setOption('#disable-snapshots', 1);
last SWITCH;
};
/^--no-(src|svn)$/ && do {
$phases->filterOutPhase('update');
last SWITCH;
};
/^--no-install$/ && do {
$phases->filterOutPhase('install');
last SWITCH;
};
/^--no-tests$/ && do {
# The "right thing" to do
$phases->filterOutPhase('test');
# What actually works at this point.
$ctx->setOption('#run-tests', 0);
last SWITCH;
};
/^--(force-build)|(no-build-when-unchanged)$/ && do {
$ctx->setOption('#build-when-unchanged', 1);
last SWITCH;
};
/^(-v)|(--verbose)$/ && do {
$ctx->setOption('#debug-level', ksb::Debug::WHISPER);
last SWITCH;
};
/^(-q)|(--quiet)$/ && do {
$ctx->setOption('#debug-level', ksb::Debug::NOTE);
last SWITCH;
};
/^--really-quiet$/ && do {
$ctx->setOption('#debug-level', ksb::Debug::WARNING);
last SWITCH;
};
/^--debug$/ && do {
$ctx->setOption('#debug-level', ksb::Debug::DEBUG);
debug ("Commandline was: ", join(', ', @savedOptions));
last SWITCH;
};
/^--reconfigure$/ && do {
$ctx->setOption('#reconfigure', 1);
last SWITCH;
};
/^--color$/ && do {
$ctx->setOption('#colorful-output', 1);
last SWITCH;
};
/^--no-color$/ && do {
$ctx->setOption('#colorful-output', 0);
last SWITCH;
};
/^--no-build$/ && do {
$phases->filterOutPhase('build');
last SWITCH;
};
/^--async$/ && do {
$ctx->setOption('#async', 1);
last SWITCH;
};
/^--no-async$/ && do {
$ctx->setOption('#async', 0);
last SWITCH;
};
# Although equivalent to --no-build at this point, someday the
# script may interpret the two differently, so get ready now.
/^--(src|svn)-only$/ && do { # Identically to --no-build
$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.
$ctx->setOption('#allow-auto-repo-move', 1);
last SWITCH;
};
# Don't run source updates or install
/^--build-only$/ && do {
$phases->phases('build');
last SWITCH;
};
# Start up a program with the environment variables as
# read from the config file.
/^--run=?/ && do {
my $program = _extractOptionValueRequired($_, \@options);
$ctx->setOption('#start-program', $program);
# Save remaining command line options to pass to the program.
return @options;
};
/^--build-system-only$/ && do {
$ctx->setOption('#build-system-only', 1);
last SWITCH;
};
/^--rc-file=?/ && do {
my $rcfile = _extractOptionValueRequired($_, \@options);
$ctx->setRcFile($rcfile);
last SWITCH;
};
/^--prefix=?/ && do {
my $prefix = _extractOptionValueRequired($_, \@options);
$ctx->setOption('#kdedir', $prefix);
$ctx->setOption('#reconfigure', 1);
last SWITCH;
};
/^--nice=?/ && do {
my $niceness = _extractOptionValueRequired($_, \@options);
$ctx->setOption('#niceness', $niceness);
last SWITCH;
};
/^--ignore-modules$/ && do {
# We need to keep _readConfigurationOptions() from adding these
# modules to the build list, taken care of by ignore_list. We
# then need to remove the modules from the command line, taken
# care of by the @options = () statement;
my @innerOptions = ();
foreach (@options)
{
if (/^-/)
{
push @innerOptions, $_;
}
else
{
$ignore_list{$_} = 1;
# the pattern match doesn't work with $_, alias it.
my $module = $_;
@enteredModules = grep (!/^$module$/, @enteredModules);
}
}
@options = @innerOptions;
last SWITCH;
};
/^(--dry-run)|(--pretend)|(-p)$/ && do {
$ctx->setOption('#pretend', 1);
# Simulate the build process too.
$ctx->setOption('#build-when-unchanged', 1);
last SWITCH;
};
/^--refresh-build$/ && do {
$ctx->setOption('#refresh-build', 1);
last SWITCH;
};
/^--delete-my-patches$/ && do {
$ctx->setOption('#delete-my-patches', 1);
last SWITCH;
};
/^--delete-my-settings$/ && do {
$ctx->setOption('#delete-my-settings', 1);
last SWITCH;
};
/^(--revision|-r)=?/ && do {
my $revision = _extractOptionValueRequired($_, \@options);
$ctx->setOption('#revision', $revision);
last SWITCH;
};
/^--resume-from=?/ && do {
$_ = _extractOptionValueRequired($_, \@options);
$ctx->setOption('#resume-from', $_);
last SWITCH;
};
/^--resume-after=?/ && do {
$_ = _extractOptionValueRequired($_, \@options);
$ctx->setOption('#resume-after', $_);
last SWITCH;
};
/^--stop-after=?/ && do {
$_ = _extractOptionValueRequired($_, \@options);
$ctx->setOption('#stop-after', $_);
last SWITCH;
};
/^--stop-before=?/ && do {
$_ = _extractOptionValueRequired($_, \@options);
$ctx->setOption('#stop-before', $_);
last SWITCH;
};
/^--/ && do {
# First let's see if they're trying to override a global option.
my ($option) = /^--([-\w\d\/]+)/;
my $value = _extractOptionValue($_, \@options);
if ($ctx->hasOption($option))
{
$ctx->setOption("#$option", $value);
}
else
{
# Module specific option. The module options haven't been
# read in, so we'll just have to assume that the module the
# user passes actually does exist.
my ($module, $option) = /^--([\w\/-]+),([-\w\d\/]+)/;
if (not $module)
{
print "Unknown option $_\n";
exit 8;
}
${$pendingOptions}{$module}{"$option"} = $value;
}
last SWITCH;
};
/^-/ && do { print "WARNING: Unknown option $_\n"; last SWITCH; };
# Strip trailing slashes.
s/\/*$//;
push @enteredModules, $_; # Reconstruct correct @options
}
}
# Don't go async if only performing one phase. It (should) work but why
# risk it?
if (scalar $phases->phases() == 1)
{
$ctx->setOption('#async', 0);
}
return map {
my $module = ksb::Module->new($ctx, $_);
# Following will be replaced by option modules if present in rc-file.
$module->setScmType('proj');
$module->setOption('#guessed-kde-project', 1);
$module->phases()->phases($phases->phases());
$module;
} (@enteredModules);
}
# Accessors
sub context
{
my $self = shift;
return $self->{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;