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.
232 lines
7.7 KiB
232 lines
7.7 KiB
package ksb::DependencyResolver; |
|
|
|
# This module handles resolving dependencies between modules. Each "module" |
|
# from the perspective of this resolver is simply a module full name, as |
|
# given by the KDE Project database. (e.g. extragear/utils/kdesrc-build) |
|
|
|
use strict; |
|
use warnings; |
|
use v5.10; |
|
|
|
our $VERSION = '0.10'; |
|
|
|
use ksb::Debug; |
|
use ksb::Util; |
|
use List::Util qw(first); |
|
|
|
sub new |
|
{ |
|
my $class = shift; |
|
|
|
my $self = { |
|
# hash table mapping full module names (m) to a list reference |
|
# containing the full module names of modules that depend on m. |
|
dependenciesOf => { }, |
|
}; |
|
|
|
return bless $self, $class; |
|
} |
|
|
|
# Reads in dependency data in a psuedo-Makefile format. |
|
# See kde-build-metadata/dependency-data. |
|
# |
|
# Object method. |
|
# First parameter is the filehandle to read from. |
|
sub readDependencyData |
|
{ |
|
my $self = assert_isa(shift, 'ksb::DependencyResolver'); |
|
my $fh = shift; |
|
|
|
my $dependenciesOfRef = $self->{dependenciesOf}; |
|
my $dependencyAtom = |
|
qr/ |
|
^\s* # Clear leading whitespace |
|
([^\[:\s]+) # (1) Capture anything not a [, :, or whitespace (dependent item) |
|
\s* # Clear whitespace we didn't capture |
|
(?:\[ # Open a non-capture group... |
|
([^\]:\s]+) # (2) Capture branch name without brackets |
|
])?+ # Close group, make optional, no backtracking |
|
\s* # Clear whitespace we didn't capture |
|
: |
|
\s* |
|
([^\s\[]+) # (3) Capture all non-whitespace (source item) |
|
(?:\s*\[ # Open a non-capture group... |
|
([^\]\s]+) # (4) Capture branch name without brackets |
|
])?+ # Close group, make optional, no backtracking |
|
\s*$ # Ensure no trailing cruft. Any whitespace should end line |
|
/x; # /x Enables extended whitespace mode |
|
|
|
while(my $line = <$fh>) { |
|
# Strip comments, skip empty lines. |
|
$line =~ s{#.*$}{}; |
|
next if $line =~ /^\s*$/; |
|
|
|
if ($line !~ $dependencyAtom) { |
|
croak_internal("Invalid line $line when reading dependency data."); |
|
} |
|
|
|
my ($dependentItem, $dependentBranch, |
|
$sourceItem, $sourceBranch) = $line =~ $dependencyAtom; |
|
|
|
# Ignore "doesn't use" markers. |
|
next if index($sourceItem, '-') == 0; |
|
|
|
# Ignore "catch-all" dependencies. |
|
next if $sourceItem =~ m,/\*$, || $dependentItem =~ m,/\*$,; |
|
|
|
# Ignore deps on Qt, since we allow system Qt. |
|
next if $sourceItem =~ /^\s*Qt/ || $dependentItem =~ /^\s*Qt/; |
|
|
|
# TODO: Utilize branch information in dependency tracking. |
|
if ($dependentBranch || $sourceBranch) { |
|
ksb::Debug::whisper ("$dependentItem only partially depends on $sourceItem"); |
|
next; |
|
} |
|
|
|
# Initialize with array if not already defined. |
|
$dependenciesOfRef->{$dependentItem} //= [ ]; |
|
|
|
push @{$dependenciesOfRef->{$dependentItem}}, $sourceItem; |
|
} |
|
} |
|
|
|
# Internal. |
|
# This method adds any full module names as dependencies of any module that |
|
# begins with that full module name. E.g. kde/kdelibs/foo automatically |
|
# depends on kde/kdelibs if both are present in the build. |
|
# |
|
# Static method. |
|
# First parameter: Reference to a hash of parameters. |
|
# Return: Nothing. |
|
sub _addInherentDependencies |
|
{ |
|
my $optionsRef = shift; |
|
my $dependenciesOfRef = $optionsRef->{dependenciesOf}; |
|
my $modulesFromNameRef = $optionsRef->{modulesFromName}; |
|
|
|
# It's not good enough to just sort modules and compare one to its |
|
# successor. Consider kde/foo, kde/foobar, kde/foo/a. The dependency |
|
# here would be missed that way. Instead we strip off the last path |
|
# component and see if that matches an existing module name. |
|
for my $testModule (keys %{$modulesFromNameRef}) { |
|
my $candidateBaseModule = $testModule; |
|
|
|
# Remove trailing component, bail if unable to do so. |
|
next unless $candidateBaseModule =~ s(/[^/]+$)(); |
|
|
|
if ($candidateBaseModule && |
|
exists $modulesFromNameRef->{$candidateBaseModule}) |
|
{ |
|
# Add candidateBaseModule as dependency of testModule. |
|
$dependenciesOfRef->{$testModule} //= [ ]; |
|
|
|
my $moduleDepsRef = $dependenciesOfRef->{$testModule}; |
|
if (!first { $_ eq $candidateBaseModule } @{$moduleDepsRef}) { |
|
debug ("dep-resolv: Adding $testModule as dependency of $candidateBaseModule"); |
|
push @{$moduleDepsRef}, $candidateBaseModule; |
|
} |
|
} |
|
} |
|
} |
|
|
|
# Internal. |
|
# This method is used to topographically sort dependency data. It accepts |
|
# a ksb::Module, ensures that any KDE Projects it depends on are already on the |
|
# build list, and then adds the ksb::Module to the build list (whether it is |
|
# a KDE Project or not, to preserve ordering). |
|
# |
|
# Static method. |
|
# First parameter: Reference to a hash of parameters. |
|
# Second parameter: ksb::Module to "visit". Does not have to be a KDE Project. |
|
# Return: Nothing. |
|
sub _visitModuleAndDependencies |
|
{ |
|
my ($optionsRef, $module) = @_; |
|
assert_isa($module, 'ksb::Module'); |
|
|
|
my $visitedItemsRef = $optionsRef->{visitedItems}; |
|
my $properBuildOrderRef = $optionsRef->{properBuildOrder}; |
|
my $dependenciesOfRef = $optionsRef->{dependenciesOf}; |
|
my $modulesFromNameRef = $optionsRef->{modulesFromName}; |
|
|
|
my $item = $module->getOption('#xml-full-path'); |
|
|
|
if (!$item) { |
|
push @{$properBuildOrderRef}, $module; |
|
return; |
|
} |
|
|
|
debug ("dep-resolv: Visiting $item"); |
|
|
|
$visitedItemsRef->{$item} //= 0; |
|
|
|
# This module may have already been added to build. |
|
return if $visitedItemsRef->{$item} == 1; |
|
|
|
# But if the value is 2 that means we've detected a cycle. |
|
if ($visitedItemsRef->{$item} > 1) { |
|
croak_internal("Somehow there is a dependency cycle involving $item! :("); |
|
} |
|
|
|
$visitedItemsRef->{$item} = 2; # Mark as currently-visiting for cycle detection. |
|
for my $subItem (@{$dependenciesOfRef->{$item}}) { |
|
debug ("\tdep-resolv: $item depends on $subItem"); |
|
|
|
my $subModule = $modulesFromNameRef->{$subItem}; |
|
if (!$subModule) { |
|
whisper (" y[b[*] $module depends on $subItem, but no module builds $subItem for this run."); |
|
next; |
|
} |
|
|
|
_visitModuleAndDependencies($optionsRef, $subModule); |
|
} |
|
|
|
$visitedItemsRef->{$item} = 1; # Mark as done visiting. |
|
push @{$properBuildOrderRef}, $module; |
|
return; |
|
} |
|
|
|
# This method takes a list of Modules (real ksb::Module objects, not just module |
|
# names). |
|
# |
|
# These modules have their dependencies resolved, and a new list of Modules |
|
# is returned, containing the proper build order for the module given. |
|
# |
|
# Only "KDE Project" modules can be re-ordered or otherwise affect the |
|
# build so this currently won't affect Subversion modules or "plain Git" |
|
# modules. |
|
# |
|
# The dependency data must have been read in first (readDependencyData). |
|
# |
|
# Object method |
|
# Parameters: Modules to evaluate, in suggested build order. |
|
# Return: Modules to build, with any KDE Project modules in a valid |
|
# ordering based on the currently-read dependency data. |
|
sub resolveDependencies |
|
{ |
|
my $self = assert_isa(shift, 'ksb::DependencyResolver'); |
|
my @modules = @_; |
|
|
|
my $optionsRef = { |
|
visitedItems => { }, |
|
properBuildOrder => [ ], |
|
dependenciesOf => $self->{dependenciesOf}, |
|
|
|
# will map names back to their Modules |
|
modulesFromName => { |
|
map { $_->getOption('#xml-full-path') => $_ } @modules |
|
}, |
|
}; |
|
|
|
# Adds things like kde/kdelibs/foo to automatically depend on |
|
# kde/kdelibs if both are present in the build. |
|
_addInherentDependencies($optionsRef); |
|
|
|
for my $module (@modules) { |
|
_visitModuleAndDependencies($optionsRef, $module); |
|
} |
|
|
|
return @{$optionsRef->{properBuildOrder}}; |
|
} |
|
|
|
1;
|
|
|