diff --git a/kdesrc-build b/kdesrc-build index b8a5e17..cbffcd9 100755 --- a/kdesrc-build +++ b/kdesrc-build @@ -1,313 +1,387 @@ #!/usr/bin/env perl # Script to handle building KDE from source code. All of the configuration is # stored in the file ./kdesrc-buildrc (or ~/.kdesrc-buildrc, if that's not # present). # # Please also see the documentation that should be included with this program, # in the doc/ directory. # # Copyright © 2003 - 2018 Michael Pyne. # Home page: https://kdesrc-build.kde.org/ # # Copyright © 2005, 2006, 2008 - 2011 David Faure # Copyright © 2005 Thiago Macieira # Copyright © 2006 Stephan Kulow # Copyright © 2006, 2008 Dirk Mueller # ... and possibly others. Check the git source repository for specifics. # # 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 # Adding an option? Grep for 'defaultGlobalOptions' in ksb::BuildContext --mpyne -use FindBin qw($RealBin); -use lib "$RealBin/../share/kdesrc-build/modules"; -use lib "$RealBin/modules"; - -# Force all symbols to be in this package. We can tell if we're being called -# through require/eval/etc. by using the "caller" function. -package main; - +use 5.014; # Require Perl 5.14 use strict; use warnings; -use Carp; -use Data::Dumper; -use File::Find; # For our lndir reimplementation. -use File::Path qw(remove_tree); +# On many container-based distros, even FindBin is missing to conserve space. +# But we can use File::Spec to do nearly the same. +my $RealBin; +my $modPath; -use ksb::Debug; -use ksb::Util; -use ksb::Version qw(scriptVersion); -use ksb::Application; +# The File::Spec calls have to run when parsing (i.e. in BEGIN) to make the +# 'use lib' below work (which itself implicitly uses BEGIN { }) +BEGIN { + use File::Spec; + my ($volume, $directories, $script) = File::Spec->splitpath($0); -use 5.014; # Require Perl 5.14 + $RealBin = File::Spec->catpath($volume, $directories, ''); + die "Couldn't find base directory!" unless $RealBin; + + # Use modules in git repo if running from git dir, otherwise assume + # system install + $modPath = File::Spec->rel2abs('modules', $RealBin); + $modPath = File::Spec->rel2abs('../share/kdesrc-build/modules', $RealBin) + unless -d $modPath; + + die "Couldn't find modules for kdesrc-build!" unless $modPath; +} + +use lib "$modPath"; # Make ksb:: modules available + +sub dumpError +{ + my $err = $@; + open my $fh, '>>', "error-$$.log" or return; + my $time = localtime; + say $fh $time; + say $fh $@; +} + +# When running in a limited environment, we might not be able to load +# our modules although we can find them. In this case we should help user +# by setting up system dependencies. +eval { + if (grep { $_ eq '--initial-setup' } @ARGV) { + require ksb::FirstRun; + require ksb::Debug; + ksb::Debug::setColorfulOutput(1); + exit ksb::FirstRun::setupUserSystem($RealBin); + } +}; + +if ($@) { + dumpError(); + say STDERR <import(); +ksb::Util->import(); +ksb::BuildException->import(); +ksb::Version->import(qw(scriptVersion)); +ksb::Application->import(); # Make Perl 'plain die' exceptions use Carp::confess instead of their core # support. This is not supported by the Perl 5 authors but assuming it works # will be better than the alternative backtrace we get (which is to say, none) $SIG{__DIE__} = \&Carp::confess; -$ksb::Version::SCRIPT_PATH = $RealBin; +ksb::Version->path($RealBin); ### Script-global functions. # These functions might be called at runtime via log_command, using # log_command's support for symbolic execution of a named subroutine. Because # of that, they have been left in the top-level script. # # Everything else should be in an appropriate class. # Subroutine to recursively symlink a directory into another location, in a # similar fashion to how the XFree/X.org lndir() program does it. This is # reimplemented here since some systems lndir doesn't seem to work right. # # Used from ksb::l10nSystem # # As a special exception to the GNU GPL, you may use and redistribute this # function however you would like (i.e. consider it public domain). # # The first parameter is the directory to symlink from. # The second parameter is the destination directory name. # # e.g. if you have $from/foo and $from/bar, lndir would create $to/foo and # $to/bar. # # All intervening directories will be created as needed. In addition, you # may safely run this function again if you only want to catch additional files # in the source directory. # # Note that this function will unconditionally output the files/directories # created, as it is meant to be a close match to lndir. # # RETURN VALUE: Boolean true (non-zero) if successful, Boolean false (0, "") # if unsuccessful. sub safe_lndir { my ($from, $to) = @_; # Create destination directory. if (not -e $to) { print "$to\n"; if (not pretending() and not super_mkdir($to)) { error ("Couldn't create directory r[$to]: b[r[$!]"); return 0; } } # Create closure callback subroutine. my $wanted = sub { my $dir = $File::Find::dir; my $file = $File::Find::fullname; $dir =~ s/$from/$to/; # Ignore the .svn directory and files. return if $dir =~ m,/\.svn,; # Create the directory. if (not -e $dir) { print "$dir\n"; if (not pretending()) { super_mkdir ($dir) or croak_runtime("Couldn't create directory $dir: $!"); } } # Symlink the file. Check if it's a regular file because File::Find # has no qualms about telling you you have a file called "foo/bar" # before pointing out that it was really a directory. if (-f $file and not -e "$dir/$_") { print "$dir/$_\n"; if (not pretending()) { symlink $File::Find::fullname, "$dir/$_" or croak_runtime("Couldn't create file $dir/$_: $!"); } } }; # Recursively descend from source dir using File::Find eval { find ({ 'wanted' => $wanted, 'follow_fast' => 1, 'follow_skip' => 2}, $from); }; if ($@) { error ("Unable to symlink $from to $to: $@"); return 0; } return 1; } # Subroutine to delete recursively, everything under the given directory, # unless we're in pretend mode. # # Used from ksb::BuildSystem to handle cleaning a build directory. # # i.e. the effect is similar to "rm -r $arg/* $arg/.*". # # This assumes we're called from a separate child process. Therefore the # normal logging routines are /not used/, since our output will be logged # by the parent kdesrc-build. # # The first parameter should be the absolute path to the directory to delete. # # Returns boolean true on success, boolean false on failure. sub prune_under_directory { my $dir = shift; my $errorRef; print "starting delete of $dir\n"; eval { remove_tree($dir, { keep_root => 1, error => \$errorRef }); }; if ($@ || @$errorRef) { error ("\tUnable to clean r[$dir]:\n\ty[b[$@]"); return 0; } return 1; } sub findMissingModules { # should be either strings of module names to be found or a listref containing # a list of modules where any one of which will work. my @requiredModules = ( 'HTTP::Tiny', 'IO::Socket::SSL', [qw(JSON::XS JSON::PP)], [qw(YAML::XS YAML::PP YAML::Syck)] ); my @missingModules; my $validateMod = sub { return eval "require $_[0]; 1;"; }; my $description; foreach my $neededModule (@requiredModules) { if (ref $neededModule) { # listref of options my @moduleOptions = @$neededModule; next if (ksb::Util::any (sub { $validateMod->($_); }, $neededModule)); $description = 'one of (' . join(', ', @moduleOptions) . ')'; } else { next if $validateMod->($neededModule); $description = $neededModule; } push @missingModules, $description; } return @missingModules; } # Script starts. # Ensure some critical Perl modules are available so that the user isn't surprised # later with a Perl exception if(my @missingModuleDescriptions = findMissingModules()) { say <new(@ARGV); - # Hack for debugging current state. - if (exists $ENV{KDESRC_BUILD_DUMP_CONTEXT}) { - local $Data::Dumper::Indent = 1; - local $Data::Dumper::Sortkeys = 1; - - # This method call dumps the first list with the variables named by the - # second list. - print Data::Dumper->Dump([$app->context()], [qw(ctx)]); - } - push @atexit_subs, sub { $app->finish(99) }; my $result = $app->runAllModulePhases(); @atexit_subs = (); # Clear exit handlers $app->finish($result); }; if (my $err = $@) { if (had_an_exception()) { print "kdesrc-build encountered an exceptional error condition:\n"; print " ========\n"; print " $err\n"; print " ========\n"; print "\tCan't continue, so stopping now.\n"; if ($err->{'exception_type'} eq 'Internal') { print "\nPlease submit a bug against kdesrc-build on https://bugs.kde.org/\n" } } else { # We encountered an error. print "Encountered an error in the execution of the script.\n"; print "The error reported was $err\n"; print "Please submit a bug against kdesrc-build on https://bugs.kde.org/\n"; } exit 99; } # vim: set et sw=4 ts=4 fdm=marker: diff --git a/modules/ksb/FirstRun.pm b/modules/ksb/FirstRun.pm index 0c037b5..160e1cf 100644 --- a/modules/ksb/FirstRun.pm +++ b/modules/ksb/FirstRun.pm @@ -1,192 +1,246 @@ 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(); + _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*#/; + next if $line =~ /^\s*#/ and $cur_file !~ /sample-rc/; chomp $line; my ($fname) = ($line =~ /^@@ *([^ ]+)$/); if ($fname) { $commit->(); $cur_file = $fname; - $cur_value = ''; } else { - $cur_value .= "$line "; + $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; - my @packages = _findBestVendorPackageList($os); - say colorize(<splitpath($0); - _throw("Can't find setup script") - unless -e "$baseDir/kdesrc-build-setup" && -x _; - - my $result = system("$baseDir/kdesrc-build-setup"); - _throw("setup script failed: $!") - unless ($result >> 8) == 0; + + my $sampleRc = $packages{'sample-rc'} or + _throw("Embedded sample file missing!"); + + my $numCpus = `nproc 2>/dev/null` || 4; + $sampleRc =~ s/%\{num_cpus}/$numCpus/; + $sampleRc =~ s/%\{base_dir}/$baseDir/; + + 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); - return _packagesForVendor($bestVendor); + my $version = $os->vendorVersion(); + say colorize (" Installing packages for b[$bestVendor]/b[$version]"); + return _packagesForVendor($bestVendor, $version); } sub _packagesForVendor { - my $vendor = shift; + my ($vendor, $version) = @_; my $packagesRef = _readPackages(); - my @opts = grep { /^pkg\/$vendor\b/ } keys %{$packagesRef}; - # TODO Narrow to one set based on distro version - my @packages; - foreach my $opt (@opts) { - @packages = split(' ', $packagesRef->{$opt}); + foreach my $opt ("pkg/$vendor/$version", "pkg/$vendor/unknown") { + next unless exists $packagesRef->{$opt}; + my @packages = split(' ', $packagesRef->{$opt}); + return @packages; } - return @packages; + return; } 1; __DATA__ @@ pkg/debian/unknown shared-mime-info -@@ pkg/opensuse/tumbleweed -shared-mime-info +@@ pkg/opensuse/unknown +perl perl-IO-Socket-SSL perl-JSON perl-YAML-LibYAML +git shared-mime-info @@ pkg/fedora/unknown git @@ pkg/gentoo/unknown dev-util/cmake dev-lang/perl @@ pkg/arch/unknown perl-json + +@@ 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 + + # 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 + + cmake-options -DCMAKE_BUILD_TYPE=RelWithDebInfo + make-options -j%{num_cpus} +end global + +# With base options set, the remainer 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. + +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/Version.pm b/modules/ksb/Version.pm index f72fb9c..2eefed7 100644 --- a/modules/ksb/Version.pm +++ b/modules/ksb/Version.pm @@ -1,36 +1,42 @@ package ksb::Version; # Pretty much just records the program-wide version number... use strict; use warnings; use 5.014; use IPC::Cmd qw(run can_run); # It is expected that future git tags will be in the form 'YY.MM' and will # be time-based instead of event-based as with previous releases. our $VERSION = '18.10'; -our $SCRIPT_PATH = ''; # For auto git-versioning +my $SCRIPT_PATH = ''; # For auto git-versioning our $SCRIPT_VERSION = $VERSION; use Exporter qw(import); our @EXPORT = qw(scriptVersion); +sub path +{ + my ($self, $newPath) = @_; + $SCRIPT_PATH = $newPath // $SCRIPT_PATH; +} + sub scriptVersion() { if ($SCRIPT_PATH && can_run('git') && -d "$SCRIPT_PATH/.git") { my ($ok, $err_msg, undef, $stdout) = IPC::Cmd::run( command => ['git', "--git-dir=$SCRIPT_PATH/.git", 'describe'], verbose => 0); my $output = $stdout->[0] // ''; chomp $output; return "$SCRIPT_VERSION ($output)" if ($ok and $output); } return $SCRIPT_VERSION; } 1; diff --git a/t/os-release-basics.t b/t/os-release-basics.t index afe87bf..5248ad9 100644 --- a/t/os-release-basics.t +++ b/t/os-release-basics.t @@ -1,30 +1,30 @@ use 5.014; use strict; use warnings; # Test ksb::OSSupport use Test::More; use ksb::OSSupport; # Unit test of _readOSRelease my @kvPairs = ksb::OSSupport->_readOSRelease('t/data/os-release'); is(scalar @kvPairs, 4, 'Right number of key/value pairs'); my %opts = map { @{$_}[0,1] } @kvPairs; is($opts{NAME}, 'Totally Valid Name', 'Right NAME'); is($opts{ID}, 'kdesrc-build', 'Right ID'); is($opts{ID_LIKE}, 'sabayon gentoo-hardened gentoo', 'Right ID_LIKE'); is($opts{SPECIAL}, '$VAR \\ ` " is set', 'Right SPECIAL'); # Use tests my $os = new_ok('ksb::OSSupport', ['t/data/os-release']); is($os->bestDistroMatch(qw/arch kdesrc-build sabayon/), 'kdesrc-build', 'ID preferred'); is($os->bestDistroMatch(qw/ubuntu fedora gentoo/), 'gentoo', 'ID_LIKE respected'); -is($os->bestDistroMatch(qw/fedora gentoo gentoo-hardened sabayon/), 'gentoo', 'ID_LIKE preference order proper'); +is($os->bestDistroMatch(qw/fedora gentoo gentoo-hardened sabayon/), 'sabayon', 'ID_LIKE preference order proper'); is($os->vendorID, 'kdesrc-build', 'Right ID'); done_testing();