Commit ff876eb0 authored by Michael Pyne's avatar Michael Pyne

Merge branch 'build-failure-debug-tips' into 'master'

Build failure debug tips

See merge request !12
parents 4cdf15d6 4becb4eb
Pipeline #6847 passed with stage
in 1 minute and 13 seconds
......@@ -24,6 +24,7 @@ use ksb::ModuleSet::Qt;
use ksb::OSSupport;
use ksb::RecursiveFH;
use ksb::DependencyResolver 0.20;
use ksb::DebugOrderHints;
use ksb::IPC::Pipe 0.20;
use ksb::IPC::Null;
use ksb::Updater::Git;
......@@ -65,14 +66,15 @@ sub new
# Default to colorized output if sending to TTY
ksb::Debug::setColorfulOutput(-t STDOUT);
my @moduleList = $self->generateModuleList(@options);
$self->{modules} = \@moduleList;
if (!@moduleList) {
my $workLoad = $self->generateModuleList(@options);
if (!$workLoad->{build}) {
print "No modules to build, exiting.\n";
exit 0;
}
$self->{modules} = $workLoad->{selectedModules};
$self->{workLoad} = $workLoad;
$self->context()->setupOperatingEnvironment(); # i.e. niceness, ulimits, etc.
# After this call, we must run the finish() method
......@@ -384,7 +386,12 @@ sub _yieldModuleDependencyTreeEntry
# After this function is called all module set selectors will have been
# expanded, and we will have downloaded kde-projects metadata.
#
# Returns: List of Modules to build.
# Returns: a hash containing the following entries:
#
# - selectedModules: the selected modules to build
# - dependencyInfo: reference to dependency info object as created by ksb::DependencyResolver
# - build: whether or not to actually perform a build action
#
sub generateModuleList
{
my $self = shift;
......@@ -554,7 +561,13 @@ sub generateModuleList
$depTreeCtx,
@modules
);
return;
my $result = {
dependencyInfo => $moduleGraph,
selectedModules => [],
build => 0
};
return $result;
}
@modules = ksb::DependencyResolver::sortModulesIntoBuildOrder(
......@@ -578,10 +591,21 @@ sub generateModuleList
}
print("\n");
}
return;
my $result = {
dependencyInfo => $moduleGraph,
selectedModules => [],
build => 0
};
return $result;
}
return @modules;
my $result = {
dependencyInfo => $moduleGraph,
selectedModules => \@modules,
build => 1
};
return $result;
}
# Causes kde-projects metadata to be downloaded (unless --pretend, --no-src, or
......@@ -800,7 +824,11 @@ sub runAllModulePhases
}
_cleanup_log_directory($ctx) if $ctx->getOption('purge-old-logs');
_output_failed_module_lists($ctx);
my $workLoad = $self->workLoad();
my $dependencyGraph = $workLoad->{dependencyInfo}->{graph};
_output_failed_module_lists($ctx, $dependencyGraph);
# Record all failed modules. Unlike the 'resume-list' option this doesn't
# include any successfully-built modules in between failures.
......@@ -2295,12 +2323,27 @@ sub _output_failed_module_list
sub _output_failed_module_lists
{
my $ctx = assert_isa(shift, 'ksb::BuildContext');
my $moduleGraph = shift;
my $extraDebugInfo = {
phases => {},
failCount => {}
};
my @actualFailures = ();
# This list should correspond to the possible phase names (although
# it doesn't yet since the old code didn't, TODO)
for my $phase ($ctx->phases()->phases())
{
my @failures = $ctx->failedModulesInPhase($phase);
for my $failure (@failures) {
# we already tagged the failure before, should not happen but
# make sure to check to avoid spurious duplicate output
next if $extraDebugInfo->{phases}->{$failure};
$extraDebugInfo->{phases}->{$failure} = $phase;
push @actualFailures, $failure;
}
_output_failed_module_list($ctx, "failed to $phase", @failures);
}
......@@ -2316,6 +2359,26 @@ sub _output_failed_module_lists
warning ("\nThere is probably a local error causing this kind of consistent failure, it");
warning ("is recommended to verify no issues on the system.\n");
}
my $top = 5;
my $numSuggestedModules = scalar @actualFailures;
#
# Omit listing $top modules if there are that many or fewer anyway.
# Not much point ranking 4 out of 4 failures,
# this feature is meant for 5 out of 65
#
if ($numSuggestedModules > $top) {
my @sortedForDebug = ksb::DebugOrderHints::sortFailuresInDebugOrder(
$moduleGraph,
$extraDebugInfo,
\@actualFailures
);
info ("\nThe following top $top may be the most important to fix to " .
"get the build to work, listed in order of 'probably most " .
"interesting' to 'probably least interesting' failure:\n");
info ("\tr[b[$_]") foreach (@sortedForDebug[0..($top - 1)]);
}
}
# Function: _installTemplatedFile
......@@ -2726,4 +2789,10 @@ sub modules
return @{$self->{modules}};
}
sub workLoad
{
my $self = shift;
return $self->{workLoad};
}
1;
package ksb::DebugOrderHints;
use strict;
use warnings;
use 5.014;
# ksb::DebugOrderHints
#
# This module is motivated by the desire to help the user debug a kdesrc-build
# failure more easily. It provides support code to rank build failures on a per
# module from 'most' to 'least' interesting, as well as to sort the list of
# (all) failures by their respective rankings. This ranking is determined by
# trying to evaluate whether or not a given build failure fits a number of
# assumptions/heuristics. E.g.: a module which fails to build is likely to
# trigger build failures in other modules that depend on it (because of a
# missing dependency).
#
sub _getPhaseScore
{
my $phase = shift;
#
# Assumption: build & install phases are interesting.
# Install is particularly interesting because that should 'rarely' fail,
# and so if it does there are probably underlying system issues at work.
#
# Assumption: 'test' is opt in and therefore the user has indicated a
# special interest in that particular module?
#
# Assumption: source updates are likely not that interesting due to
# e.g. transient network failure. But it might also indicate something
# more serious such as an unclean git repository, causing scm commands
# to bail.
#
return 4 if ($phase eq 'install');
return 3 if ($phase eq 'test');
return 2 if ($phase eq 'build');
return 1 if ($phase eq 'update');
return 0;
}
sub _compareDebugOrder
{
my ($moduleGraph, $extraDebugInfo, $a, $b) = @_;
my $nameA = $a->name();
my $nameB = $b->name();
#
# Enforce a strict dependency ordering.
# The case where both are true should never happen, since that would
# amount to a cycle, and cycle detection is supposed to have been
# performed beforehand.
#
# Assumption: if A depends on B, and B is broken then a failure to build
# A is probably due to lacking a working B.
#
my $bDependsOnA = $moduleGraph->{$nameA}->{votes}->{$nameB} // 0;
my $aDependsOnB = $moduleGraph->{$nameB}->{votes}->{$nameA} // 0;
my $order = $bDependsOnA ? -1 : ($aDependsOnB ? 1 : 0);
return $order if $order;
#
# TODO we could tag explicitly selected modules from command line?
# If we do so, then the user is probably more interested in debugging
# those first, rather than 'unrelated' noise from modules pulled in due
# to possibly overly broad dependency declarations. In that case we
# should sort explicitly tagged modules next highest, after dependency
# ordering.
#
#
# Assuming no dependency resolution, next favour possible root causes as
# may be inferred from the dependency tree.
#
# Assumption: there may be certain 'popular' modules which rely on a
# failed module. Those should probably not be considered as 'interesting'
# as root cause failures in less popuplar dependency trees. This is
# essentially a mitigation against noise introduced from raw 'popularity'
# contests (see below).
#
my $isRootA = (scalar keys %{$moduleGraph->{$nameA}->{deps}}) == 0;
my $isRootB = (scalar keys %{$moduleGraph->{$nameB}->{deps}}) == 0;
return -1 if $isRootA && !$isRootB;
return 1 if $isRootB && !$isRootA;
#
# Next sort by 'popularity': the item with the most votes (back edges) is
# depended on the most.
#
# Assumption: it is probably a good idea to debug that one earlier.
# This would point the user to fixing the most heavily used dependencies
# first before investing time in more 'exotic' modules
#
my $voteA = scalar keys %{$moduleGraph->{$nameA}->{votes}};
my $voteB = scalar keys %{$moduleGraph->{$nameB}->{votes}};
my $votes = $voteB <=> $voteA;
return $votes if $votes;
#
# Try and see if there is something 'interesting' that might e.g. indicate
# issues with the system itself, preventing a successful build.
#
my $phaseA = _getPhaseScore($extraDebugInfo->{phases}->{$nameA} // '');
my $phaseB = _getPhaseScore($extraDebugInfo->{phases}->{$nameB} // '');
my $phase = $phaseB <=> $phaseA;
return $phase if $phase;
#
# Assumption: persistently failing modules do not prompt the user
# to act and therefore these are likely not that interesting.
# Conversely *new* failures are.
#
# If we get this wrong the user will likely be on the case anyway:
# someone does not need prodding if they have been working on it
# for the past X builds or so already.
#
my $failCountA = $a->getPersistentOption('failure-count');
my $failCountB = $b->getPersistentOption('failure-count');
my $failCount = ($failCountA // 0) <=> ($failCountB // 0);
return $failCount if $failCount;
#
# If there is no good reason to perfer one module over another,
# simply sort by name to get a reproducible order.
# That simplifies autotesting and/or reproducible builds.
# (The items to sort are supplied as a hash so the order of keys is by
# definition not guaranteed.)
#
my $name = ($nameA cmp $nameB);
return $name;
}
sub sortFailuresInDebugOrder
{
my ($moduleGraph, $extraDebugInfo, $failuresRef) = @_;
my @failures = @{$failuresRef};
my @prioritised = sort {
_compareDebugOrder($moduleGraph, $extraDebugInfo, $a, $b);
} (@failures);
return @prioritised;
}
1;
......@@ -9,7 +9,7 @@ use Test::More;
use ksb::Application;
my $app = ksb::Application->new(qw(--pretend --rc-file t/data/branch-time-based/kdesrc-buildrc));
my @moduleList = $app->generateModuleList();
my @moduleList = $app->modules();
is(scalar @moduleList, 3, 'Right number of modules');
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment