diff --git a/CMakeLists.txt b/CMakeLists.txt index b90224a..6800a74 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,10 @@ if (KDESRC_BUILD_INSTALL_MODULES) modules/ksb/BuildSystem/Qt4.pm DESTINATION ${KDESRC_BUILD_MODULE_INSTALL_PREFIX}/ksb/BuildSystem) + install(FILES + modules/ksb/Module/BranchGroupResolver.pm + DESTINATION ${KDESRC_BUILD_MODULE_INSTALL_PREFIX}/ksb/Module) + install(FILES modules/ksb/ModuleSet/KDEProjects.pm modules/ksb/ModuleSet/Null.pm diff --git a/modules/ksb/BuildContext.pm b/modules/ksb/BuildContext.pm index 20c1ce1..8c16234 100644 --- a/modules/ksb/BuildContext.pm +++ b/modules/ksb/BuildContext.pm @@ -22,6 +22,7 @@ use ksb::Debug; use ksb::Util; use ksb::PhaseList; use ksb::Module; +use ksb::Module::BranchGroupResolver; use ksb::Updater::KDEProjectMetadata; use ksb::Version qw(scriptVersion); use File::Temp qw(tempfile); @@ -44,6 +45,7 @@ my %defaultGlobalOptions = ( "async" => 1, "binpath" => '', "branch" => "", + "branch-group" => "", # Overrides branch, uses JSON data. "build-dir" => "build", "build-system-only" => "", "build-when-unchanged" => 1, # Safe default @@ -137,6 +139,7 @@ sub new ignore_list => [ ], # List of XML paths to ignore completely. kde_projects_filehandle => undef, # Filehandle to read database from. kde_projects_metadata => undef, # See ksb::Module::KDEProjects + logical_module_resolver => undef, # For branch-group option. ); # Merge all new options into our self-hash. @@ -943,4 +946,26 @@ sub setKDEProjectMetadataModule return; } +# 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 +# http://community.kde.org/Infrastructure/Project_Metadata. +sub moduleBranchGroupResolver +{ + my $self = shift; + + if (!$self->{logical_module_resolver}) { + my $metadataModule = $self->getKDEProjectMetadataModule(); + + 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}; +} + 1; diff --git a/modules/ksb/Module.pm b/modules/ksb/Module.pm index 4b2c551..ce43857 100644 --- a/modules/ksb/Module.pm +++ b/modules/ksb/Module.pm @@ -934,6 +934,19 @@ sub fullpath return $pathinfo{'fullpath'}; } +# Returns the "full kde-projects path" for the module. As should be obvious by +# the description, this only works for modules with an scm type that is a +# Updater::KDEProject (or its subclasses). +sub fullProjectPath +{ + my $self = shift; + my $path = $self->getOption('#xml-full-path', 'module') || + croak_internal("Tried to ask for full path of a module $_ that doesn't have one!"); + + return $path; +} + + # Subroutine to return the name of the destination directory for the # checkout and build routines. Based on the dest-dir option. The return # value will be relative to the src/build dir. The user may use the diff --git a/modules/ksb/Module/BranchGroupResolver.pm b/modules/ksb/Module/BranchGroupResolver.pm new file mode 100644 index 0000000..5587be7 --- /dev/null +++ b/modules/ksb/Module/BranchGroupResolver.pm @@ -0,0 +1,89 @@ +package ksb::Module::BranchGroupResolver; + +# This provides an object that can be used to lookup the appropriate git branch +# to use for a given KDE project module and given desired logical branch group, using +# supplied JSON data (from kde-build-metadata). +# +# See also http://community.kde.org/Infrastructure/Project_Metadata + +use strict; +use warnings; +use v5.10; + +our $VERSION = '0.10'; + +use List::Util qw(first); + +sub new +{ + my ($class, $jsonData) = @_; + + my $self = { }; + my @keys = qw/layers groups/; + + # Copy just the objects we want over. + @{$self}{@keys} = @{$jsonData}{@keys}; + + # Extract wildcarded groups separately as they are handled separately + # later. Note that the specific catch-all group '*' is itself handled + # as a special case in findModuleBranch. This is important so that + # findModuleBranch can assume all these groups have at least '/*'. + + $self->{wildcardedGroups} = { + map { ($_, $self->{groups}->{$_}) } + grep { substr($_,-2) eq '/*' } + keys %{$self->{groups}} + }; + + bless $self, $class; + return $self; +} + +# Returns the branch for the given logical group and module specifier. This +# function should not be called if the module specifier does not actually +# exist. +sub _findLogicalGroup +{ + my ($self, $module, $logicalGroup) = @_; + + # Using defined-or and still returning undef is on purpose, silences + # warning about use of undefined value. + return $self->{groups}->{$module}->{$logicalGroup} // undef; +} + +sub findModuleBranch +{ + my ($self, $module, $logicalGroup) = @_; + + if (exists $self->{groups}->{$module}) { + return $self->_findLogicalGroup($module, $logicalGroup); + } + + my %catchAllGroupStats = map { + # Map module search spec to prefix string that is required for a match + $_ => substr($_, 0, rindex ($_, '/') + 1) + } keys %{$self->{wildcardedGroups}}; + + # Sort longest required-prefix to the top... first match that is valid will + # then also be the right match. + my @orderedCandidates = sort { + $catchAllGroupStats{$b} cmp $catchAllGroupStats{$a} + } keys %catchAllGroupStats; + + my $match = first { + substr($module, 0, length $catchAllGroupStats{$_}) eq + $catchAllGroupStats{$_} + } @orderedCandidates; + + if ($match) { + return $self->_findLogicalGroup($match, $logicalGroup); + } + + if (exists $self->{groups}->{'*'}) { + return $self->_findLogicalGroup('*', $logicalGroup); + } + + return; +} + +1; diff --git a/modules/ksb/Updater/Git.pm b/modules/ksb/Updater/Git.pm index cf2302b..b27284b 100644 --- a/modules/ksb/Updater/Git.pm +++ b/modules/ksb/Updater/Git.pm @@ -380,8 +380,6 @@ sub updateExistingClone # Returns the user-selected branch for the given module, or 'master' if no # branch was selected. -# -# First parameter is the module name. sub getBranch { my $self = assert_isa(shift, 'ksb::Updater::Git'); diff --git a/modules/ksb/Updater/KDEProject.pm b/modules/ksb/Updater/KDEProject.pm index 63c67b3..1868c94 100644 --- a/modules/ksb/Updater/KDEProject.pm +++ b/modules/ksb/Updater/KDEProject.pm @@ -12,9 +12,36 @@ our $VERSION = '0.10'; use ksb::Updater::Git; our @ISA = qw(ksb::Updater::Git); +use ksb::Debug; + sub name { return 'proj'; } +# Overrides ksb::Updater::Git's version to return the right branch based off +# a logical branch-group, if one is set. +sub getBranch +{ + my $self = shift; + my $module = $self->module(); + my $branchGroup = $module->getOption('branch-group'); + + return $self->SUPER::getBranch() if !$branchGroup; + + # If we're using a logical group we need to query the global build context + # to resolve it. + my $ctx = $module->buildContext(); + my $resolver = $ctx->moduleBranchGroupResolver(); + my $modulePath = $module->fullProjectPath(); + my $branch = $resolver->findModuleBranch($modulePath, $branchGroup); + + if (!$branch) { + whisper ("No specific branch set for $modulePath and $branchGroup, using b[master]"); + $branch = 'master'; + } + + return $branch; +} + 1; diff --git a/modules/ksb/Updater/KDEProjectMetadata.pm b/modules/ksb/Updater/KDEProjectMetadata.pm index 6b9ed2f..20d5774 100644 --- a/modules/ksb/Updater/KDEProjectMetadata.pm +++ b/modules/ksb/Updater/KDEProjectMetadata.pm @@ -7,7 +7,7 @@ use strict; use warnings; use v5.10; -our $VERSION = '0.10'; +our $VERSION = '0.20'; use ksb::Util; use ksb::Debug; @@ -15,6 +15,8 @@ use ksb::Updater::KDEProject; our @ISA = qw(ksb::Updater::KDEProject); +my $haveJson = eval { require JSON; JSON->import(); 1; }; + sub name { return 'metadata'; @@ -41,4 +43,26 @@ sub ignoredModules return @ignoreModules; } +# If JSON support is present, and the metadata has already been downloaded +# (e.g. with ->updateInternal), returns a hashref to the logical module group +# data contained within the kde-build-metadata, decoded from its JSON format. +# See http://community.kde.org/Infrastructure/Project_Metadata +sub logicalModuleGroups +{ + my $self = shift; + my $path = $self->module()->fullpath('source') . "/logical-module-structure"; + + croak_runtime("Logical module groups require the Perl JSON module") + unless $haveJson; + + my $fh = pretend_open($path) or + croak_internal("Unable to read logical module structure: $!"); + + # The 'local $/' disables line-by-line reading; slurps the whole file + my $json_hashref = do { local $/; decode_json(<$fh>); }; + close $fh; + + return $json_hashref; +} + 1;