From 166d56088f136729bc4ef6de6096eba16367a9da Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Mon, 8 Jul 2013 13:35:34 -0400 Subject: [PATCH 001/215] Call buildFinished when a cached build is added Signed-off-by: Shea Levy --- src/lib/Hydra/Controller/Build.pm | 2 +- src/lib/Hydra/Helper/AddBuilds.pm | 4 +++- src/lib/Hydra/Helper/PluginHooks.pm | 22 ++++++++++++++++++++++ src/script/hydra-build | 18 +++--------------- src/script/hydra-evaluator | 2 +- 5 files changed, 30 insertions(+), 18 deletions(-) create mode 100644 src/lib/Hydra/Helper/PluginHooks.pm diff --git a/src/lib/Hydra/Controller/Build.pm b/src/lib/Hydra/Controller/Build.pm index 18ca624d..1018bd60 100644 --- a/src/lib/Hydra/Controller/Build.pm +++ b/src/lib/Hydra/Controller/Build.pm @@ -575,7 +575,7 @@ sub clone_submit : Chained('buildChain') PathPart('clone/submit') Args(0) { my %currentBuilds; my $newBuild = checkBuild( $c->model('DB'), $build->project, $build->jobset, - $inputInfo, $nixExprInput, $job, \%currentBuilds, undef, {}); + $inputInfo, $nixExprInput, $job, \%currentBuilds, undef, {}, $c->hydra_plugins); error($c, "This build has already been performed.") unless $newBuild; diff --git a/src/lib/Hydra/Helper/AddBuilds.pm b/src/lib/Hydra/Helper/AddBuilds.pm index b2106ffa..6bad9e81 100644 --- a/src/lib/Hydra/Helper/AddBuilds.pm +++ b/src/lib/Hydra/Helper/AddBuilds.pm @@ -15,6 +15,7 @@ use File::Path; use File::Temp; use File::Spec; use File::Slurp; +use Hydra::Helper::PluginHooks; our @ISA = qw(Exporter); our @EXPORT = qw( @@ -389,7 +390,7 @@ sub getPrevJobsetEval { # Check whether to add the build described by $buildInfo. sub checkBuild { - my ($db, $project, $jobset, $inputInfo, $nixExprInput, $buildInfo, $buildIds, $prevEval, $jobOutPathMap) = @_; + my ($db, $project, $jobset, $inputInfo, $nixExprInput, $buildInfo, $buildIds, $prevEval, $jobOutPathMap, $plugins) = @_; my @outputNames = sort keys %{$buildInfo->{output}}; die unless scalar @outputNames; @@ -517,6 +518,7 @@ sub checkBuild { if ($build->iscachedbuild) { print STDERR " marked as cached build ", $build->id, "\n"; addBuildProducts($db, $build); + notifyBuildFinished($plugins, $build, []); } else { print STDERR " added to queue as build ", $build->id, "\n"; } diff --git a/src/lib/Hydra/Helper/PluginHooks.pm b/src/lib/Hydra/Helper/PluginHooks.pm new file mode 100644 index 00000000..4000045b --- /dev/null +++ b/src/lib/Hydra/Helper/PluginHooks.pm @@ -0,0 +1,22 @@ +package Hydra::Helper::PluginHooks; + +use strict; +use Exporter; + +our @ISA = qw(Exporter); +our @EXPORT = qw( + notifyBuildFinished); + +sub notifyBuildFinished { + my ($plugins, $build, $dependents) = @_; + foreach my $plugin (@{$plugins}) { + eval { + $plugin->buildFinished($build, $dependents); + }; + if ($@) { + print STDERR "$plugin->buildFinished: $@\n"; + } + } +} + +1; diff --git a/src/script/hydra-build b/src/script/hydra-build index ab2650db..54232458 100755 --- a/src/script/hydra-build +++ b/src/script/hydra-build @@ -8,6 +8,7 @@ use Nix::Store; use Hydra::Plugin; use Hydra::Schema; use Hydra::Helper::Nix; +use Hydra::Helper::PluginHooks; use Hydra::Model::DB; use Hydra::Helper::AddBuilds; @@ -80,19 +81,6 @@ sub failDependents { } -sub notify { - my ($build, $dependents) = @_; - foreach my $plugin (@plugins) { - eval { - $plugin->buildFinished($build, $dependents); - }; - if ($@) { - print STDERR "$plugin->buildFinished: $@\n"; - } - } -} - - sub doBuild { my ($build) = @_; @@ -319,7 +307,7 @@ sub doBuild { }); - notify($build, $dependents); + notifyBuildFinished(\@plugins, $build, $dependents); } @@ -328,7 +316,7 @@ print STDERR "performing build $buildId\n"; if ($ENV{'HYDRA_MAIL_TEST'}) { my $build = $db->resultset('Builds')->find($buildId); - notify($build, []); + notifyBuildFinished(\@plugins, $build, []); exit 0; } diff --git a/src/script/hydra-evaluator b/src/script/hydra-evaluator index 50e0e448..5bfed9e3 100755 --- a/src/script/hydra-evaluator +++ b/src/script/hydra-evaluator @@ -148,7 +148,7 @@ sub checkJobsetWrapped { foreach my $job (permute @{$jobs->{job}}) { next if $job->{jobName} eq ""; print STDERR " considering job " . $project->name, ":", $jobset->name, ":", $job->{jobName} . "\n"; - checkBuild($db, $project, $jobset, $inputInfo, $nixExprInput, $job, \%buildIds, $prevEval, $jobOutPathMap); + checkBuild($db, $project, $jobset, $inputInfo, $nixExprInput, $job, \%buildIds, $prevEval, $jobOutPathMap, $plugins); } # Update the last checked times and error messages for each From b2f6be9686c39bd276a07931db77573ff5f83d1a Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Mon, 8 Jul 2013 13:52:51 -0400 Subject: [PATCH 002/215] Don't call buildFinished after we already know it failed Signed-off-by: Shea Levy --- src/lib/Hydra/Helper/PluginHooks.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Hydra/Helper/PluginHooks.pm b/src/lib/Hydra/Helper/PluginHooks.pm index 4000045b..a5de06e0 100644 --- a/src/lib/Hydra/Helper/PluginHooks.pm +++ b/src/lib/Hydra/Helper/PluginHooks.pm @@ -14,7 +14,7 @@ sub notifyBuildFinished { $plugin->buildFinished($build, $dependents); }; if ($@) { - print STDERR "$plugin->buildFinished: $@\n"; + print STDERR "\$plugin->buildFinished: $@\n"; } } } From efd011fbc3911e5ba9af1ee560f578dd90a963c6 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Mon, 8 Jul 2013 14:30:46 -0400 Subject: [PATCH 003/215] Revert "Don't call buildFinished after we already know it failed" I don't understand perl strings. This reverts commit b2f6be9686c39bd276a07931db77573ff5f83d1a. Signed-off-by: Shea Levy --- src/lib/Hydra/Helper/PluginHooks.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Hydra/Helper/PluginHooks.pm b/src/lib/Hydra/Helper/PluginHooks.pm index a5de06e0..4000045b 100644 --- a/src/lib/Hydra/Helper/PluginHooks.pm +++ b/src/lib/Hydra/Helper/PluginHooks.pm @@ -14,7 +14,7 @@ sub notifyBuildFinished { $plugin->buildFinished($build, $dependents); }; if ($@) { - print STDERR "\$plugin->buildFinished: $@\n"; + print STDERR "$plugin->buildFinished: $@\n"; } } } From 7a0f80f0167200100a905e5563cce34fb01a2088 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Thu, 11 Jul 2013 11:01:36 -0400 Subject: [PATCH 004/215] Include the email override list in the Jobset serialization Signed-off-by: Shea Levy --- src/lib/Hydra/Controller/Jobset.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/Hydra/Controller/Jobset.pm b/src/lib/Hydra/Controller/Jobset.pm index cb52f68d..2b760103 100644 --- a/src/lib/Hydra/Controller/Jobset.pm +++ b/src/lib/Hydra/Controller/Jobset.pm @@ -59,6 +59,7 @@ sub jobset_GET { 'me.name', 'me.project', 'me.errormsg', + 'me.emailoverride', 'jobsetinputs.name', { 'jobsetinputs.jobsetinputalts.altnr' => 'jobsetinputalts.altnr', From d071bbfb286a79273b51d2d790dab23adcd94331 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 12 Jul 2013 15:02:05 +0200 Subject: [PATCH 005/215] Fix Hipchat notification --- src/lib/Hydra/Plugin/HipChatNotification.pm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/Hydra/Plugin/HipChatNotification.pm b/src/lib/Hydra/Plugin/HipChatNotification.pm index c7b11352..08049f01 100644 --- a/src/lib/Hydra/Plugin/HipChatNotification.pm +++ b/src/lib/Hydra/Plugin/HipChatNotification.pm @@ -58,7 +58,7 @@ sub buildFinished { } foreach my $commit (@commits) { - print STDERR "$commit->{revision} by $commit->{author}\n"; + #print STDERR "$commit->{revision} by $commit->{author}\n"; $authors{$commit->{author}} = $commit->{email}; $nrCommits++; } @@ -92,7 +92,6 @@ sub buildFinished { } print STDERR "sending hipchat notification to room $roomId: $msg\n"; - next; my $ua = LWP::UserAgent->new(); my $resp = $ua->post('https://api.hipchat.com/v1/rooms/message?format=json&auth_token=' . $room->{room}->{token}, { From db3647aa1588a1361cdff9138e91f228f5f5d9d1 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 9 Jul 2013 00:23:48 +0200 Subject: [PATCH 006/215] Set the character set Cherry-picked from the persona branch. --- src/root/layout.tt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/root/layout.tt b/src/root/layout.tt index 2337599b..b82dad23 100644 --- a/src/root/layout.tt +++ b/src/root/layout.tt @@ -11,6 +11,8 @@ Hydra - [% HTML.escape(title) %] + + From 438d7f7c5c832376b39dd55354f8e6fb77ce9f33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20Court=C3=A8s?= Date: Fri, 12 Jul 2013 16:52:40 +0200 Subject: [PATCH 007/215] Reply 404 for requests for non-existent .narinfo. --- src/lib/Hydra/Controller/Root.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/Hydra/Controller/Root.pm b/src/lib/Hydra/Controller/Root.pm index 52ffa653..ca57ce1c 100644 --- a/src/lib/Hydra/Controller/Root.pm +++ b/src/lib/Hydra/Controller/Root.pm @@ -282,6 +282,7 @@ sub narinfo :LocalRegex('^([a-z0-9]+).narinfo$') :Args(0) { my $path = queryPathFromHashPart($hash); if (!$path) { + $c->response->status(404); $c->response->content_type('text/plain'); $c->stash->{plain}->{data} = "does not exist\n"; $c->forward('Hydra::View::Plain'); From 2d5e06918b71ac99c1d30935d4a242057443efe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20Court=C3=A8s?= Date: Fri, 12 Jul 2013 16:53:48 +0200 Subject: [PATCH 008/215] Hydra::View::Plain: Explicitly set the response body. This fixes a bug with Catalyst 1.39 whereby a raw hash table would erroneously be returned for /nix-cache-info. --- src/lib/Hydra/View/Plain.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Hydra/View/Plain.pm b/src/lib/Hydra/View/Plain.pm index 764770a0..d34cc44c 100644 --- a/src/lib/Hydra/View/Plain.pm +++ b/src/lib/Hydra/View/Plain.pm @@ -8,7 +8,7 @@ sub process { my ($self, $c) = @_; $c->response->content_encoding("utf-8"); $c->response->content_type('text/plain') unless $c->response->content_type() ne ""; - $self->SUPER::process($c); + $c->response->body($c->stash->{plain}->{data}); } 1; From 7cd386894deb84e254e0dad14c02661b2a8dec82 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 19 Jul 2013 10:57:40 +0200 Subject: [PATCH 009/215] Don't try to open the Nix DB from configure Not sure how this ever worked before... --- configure.ac | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index c136547f..11ae91e1 100644 --- a/configure.ac +++ b/configure.ac @@ -52,8 +52,7 @@ then NIX_STATE_DIR="$TMPDIR" export NIX_STATE_DIR fi -if "$NIX_STORE_PROGRAM" --timeout 123 -q > /dev/null 2>&1 -then +if NIX_REMOTE=daemon "$NIX_STORE_PROGRAM" --timeout 123 -q; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) From 659c829e883b782137ca743df6f3b0b5d0b750b5 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 19 Jul 2013 14:36:52 +0200 Subject: [PATCH 010/215] Tweaks for nix-shell --- dev-shell | 7 +++++++ doc/manual/manual.xml | 4 ++-- release.nix | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) create mode 100755 dev-shell diff --git a/dev-shell b/dev-shell new file mode 100755 index 00000000..d996c245 --- /dev/null +++ b/dev-shell @@ -0,0 +1,7 @@ +#! /bin/sh +s=$(type -p nix-shell) +exec $s release.nix -A build.x86_64-linux --exclude tarball --command " + export NIX_REMOTE=daemon + export NIX_PATH='$NIX_PATH' + export NIX_BUILD_SHELL=$(type -p bash) + exec $s release.nix -A tarball" "$@" diff --git a/doc/manual/manual.xml b/doc/manual/manual.xml index 2d2b6500..c9273cde 100644 --- a/doc/manual/manual.xml +++ b/doc/manual/manual.xml @@ -52,8 +52,7 @@ - 2009 - 2010 + 2009-2013 Eelco Dolstra @@ -64,6 +63,7 @@ + diff --git a/release.nix b/release.nix index 94cf22e4..aaab87d6 100644 --- a/release.nix +++ b/release.nix @@ -24,7 +24,7 @@ in rec { versionSuffix = if officialRelease then "" else "pre${toString hydraSrc.revCount}-${hydraSrc.gitTag}"; - preConfigure = '' + preHook = '' # TeX needs a writable font cache. export VARTEXFONTS=$TMPDIR/texfonts ''; @@ -118,7 +118,7 @@ in rec { gzip bzip2 lzma gnutar unzip git gitAndTools.topGit mercurial gnused graphviz bazaar ] ++ lib.optionals stdenv.isLinux [ rpm dpkg cdrkit ] ); - preConfigure = "patchShebangs ."; + preCheck = "patchShebangs ."; postInstall = '' mkdir -p $out/nix-support From b47d9814e3816e119a3441d5f89fc2daf42c3b51 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 19 Jul 2013 14:42:34 +0200 Subject: [PATCH 011/215] Clear $HYDRA_CONFIG in the tests Otherwise one might accidentally send out HipChat notifications when running the tests... --- tests/Makefile.am | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Makefile.am b/tests/Makefile.am index b4242741..ab848d85 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -3,6 +3,7 @@ TESTS_ENVIRONMENT = \ HYDRA_DBI="dbi:SQLite:db.sqlite" \ HYDRA_DATA="$(abs_builddir)/data" \ HYDRA_HOME="$(top_srcdir)/src" \ + HYDRA_CONFIG= \ NIX_REMOTE= \ NIX_CONF_DIR="$(abs_builddir)/nix/etc/nix" \ NIX_STATE_DIR="$(abs_builddir)/nix/var/nix" \ From 6574d125c7ddc982cad2f83a4fa590840aba4764 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 19 Jul 2013 14:43:04 +0200 Subject: [PATCH 012/215] Get rid of a warning in the HipChat plugin --- src/lib/Hydra/Plugin/HipChatNotification.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Hydra/Plugin/HipChatNotification.pm b/src/lib/Hydra/Plugin/HipChatNotification.pm index 08049f01..5ca61378 100644 --- a/src/lib/Hydra/Plugin/HipChatNotification.pm +++ b/src/lib/Hydra/Plugin/HipChatNotification.pm @@ -9,7 +9,7 @@ sub buildFinished { my ($self, $build, $dependents) = @_; my $cfg = $self->{config}->{hipchat}; - my @config = ref $cfg eq "ARRAY" ? @$cfg : ($cfg); + my @config = defined $cfg ? ref $cfg eq "ARRAY" ? @$cfg : ($cfg) : (); my $baseurl = $self->{config}->{'base_uri'} || "http://localhost:3000"; From 1b5e0821d1384f11f9041aa568054c5e3152a7cb Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 19 Jul 2013 15:01:18 +0200 Subject: [PATCH 013/215] Add hacking.xml to the distribution --- doc/manual/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/Makefile.am b/doc/manual/Makefile.am index 7c788727..67fa3da6 100644 --- a/doc/manual/Makefile.am +++ b/doc/manual/Makefile.am @@ -1,4 +1,4 @@ -DOCBOOK_FILES = installation.xml introduction.xml manual.xml projects.xml +DOCBOOK_FILES = installation.xml introduction.xml manual.xml projects.xml hacking.xml EXTRA_DIST = $(DOCBOOK_FILES) From 967791f6f3a56cc99875df7942a962cd8df60625 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 19 Jul 2013 15:02:01 +0200 Subject: [PATCH 014/215] Add the actual file --- doc/manual/hacking.xml | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 doc/manual/hacking.xml diff --git a/doc/manual/hacking.xml b/doc/manual/hacking.xml new file mode 100644 index 00000000..5a5ff9ca --- /dev/null +++ b/doc/manual/hacking.xml @@ -0,0 +1,39 @@ + + +Hacking + +This section provides some notes on how to hack on Hydra. To +get the latest version of Hydra from GitHub: + +$ git clone git://github.com/NixOS/hydra.git +$ cd hydra + + + +To build it and its dependencies: + +$ nix-build release.nix -A build.x86_64-linux + + + +To build all dependencies and start a shell in which all +environment variables (such as PERL5LIB) are set up so +that those dependencies can be found: + +$ ./dev-shell + +To build Hydra, you should then do: + +[nix-shell]$ ./bootstrap +[nix-shell]$ configurePhase +[nix-shell]$ make + +You can run the Hydra web server in your source tree as follows: + +$ ./src/script/hydra-server + + + + From 513c03026864aa8ced8f4e10fb16f5f2893189a7 Mon Sep 17 00:00:00 2001 From: Rob Vermaas Date: Mon, 22 Jul 2013 20:42:17 +0200 Subject: [PATCH 015/215] Do not use local clone for Bazaar inputs. --- src/lib/Hydra/Plugin/BazaarInput.pm | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/lib/Hydra/Plugin/BazaarInput.pm b/src/lib/Hydra/Plugin/BazaarInput.pm index 5aa4b7a8..26d7217b 100644 --- a/src/lib/Hydra/Plugin/BazaarInput.pm +++ b/src/lib/Hydra/Plugin/BazaarInput.pm @@ -25,21 +25,8 @@ sub fetchInput { my $stdout; my $stderr; - my $cacheDir = getSCMCacheDir . "/bzr"; - mkpath($cacheDir); - my $clonePath = $cacheDir . "/" . sha256_hex($uri); - - if (! -d $clonePath) { - (my $res, $stdout, $stderr) = captureStdoutStderr(600, "bzr", "branch", $uri, $clonePath); - die "error cloning bazaar branch at `$uri':\n$stderr" if $res; - } - - chdir $clonePath or die $!; - (my $res, $stdout, $stderr) = captureStdoutStderr(600, "bzr", "pull"); - die "error pulling latest change bazaar branch at `$uri':\n$stderr" if $res; - # First figure out the last-modified revision of the URI. - my @cmd = (["bzr", "revno"], "|", ["sed", 's/^ *\([0-9]*\).*/\1/']); + my @cmd = (["bzr", "revno", $uri], "|", ["sed", 's/^ *\([0-9]*\).*/\1/']); IPC::Run::run(@cmd, \$stdout, \$stderr); die "cannot get head revision of Bazaar branch at `$uri':\n$stderr" if $?; @@ -61,7 +48,7 @@ sub fetchInput { $ENV{"NIX_PREFETCH_BZR_LEAVE_DOT_BZR"} = $type eq "bzr-checkout" ? "1" : "0"; (my $res, $stdout, $stderr) = captureStdoutStderr(600, - "nix-prefetch-bzr", $clonePath, $revision); + "nix-prefetch-bzr", $uri, $revision); die "cannot check out Bazaar branch `$uri':\n$stderr" if $res; ($sha256, $storePath) = split ' ', $stdout; From 1404d33005622a4741b6d79480995ff6d29b6c30 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Thu, 25 Jul 2013 17:48:28 -0400 Subject: [PATCH 016/215] Show when a Project's jobsets are disabled Signed-off-by: Shea Levy --- src/lib/Hydra/Controller/Project.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/Hydra/Controller/Project.pm b/src/lib/Hydra/Controller/Project.pm index c0c9145f..f1519eca 100644 --- a/src/lib/Hydra/Controller/Project.pm +++ b/src/lib/Hydra/Controller/Project.pm @@ -23,6 +23,7 @@ sub projectChain :Chained('/') :PathPart('project') :CaptureArgs(1) { "releases.name", "releases.timestamp", "jobsets.name", + "jobsets.disabled", ], join => [ 'owner', 'views', 'releases', 'jobsets' ], order_by => { -desc => "releases.timestamp" }, collapse => 1 }); if ($project) { From f7bcf9fc1980fad58b36505b61a18d4403c90186 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Thu, 25 Jul 2013 17:59:13 -0400 Subject: [PATCH 017/215] The field is actually enabled, not disabled Signed-off-by: Shea Levy --- src/lib/Hydra/Controller/Project.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Hydra/Controller/Project.pm b/src/lib/Hydra/Controller/Project.pm index f1519eca..c8ace0a5 100644 --- a/src/lib/Hydra/Controller/Project.pm +++ b/src/lib/Hydra/Controller/Project.pm @@ -23,7 +23,7 @@ sub projectChain :Chained('/') :PathPart('project') :CaptureArgs(1) { "releases.name", "releases.timestamp", "jobsets.name", - "jobsets.disabled", + "jobsets.enabled", ], join => [ 'owner', 'views', 'releases', 'jobsets' ], order_by => { -desc => "releases.timestamp" }, collapse => 1 }); if ($project) { From d6b23272e3e6bc42a9b3659f3888b9c1881d48f5 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Fri, 26 Jul 2013 12:04:27 -0400 Subject: [PATCH 018/215] Don't try to serialize if there's nothing to serialize Signed-off-by: Shea Levy --- src/lib/Hydra/Controller/Root.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Hydra/Controller/Root.pm b/src/lib/Hydra/Controller/Root.pm index ca57ce1c..cde5fb10 100644 --- a/src/lib/Hydra/Controller/Root.pm +++ b/src/lib/Hydra/Controller/Root.pm @@ -239,7 +239,7 @@ sub end : ActionClass('RenderView') { $c->response->status . " " . HTTP::Status::status_message($c->response->status); } - $c->forward('serialize'); + $c->forward('serialize') if defined $c->stash->{resource}; } sub serialize : ActionClass('Serialize') { } From eab13d87366156114acdb74ce9be6c8c5d2305b3 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Fri, 26 Jul 2013 13:54:07 -0400 Subject: [PATCH 019/215] ToJSON is automatically called recursively Signed-off-by: Shea Levy --- src/lib/Hydra/Component/ToJSON.pm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/Hydra/Component/ToJSON.pm b/src/lib/Hydra/Component/ToJSON.pm index 6abc4877..f69161bb 100644 --- a/src/lib/Hydra/Component/ToJSON.pm +++ b/src/lib/Hydra/Component/ToJSON.pm @@ -16,7 +16,7 @@ sub TO_JSON { next unless defined $relinfo->{attrs}->{accessor}; my $accessor = $relinfo->{attrs}->{accessor}; if ($accessor eq "single" and exists $self->{_relationship_data}{$relname}) { - $json->{$relname} = $self->$relname->TO_JSON; + $json->{$relname} = $self->$relname; } else { unless (defined $self->{related_resultsets}{$relname}) { my $cond = $relinfo->{cond}; @@ -30,9 +30,9 @@ sub TO_JSON { } if (defined $self->related_resultset($relname)->get_cache) { if ($accessor eq "multi") { - $json->{$relname} = [ map { $_->TO_JSON } $self->$relname ]; + $json->{$relname} = [ $self->$relname ]; } else { - $json->{$relname} = $self->$relname->TO_JSON; + $json->{$relname} = $self->$relname; } } } From f231c23b759b07a90bd04f53619c980c30e95f14 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Fri, 26 Jul 2013 14:25:25 -0400 Subject: [PATCH 020/215] Only serialize JSON and HTML, not the C::C::REST defaults Signed-off-by: Shea Levy --- src/lib/Hydra/Base/Controller/REST.pm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/Hydra/Base/Controller/REST.pm b/src/lib/Hydra/Base/Controller/REST.pm index 606f0e09..7adb1cf4 100644 --- a/src/lib/Hydra/Base/Controller/REST.pm +++ b/src/lib/Hydra/Base/Controller/REST.pm @@ -4,8 +4,12 @@ use strict; use warnings; use base 'Catalyst::Controller::REST'; +# Hack: Erase the map set by C::C::REST +__PACKAGE__->config( map => undef ); __PACKAGE__->config( map => { + 'application/json' => 'JSON', + 'text/x-json' => 'JSON', 'text/html' => [ 'View', 'TT' ] }, default => 'text/html', From 0bb568912b82695790282d4c977e53bdb4dbded8 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Sun, 28 Jul 2013 11:05:03 -0400 Subject: [PATCH 021/215] hydra-module.nix: Automatically create postgres db user for hydra and an admin hydra account The initial password for the admin account can be found in /var/lib/hydra/.pgpass. Signed-off-by: Shea Levy --- hydra-module.nix | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/hydra-module.nix b/hydra-module.nix index 16f95102..86afad76 100644 --- a/hydra-module.nix +++ b/hydra-module.nix @@ -151,14 +151,36 @@ in systemd.services."hydra-init" = { wantedBy = [ "multi-user.target" ]; + requires = [ "postgresql.service" ]; + after = [ "postgresql.service" ]; environment = env; script = '' mkdir -p ${baseDir}/data chown hydra ${baseDir}/data ln -sf ${hydraConf} ${baseDir}/data/hydra.conf + pass=$(HOME=/root ${pkgs.openssl}/bin/openssl rand -base64 32) + if [ ! -f ${baseDir}/.pgpass ]; then + ${config.services.postgresql.package}/bin/psql postgres << EOF + CREATE USER hydra PASSWORD '$pass'; + EOF + ${config.services.postgresql.package}/bin/createdb -O hydra hydra + cat > ${baseDir}/.pgpass-tmp << EOF + localhost:*:hydra:hydra:$pass + EOF + chown hydra ${baseDir}/.pgpass-tmp + chmod 600 ${baseDir}/.pgpass-tmp + mv ${baseDir}/.pgpass-tmp ${baseDir}/.pgpass + fi ${pkgs.shadow}/bin/su hydra -c ${cfg.hydra}/bin/hydra-init + ${config.services.postgresql.package}/bin/psql hydra << EOF + BEGIN; + INSERT INTO Users(userName, emailAddress, password) VALUES ('admin', '${cfg.notificationSender}', '$(echo -n $pass | sha1sum | cut -c1-40)'); + INSERT INTO UserRoles(userName, role) values('admin', 'admin'); + COMMIT; + EOF ''; serviceConfig.Type = "oneshot"; + serviceConfig.RemainAfterExit = true; }; systemd.services."hydra-server" = From 10cad61231c2d6ce0d9a542ae4d10147b18d9ec5 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Sun, 28 Jul 2013 11:06:02 -0400 Subject: [PATCH 022/215] Don't put ssmtp in hydra's paths Some installations may want to use system-wide sendmail (i.e. /run/setuid-wrappers/sendmail) and those that want ssmtp can add it to hydra's path themselves. Signed-off-by: Shea Levy --- hydra-module.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hydra-module.nix b/hydra-module.nix index 86afad76..d6b3de4b 100644 --- a/hydra-module.nix +++ b/hydra-module.nix @@ -199,7 +199,7 @@ in { wantedBy = [ "multi-user.target" ]; wants = [ "hydra-init.service" ]; after = [ "hydra-init.service" "network.target" ]; - path = [ pkgs.nettools pkgs.ssmtp ]; + path = [ pkgs.nettools ]; environment = env; serviceConfig = { ExecStartPre = "${cfg.hydra}/bin/hydra-queue-runner --unlock"; @@ -213,7 +213,7 @@ in { wantedBy = [ "multi-user.target" ]; wants = [ "hydra-init.service" ]; after = [ "hydra-init.service" "network.target" ]; - path = [ pkgs.nettools pkgs.ssmtp ]; + path = [ pkgs.nettools ]; environment = env; serviceConfig = { ExecStart = "@${cfg.hydra}/bin/hydra-evaluator hydra-evaluator"; From 0c0cf4113e1ac5238f4461b400da5431bb846e38 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Sun, 28 Jul 2013 11:11:09 -0400 Subject: [PATCH 023/215] Add an option to run the hydra server in debug mode Signed-off-by: Shea Levy --- hydra-module.nix | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/hydra-module.nix b/hydra-module.nix index d6b3de4b..9b2cdffb 100644 --- a/hydra-module.nix +++ b/hydra-module.nix @@ -28,7 +28,7 @@ let serverEnv = env // { HYDRA_LOGO = if cfg.logo != null then cfg.logo else ""; HYDRA_TRACKER = cfg.tracker; - }; + } // (optionalAttrs cfg.debugMode { DBIC_TRACE = 1; }); in { @@ -112,6 +112,12 @@ in ''; }; + debugServer = mkOption { + default = false; + type = types.bool; + description = "Whether to run the server in debug mode"; + }; + }; }; @@ -189,7 +195,7 @@ in after = [ "hydra-init.service" ]; environment = serverEnv; serviceConfig = - { ExecStart = "@${cfg.hydra}/bin/hydra-server hydra-server -f -h \* --max_spare_servers 5 --max_servers 25 --max_requests 100"; + { ExecStart = "@${cfg.hydra}/bin/hydra-server hydra-server -f -h \* --max_spare_servers 5 --max_servers 25 --max_requests 100${optionalString cfg.debugServer " -d"}"; User = "hydra"; Restart = "always"; }; From 30e3d5748224082c1a253807682ffbefe68cf02e Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Sun, 28 Jul 2013 11:24:31 -0400 Subject: [PATCH 024/215] Install hydra-module.nix into $out/share/nix Signed-off-by: Shea Levy --- Makefile.am | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile.am b/Makefile.am index 339af868..ad911b4d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,4 +1,8 @@ SUBDIRS = src tests doc BOOTCLEAN_SUBDIRS = $(SUBDIRS) DIST_SUBDIRS = $(SUBDIRS) +EXTRA_DIST = hydra-module.nix +install-data-local: hydra-module.nix + $(INSTALL) -d $(DESTDIR)$(datadir)/nix + $(INSTALL_DATA) hydra-module.nix $(DESTDIR)$(datadir)/nix/ From 5efe8365efe5f4ac0ea1162f6b5c5e904841946f Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Sun, 28 Jul 2013 12:16:46 -0400 Subject: [PATCH 025/215] Whoops Signed-off-by: Shea Levy --- hydra-module.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hydra-module.nix b/hydra-module.nix index 9b2cdffb..7a666a56 100644 --- a/hydra-module.nix +++ b/hydra-module.nix @@ -28,7 +28,7 @@ let serverEnv = env // { HYDRA_LOGO = if cfg.logo != null then cfg.logo else ""; HYDRA_TRACKER = cfg.tracker; - } // (optionalAttrs cfg.debugMode { DBIC_TRACE = 1; }); + } // (optionalAttrs cfg.debugServer { DBIC_TRACE = 1; }); in { From 687ca429c3beddf7fc87382dada7135e9a0a60a1 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Mon, 29 Jul 2013 15:33:22 -0400 Subject: [PATCH 026/215] Pass project and jobset to fetchInput Signed-off-by: Shea Levy --- src/lib/Hydra/Helper/AddBuilds.pm | 2 +- src/lib/Hydra/Plugin.pm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/Hydra/Helper/AddBuilds.pm b/src/lib/Hydra/Helper/AddBuilds.pm index 6bad9e81..734a97aa 100644 --- a/src/lib/Hydra/Helper/AddBuilds.pm +++ b/src/lib/Hydra/Helper/AddBuilds.pm @@ -171,7 +171,7 @@ sub fetchInput { else { my $found = 0; foreach my $plugin (@{$plugins}) { - @inputs = $plugin->fetchInput($type, $name, $value); + @inputs = $plugin->fetchInput($type, $name, $value, $project, $jobset); if (defined $inputs[0]) { $found = 1; last; diff --git a/src/lib/Hydra/Plugin.pm b/src/lib/Hydra/Plugin.pm index 8b3df782..4a8ef69e 100644 --- a/src/lib/Hydra/Plugin.pm +++ b/src/lib/Hydra/Plugin.pm @@ -38,7 +38,7 @@ sub supportedInputTypes { # Called to fetch an input of type ‘$type’. ‘$value’ is the input # location, typically the repository URL. sub fetchInput { - my ($self, $type, $name, $value) = @_; + my ($self, $type, $name, $value, $project, $jobset) = @_; return undef; } From 96e987bbfaafbd323455398310782fb6eaa42e2f Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Mon, 29 Jul 2013 17:42:49 -0400 Subject: [PATCH 027/215] Use inputTypes from plugins to determine valid input types Signed-off-by: Shea Levy --- src/lib/Hydra/Controller/Jobset.pm | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/Hydra/Controller/Jobset.pm b/src/lib/Hydra/Controller/Jobset.pm index 2b760103..f1fed9dc 100644 --- a/src/lib/Hydra/Controller/Jobset.pm +++ b/src/lib/Hydra/Controller/Jobset.pm @@ -328,10 +328,8 @@ sub updateJobset { error($c, "Invalid input name: $inputName") unless $inputName =~ /^[[:alpha:]]\w*$/; my $inputType = $inputData->{type}; - error($c, "Invalid input type: $inputType") unless - $inputType eq "svn" || $inputType eq "svn-checkout" || $inputType eq "hg" || $inputType eq "tarball" || - $inputType eq "string" || $inputType eq "path" || $inputType eq "boolean" || $inputType eq "bzr" || $inputType eq "bzr-checkout" || - $inputType eq "git" || $inputType eq "build" || $inputType eq "sysbuild" ; + + error($c, "Invalid input type: $inputType") unless defined $c->stash->{inputTypes}->{$inputType}; my $input; unless (defined $inputData->{oldName}) { From 90eedcf2567b4125197095b1f1310fba783c10e3 Mon Sep 17 00:00:00 2001 From: Rob Vermaas Date: Wed, 7 Aug 2013 08:53:32 +0000 Subject: [PATCH 028/215] HipChat notification: add support for Mercurial inputs for determining who might have broken the build. --- src/lib/Hydra/Plugin/HipChatNotification.pm | 2 +- src/lib/Hydra/Plugin/MercurialInput.pm | 48 +++++++++++++++++++-- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/lib/Hydra/Plugin/HipChatNotification.pm b/src/lib/Hydra/Plugin/HipChatNotification.pm index 5ca61378..91e5464f 100644 --- a/src/lib/Hydra/Plugin/HipChatNotification.pm +++ b/src/lib/Hydra/Plugin/HipChatNotification.pm @@ -45,7 +45,7 @@ sub buildFinished { if ($prevBuild) { foreach my $curInput ($build->buildinputs_builds) { - next unless $curInput->type eq "git"; + next unless ($curInput->type eq "git" || $curInput->type eq "hg"); my $prevInput = $prevBuild->buildinputs_builds->find({ name => $curInput->name }); next unless defined $prevInput; diff --git a/src/lib/Hydra/Plugin/MercurialInput.pm b/src/lib/Hydra/Plugin/MercurialInput.pm index 02ea0ccc..160be7b4 100644 --- a/src/lib/Hydra/Plugin/MercurialInput.pm +++ b/src/lib/Hydra/Plugin/MercurialInput.pm @@ -12,21 +12,33 @@ sub supportedInputTypes { $inputTypes->{'hg'} = 'Mercurial checkout'; } +sub _parseValue { + my ($value) = @_; + (my $uri, my $id) = split ' ', $value; + $id = defined $id ? $id : "default"; + return ($uri, $id); +} + +sub _clonePath { + my ($uri) = @_; + my $cacheDir = getSCMCacheDir . "/hg"; + mkpath($cacheDir); + return $cacheDir . "/" . sha256_hex($uri); +} + sub fetchInput { my ($self, $type, $name, $value) = @_; return undef if $type ne "hg"; - (my $uri, my $id) = split ' ', $value; + (my $uri, my $id) = _parseValue($value); $id = defined $id ? $id : "default"; # init local hg clone my $stdout = ""; my $stderr = ""; - my $cacheDir = getSCMCacheDir . "/hg"; - mkpath($cacheDir); - my $clonePath = $cacheDir . "/" . sha256_hex($uri); + my $clonePath = _clonePath($uri); if (! -d $clonePath) { (my $res, $stdout, $stderr) = captureStdoutStderr(600, @@ -85,4 +97,32 @@ sub fetchInput { }; } +sub getCommits { + my ($self, $type, $value, $rev1, $rev2) = @_; + return [] if $type ne "hg"; + + return [] unless $rev1 =~ /^[0-9a-f]+$/; + return [] unless $rev2 =~ /^[0-9a-f]+$/; + + my ($uri, $id) = _parseValue($value); + + my $clonePath = _clonePath($uri); + chdir $clonePath or die $!; + + my $out; + IPC::Run::run(["hg", "log", "--template", "{node|short}\t{author|person}\t{author|email}\n", "-r", "$rev1:$rev2", $clonePath], \undef, \$out) + or die "cannot get mercurial logs: $?"; + + my $res = []; + foreach my $line (split /\n/, $out) { + if ($line ne "") { + my ($revision, $author, $email) = split "\t", $line; + push @$res, { revision => $revision, author => $author, email => $email }; + } + } + + return $res; +} + + 1; From 1481badf217e181810f3c1010a329194671ee4d7 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 12 Aug 2013 17:23:33 +0200 Subject: [PATCH 029/215] For nix-shell, set some more variables in preHook --- release.nix | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/release.nix b/release.nix index aaab87d6..867290b2 100644 --- a/release.nix +++ b/release.nix @@ -27,6 +27,10 @@ in rec { preHook = '' # TeX needs a writable font cache. export VARTEXFONTS=$TMPDIR/texfonts + + addToSearchPath PATH $(pwd)/src/script + addToSearchPath PATH $(pwd)/src/c + addToSearchPath PERL5LIB $(pwd)/src/lib ''; configureFlags = From d96df42c03f800beb34c28595720ce4ee29845d1 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 12 Aug 2013 17:25:59 +0200 Subject: [PATCH 030/215] GitInput.pm: Don't do a chdir to the Git clone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Doing a chdir in the parent is evil. For instance, we had Hydra core dumps ending up in the cloned directory. Therefore, the function ‘run’ allows doing a chdir in the child. The function ‘grab’ returns the child's stdout and throws an exception if the child fails. --- src/lib/Hydra/Helper/Nix.pm | 38 +++++++++++++++++++++- src/lib/Hydra/Plugin/GitInput.pm | 56 ++++++++++---------------------- 2 files changed, 55 insertions(+), 39 deletions(-) diff --git a/src/lib/Hydra/Helper/Nix.pm b/src/lib/Hydra/Helper/Nix.pm index 571788b4..b4278027 100644 --- a/src/lib/Hydra/Helper/Nix.pm +++ b/src/lib/Hydra/Helper/Nix.pm @@ -20,7 +20,7 @@ our @EXPORT = qw( getMainOutput getEvals getMachines pathIsInsidePrefix - captureStdoutStderr); + captureStdoutStderr run grab); sub getHydraHome { @@ -472,4 +472,40 @@ sub captureStdoutStderr { } +sub run { + my (%args) = @_; + my $res = { stdout => "", stderr => "" }; + my $stdin = ""; + + eval { + local $SIG{ALRM} = sub { die "timeout\n" }; # NB: \n required + alarm $args{timeout} if defined $args{timeout}; + my @x = ($args{cmd}, \$stdin, \$res->{stdout}); + push @x, \$res->{stderr} if $args{grabStderr} // 1; + IPC::Run::run(@x, + init => sub { chdir $args{dir} or die "changing to $args{dir}" if defined $args{dir}; }); + alarm 0; + }; + + if ($@) { + die unless $@ eq "timeout\n"; # propagate unexpected errors + $res->{status} = -1; + $res->{stderr} = "timeout\n"; + } else { + $res->{status} = $?; + chomp $res->{stdout} if $args{chomp} // 0; + } + + return $res; +} + + +sub grab { + my (%args) = @_; + my $res = run(%args, grabStderr => 0); + die "command `@{$args{cmd}}' failed with exit status $res->{status}" if $res->{status}; + return $res->{stdout}; +} + + 1; diff --git a/src/lib/Hydra/Plugin/GitInput.pm b/src/lib/Hydra/Plugin/GitInput.pm index fc267e70..53f373d0 100644 --- a/src/lib/Hydra/Plugin/GitInput.pm +++ b/src/lib/Hydra/Plugin/GitInput.pm @@ -20,39 +20,34 @@ sub _cloneRepo { mkpath($cacheDir); my $clonePath = $cacheDir . "/" . sha256_hex($uri); - my $stdout = ""; my $stderr = ""; my $res; + my $res; if (! -d $clonePath) { # Clone everything and fetch the branch. # TODO: Optimize the first clone by using "git init $clonePath" and "git remote add origin $uri". - ($res, $stdout, $stderr) = captureStdoutStderr(600, "git", "clone", "--branch", $branch, $uri, $clonePath); - die "error cloning git repo at `$uri':\n$stderr" if $res; + $res = run(cmd => ["git", "clone", "--branch", $branch, $uri, $clonePath], timeout => 600); + die "error cloning git repo at `$uri':\n$res->{stderr}" if $res->{status}; } - chdir $clonePath or die $!; # !!! urgh, shouldn't do a chdir - # This command forces the update of the local branch to be in the same as # the remote branch for whatever the repository state is. This command mirrors # only one branch of the remote repository. - ($res, $stdout, $stderr) = captureStdoutStderr(600, - "git", "fetch", "-fu", "origin", "+$branch:$branch"); - ($res, $stdout, $stderr) = captureStdoutStderr(600, - "git", "fetch", "-fu", "origin") if $res; - die "error fetching latest change from git repo at `$uri':\n$stderr" if $res; + $res = run(cmd => ["git", "fetch", "-fu", "origin", "+$branch:$branch"], dir => $clonePath, timeout => 600); + $res = run(cmd => ["git", "fetch", "-fu", "origin"], dir => $clonePath, timeout => 600) if $res->{status}; + die "error fetching latest change from git repo at `$uri':\n$res->{stderr}" if $res->{status}; # If deepClone is defined, then we look at the content of the repository # to determine if this is a top-git branch. if (defined $deepClone) { # Checkout the branch to look at its content. - ($res, $stdout, $stderr) = captureStdoutStderr(600, "git", "checkout", "$branch"); - die "error checking out Git branch '$branch' at `$uri':\n$stderr" if $res; + $res = run(cmd => ["git", "checkout", "$branch"], dir => $clonePath); + die "error checking out Git branch '$branch' at `$uri':\n$res->{stderr}" if $res->{status}; if (-f ".topdeps") { # This is a TopGit branch. Fetch all the topic branches so # that builders can run "tg patch" and similar. - ($res, $stdout, $stderr) = captureStdoutStderr(600, - "tg", "remote", "--populate", "origin"); - print STDERR "warning: `tg remote --populate origin' failed:\n$stderr" if $res; + $res = run(cmd => ["tg", "remote", "--populate", "origin"], dir => $clonePath, timeout => 600); + print STDERR "warning: `tg remote --populate origin' failed:\n$res->{stderr}" if $res->{status}; } } @@ -64,7 +59,6 @@ sub _parseValue { (my $uri, my $branch, my $deepClone) = split ' ', $value; $branch = defined $branch ? $branch : "master"; return ($uri, $branch, $deepClone); - } sub fetchInput { @@ -80,19 +74,13 @@ sub fetchInput { my $sha256; my $storePath; - my ($res, $stdout, $stderr) = captureStdoutStderr(600, - ("git", "rev-parse", "$branch")); - die "error getting revision number of Git branch '$branch' at `$uri':\n$stderr" if $res; - - my ($revision) = split /\n/, $stdout; - die "error getting a well-formated revision number of Git branch '$branch' at `$uri':\n$stdout" + my $revision = grab(cmd => ["git", "rev-parse", "$branch"], dir => $clonePath, chomp => 1); + die "did not get a well-formated revision number of Git branch '$branch' at `$uri'" unless $revision =~ /^[0-9a-fA-F]+$/; - my $ref = "refs/heads/$branch"; - # Some simple caching: don't check a uri/branch/revision more than once. # TODO: Fix case where the branch is reset to a previous commit. - my $cachedInput ; + my $cachedInput; ($cachedInput) = $self->{db}->resultset('CachedGitInputs')->search( {uri => $uri, branch => $branch, revision => $revision}, {rows => 1}); @@ -123,10 +111,7 @@ sub fetchInput { $ENV{"NIX_PREFETCH_GIT_DEEP_CLONE"} = "1"; } - ($res, $stdout, $stderr) = captureStdoutStderr(600, "nix-prefetch-git", $clonePath, $revision); - die "cannot check out Git repository branch '$branch' at `$uri':\n$stderr" if $res; - - ($sha256, $storePath) = split ' ', $stdout; + ($sha256, $storePath) = split ' ', grab(cmd => ["nix-prefetch-git", $clonePath, $revision], chomp => 1); txn_do($self->{db}, sub { $self->{db}->resultset('CachedGitInputs')->update_or_create( @@ -143,12 +128,9 @@ sub fetchInput { # number of commits in the history of this revision (‘revCount’) # the output of git-describe (‘gitTag’), and the abbreviated # revision (‘shortRev’). - my $revCount = `git rev-list $revision | wc -l`; chomp $revCount; - die "git rev-list failed" if $? != 0; - my $gitTag = `git describe --always $revision`; chomp $gitTag; - die "git describe failed" if $? != 0; - my $shortRev = `git rev-parse --short $revision`; chomp $shortRev; - die "git rev-parse failed" if $? != 0; + my $revCount = scalar(split '\n', grab(cmd => ["git", "rev-list", "$revision"], dir => $clonePath)); + my $gitTag = grab(cmd => ["git", "describe", "--always", "$revision"], dir => $clonePath, chomp => 1); + my $shortRev = grab(cmd => ["git", "rev-parse", "--short", "$revision"], dir => $clonePath, chomp => 1); return { uri => $uri @@ -172,9 +154,7 @@ sub getCommits { my $clonePath = $self->_cloneRepo($uri, $branch, $deepClone); - my $out; - IPC::Run::run(["git", "log", "--pretty=format:%H%x09%an%x09%ae%x09%at", "$rev1..$rev2"], \undef, \$out) - or die "cannot get git logs: $?"; + my $out = grab(cmd => ["git", "log", "--pretty=format:%H%x09%an%x09%ae%x09%at", "$rev1..$rev2"], dir => $clonePath); my $res = []; foreach my $line (split /\n/, $out) { From 182f725612ec2b71009c02fc80cbc8d2cffbe7c3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 12 Aug 2013 18:15:11 +0200 Subject: [PATCH 031/215] Don't pass an undefined input --- src/lib/Hydra/Helper/AddBuilds.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Hydra/Helper/AddBuilds.pm b/src/lib/Hydra/Helper/AddBuilds.pm index 734a97aa..6f51b98d 100644 --- a/src/lib/Hydra/Helper/AddBuilds.pm +++ b/src/lib/Hydra/Helper/AddBuilds.pm @@ -89,7 +89,7 @@ sub fetchInputBuild { if (!defined $prevBuild || !isValidPath(getMainOutput($prevBuild)->path)) { print STDERR "input `", $name, "': no previous build available\n"; - return undef; + return (); } #print STDERR "input `", $name, "': using build ", $prevBuild->id, "\n"; From 452c8e36d17f33bafbdc540593cb8e381d40e843 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 12 Aug 2013 20:11:34 +0200 Subject: [PATCH 032/215] Materialize the number of finished builds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The NrBuilds table tracks the value of ‘select count(*) from Builds where finished = 0’, keeping it up to date via a trigger. This is necessary to make the /all page fast, since otherwise it needs to do a sequential scan on the Builds table. --- src/lib/Hydra/Base/Controller/ListBuilds.pm | 5 +- src/lib/Hydra/Controller/Root.pm | 1 + src/lib/Hydra/Schema/NrBuilds.pm | 75 +++++++++++++++++++++ src/script/hydra-init | 4 +- src/sql/hydra.sql | 32 ++++++++- src/sql/upgrade-17.sql | 23 +++++++ tests/query-all-tables.pl | 4 +- 7 files changed, 136 insertions(+), 8 deletions(-) create mode 100644 src/lib/Hydra/Schema/NrBuilds.pm create mode 100644 src/sql/upgrade-17.sql diff --git a/src/lib/Hydra/Base/Controller/ListBuilds.pm b/src/lib/Hydra/Base/Controller/ListBuilds.pm index 6dce9c0d..59163e77 100644 --- a/src/lib/Hydra/Base/Controller/ListBuilds.pm +++ b/src/lib/Hydra/Base/Controller/ListBuilds.pm @@ -56,13 +56,12 @@ sub all : Chained('get_builds') PathPart { my $resultsPerPage = 20; - my $nrBuilds = $c->stash->{allBuilds}->search({finished => 1})->count; - $c->stash->{baseUri} = $c->uri_for($self->action_for("all"), $c->req->captures); $c->stash->{page} = $page; $c->stash->{resultsPerPage} = $resultsPerPage; - $c->stash->{total} = $nrBuilds; + $c->stash->{total} = $c->stash->{allBuilds}->search({finished => 1})->count + unless defined $c->stash->{total}; $c->stash->{builds} = [ $c->stash->{allBuilds}->search( { finished => 1 }, diff --git a/src/lib/Hydra/Controller/Root.pm b/src/lib/Hydra/Controller/Root.pm index cde5fb10..1ddd6a0c 100644 --- a/src/lib/Hydra/Controller/Root.pm +++ b/src/lib/Hydra/Controller/Root.pm @@ -155,6 +155,7 @@ sub get_builds : Chained('/') PathPart('') CaptureArgs(0) { $c->stash->{allJobs} = $c->model('DB::Jobs'); $c->stash->{latestSucceeded} = $c->model('DB')->resultset('LatestSucceeded'); $c->stash->{channelBaseName} = "everything"; + $c->stash->{total} = $c->model('DB::NrBuilds')->find('finished')->count; } diff --git a/src/lib/Hydra/Schema/NrBuilds.pm b/src/lib/Hydra/Schema/NrBuilds.pm new file mode 100644 index 00000000..27ae2e83 --- /dev/null +++ b/src/lib/Hydra/Schema/NrBuilds.pm @@ -0,0 +1,75 @@ +use utf8; +package Hydra::Schema::NrBuilds; + +# Created by DBIx::Class::Schema::Loader +# DO NOT MODIFY THE FIRST PART OF THIS FILE + +=head1 NAME + +Hydra::Schema::NrBuilds + +=cut + +use strict; +use warnings; + +use base 'DBIx::Class::Core'; + +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + +=head1 TABLE: C + +=cut + +__PACKAGE__->table("NrBuilds"); + +=head1 ACCESSORS + +=head2 what + + data_type: 'text' + is_nullable: 0 + +=head2 count + + data_type: 'integer' + is_nullable: 0 + +=cut + +__PACKAGE__->add_columns( + "what", + { data_type => "text", is_nullable => 0 }, + "count", + { data_type => "integer", is_nullable => 0 }, +); + +=head1 PRIMARY KEY + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->set_primary_key("what"); + + +# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-08-12 17:59:18 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:CK8eJGC803nGj0wnete9xg + + +# You can replace this text with custom code or comments, and it will be preserved on regeneration +1; diff --git a/src/script/hydra-init b/src/script/hydra-init index 25abde15..33c60408 100755 --- a/src/script/hydra-init +++ b/src/script/hydra-init @@ -51,12 +51,12 @@ for (my $n = $schemaVersion; $n < $maxSchemaVersion; $n++) { my @statements = $sql_splitter->split($schema); eval { $dbh->begin_work; - sub run { + sub run_ { my ($stm) = @_; print STDERR "executing SQL statement: $stm\n"; $dbh->do($_); } - run($_) foreach @statements; + run_($_) foreach @statements; $db->resultset('SchemaVersion')->update({version => $m}); $dbh->commit; }; diff --git a/src/sql/hydra.sql b/src/sql/hydra.sql index c8108d73..4562dc41 100644 --- a/src/sql/hydra.sql +++ b/src/sql/hydra.sql @@ -514,6 +514,36 @@ create table NewsItems ( ); +-- Cache of the number of finished builds. +create table NrBuilds ( + what text primary key not null, + count integer not null +); + +insert into NrBuilds(what, count) values('finished', 0); + +#ifdef POSTGRESQL + +create function modifyNrBuildsFinished() returns trigger as $$ + begin + if ((tg_op = 'INSERT' and new.finished = 1) or + (tg_op = 'UPDATE' and old.finished = 0 and new.finished = 1)) then + update NrBuilds set count = count + 1 where what = 'finished'; + elsif ((tg_op = 'DELETE' and old.finished = 1) or + (tg_op = 'UPDATE' and old.finished = 1 and new.finished = 0)) then + update NrBuilds set count = count - 1 where what = 'finished'; + end if; + return null; + end; +$$ language plpgsql; + +create trigger NrBuildsFinished after insert or update or delete on Builds + for each row + execute procedure modifyNrBuildsFinished(); + +#endif + + -- Some indices. create index IndexBuildInputsOnBuild on BuildInputs(build); @@ -534,7 +564,7 @@ create index IndexBuildsOnJobAndSystem on Builds(project, jobset, job, system); create index IndexBuildsOnJobset on Builds(project, jobset); create index IndexBuildsOnProject on Builds(project); create index IndexBuildsOnTimestamp on Builds(timestamp); -create index IndexBuildsOnJobsetFinishedTimestamp on Builds(project, jobset, finished, timestamp DESC); +create index IndexBuildsOnJobsetFinishedTimestamp on Builds(project, jobset, finished, timestamp DESC); -- obsolete? create index IndexBuildsOnJobFinishedId on builds(project, jobset, job, system, finished, id DESC); create index IndexBuildsOnJobSystemCurrent on Builds(project, jobset, job, system, isCurrent); create index IndexBuildsOnDrvPath on Builds(drvPath); diff --git a/src/sql/upgrade-17.sql b/src/sql/upgrade-17.sql new file mode 100644 index 00000000..bf827f75 --- /dev/null +++ b/src/sql/upgrade-17.sql @@ -0,0 +1,23 @@ +create table NrBuilds ( + what text primary key not null, + count integer not null +); + +create function modifyNrBuildsFinished() returns trigger as $$ + begin + if ((tg_op = 'INSERT' and new.finished = 1) or + (tg_op = 'UPDATE' and old.finished = 0 and new.finished = 1)) then + update NrBuilds set count = count + 1 where what = 'finished'; + elsif ((tg_op = 'DELETE' and old.finished = 1) or + (tg_op = 'UPDATE' and old.finished = 1 and new.finished = 0)) then + update NrBuilds set count = count - 1 where what = 'finished'; + end if; + return null; + end; +$$ language plpgsql; + +create trigger NrBuildsFinished after insert or update or delete on Builds + for each row + execute procedure modifyNrBuildsFinished(); + +insert into NrBuilds(what, count) select 'finished', count(*) from Builds where finished = 1; diff --git a/tests/query-all-tables.pl b/tests/query-all-tables.pl index 55cc779f..fc3234ed 100755 --- a/tests/query-all-tables.pl +++ b/tests/query-all-tables.pl @@ -7,11 +7,11 @@ my $db = Hydra::Model::DB->new; my @sources = $db->schema->sources; my $nrtables = scalar(@sources); -use Test::Simple tests => 43; +use Test::Simple tests => 44; foreach my $source (@sources) { my $title = "Basic select query for $source"; - if ($source eq "SchemaVersion") { + if ($source eq "SchemaVersion" || $source eq "NrBuilds") { ok(scalar($db->resultset($source)->all) == 1, $title); } elsif( $source !~ m/^(LatestSucceeded|JobStatus|ActiveJobs)/) { ok(scalar($db->resultset($source)->all) == 0, $title); From 84acccb3ea7af201e4ae1404c13c1fe0040eab69 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 12 Aug 2013 20:16:28 +0200 Subject: [PATCH 033/215] Index builds on stop time This is necessary to make the /all page fast, since it sorts builds on descending stop time. --- src/sql/hydra.sql | 1 + src/sql/upgrade-18.sql | 1 + 2 files changed, 2 insertions(+) create mode 100644 src/sql/upgrade-18.sql diff --git a/src/sql/hydra.sql b/src/sql/hydra.sql index 4562dc41..3001ab10 100644 --- a/src/sql/hydra.sql +++ b/src/sql/hydra.sql @@ -564,6 +564,7 @@ create index IndexBuildsOnJobAndSystem on Builds(project, jobset, job, system); create index IndexBuildsOnJobset on Builds(project, jobset); create index IndexBuildsOnProject on Builds(project); create index IndexBuildsOnTimestamp on Builds(timestamp); +create index IndexBuildsOnFinishedStopTime on Builds(finished, stoptime DESC); create index IndexBuildsOnJobsetFinishedTimestamp on Builds(project, jobset, finished, timestamp DESC); -- obsolete? create index IndexBuildsOnJobFinishedId on builds(project, jobset, job, system, finished, id DESC); create index IndexBuildsOnJobSystemCurrent on Builds(project, jobset, job, system, isCurrent); diff --git a/src/sql/upgrade-18.sql b/src/sql/upgrade-18.sql new file mode 100644 index 00000000..acd1f397 --- /dev/null +++ b/src/sql/upgrade-18.sql @@ -0,0 +1 @@ +create index IndexBuildsOnFinishedStopTime on Builds(finished, stoptime DESC); From bef263c9306699c5c317ced28373bfebf053c520 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 12 Aug 2013 22:17:04 +0200 Subject: [PATCH 034/215] =?UTF-8?q?Add=20a=20=E2=80=98latest-finished?= =?UTF-8?q?=E2=80=99=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It redirects to the latest successful build from a finished evaluation. This is mostly useful for the Nixpkgs/NixOS mirroring script, which need the latest finished evaluation in which some aggregate job (such as ‘tested’ in NixOS) succeeded. --- src/lib/Hydra/Base/Controller/ListBuilds.pm | 18 ++++++++++++++++++ src/lib/Hydra/Controller/Root.pm | 2 +- src/root/job.tt | 1 + 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/lib/Hydra/Base/Controller/ListBuilds.pm b/src/lib/Hydra/Base/Controller/ListBuilds.pm index 59163e77..f3c69263 100644 --- a/src/lib/Hydra/Base/Controller/ListBuilds.pm +++ b/src/lib/Hydra/Base/Controller/ListBuilds.pm @@ -119,4 +119,22 @@ sub latest_for : Chained('get_builds') PathPart('latest-for') { } +# Redirect to the latest successful build in a finished evaluation +# (i.e. an evaluation that has no unfinished builds). +sub latest_finished : Chained('get_builds') PathPart('latest-finished') { + my ($self, $c, @rest) = @_; + + my $latest = $c->stash->{allBuilds}->find( + { finished => 1, buildstatus => 0 }, + { order_by => ["id DESC"], rows => 1, join => ["jobsetevalmembers"] + , where => \ + "not exists (select 1 from jobsetevalmembers m2 join builds b2 on jobsetevalmembers.eval = m2.eval and m2.build = b2.id and b2.finished = 0)" + }); + + notFound($c, "There is no successful build to redirect to.") unless defined $latest; + + $c->res->redirect($c->uri_for($c->controller('Build')->action_for("build"), [$latest->id], @rest)); +} + + 1; diff --git a/src/lib/Hydra/Controller/Root.pm b/src/lib/Hydra/Controller/Root.pm index 1ddd6a0c..8b7cf4ee 100644 --- a/src/lib/Hydra/Controller/Root.pm +++ b/src/lib/Hydra/Controller/Root.pm @@ -283,7 +283,7 @@ sub narinfo :LocalRegex('^([a-z0-9]+).narinfo$') :Args(0) { my $path = queryPathFromHashPart($hash); if (!$path) { - $c->response->status(404); + $c->response->status(404); $c->response->content_type('text/plain'); $c->stash->{plain}->{data} = "does not exist\n"; $c->forward('Hydra::View::Plain'); diff --git a/src/root/job.tt b/src/root/job.tt index 5ccef23f..1361fccd 100644 --- a/src/root/job.tt +++ b/src/root/job.tt @@ -24,6 +24,7 @@ + [% IF build.members_ %] + +
+ +

This build is an aggregate of the following builds:

+ + [% INCLUDE renderBuildList builds=build.members_ %] + +
+ + [% END %] +
diff --git a/src/root/common.tt b/src/root/common.tt index aec403a2..664853a6 100644 --- a/src/root/common.tt +++ b/src/root/common.tt @@ -99,7 +99,7 @@ BLOCK renderBuildListBody; [% END %] - + [% IF showStatusChange %] - + @@ -147,7 +169,7 @@ [% END %] - [% IF build.finished %] + [% IF !isAggregate && build.finished %] [% END %] - [% IF log_exists(build.drvpath) %] + [% IF !isAggregate && log_exists(build.drvpath) %]
[% !showSchedulingInfo and build.get_column('releasename') ? build.get_column('releasename') : build.nixname %] [% build.system %][% date.format(showSchedulingInfo ? build.timestamp : build.stoptime, '%Y-%m-%d %H:%M:%S') %][% t = showSchedulingInfo ? build.timestamp : build.stoptime; IF t; date.format(showSchedulingInfo ? build.timestamp : build.stoptime, '%Y-%m-%d %H:%M:%S'); ELSE; "-"; END %] [% IF build.get_column('statusChangeTime') %] From e4141afcc9605e77e761c615bf1eaa79f9925172 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 15 Aug 2013 02:17:06 +0200 Subject: [PATCH 042/215] On the build page, show how many aggregate constituents failed (Also, renamed aggregate "member" to "constituent", since "member" is rather vague.) --- src/root/build.tt | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/src/root/build.tt b/src/root/build.tt index 47d3061b..cb268b61 100644 --- a/src/root/build.tt +++ b/src/root/build.tt @@ -7,6 +7,7 @@ [% project = build.project %] [% jobset = build.jobset %] [% job = build.job %] +[% isAggregate = build.members_ ? 1 : 0 %] [% BLOCK renderOutputs %] [% start=1; FOREACH output IN outputs %] @@ -68,7 +69,7 @@
Status:[% INCLUDE renderStatus build=build icon=0 %] + [% INCLUDE renderStatus build=build icon=0 %] + [% IF isAggregate; + nrMembers = 0; + nrFinished = 0; + nrFailedMembers = 0; + FOREACH b IN build.members_; + nrMembers = nrMembers + 1; + IF b.finished; nrFinished = nrFinished + 1; END; + IF b.finished && b.buildstatus != 0; nrFailedMembers = nrFailedMembers + 1; END; + END; + %]; + [%+ IF nrFinished == 0 && nrFailedMembers == 0 %] + all [% nrMembers %] constituent builds succeeded + [% ELSE %] + [% nrFailedMembers %] out of [% nrMembers %] constituent builds failed + [% IF nrFinished < nrMembers %] + ([% nrMembers - nrFinished %] still pending) + [% END %] + [% END %] + [% END %] +
System:[% IF cachedBuild; INCLUDE renderFullBuildLink build=cachedBuild; ELSE %]unknown[% END %]
Duration: [% actualBuild = build.iscachedbuild ? cachedBuild : build; @@ -155,7 +177,7 @@ finished at [% INCLUDE renderDateTime timestamp = actualBuild.stoptime %]
Logfile: @@ -183,7 +205,7 @@ [% END %] - [% IF build.buildproducts %] + [% IF build.buildproducts && !isAggregate %]

Build products

@@ -252,9 +274,9 @@ - [% IF build.members_ %] + [% IF isAggregate %] -
+

This build is an aggregate of the following builds:

From 1776d9118f9422915557d01c0ecb888a2adc8342 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 15 Aug 2013 02:33:10 +0200 Subject: [PATCH 043/215] Rename aggregate members to constituents --- src/c/hydra-eval-jobs.cc | 8 ++-- ...ateMembers.pm => AggregateConstituents.pm} | 26 ++++++------ src/lib/Hydra/Schema/Builds.pm | 42 +++++++++++-------- src/root/build.tt | 24 +++++------ src/script/hydra-evaluator | 14 +++---- src/sql/hydra.sql | 6 +-- src/sql/upgrade-19.sql | 6 +-- 7 files changed, 67 insertions(+), 59 deletions(-) rename src/lib/Hydra/Schema/{AggregateMembers.pm => AggregateConstituents.pm} (72%) diff --git a/src/c/hydra-eval-jobs.cc b/src/c/hydra-eval-jobs.cc index ca2fb7fb..7680a6d3 100644 --- a/src/c/hydra-eval-jobs.cc +++ b/src/c/hydra-eval-jobs.cc @@ -160,12 +160,12 @@ static void findJobsWrapped(EvalState & state, XMLWriter & doc, } xmlAttrs["maintainers"] = maintainers; - /* If this is an aggregate, then get its members. */ + /* If this is an aggregate, then get its constituents. */ Bindings::iterator a = v.attrs->find(state.symbols.create("_hydraAggregate")); if (a != v.attrs->end() && state.forceBool(*a->value)) { - Bindings::iterator a = v.attrs->find(state.symbols.create("members")); + Bindings::iterator a = v.attrs->find(state.symbols.create("constituents")); if (a == v.attrs->end()) - throw EvalError("derivation must have a ‘members’ attribute"); + throw EvalError("derivation must have a ‘constituents’ attribute"); PathSet context; state.coerceToString(*a->value, context, true, false); PathSet drvs; @@ -174,7 +174,7 @@ static void findJobsWrapped(EvalState & state, XMLWriter & doc, size_t index = i->find("!", 1); drvs.insert(string(*i, index + 1)); } - xmlAttrs["members"] = concatStringsSep(" ", drvs); + xmlAttrs["constituents"] = concatStringsSep(" ", drvs); } /* Register the derivation as a GC root. !!! This diff --git a/src/lib/Hydra/Schema/AggregateMembers.pm b/src/lib/Hydra/Schema/AggregateConstituents.pm similarity index 72% rename from src/lib/Hydra/Schema/AggregateMembers.pm rename to src/lib/Hydra/Schema/AggregateConstituents.pm index 4a037663..8112a49c 100644 --- a/src/lib/Hydra/Schema/AggregateMembers.pm +++ b/src/lib/Hydra/Schema/AggregateConstituents.pm @@ -1,12 +1,12 @@ use utf8; -package Hydra::Schema::AggregateMembers; +package Hydra::Schema::AggregateConstituents; # Created by DBIx::Class::Schema::Loader # DO NOT MODIFY THE FIRST PART OF THIS FILE =head1 NAME -Hydra::Schema::AggregateMembers +Hydra::Schema::AggregateConstituents =cut @@ -27,11 +27,11 @@ use base 'DBIx::Class::Core'; __PACKAGE__->load_components("+Hydra::Component::ToJSON"); -=head1 TABLE: C +=head1 TABLE: C =cut -__PACKAGE__->table("AggregateMembers"); +__PACKAGE__->table("AggregateConstituents"); =head1 ACCESSORS @@ -41,7 +41,7 @@ __PACKAGE__->table("AggregateMembers"); is_foreign_key: 1 is_nullable: 0 -=head2 member +=head2 constituent data_type: 'integer' is_foreign_key: 1 @@ -52,7 +52,7 @@ __PACKAGE__->table("AggregateMembers"); __PACKAGE__->add_columns( "aggregate", { data_type => "integer", is_foreign_key => 1, is_nullable => 0 }, - "member", + "constituent", { data_type => "integer", is_foreign_key => 1, is_nullable => 0 }, ); @@ -62,13 +62,13 @@ __PACKAGE__->add_columns( =item * L -=item * L +=item * L =back =cut -__PACKAGE__->set_primary_key("aggregate", "member"); +__PACKAGE__->set_primary_key("aggregate", "constituent"); =head1 RELATIONS @@ -87,7 +87,7 @@ __PACKAGE__->belongs_to( { is_deferrable => 0, on_delete => "CASCADE", on_update => "NO ACTION" }, ); -=head2 member +=head2 constituent Type: belongs_to @@ -96,15 +96,15 @@ Related object: L =cut __PACKAGE__->belongs_to( - "member", + "constituent", "Hydra::Schema::Builds", - { id => "member" }, + { id => "constituent" }, { is_deferrable => 0, on_delete => "CASCADE", on_update => "NO ACTION" }, ); -# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-08-13 22:17:52 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:jHJtO2baXiprv0OcWCLZ+w +# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-08-15 00:20:01 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:TLNenyPLIWw2gWsOVhplZw # You can replace this text with custom code or comments, and it will be preserved on regeneration diff --git a/src/lib/Hydra/Schema/Builds.pm b/src/lib/Hydra/Schema/Builds.pm index 2bf821b6..f67fabda 100644 --- a/src/lib/Hydra/Schema/Builds.pm +++ b/src/lib/Hydra/Schema/Builds.pm @@ -288,33 +288,33 @@ __PACKAGE__->set_primary_key("id"); =head1 RELATIONS -=head2 aggregatemembers_aggregates +=head2 aggregateconstituents_aggregates Type: has_many -Related object: L +Related object: L =cut __PACKAGE__->has_many( - "aggregatemembers_aggregates", - "Hydra::Schema::AggregateMembers", + "aggregateconstituents_aggregates", + "Hydra::Schema::AggregateConstituents", { "foreign.aggregate" => "self.id" }, undef, ); -=head2 aggregatemembers_members +=head2 aggregateconstituents_constituents Type: has_many -Related object: L +Related object: L =cut __PACKAGE__->has_many( - "aggregatemembers_members", - "Hydra::Schema::AggregateMembers", - { "foreign.member" => "self.id" }, + "aggregateconstituents_constituents", + "Hydra::Schema::AggregateConstituents", + { "foreign.constituent" => "self.id" }, undef, ); @@ -502,25 +502,33 @@ __PACKAGE__->has_many( Type: many_to_many -Composing rels: L -> aggregate +Composing rels: L -> aggregate =cut -__PACKAGE__->many_to_many("aggregates", "aggregatemembers_members", "aggregate"); +__PACKAGE__->many_to_many( + "aggregates", + "aggregateconstituents_constituents", + "aggregate", +); -=head2 members +=head2 constituents Type: many_to_many -Composing rels: L -> member +Composing rels: L -> constituent =cut -__PACKAGE__->many_to_many("members", "aggregatemembers_members", "member"); +__PACKAGE__->many_to_many( + "constituents", + "aggregateconstituents_constituents", + "constituent", +); -# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-08-13 22:17:52 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:9jqsol/evbHYjusT09hLtw +# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-08-15 00:20:01 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:U1j/qm0vslb6Jvgu5mGMtw __PACKAGE__->has_many( "dependents", @@ -552,7 +560,7 @@ __PACKAGE__->has_many( __PACKAGE__->many_to_many("jobsetevals", "jobsetevalmembers", "eval"); -__PACKAGE__->many_to_many("members_", "aggregatemembers_aggregates", "member"); +__PACKAGE__->many_to_many("constituents_", "aggregateconstituents_aggregates", "constituent"); sub makeSource { my ($name, $query) = @_; diff --git a/src/root/build.tt b/src/root/build.tt index cb268b61..73dc0c54 100644 --- a/src/root/build.tt +++ b/src/root/build.tt @@ -7,7 +7,7 @@ [% project = build.project %] [% jobset = build.jobset %] [% job = build.job %] -[% isAggregate = build.members_ ? 1 : 0 %] +[% isAggregate = build.constituents_ ? 1 : 0 %] [% BLOCK renderOutputs %] [% start=1; FOREACH output IN outputs %] @@ -119,21 +119,21 @@
[% INCLUDE renderStatus build=build icon=0 %] [% IF isAggregate; - nrMembers = 0; + nrConstituents = 0; nrFinished = 0; - nrFailedMembers = 0; - FOREACH b IN build.members_; - nrMembers = nrMembers + 1; + nrFailedConstituents = 0; + FOREACH b IN build.constituents_; + nrConstituents = nrConstituents + 1; IF b.finished; nrFinished = nrFinished + 1; END; - IF b.finished && b.buildstatus != 0; nrFailedMembers = nrFailedMembers + 1; END; + IF b.finished && b.buildstatus != 0; nrFailedConstituents = nrFailedConstituents + 1; END; END; %]; - [%+ IF nrFinished == 0 && nrFailedMembers == 0 %] - all [% nrMembers %] constituent builds succeeded + [%+ IF nrFinished == nrMembers && nrFailedConstituents == 0 %] + all [% nrConstituents %] constituent builds succeeded [% ELSE %] - [% nrFailedMembers %] out of [% nrMembers %] constituent builds failed - [% IF nrFinished < nrMembers %] - ([% nrMembers - nrFinished %] still pending) + [% nrFailedConstituents %] out of [% nrConstituents %] constituent builds failed + [% IF nrFinished < nrConstituents %] + ([% nrConstituents - nrFinished %] still pending) [% END %] [% END %] [% END %] @@ -280,7 +280,7 @@

This build is an aggregate of the following builds:

- [% INCLUDE renderBuildList builds=build.members_ %] + [% INCLUDE renderBuildList builds=build.constituents_ %] diff --git a/src/script/hydra-evaluator b/src/script/hydra-evaluator index a6dfde46..0fdca76a 100755 --- a/src/script/hydra-evaluator +++ b/src/script/hydra-evaluator @@ -183,16 +183,16 @@ sub checkJobsetWrapped { $drvPathToId{$x->{drvPath}} = $id; } - # Create AggregateMembers mappings. + # Create AggregateConstituents mappings. foreach my $job (@{$jobs->{job}}) { - next unless $job->{members}; + next unless $job->{constituents}; my $id = $drvPathToId{$job->{drvPath}} or die; - foreach my $drvPath (split / /, $job->{members}) { - my $member = $drvPathToId{$drvPath}; - if (defined $member) { - $db->resultset('AggregateMembers')->update_or_create({aggregate => $id, member => $member}); + foreach my $drvPath (split / /, $job->{constituents}) { + my $constituent = $drvPathToId{$drvPath}; + if (defined $constituent) { + $db->resultset('AggregateConstituents')->update_or_create({aggregate => $id, constituent => $constituent}); } else { - warn "aggregate job ‘$job->{jobName}’ has a member ‘$drvPath’ that doesn't correspond to a Hydra build\n"; + warn "aggregate job ‘$job->{jobName}’ has a constituent ‘$drvPath’ that doesn't correspond to a Hydra build\n"; } } } diff --git a/src/sql/hydra.sql b/src/sql/hydra.sql index ee601c62..7bbd26b3 100644 --- a/src/sql/hydra.sql +++ b/src/sql/hydra.sql @@ -514,10 +514,10 @@ create table NewsItems ( ); -create table AggregateMembers ( +create table AggregateConstituents ( aggregate integer not null references Builds(id) on delete cascade, - member integer not null references Builds(id) on delete cascade, - primary key (aggregate, member) + constituent integer not null references Builds(id) on delete cascade, + primary key (aggregate, constituent) ); diff --git a/src/sql/upgrade-19.sql b/src/sql/upgrade-19.sql index 3d1e849b..72462d62 100644 --- a/src/sql/upgrade-19.sql +++ b/src/sql/upgrade-19.sql @@ -1,5 +1,5 @@ -create table AggregateMembers ( +create table AggregateConstituents ( aggregate integer not null references Builds(id) on delete cascade, - member integer not null references Builds(id) on delete cascade, - primary key (aggregate, member) + constituent integer not null references Builds(id) on delete cascade, + primary key (aggregate, constituent) ); From d92d83a82ad6f3b5e7ca2b37a0072db07b111bac Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 15 Aug 2013 02:57:36 +0200 Subject: [PATCH 044/215] Fix broken redirect when editing a release --- src/lib/Hydra/Controller/Release.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Hydra/Controller/Release.pm b/src/lib/Hydra/Controller/Release.pm index 126cad6f..5007fdec 100644 --- a/src/lib/Hydra/Controller/Release.pm +++ b/src/lib/Hydra/Controller/Release.pm @@ -72,7 +72,7 @@ sub submit : Chained('release') PathPart('submit') Args(0) { txn_do($c->model('DB')->schema, sub { updateRelease($c, $c->stash->{release}); }); - $c->res->redirect($c->uri_for($self->action_for("project"), + $c->res->redirect($c->uri_for($self->action_for("view"), [$c->stash->{project}->name, $c->stash->{release}->name])); } } From 06c74085b55536f70532bbba9b48e2c5e6d9ca5f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 15 Aug 2013 03:07:20 +0200 Subject: [PATCH 045/215] Make "Add to release" a modal dialog --- src/root/build.tt | 37 ++++++++++++++++++++++++------------- src/root/topbar.tt | 8 +++++++- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/root/build.tt b/src/root/build.tt index 73dc0c54..b58dbff1 100644 --- a/src/root/build.tt +++ b/src/root/build.tt @@ -192,19 +192,6 @@
- [% IF c.user_exists && available %] -
-
-
- -
- - -
-
-
- [% END %] - [% IF build.buildproducts && !isAggregate %]

Build products

@@ -559,4 +546,28 @@
+[% IF c.user_exists && available && project.releases %] + +[% END %] + + [% END %] diff --git a/src/root/topbar.tt b/src/root/topbar.tt index fd924615..8b92e9ba 100644 --- a/src/root/topbar.tt +++ b/src/root/topbar.tt @@ -1,6 +1,6 @@ [% BLOCK menuItem %]
  • - [% title %] + [% title %]
  • [% END %] @@ -155,6 +155,12 @@ uri = c.uri_for('/build' build.id 'cancel') title = "Cancel build" %] [% END %] + [% IF available && project.releases %] + [% INCLUDE menuItem + uri = "#add-to-release" + title = "Add to release" + modal = 1 %] + [% END %] [% END %] [% END %] [% END %] From 72a0fa6ec5a5b86cc2f12b04686de4555546c703 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 15 Aug 2013 03:28:21 +0200 Subject: [PATCH 046/215] Sort constituents by job name --- src/lib/Hydra/Controller/Build.pm | 3 +++ src/root/build.tt | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/lib/Hydra/Controller/Build.pm b/src/lib/Hydra/Controller/Build.pm index 6fef3ed8..77532657 100644 --- a/src/lib/Hydra/Controller/Build.pm +++ b/src/lib/Hydra/Controller/Build.pm @@ -118,6 +118,9 @@ sub build_GET { ] }) ); + + # If this is an aggregate build, get its constituents. + $c->stash->{constituents} = [$c->stash->{build}->constituents_->search({}, {order_by => ["job"]})]; } diff --git a/src/root/build.tt b/src/root/build.tt index b58dbff1..a28463a7 100644 --- a/src/root/build.tt +++ b/src/root/build.tt @@ -7,7 +7,7 @@ [% project = build.project %] [% jobset = build.jobset %] [% job = build.job %] -[% isAggregate = build.constituents_ ? 1 : 0 %] +[% isAggregate = constituents ? 1 : 0 %] [% BLOCK renderOutputs %] [% start=1; FOREACH output IN outputs %] @@ -122,7 +122,7 @@ nrConstituents = 0; nrFinished = 0; nrFailedConstituents = 0; - FOREACH b IN build.constituents_; + FOREACH b IN constituents; nrConstituents = nrConstituents + 1; IF b.finished; nrFinished = nrFinished + 1; END; IF b.finished && b.buildstatus != 0; nrFailedConstituents = nrFailedConstituents + 1; END; @@ -267,7 +267,7 @@

    This build is an aggregate of the following builds:

    - [% INCLUDE renderBuildList builds=build.constituents_ %] + [% INCLUDE renderBuildList builds=constituents %] From c9a0e128048b769a696c84809d622027e3228357 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 15 Aug 2013 03:35:18 +0200 Subject: [PATCH 047/215] Hide project/jobset in constituent list --- src/root/build.tt | 2 +- src/root/common.tt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/root/build.tt b/src/root/build.tt index a28463a7..11dd1b92 100644 --- a/src/root/build.tt +++ b/src/root/build.tt @@ -267,7 +267,7 @@

    This build is an aggregate of the following builds:

    - [% INCLUDE renderBuildList builds=constituents %] + [% INCLUDE renderBuildList builds=constituents hideProjectName=1 hideJobsetName=1 %] diff --git a/src/root/common.tt b/src/root/common.tt index 664853a6..35e4b032 100644 --- a/src/root/common.tt +++ b/src/root/common.tt @@ -64,7 +64,7 @@ BLOCK renderBuildListHeader %] [% IF !hideJobName %] Job [% END %] - Release Name + Release name System [% IF showSchedulingInfo %]Queued at[% ELSE %]Finished at[% END %] [% IF showStatusChange %] From 242072bbd64b30d27c25ac6130d874ee7132289f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 15 Aug 2013 13:54:23 +0200 Subject: [PATCH 048/215] Hide the views tab for project that don't have them Views are obsolete (replaced by the declarative "aggregate" build mechanism) so we don't want people creating new ones. --- src/root/project.tt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/root/project.tt b/src/root/project.tt index 2e35ec8b..c98876e6 100644 --- a/src/root/project.tt +++ b/src/root/project.tt @@ -5,7 +5,9 @@
  • Jobsets
  • Configuration
  • Releases
  • -
  • Views
  • + [% IF views.size > 0 %] +
  • Views
  • + [% END %]
    From 8e1ade442224463f370ab752d45181e6b2d8b0b6 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 15 Aug 2013 13:57:47 +0200 Subject: [PATCH 049/215] Fix display of non-aggregate builds --- src/root/build.tt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/root/build.tt b/src/root/build.tt index 11dd1b92..061e4afe 100644 --- a/src/root/build.tt +++ b/src/root/build.tt @@ -7,7 +7,7 @@ [% project = build.project %] [% jobset = build.jobset %] [% job = build.job %] -[% isAggregate = constituents ? 1 : 0 %] +[% isAggregate = constituents.size > 0 %] [% BLOCK renderOutputs %] [% start=1; FOREACH output IN outputs %] From d16738e1300a4efe7d7d1c3c44e6adf30b5716ef Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 16 Aug 2013 16:21:30 +0200 Subject: [PATCH 050/215] hydra-update-gc-roots: Keep the most recent evaluations We now keep all builds in the N most recent evaluations of a jobset, rather than the N most recent builds of every job. Note that this means that typically fewer builds will be kept (since jobs may be unchanged across evaluations). --- src/root/edit-jobset.tt | 2 +- src/root/jobset.tt | 2 +- src/script/hydra-update-gc-roots | 22 +++++++++------------- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/root/edit-jobset.tt b/src/root/edit-jobset.tt index efac4790..c7c98e95 100644 --- a/src/root/edit-jobset.tt +++ b/src/root/edit-jobset.tt @@ -110,7 +110,7 @@
    - +
    jobset.keepnr) %]>
    diff --git a/src/root/jobset.tt b/src/root/jobset.tt index 156ef5df..13db0e3d 100644 --- a/src/root/jobset.tt +++ b/src/root/jobset.tt @@ -133,7 +133,7 @@ [% HTML.escape(jobset.emailoverride) %] - Number of builds to keep: + Number of evaluations to keep: [% jobset.keepnr %] diff --git a/src/script/hydra-update-gc-roots b/src/script/hydra-update-gc-roots index 43b17a4b..75475c42 100755 --- a/src/script/hydra-update-gc-roots +++ b/src/script/hydra-update-gc-roots @@ -89,20 +89,16 @@ foreach my $project ($db->resultset('Projects')->search({}, { order_by => ["name next; } - # FIXME: base this on jobset evals? - print STDERR "*** looking for the $keepnr most recent successful builds of each job in jobset ", + print STDERR "*** looking for all builds in the $keepnr most recent evaluations of jobset ", $project->name, ":", $jobset->name, "\n"; - keepBuild $_ foreach $jobset->builds->search( - { 'me.id' => { 'in' => \ - [ "select b2.id from Builds b2 join " . - " (select distinct job, system, coalesce( " . - " (select id from builds where project = b.project and jobset = b.jobset and job = b.job and system = b.system and finished = 1 and buildStatus = 0 order by id desc offset ? limit 1)" . - " , 0) as nth from builds b where project = ? and jobset = ? and isCurrent = 1) x " . - " on b2.project = ? and b2.jobset = ? and b2.job = x.job and b2.system = x.system and (id >= x.nth) where finished = 1 and buildStatus = 0" - , [ '', $keepnr - 1 ], [ '', $project->name ], [ '', $jobset->name ], [ '', $project->name ], [ '', $jobset->name ] ] } - }, - { order_by => ["job", "system", "id"], columns => [ @columns ] }); + keepBuild $_ foreach $jobset->builds->search( + { finished => 1, buildStatus => 0 + , id => { -in => + \ [ "select build from JobsetEvalMembers where eval in (select id from JobsetEvals where project = ? and jobset = ? and hasNewBuilds = 1 order by id desc limit ?)", + [ '', $project->name ], [ '', $jobset->name ], [ '', $keepnr ] ] } + }, + { order_by => ["job", "id"], columns => [ @columns ] }); } # Go over all views in this project. @@ -115,7 +111,7 @@ foreach my $project ($db->resultset('Projects')->search({}, { order_by => ["name # Keep all builds belonging to the most recent successful view result. my $latest = getLatestSuccessfulViewResult($project, $primaryJob, $jobs, 0); if (defined $latest) { - print STDERR " keeping latest successful view result ", $latest->id, " (", $latest->get_column('releasename'), ")\n"; + print STDERR " keeping latest successful view result ", $latest->id, " (", $latest->get_column('releasename') // "unnamed", ")\n"; my $result = getViewResult($latest, $jobs); keepBuild $_->{build} foreach @{$result->{jobs}}; } From 46f8b25c1f33870f3cd21bd1ef5d6bdcfea40e79 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 16 Aug 2013 16:36:06 +0200 Subject: [PATCH 051/215] Keep builds that failed with output The user may want to look at the output, so they shouldn't be GC'ed right away. --- src/script/hydra-update-gc-roots | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/script/hydra-update-gc-roots b/src/script/hydra-update-gc-roots index 75475c42..e8a1337c 100755 --- a/src/script/hydra-update-gc-roots +++ b/src/script/hydra-update-gc-roots @@ -92,12 +92,12 @@ foreach my $project ($db->resultset('Projects')->search({}, { order_by => ["name print STDERR "*** looking for all builds in the $keepnr most recent evaluations of jobset ", $project->name, ":", $jobset->name, "\n"; - keepBuild $_ foreach $jobset->builds->search( - { finished => 1, buildStatus => 0 - , id => { -in => + keepBuild $_ foreach $jobset->builds->search( + { finished => 1, buildStatus => { -in => [0, 6] } + , id => { -in => \ [ "select build from JobsetEvalMembers where eval in (select id from JobsetEvals where project = ? and jobset = ? and hasNewBuilds = 1 order by id desc limit ?)", [ '', $project->name ], [ '', $jobset->name ], [ '', $keepnr ] ] } - }, + }, { order_by => ["job", "id"], columns => [ @columns ] }); } From a9c6f522e6ae592b0589853577e071c8c2364811 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 16 Aug 2013 15:15:13 +0200 Subject: [PATCH 052/215] clear_queue_non_current: Don't use isCurrent --- src/lib/Hydra/Controller/Admin.pm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/Hydra/Controller/Admin.pm b/src/lib/Hydra/Controller/Admin.pm index c9ef09fc..d533ed5c 100644 --- a/src/lib/Hydra/Controller/Admin.pm +++ b/src/lib/Hydra/Controller/Admin.pm @@ -35,7 +35,11 @@ sub machines : Chained('admin') PathPart('machines') Args(0) { sub clear_queue_non_current : Chained('admin') PathPart('clear-queue-non-current') Args(0) { my ($self, $c) = @_; my $time = time(); - $c->model('DB::Builds')->search({finished => 0, iscurrent => 0, busy => 0})->update({ finished => 1, buildstatus => 4, starttime => $time, stoptime => $time }); + $c->model('DB::Builds')->search( + { finished => 0, busy => 0 + , id => { -not_in => \ "select build from JobsetEvalMembers where eval in (select max(id) from JobsetEvals where hasNewBuilds = 1 group by project, jobset)" } + }, {}) + ->update({ finished => 1, buildstatus => 4, starttime => $time, stoptime => $time }); $c->res->redirect($c->request->referer // "/admin"); } From 056e2ce5036911716836b4fa7b54c536f6848295 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 16 Aug 2013 16:15:09 +0200 Subject: [PATCH 053/215] Don't mess with $LOGNAME in nix-shell --- release.nix | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/release.nix b/release.nix index 867290b2..81e63611 100644 --- a/release.nix +++ b/release.nix @@ -122,7 +122,10 @@ in rec { gzip bzip2 lzma gnutar unzip git gitAndTools.topGit mercurial gnused graphviz bazaar ] ++ lib.optionals stdenv.isLinux [ rpm dpkg cdrkit ] ); - preCheck = "patchShebangs ."; + preCheck = '' + patchShebangs . + export LOGNAME=${LOGNAME:-foo} + ''; postInstall = '' mkdir -p $out/nix-support @@ -138,8 +141,6 @@ in rec { done ''; # */ - LOGNAME = "foo"; - meta.description = "Build of Hydra on ${system}"; }); From e54c361a9560f36605459b02f000faa001fc1ab3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 16 Aug 2013 16:39:42 +0200 Subject: [PATCH 054/215] Remove per-platform links from the job page Having different builds within a job is obsolete (issue #60), one should have different job per platform (e.g. build.x86_64-linux). --- src/lib/Hydra/Controller/Job.pm | 2 -- src/root/job.tt | 3 --- 2 files changed, 5 deletions(-) diff --git a/src/lib/Hydra/Controller/Job.pm b/src/lib/Hydra/Controller/Job.pm index e062e14a..50b6b875 100644 --- a/src/lib/Hydra/Controller/Job.pm +++ b/src/lib/Hydra/Controller/Job.pm @@ -36,8 +36,6 @@ sub overview : Chained('job') PathPart('') Args(0) { , '+as' => ['enabled'] } ) ]; - - $c->stash->{systems} = [$c->stash->{job}->builds->search({iscurrent => 1}, {select => ["system"], distinct => 1})]; } diff --git a/src/root/job.tt b/src/root/job.tt index 1361fccd..8722d3df 100644 --- a/src/root/job.tt +++ b/src/root/job.tt @@ -25,9 +25,6 @@
    From 62649951988b3186fc83b4b2066fa46aa2b7b673 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 16 Aug 2013 17:16:15 +0200 Subject: [PATCH 055/215] Remove the jobs status page The per-system presentation doesn't make much sense any more given issue #60. It should be replaced by (say) a grid showing each job per evaluation. --- src/lib/Hydra/Controller/Jobset.pm | 40 ++---------------------------- src/root/jobset-status-tab.tt | 23 ----------------- src/root/jobset.tt | 3 --- 3 files changed, 2 insertions(+), 64 deletions(-) delete mode 100644 src/root/jobset-status-tab.tt diff --git a/src/lib/Hydra/Controller/Jobset.pm b/src/lib/Hydra/Controller/Jobset.pm index f1fed9dc..2dc2aa3b 100644 --- a/src/lib/Hydra/Controller/Jobset.pm +++ b/src/lib/Hydra/Controller/Jobset.pm @@ -151,8 +151,8 @@ sub jobs_tab : Chained('jobsetChain') PathPart('jobs-tab') Args(0) { $c->stash->{activeJobs} = []; $c->stash->{inactiveJobs} = []; - (my $latestEval) = $c->stash->{jobset}->jobsetevals->search( - { hasnewbuilds => 1}, { limit => 1, order_by => ["id desc"] }); + my $latestEval = $c->stash->{jobset}->jobsetevals->search( + { hasnewbuilds => 1}, { limit => 1, order_by => ["id desc"] })->single; my %activeJobs; if (defined $latestEval) { @@ -173,42 +173,6 @@ sub jobs_tab : Chained('jobsetChain') PathPart('jobs-tab') Args(0) { } -sub status_tab : Chained('jobsetChain') PathPart('status-tab') Args(0) { - my ($self, $c) = @_; - $c->stash->{template} = 'jobset-status-tab.tt'; - - # FIXME: use latest eval instead of iscurrent. - - $c->stash->{systems} = - [ $c->stash->{jobset}->builds->search({ iscurrent => 1 }, { select => ["system"], distinct => 1, order_by => "system" }) ]; - - # status per system - my @systems = (); - foreach my $system (@{$c->stash->{systems}}) { - push(@systems, $system->system); - } - - my @select = (); - my @as = (); - push(@select, "job"); push(@as, "job"); - foreach my $system (@systems) { - push(@select, "(select buildstatus from Builds b where b.id = (select max(id) from Builds t where t.project = me.project and t.jobset = me.jobset and t.job = me.job and t.system = '$system' and t.iscurrent = 1 ))"); - push(@as, $system); - push(@select, "(select b.id from Builds b where b.id = (select max(id) from Builds t where t.project = me.project and t.jobset = me.jobset and t.job = me.job and t.system = '$system' and t.iscurrent = 1 ))"); - push(@as, "$system-build"); - } - - $c->stash->{activeJobsStatus} = [ - $c->model('DB')->resultset('ActiveJobsForJobset')->search( - {}, - { bind => [$c->stash->{project}->name, $c->stash->{jobset}->name] - , select => \@select - , as => \@as - , order_by => ["job"] - }) ]; -} - - # Hydra::Base::Controller::ListBuilds needs this. sub get_builds : Chained('jobsetChain') PathPart('') CaptureArgs(0) { my ($self, $c) = @_; diff --git a/src/root/jobset-status-tab.tt b/src/root/jobset-status-tab.tt deleted file mode 100644 index d12d4ae5..00000000 --- a/src/root/jobset-status-tab.tt +++ /dev/null @@ -1,23 +0,0 @@ -[% PROCESS common.tt %] - - - [% FOREACH s IN systems %][% END %] - - [% FOREACH j IN activeJobsStatus %] - - - [% FOREACH s IN systems %] - [% system = s.system %] - [% systemStatus = j.get_column(system) %] - - [% END %] - - [% END %] - -
    Job[% s.system %]
    [% INCLUDE renderJobName project=project.name jobset = jobset.name job = j.get_column('job') %] - [% IF systemStatus != undef %] - - [% INCLUDE renderBuildStatusIcon buildstatus=systemStatus size=16 %] - - [% END %] -
    diff --git a/src/root/jobset.tt b/src/root/jobset.tt index 13db0e3d..d24f0880 100644 --- a/src/root/jobset.tt +++ b/src/root/jobset.tt @@ -45,7 +45,6 @@ [% IF jobset.errormsg %]
  • Evaluation errors
  • [% END %] -
  • Job status
  • Jobs
  • Configuration
  • @@ -91,8 +90,6 @@ - [% INCLUDE makeLazyTab tabName="tabs-status" uri=c.uri_for('/jobset' project.name jobset.name "status-tab") %] - [% IF jobset.errormsg %]

    Errors occurred at [% INCLUDE renderDateTime timestamp=jobset.errortime %].

    From 14e418cafa7428a66865aafcf67b09031832a9c5 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 16 Aug 2013 18:26:01 +0200 Subject: [PATCH 056/215] Don't show bogus last-checked times --- src/root/project.tt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/root/project.tt b/src/root/project.tt index c98876e6..bf36d76d 100644 --- a/src/root/project.tt +++ b/src/root/project.tt @@ -43,7 +43,7 @@ [% INCLUDE renderJobsetName project=project.name jobset=j.name inRow=1 %] [% HTML.escape(j.description) %] - [% INCLUDE renderDateTime timestamp = j.lastcheckedtime %] + [% IF j.lastcheckedtime; INCLUDE renderDateTime timestamp = j.lastcheckedtime; ELSE; "-"; END %] [% IF j.get_column('nrtotal') > 0 %] [% successrate = ( j.get_column('nrsucceeded') / j.get_column('nrtotal') )*100 %] [% IF j.get_column('nrscheduled') > 0 %] From edb88ef452546533cb8b24134b2c8b686b732134 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 20 Aug 2013 15:22:46 +0200 Subject: [PATCH 057/215] Remove unused ActiveJobs source --- src/lib/Hydra/Schema/Builds.pm | 2 -- tests/query-all-tables.pl | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/Hydra/Schema/Builds.pm b/src/lib/Hydra/Schema/Builds.pm index f67fabda..e6daced1 100644 --- a/src/lib/Hydra/Schema/Builds.pm +++ b/src/lib/Hydra/Schema/Builds.pm @@ -604,8 +604,6 @@ sub makeQueries { QUERY ); - makeSource("ActiveJobs$name", "select distinct project, jobset, job from Builds where isCurrent = 1 $constraint"); - makeSource( "LatestSucceeded$name", <resultset($source)->all) == 1, $title); - } elsif( $source !~ m/^(LatestSucceeded|JobStatus|ActiveJobs)/) { + } elsif( $source !~ m/^(LatestSucceeded|JobStatus)/) { ok(scalar($db->resultset($source)->all) == 0, $title); } else { ok(scalar($db->resultset($source)->search({},{ bind => ["", "", ""] })) == 0, $title); From fda9b66dc719acbec7ad22a22e4794de92d3dff3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 20 Aug 2013 17:37:15 +0200 Subject: [PATCH 058/215] Doh --- tests/query-all-tables.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/query-all-tables.pl b/tests/query-all-tables.pl index d786505c..c484c4a8 100755 --- a/tests/query-all-tables.pl +++ b/tests/query-all-tables.pl @@ -7,7 +7,7 @@ my $db = Hydra::Model::DB->new; my @sources = $db->schema->sources; my $nrtables = scalar(@sources); -use Test::Simple tests => 45; +use Test::Simple tests => 41; foreach my $source (@sources) { my $title = "Basic select query for $source"; From 02cba7561022a2c1bc0ad73453e81fd84e772e20 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 21 Aug 2013 14:30:38 +0200 Subject: [PATCH 059/215] Add an action to download a specific output of a build as a .nar.bz2 E.g. http://hydra/build/3515983/output/out downloads the output named "out" as a bzip2-compressed NAR. --- src/lib/Hydra/Controller/Build.pm | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/lib/Hydra/Controller/Build.pm b/src/lib/Hydra/Controller/Build.pm index 77532657..46fe1d5c 100644 --- a/src/lib/Hydra/Controller/Build.pm +++ b/src/lib/Hydra/Controller/Build.pm @@ -245,6 +245,21 @@ sub download : Chained('buildChain') PathPart { } +sub output : Chained('buildChain') PathPart Args(1) { + my ($self, $c, $outputName) = @_; + my $build = $c->stash->{build}; + + error($c, "This build is not finished yet.") unless $build->finished; + my $output = $build->buildoutputs->find({name => $outputName}); + notFound($c, "This build has no output named ‘$outputName’") unless defined $output; + error($c, "Output is not available.") unless isValidPath $output->path; + + $c->response->header('Content-Disposition', "attachment; filename=\"build-${\$build->id}-${\$outputName}.nar.bz2\""); + $c->stash->{current_view} = 'NixNAR'; + $c->stash->{storePath} = $output->path; +} + + # Redirect to a download with the given type. Useful when you want to # link to some build product of the latest build (i.e. in conjunction # with the .../latest redirect). From 9a9b798939cb9643c11567d83fe8776ea00698ad Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 21 Aug 2013 15:10:40 +0200 Subject: [PATCH 060/215] Work around 9P corruption on 32-bit On 32-bit, Linux 3.4, and if the memory size is bigger than a certain value, starting the stage 2 init script fails with "Exec format error" because the 9P filesystem is returning garbage. No such problem with Linux 3.10. http://hydra.nixos.org/build/5737226 --- release.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/release.nix b/release.nix index 81e63611..e5603dcb 100644 --- a/release.nix +++ b/release.nix @@ -175,7 +175,7 @@ in rec { tests.api = genAttrs' (system: with import { inherit system; }; - let hydra = builtins.getAttr system build; in # build.${system} + let hydra = builtins.getAttr system build; in # build."${system}" simpleTest { machine = { config, pkgs, ... }: @@ -183,6 +183,7 @@ in rec { services.postgresql.package = pkgs.postgresql92; environment.systemPackages = [ hydra pkgs.perlPackages.LWP pkgs.perlPackages.JSON ]; virtualisation.memorySize = 2047; + boot.kernelPackages = pkgs.linuxPackages_3_10; }; testScript = From a98075f38658131cacac9e1be58e0baedfe6c14f Mon Sep 17 00:00:00 2001 From: Rob Vermaas Date: Mon, 26 Aug 2013 11:06:10 +0000 Subject: [PATCH 061/215] HipChat notification: do not include latest commits of all inputs in 'who-broke-the-build' list. Use only committers from inputs that have actually changed since previous build. --- src/lib/Hydra/Plugin/HipChatNotification.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/Hydra/Plugin/HipChatNotification.pm b/src/lib/Hydra/Plugin/HipChatNotification.pm index 91e5464f..fb8f538b 100644 --- a/src/lib/Hydra/Plugin/HipChatNotification.pm +++ b/src/lib/Hydra/Plugin/HipChatNotification.pm @@ -51,6 +51,7 @@ sub buildFinished { next if $curInput->type ne $prevInput->type; next if $curInput->uri ne $prevInput->uri; + next if $curInput->revision eq $prevInput->revision; my @commits; foreach my $plugin (@{$self->{plugins}}) { From a57957df841755af8006349c972259985952e8b1 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 27 Aug 2013 11:48:02 +0200 Subject: [PATCH 062/215] Handle job aliases in AggregateConstituents Aggregate constituents are derivations. However there can be multiple builds in an evaluation that have the same derivation, i.e. they can alias each other (e.g. "emacs", "emacs24" and "emacs24Packages.emacs" in Nixpkgs). Previously we picked a build arbitrarily for the AggregateConstituents table. Now we pick the one with the shortest name (e.g. "emacs"). --- src/lib/Hydra/Helper/AddBuilds.pm | 4 ++-- src/script/hydra-evaluator | 21 ++++++++++++++++----- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/lib/Hydra/Helper/AddBuilds.pm b/src/lib/Hydra/Helper/AddBuilds.pm index f1e21b49..ec62811d 100644 --- a/src/lib/Hydra/Helper/AddBuilds.pm +++ b/src/lib/Hydra/Helper/AddBuilds.pm @@ -433,7 +433,7 @@ sub checkBuild { { rows => 1, columns => ['id'], join => ['buildoutputs'] }); if (defined $prevBuild) { print STDERR " already scheduled/built as build ", $prevBuild->id, "\n"; - $buildMap->{$prevBuild->id} = { new => 0, drvPath => $drvPath }; + $buildMap->{$prevBuild->id} = { id => $prevBuild->id, jobName => $jobName, new => 0, drvPath => $drvPath }; return; } } @@ -506,7 +506,7 @@ sub checkBuild { $build->buildoutputs->create({ name => $_, path => $buildInfo->{output}->{$_}->{path} }) foreach @outputNames; - $buildMap->{$build->id} = { new => 1, drvPath => $drvPath }; + $buildMap->{$build->id} = { id => $build->id, jobName => $jobName, new => 1, drvPath => $drvPath }; $$jobOutPathMap{$jobName . "\t" . $firstOutputPath} = $build->id; if ($build->iscachedbuild) { diff --git a/src/script/hydra-evaluator b/src/script/hydra-evaluator index 0fdca76a..7505b93b 100755 --- a/src/script/hydra-evaluator +++ b/src/script/hydra-evaluator @@ -177,20 +177,31 @@ sub checkJobsetWrapped { if ($hasNewBuilds) { # Create JobsetEvalMembers mappings. - my %drvPathToId; while (my ($id, $x) = each %buildMap) { $ev->jobsetevalmembers->create({ build => $id, isnew => $x->{new} }); - $drvPathToId{$x->{drvPath}} = $id; } - # Create AggregateConstituents mappings. + # Create AggregateConstituents mappings. Since there can + # be jobs that alias each other, if there are multiple + # builds for the same derivation, pick the one with the + # shortest name. + my %drvPathToId; + while (my ($id, $x) = each %buildMap) { + my $y = $drvPathToId{$x->{drvPath}}; + if (defined $y) { + next if length $x->{jobName} > length $y->{jobName}; + next if length $x->{jobName} == length $y->{jobName} && $x->{jobName} ge $y->{jobName}; + } + $drvPathToId{$x->{drvPath}} = $x; + } + foreach my $job (@{$jobs->{job}}) { next unless $job->{constituents}; - my $id = $drvPathToId{$job->{drvPath}} or die; + my $x = $drvPathToId{$job->{drvPath}} or die; foreach my $drvPath (split / /, $job->{constituents}) { my $constituent = $drvPathToId{$drvPath}; if (defined $constituent) { - $db->resultset('AggregateConstituents')->update_or_create({aggregate => $id, constituent => $constituent}); + $db->resultset('AggregateConstituents')->update_or_create({aggregate => $x->{id}, constituent => $constituent->{id}}); } else { warn "aggregate job ‘$job->{jobName}’ has a constituent ‘$drvPath’ that doesn't correspond to a Hydra build\n"; } From bf42392fe4ad0408b379f0daabb0ec0dc9bf5c75 Mon Sep 17 00:00:00 2001 From: Rob Vermaas Date: Tue, 27 Aug 2013 15:12:41 +0200 Subject: [PATCH 063/215] Fix typo. --- src/script/hydra-evaluator | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/script/hydra-evaluator b/src/script/hydra-evaluator index 7505b93b..3d76589b 100755 --- a/src/script/hydra-evaluator +++ b/src/script/hydra-evaluator @@ -65,7 +65,7 @@ sub sendJobsetErrorNotification() { my $body = "Hi,\n" . "\n" - . "This is to let you know that Hydra jobset evalation of $projectName:$jobsetName " + . "This is to let you know that Hydra jobset evaluation of $projectName:$jobsetName " . "resulted in the following error:\n" . "\n" . "$errorMsg" From 7725038821337dfd2557dda85b18b3fc3b0fd9d2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 26 Aug 2013 18:58:04 +0200 Subject: [PATCH 064/215] On aggregate job pages, show a matrix showing all the constituent builds --- src/lib/Hydra/Controller/Job.pm | 26 ++++++++++++- src/root/job.tt | 68 +++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/src/lib/Hydra/Controller/Job.pm b/src/lib/Hydra/Controller/Job.pm index 50b6b875..2e272395 100644 --- a/src/lib/Hydra/Controller/Job.pm +++ b/src/lib/Hydra/Controller/Job.pm @@ -20,15 +20,16 @@ sub job : Chained('/') PathPart('job') CaptureArgs(3) { sub overview : Chained('job') PathPart('') Args(0) { my ($self, $c) = @_; + my $job = $c->stash->{job}; $c->stash->{template} = 'job.tt'; $c->stash->{lastBuilds} = - [ $c->stash->{job}->builds->search({ finished => 1 }, + [ $job->builds->search({ finished => 1 }, { order_by => 'id DESC', rows => 10, columns => [@buildListColumns] }) ]; $c->stash->{queuedBuilds} = [ - $c->stash->{job}->builds->search( + $job->builds->search( { finished => 0 }, { join => ['project'] , order_by => ["priority DESC", "id"] @@ -36,6 +37,27 @@ sub overview : Chained('job') PathPart('') Args(0) { , '+as' => ['enabled'] } ) ]; + + # If this is an aggregate job, then get its constituents. + my @constituents = $c->model('DB::Builds')->search( + { aggregate => { -in => $job->builds->search({}, { columns => ["id"], order_by => "id desc", rows => 10 })->as_query } }, + { join => 'aggregateconstituents_constituents', + columns => ['id', 'job', 'finished', 'buildstatus'], + +select => ['aggregateconstituents_constituents.aggregate'], + +as => ['aggregate'] + }); + + my $aggregates = {}; + my %constituentJobs; + foreach my $b (@constituents) { + my $jobName = $b->get_column('job'); + $aggregates->{$b->get_column('aggregate')}->{$jobName} = + { id => $b->id, finished => $b->finished, buildstatus => $b->buildstatus}; + $constituentJobs{$jobName} = 1; + } + + $c->stash->{aggregates} = $aggregates; + $c->stash->{constituentJobs} = [sort (keys %constituentJobs)]; } diff --git a/src/root/job.tt b/src/root/job.tt index 8722d3df..c9c4b6cb 100644 --- a/src/root/job.tt +++ b/src/root/job.tt @@ -4,6 +4,9 @@ @@ -21,6 +24,71 @@ [% END %]
    + [% IF constituentJobs.size > 0 %] + +
    + + + + + + [% FOREACH j IN constituentJobs %] + + [% END %] + + + + [% FOREACH agg IN aggregates.keys.nsort.reverse %] + + + [% FOREACH j IN constituentJobs %] + + [% END %] + + [% END %] + +
    #[% HTML.escape(j) %]
    [% agg %] + [% r = aggregates.$agg.$j; IF r.id %] + + [% INCLUDE renderBuildStatusIcon size=16 build=r %] + + [% END %] +
    + +
    + + + + + + [% FOREACH j IN constituentJobs %] + + [% END %] + + + + [% FOREACH agg IN aggregates.keys.nsort.reverse %] + + + [% FOREACH j IN constituentJobs %] + + [% END %] + + [% END %] + +
    #[% HTML.escape(j) %]
    [% agg %] + [% r = aggregates.$agg.$j; IF r.id %] + + [% INCLUDE renderBuildStatusIcon size=16 build=r %] + + [% END %] +
    + + +
    + + [% END %] + - [% IF jobset.errormsg %] + [% IF jobset.errormsg || jobset.fetcherrormsg %]

    Errors occurred at [% INCLUDE renderDateTime timestamp=jobset.errortime %].

    -
    [% HTML.escape(jobset.errormsg) %]
    +
    [% HTML.escape(jobset.fetcherrormsg || jobset.errormsg) %]
    [% END %] diff --git a/src/script/hydra-evaluator b/src/script/hydra-evaluator index 3a089e58..73f0ed9e 100755 --- a/src/script/hydra-evaluator +++ b/src/script/hydra-evaluator @@ -46,7 +46,7 @@ sub setJobsetError { eval { txn_do($db, sub { - $jobset->update({errormsg => $errorMsg, errortime => time}); + $jobset->update({ errormsg => $errorMsg, errortime => time, fetcherrormsg => undef }); }); }; if ($errorMsg ne $prevError) { @@ -115,7 +115,17 @@ sub checkJobsetWrapped { # Fetch all values for all inputs. my $checkoutStart = time; - fetchInputs($project, $jobset, $inputInfo); + eval { + fetchInputs($project, $jobset, $inputInfo); + }; + if ($@) { + my $msg = $@; + print STDERR $msg; + txn_do($db, sub { + $jobset->update({ lastcheckedtime => time, fetcherrormsg => $msg }); + }); + return; + } my $checkoutStop = time; # Hash the arguments to hydra-eval-jobs and check the @@ -127,7 +137,7 @@ sub checkJobsetWrapped { if (defined $prevEval && $prevEval->hash eq $argsHash) { print STDERR " jobset is unchanged, skipping\n"; txn_do($db, sub { - $jobset->update({lastcheckedtime => time}); + $jobset->update({ lastcheckedtime => time, fetcherrormsg => undef }); }); return; } @@ -253,7 +263,7 @@ sub checkJobsetWrapped { } $msg .= ($error->{location} ne "" ? "in job ‘$error->{location}’" : "at top-level") . - " [$bindings]:\n" . $error->{msg} . "\n\n"; + ":\n" . $error->{msg} . "\n\n"; } setJobsetError($jobset, $msg); } @@ -275,7 +285,7 @@ sub checkJobset { if ($@) { my $msg = $@; - print STDERR "error evaluating jobset ", $jobset->name, ": $msg"; + print STDERR $msg; txn_do($db, sub { $jobset->update({lastcheckedtime => time}); setJobsetError($jobset, $msg); diff --git a/src/sql/hydra.sql b/src/sql/hydra.sql index 388fd7c9..a1fe253d 100644 --- a/src/sql/hydra.sql +++ b/src/sql/hydra.sql @@ -62,6 +62,7 @@ create table Jobsets ( keepnr integer not null default 3, checkInterval integer not null default 300, -- minimum time in seconds between polls (0 = disable polling) schedulingShares integer not null default 100, + fetchErrorMsg text, primary key (project, name), foreign key (project) references Projects(name) on delete cascade on update cascade #ifdef SQLITE diff --git a/src/sql/upgrade-22.sql b/src/sql/upgrade-22.sql new file mode 100644 index 00000000..c2a182c4 --- /dev/null +++ b/src/sql/upgrade-22.sql @@ -0,0 +1 @@ +alter table Jobsets add column fetchErrorMsg text; From baafe77489ef37d786dbf1f50fe5e853db626e19 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 30 Sep 2013 11:18:48 +0200 Subject: [PATCH 130/215] Fix HTML error From Mats Erik Andersson. --- src/root/jobset-eval.tt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/root/jobset-eval.tt b/src/root/jobset-eval.tt index 31df8a3a..1187a9d0 100644 --- a/src/root/jobset-eval.tt +++ b/src/root/jobset-eval.tt @@ -4,11 +4,11 @@
    Compare to...
    - Edit @@ -186,4 +186,18 @@ + + [% END %] diff --git a/src/root/static/js/common.js b/src/root/static/js/common.js index 69984746..78269b76 100644 --- a/src/root/static/js/common.js +++ b/src/root/static/js/common.js @@ -88,3 +88,29 @@ var makeLazyTab = function(tabName, uri) { } }); } + +var requestJSON = function(args) { + args.dataType = 'json'; + args.error = function(data) { + json = {}; + try { + if (data.responseText) + json = $.parseJSON(data.responseText); + } catch (err) { + } + if (json.error) + bootbox.alert(json.error); + else if (data.responseText) + bootbox.alert("Server error: " + data.responseText); + else + bootbox.alert("Unknown server error!"); + }; + return $.ajax(args); +} + +var redirectJSON = function(args) { + args.success = function(data) { + window.location = data.redirect; + }; + return requestJSON(args); +} From 851c3329d097d818d288eff49d7b396d1faf90f2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 3 Oct 2013 17:54:40 +0200 Subject: [PATCH 155/215] Implement DELETE for jobsets and use it in the web interface --- src/lib/Hydra/Controller/Jobset.pm | 25 +++++++++++++-------- src/root/common.tt | 5 ++++- src/root/edit-jobset.tt | 12 ---------- src/root/jobset.tt | 36 ++++++++++++++++++------------ src/root/project.tt | 8 +++---- 5 files changed, 46 insertions(+), 40 deletions(-) diff --git a/src/lib/Hydra/Controller/Jobset.pm b/src/lib/Hydra/Controller/Jobset.pm index daa7ecb6..1cbca3ea 100644 --- a/src/lib/Hydra/Controller/Jobset.pm +++ b/src/lib/Hydra/Controller/Jobset.pm @@ -1,5 +1,6 @@ package Hydra::Controller::Jobset; +use utf8; use strict; use warnings; use base 'Hydra::Base::Controller::ListBuilds'; @@ -135,6 +136,21 @@ sub jobset_PUT { } } +sub jobset_DELETE { + my ($self, $c) = @_; + + requireProjectOwner($c, $c->stash->{project}); + + txn_do($c->model('DB')->schema, sub { + $c->stash->{jobset}->jobsetevals->delete_all; + $c->stash->{jobset}->builds->delete_all; + $c->stash->{jobset}->delete; + }); + + my $uri = $c->uri_for($c->controller('Project')->action_for("project"), [$c->stash->{project}->name]); + $self->status_ok($c, entity => { redirect => "$uri" }); +} + sub jobs_tab : Chained('jobsetChain') PathPart('jobs-tab') Args(0) { my ($self, $c) = @_; @@ -207,15 +223,6 @@ sub submit : Chained('jobsetChain') PathPart Args(0) { requirePost($c); requireProjectOwner($c, $c->stash->{project}); - if (($c->request->params->{submit} // "") eq "delete") { - txn_do($c->model('DB')->schema, sub { - $c->stash->{jobset}->jobsetevals->delete_all; - $c->stash->{jobset}->builds->delete_all; - $c->stash->{jobset}->delete; - }); - return $c->res->redirect($c->uri_for($c->controller('Project')->action_for("project"), [$c->stash->{project}->name])); - } - my $newName = trim $c->stash->{params}->{name}; my $oldName = trim $c->stash->{jobset}->name; unless ($oldName eq $newName) { diff --git a/src/root/common.tt b/src/root/common.tt index b99e3408..5107190e 100644 --- a/src/root/common.tt +++ b/src/root/common.tt @@ -452,7 +452,10 @@ BLOCK makePopover %] BLOCK menuItem %]
  • - [% title %] + + [% IF icon %] [%+ END %] + [% title %] +
  • [% END; diff --git a/src/root/edit-jobset.tt b/src/root/edit-jobset.tt index 0ec080a7..fa364a55 100644 --- a/src/root/edit-jobset.tt +++ b/src/root/edit-jobset.tt @@ -133,18 +133,6 @@
    - - [% IF !create %] - - - [% END %]
    diff --git a/src/root/jobset.tt b/src/root/jobset.tt index 541785c2..54dbfb1e 100644 --- a/src/root/jobset.tt +++ b/src/root/jobset.tt @@ -49,9 +49,10 @@ [% END %] @@ -114,8 +115,6 @@
    - Edit -
    Display name:
    @@ -167,16 +166,25 @@ 'Are you sure you want to force evaluation of this jobset?', function(c) { if (!c) return; - $.post("[% HTML.escape(c.uri_for('/api/push', { jobsets = project.name _ ':' _ jobset.name, force = "1" })) %]") - .done(function(data) { - if (data.error) - bootbox.alert("Unable to schedule the jobset for evaluation: " + data.error); - else - bootbox.alert("The jobset has been scheduled for evaluation."); - }) - .fail(function() { bootbox.alert("Server request failed!"); }); + requestJSON({ + url: "[% HTML.escape(c.uri_for('/api/push', { jobsets = project.name _ ':' _ jobset.name, force = "1" })) %]", + success: function(data) { + bootbox.alert("The jobset has been scheduled for evaluation."); + } + }); + }); + }; + + function deleteJobset() { + bootbox.confirm( + 'Are you sure you want to delete this jobset?', + function(c) { + if (!c) return; + redirectJSON({ + url: "[% c.uri_for(c.controller('Jobset').action_for('jobset'), [project.name, jobset.name]) %]", + type: 'DELETE' + }); }); - return; }; diff --git a/src/root/project.tt b/src/root/project.tt index 9b5721e1..2398f53d 100644 --- a/src/root/project.tt +++ b/src/root/project.tt @@ -9,10 +9,10 @@ [% END %] From 63062f7bba05e2aae20799cf35c1a866d149390a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 3 Oct 2013 18:05:37 +0200 Subject: [PATCH 156/215] Instead hard breaks in multi-line error messages --- src/root/error.tt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/root/error.tt b/src/root/error.tt index 928c1baa..c591ccc0 100644 --- a/src/root/error.tt +++ b/src/root/error.tt @@ -2,7 +2,7 @@ [% USE HTML %] [% FOREACH error IN errors %] -
    [% HTML.escape(error) %]
    +
    [% HTML.escape(error).replace('\n', '
    ') %]
    [% END %] [% END %] From 232f46c750af491ac80ab845ef36182d7bc88e48 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 3 Oct 2013 18:49:37 +0200 Subject: [PATCH 157/215] Use the REST API in the web interface for editing jobsets --- src/lib/Hydra/Controller/Jobset.pm | 124 ++++++---------------------- src/lib/Hydra/Controller/Project.pm | 11 +-- src/root/edit-jobset.tt | 60 +++++++++----- src/root/edit-project.tt | 36 ++++---- src/root/static/js/common.js | 20 +++-- 5 files changed, 93 insertions(+), 158 deletions(-) diff --git a/src/lib/Hydra/Controller/Jobset.pm b/src/lib/Hydra/Controller/Jobset.pm index 1cbca3ea..5f3cdb74 100644 --- a/src/lib/Hydra/Controller/Jobset.pm +++ b/src/lib/Hydra/Controller/Jobset.pm @@ -10,6 +10,7 @@ use Hydra::Helper::CatalystUtils; sub jobsetChain :Chained('/') :PathPart('jobset') :CaptureArgs(2) { my ($self, $c, $projectName, $jobsetName) = @_; + $c->stash->{jobsetName} //= $jobsetName; my $project = $c->model('DB::Projects')->find($projectName); @@ -17,18 +18,10 @@ sub jobsetChain :Chained('/') :PathPart('jobset') :CaptureArgs(2) { $c->stash->{project} = $project; - $c->stash->{jobset_} = $project->jobsets->search({'me.name' => $jobsetName}); - my $jobset = $c->stash->{jobset_}->single; + $c->stash->{jobset} = $project->jobsets->find({ name => $jobsetName }); - if ($jobset) { - $c->stash->{jobset} = $jobset; - } else { - if ($c->action->name eq "jobset" and $c->request->method eq "PUT") { - $c->stash->{jobsetName} = $jobsetName; - } else { - notFound($c, "Jobset ‘$jobsetName’ doesn't exist."); - } - } + notFound($c, "Jobset ‘$jobsetName’ doesn't exist.") + if !$c->stash->{jobset} && !($c->action->name eq "jobset" and $c->request->method eq "PUT"); } @@ -45,25 +38,7 @@ sub jobset_GET { $c->stash->{totalShares} = getTotalShares($c->model('DB')->schema); - $self->status_ok( - $c, - entity => $c->stash->{jobset_}->find({}, { - columns => [ - 'me.name', - 'me.project', - 'me.errormsg', - 'me.emailoverride', - 'jobsetinputs.name', - { - 'jobsetinputs.jobsetinputalts.altnr' => 'jobsetinputalts.altnr', - 'jobsetinputs.jobsetinputalts.value' => 'jobsetinputalts.value' - } - ], - join => { 'jobsetinputs' => 'jobsetinputalts' }, - collapse => 1, - order_by => "me.name" - }) - ); + $self->status_ok($c, entity => $c->stash->{jobset}); } sub jobset_PUT { @@ -72,67 +47,28 @@ sub jobset_PUT { requireProjectOwner($c, $c->stash->{project}); if (defined $c->stash->{jobset}) { - error($c, "Cannot rename jobset `$c->stash->{params}->{oldName}' over existing jobset `$c->stash->{jobset}->name") if defined $c->stash->{params}->{oldName} and $c->stash->{params}->{oldName} ne $c->stash->{jobset}->name; txn_do($c->model('DB')->schema, sub { updateJobset($c, $c->stash->{jobset}); }); - if ($c->req->looks_like_browser) { - $c->res->redirect($c->uri_for($self->action_for("jobset"), - [$c->stash->{project}->name, $c->stash->{jobset}->name]) . "#tabs-configuration"); - } else { - $self->status_no_content($c); - } - } elsif (defined $c->stash->{params}->{oldName}) { - my $jobset = $c->stash->{project}->jobsets->find({'me.name' => $c->stash->{params}->{oldName}}); - - if (defined $jobset) { - txn_do($c->model('DB')->schema, sub { - updateJobset($c, $jobset); - }); - - my $uri = $c->uri_for($self->action_for("jobset"), [$c->stash->{project}->name, $jobset->name]); - - if ($c->req->looks_like_browser) { - $c->res->redirect($uri . "#tabs-configuration"); - } else { - $self->status_created( - $c, - location => "$uri", - entity => { name => $jobset->name, uri => "$uri", type => "jobset" } - ); - } - } else { - $self->status_not_found( - $c, - message => "Jobset $c->stash->{params}->{oldName} doesn't exist." - ); - } - } else { - my $exprType = - $c->stash->{params}->{"nixexprpath"} =~ /.scm$/ ? "guile" : "nix"; - - error($c, "Invalid jobset name: ‘$c->stash->{jobsetName}’") if $c->stash->{jobsetName} !~ /^$jobsetNameRE$/; + my $uri = $c->uri_for($self->action_for("jobset"), [$c->stash->{project}->name, $c->stash->{jobset}->name]) . "#tabs-configuration"; + $self->status_ok($c, entity => { redirect => "$uri" }); + } + else { my $jobset; txn_do($c->model('DB')->schema, sub { # Note: $jobsetName is validated in updateProject, which will # abort the transaction if the name isn't valid. $jobset = $c->stash->{project}->jobsets->create( - {name => $c->stash->{jobsetName}, nixexprinput => "", nixexprpath => "", emailoverride => ""}); + {name => ".tmp", nixexprinput => "", nixexprpath => "", emailoverride => ""}); updateJobset($c, $jobset); }); my $uri = $c->uri_for($self->action_for("jobset"), [$c->stash->{project}->name, $jobset->name]); - if ($c->req->looks_like_browser) { - $c->res->redirect($uri . "#tabs-configuration"); - } else { - $self->status_created( - $c, - location => "$uri", - entity => { name => $jobset->name, uri => "$uri", type => "jobset" } - ); - } + $self->status_created($c, + location => "$uri", + entity => { name => $jobset->name, uri => "$uri", redirect => "$uri", type => "jobset" }); } } @@ -217,32 +153,15 @@ sub edit : Chained('jobsetChain') PathPart Args(0) { } -sub submit : Chained('jobsetChain') PathPart Args(0) { - my ($self, $c) = @_; - - requirePost($c); - requireProjectOwner($c, $c->stash->{project}); - - my $newName = trim $c->stash->{params}->{name}; - my $oldName = trim $c->stash->{jobset}->name; - unless ($oldName eq $newName) { - $c->stash->{params}->{oldName} = $oldName; - $c->stash->{jobsetName} = $newName; - undef $c->stash->{jobset}; - } - jobset_PUT($self, $c); -} - - sub nixExprPathFromParams { my ($c) = @_; # The Nix expression path must be relative and can't contain ".." elements. my $nixExprPath = trim $c->stash->{params}->{"nixexprpath"}; - error($c, "Invalid Nix expression path: $nixExprPath") if $nixExprPath !~ /^$relPathRE$/; + error($c, "Invalid Nix expression path ‘$nixExprPath’.") if $nixExprPath !~ /^$relPathRE$/; my $nixExprInput = trim $c->stash->{params}->{"nixexprinput"}; - error($c, "Invalid Nix expression input name: $nixExprInput") unless $nixExprInput =~ /^[[:alpha:]][\w-]*$/; + error($c, "Invalid Nix expression input name ‘$nixExprInput’.") unless $nixExprInput =~ /^[[:alpha:]][\w-]*$/; return ($nixExprPath, $nixExprInput); } @@ -251,7 +170,7 @@ sub nixExprPathFromParams { sub checkInputValue { my ($c, $type, $value) = @_; $value = trim $value; - error($c, "Invalid Boolean value: $value") if + error($c, "Invalid Boolean value ‘$value’.") if $type eq "boolean" && !($value eq "true" || $value eq "false"); return $value; } @@ -260,8 +179,11 @@ sub checkInputValue { sub updateJobset { my ($c, $jobset) = @_; - my $jobsetName = $c->stash->{jobsetName} // $jobset->name; - error($c, "Invalid jobset name: ‘$jobsetName’") if $jobsetName !~ /^$jobsetNameRE$/; + my $jobsetName = $c->stash->{params}->{name}; + error($c, "Invalid jobset identifier ‘$jobsetName’.") if $jobsetName !~ /^$jobsetNameRE$/; + + error($c, "Cannot rename jobset to ‘$jobsetName’ since that identifier is already taken.") + if $jobsetName ne $jobset->name && defined $c->stash->{project}->jobsets->find({ name => $jobsetName }); # When the expression is in a .scm file, assume it's a Guile + Guix # build expression. @@ -303,11 +225,11 @@ sub updateJobset { foreach my $inputName (keys %{$c->stash->{params}->{inputs}}) { my $inputData = $c->stash->{params}->{inputs}->{$inputName}; - error($c, "Invalid input name: $inputName") unless $inputName =~ /^[[:alpha:]][\w-]*$/; + error($c, "Invalid input name ‘$inputName’.") unless $inputName =~ /^[[:alpha:]][\w-]*$/; my $inputType = $inputData->{type}; - error($c, "Invalid input type: $inputType") unless defined $c->stash->{inputTypes}->{$inputType}; + error($c, "Invalid input type ‘$inputType’.") unless defined $c->stash->{inputTypes}->{$inputType}; my $input; unless (defined $inputData->{oldName}) { diff --git a/src/lib/Hydra/Controller/Project.pm b/src/lib/Hydra/Controller/Project.pm index 78c42db0..09d9bb7e 100644 --- a/src/lib/Hydra/Controller/Project.pm +++ b/src/lib/Hydra/Controller/Project.pm @@ -28,7 +28,7 @@ sub projectChain :Chained('/') :PathPart('project') :CaptureArgs(1) { ], join => [ 'owner', 'releases', 'jobsets' ], order_by => { -desc => "releases.timestamp" }, collapse => 1 }); notFound($c, "Project ‘$projectName’ doesn't exist.") - if (!$c->stash->{project} && !($c->action->name eq "project" and $c->request->method eq "PUT")); + if !$c->stash->{project} && !($c->action->name eq "project" and $c->request->method eq "PUT"); } @@ -141,15 +141,6 @@ sub create_jobset : Chained('projectChain') PathPart('create-jobset') Args(0) { } -sub create_jobset_submit : Chained('projectChain') PathPart('create-jobset/submit') Args(0) { - my ($self, $c) = @_; - - $c->stash->{jobsetName} = trim $c->stash->{params}->{name}; - - Hydra::Controller::Jobset::jobset_PUT($self, $c); -} - - sub updateProject { my ($c, $project) = @_; diff --git a/src/root/edit-jobset.tt b/src/root/edit-jobset.tt index fa364a55..16076692 100644 --- a/src/root/edit-jobset.tt +++ b/src/root/edit-jobset.tt @@ -44,7 +44,7 @@
    Description:
    [% END %] - +
    @@ -132,7 +132,7 @@ [% INCLUDE renderJobsetInputs %]
    - +
    @@ -145,26 +145,42 @@ [% INCLUDE renderJobsetInputAlt alt=alt %] - - + + [% END %] diff --git a/src/root/edit-project.tt b/src/root/edit-project.tt index b5da303e..233229ab 100644 --- a/src/root/edit-project.tt +++ b/src/root/edit-project.tt @@ -57,23 +57,6 @@
    @@ -81,4 +64,23 @@ + + + [% END %] diff --git a/src/root/static/js/common.js b/src/root/static/js/common.js index 78269b76..707c0a53 100644 --- a/src/root/static/js/common.js +++ b/src/root/static/js/common.js @@ -74,7 +74,7 @@ $(document).ready(function() { var tabsLoaded = {}; -var makeLazyTab = function(tabName, uri) { +function makeLazyTab(tabName, uri) { $('.nav-tabs').bind('show', function(e) { var pattern = /#.+/gi; var id = e.target.toString().match(pattern)[0]; @@ -87,9 +87,13 @@ var makeLazyTab = function(tabName, uri) { }); } }); -} +}; -var requestJSON = function(args) { +function escapeHTML(s) { + return $('
    ').text(s).html(); +}; + +function requestJSON(args) { args.dataType = 'json'; args.error = function(data) { json = {}; @@ -99,18 +103,18 @@ var requestJSON = function(args) { } catch (err) { } if (json.error) - bootbox.alert(json.error); + bootbox.alert(escapeHTML(json.error)); else if (data.responseText) - bootbox.alert("Server error: " + data.responseText); + bootbox.alert("Server error: " + escapeHTML(data.responseText)); else bootbox.alert("Unknown server error!"); }; return $.ajax(args); -} +}; -var redirectJSON = function(args) { +function redirectJSON(args) { args.success = function(data) { window.location = data.redirect; }; return requestJSON(args); -} +}; From f32077b5e8f225d8f2d7cef2325e92fbeefe4813 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 3 Oct 2013 17:26:17 +0000 Subject: [PATCH 158/215] Simplify jobset cloning We can just show the normal "edit jobset" page for the original jobset and then do a PUT request to create a new jobset. Also simplified updating the jobset inputs. We can just delete all of them and recreate them from the user parameters. That's safe because it's done in a transaction. --- src/lib/Hydra/Controller/Jobset.pm | 102 ++++++----------------------- src/root/clone-jobset.tt | 24 ------- src/root/edit-jobset.tt | 8 +-- src/root/edit-project.tt | 2 +- src/root/jobset.tt | 2 +- 5 files changed, 27 insertions(+), 111 deletions(-) delete mode 100644 src/root/clone-jobset.tt diff --git a/src/lib/Hydra/Controller/Jobset.pm b/src/lib/Hydra/Controller/Jobset.pm index 5f3cdb74..0dfa824b 100644 --- a/src/lib/Hydra/Controller/Jobset.pm +++ b/src/lib/Hydra/Controller/Jobset.pm @@ -149,6 +149,7 @@ sub edit : Chained('jobsetChain') PathPart Args(0) { $c->stash->{template} = 'edit-jobset.tt'; $c->stash->{edit} = 1; + $c->stash->{clone} = defined $c->stash->{params}->{clone}; $c->stash->{totalShares} = getTotalShares($c->model('DB')->schema); } @@ -209,102 +210,41 @@ sub updateJobset { , schedulingshares => int($c->stash->{params}->{schedulingshares}) }); - # Process the inputs of this jobset. - unless (defined $c->stash->{params}->{inputs}) { - $c->stash->{params}->{inputs} = {}; - foreach my $param (keys %{$c->stash->{params}}) { - next unless $param =~ /^input-(\w+)-name$/; - my $baseName = $1; - next if $baseName eq "template"; - $c->stash->{params}->{inputs}->{$c->stash->{params}->{$param}} = { type => $c->stash->{params}->{"input-$baseName-type"}, values => $c->stash->{params}->{"input-$baseName-values"} }; - unless ($baseName =~ /^\d+$/) { # non-numeric base name is an existing entry - $c->stash->{params}->{inputs}->{$c->stash->{params}->{$param}}->{oldName} = $baseName; - } - } - } + # Set the inputs of this jobset. + $jobset->jobsetinputs->delete; - foreach my $inputName (keys %{$c->stash->{params}->{inputs}}) { - my $inputData = $c->stash->{params}->{inputs}->{$inputName}; - error($c, "Invalid input name ‘$inputName’.") unless $inputName =~ /^[[:alpha:]][\w-]*$/; + foreach my $param (keys %{$c->stash->{params}}) { + next unless $param =~ /^input-(\w+)-name$/; + my $baseName = $1; + next if $baseName eq "template"; + my $name = $c->stash->{params}->{$param}; + my $type = $c->stash->{params}->{"input-$baseName-type"}; + my $values = $c->stash->{params}->{"input-$baseName-values"}; - my $inputType = $inputData->{type}; + error($c, "Invalid input name ‘$name’.") unless $name =~ /^[[:alpha:]][\w-]*$/; + error($c, "Invalid input type ‘$type’.") unless defined $c->stash->{inputTypes}->{$type}; - error($c, "Invalid input type ‘$inputType’.") unless defined $c->stash->{inputTypes}->{$inputType}; + my $input = $jobset->jobsetinputs->create({ name => $name, type => $type }); - my $input; - unless (defined $inputData->{oldName}) { - $input = $jobset->jobsetinputs->update_or_create( - { name => $inputName - , type => $inputType - }); - } else { # it's an existing input - $input = ($jobset->jobsetinputs->search({name => $inputData->{oldName}}))[0]; - die unless defined $input; - $input->update({name => $inputName, type => $inputType}); - } - - # Update the values for this input. Just delete all the - # current ones, then create the new values. - $input->jobsetinputalts->delete_all; - my $values = $inputData->{values}; - $values = [] unless defined $values; - $values = [$values] unless ref($values) eq 'ARRAY'; + # Set the values for this input. + my @values = ref($values) eq 'ARRAY' ? @{$values} : ($values); my $altnr = 0; - foreach my $value (@{$values}) { - $value = checkInputValue($c, $inputType, $value); + foreach my $value (@values) { + $value = checkInputValue($c, $type, $value); $input->jobsetinputalts->create({altnr => $altnr++, value => $value}); } } - - # Get rid of deleted inputs. - my @inputs = $jobset->jobsetinputs->all; - foreach my $input (@inputs) { - $input->delete unless defined $c->stash->{params}->{inputs}->{$input->name}; - } } sub clone : Chained('jobsetChain') PathPart('clone') Args(0) { my ($self, $c) = @_; - my $jobset = $c->stash->{jobset}; - requireProjectOwner($c, $jobset->project); + requireProjectOwner($c, $c->stash->{project}); - $c->stash->{template} = 'clone-jobset.tt'; -} - - -sub clone_submit : Chained('jobsetChain') PathPart('clone/submit') Args(0) { - my ($self, $c) = @_; - - my $jobset = $c->stash->{jobset}; - requireProjectOwner($c, $jobset->project); - requirePost($c); - - my $newJobsetName = trim $c->stash->{params}->{"newjobset"}; - error($c, "Invalid jobset name: $newJobsetName") if $newJobsetName !~ /^$jobsetNameRE$/; - - my $newJobset; - txn_do($c->model('DB')->schema, sub { - $newJobset = $jobset->project->jobsets->create( - { name => $newJobsetName - , description => $jobset->description - , nixexprpath => $jobset->nixexprpath - , nixexprinput => $jobset->nixexprinput - , enabled => 0 - , enableemail => $jobset->enableemail - , emailoverride => $jobset->emailoverride || "" - }); - - foreach my $input ($jobset->jobsetinputs) { - my $newinput = $newJobset->jobsetinputs->create({name => $input->name, type => $input->type}); - foreach my $inputalt ($input->jobsetinputalts) { - $newinput->jobsetinputalts->create({altnr => $inputalt->altnr, value => $inputalt->value}); - } - } - }); - - $c->res->redirect($c->uri_for($c->controller('Jobset')->action_for("edit"), [$jobset->project->name, $newJobsetName])); + $c->stash->{template} = 'edit-jobset.tt'; + $c->stash->{clone} = 1; + $c->stash->{totalShares} = getTotalShares($c->model('DB')->schema); } diff --git a/src/root/clone-jobset.tt b/src/root/clone-jobset.tt deleted file mode 100644 index 386f06ed..00000000 --- a/src/root/clone-jobset.tt +++ /dev/null @@ -1,24 +0,0 @@ -[% WRAPPER layout.tt title="Clone jobset $jobset.project.name:$jobset.name" %] -[% PROCESS common.tt %] -[% USE HTML %] -[% edit=1 %] - -
    - -
    -
    - -
    - -
    -
    - -
    - -
    - -
    - -
    - -[% END %] diff --git a/src/root/edit-jobset.tt b/src/root/edit-jobset.tt index 16076692..0cc48c89 100644 --- a/src/root/edit-jobset.tt +++ b/src/root/edit-jobset.tt @@ -1,4 +1,4 @@ -[% WRAPPER layout.tt title=(create ? "Create jobset in project $project.name" : "Editing jobset $project.name:$jobset.name") %] +[% WRAPPER layout.tt title=(create ? "Create jobset in project $project.name" : clone ? "Cloning jobset $project.name:$jobset.name" : "Editing jobset $project.name:$jobset.name") %] [% PROCESS common.tt %] [% USE format %] @@ -64,7 +64,7 @@
    - jobset.name) %]/> + clone ? "" : jobset.name) %]/>
    @@ -132,7 +132,7 @@ [% INCLUDE renderJobsetInputs %]
    - +
    @@ -168,7 +168,7 @@ $("#submit-jobset").click(function() { requestJSON({ - [% IF create %] + [% IF create || clone %] url: "[% c.uri_for('/jobset' project.name '.new') %]", [% ELSE %] url: "[% c.uri_for('/jobset' project.name jobset.name) %]", diff --git a/src/root/edit-project.tt b/src/root/edit-project.tt index 233229ab..58fcfecf 100644 --- a/src/root/edit-project.tt +++ b/src/root/edit-project.tt @@ -56,7 +56,7 @@
    diff --git a/src/root/jobset.tt b/src/root/jobset.tt index 54dbfb1e..8bffd227 100644 --- a/src/root/jobset.tt +++ b/src/root/jobset.tt @@ -51,7 +51,7 @@ From 720c3892a36ad96c67539f6a73484ddb755d89cf Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 3 Oct 2013 19:42:44 +0200 Subject: [PATCH 159/215] Use delete instead of delete_all DBIC's delete_all method fetches all rows separately, which is slow. --- src/lib/Hydra/Controller/Admin.pm | 18 +++++------------- src/lib/Hydra/Controller/Jobset.pm | 4 ++-- src/lib/Hydra/Controller/Project.pm | 4 ++-- src/lib/Hydra/Controller/Release.pm | 2 +- src/lib/Hydra/Controller/User.pm | 2 +- src/lib/Hydra/Controller/View.pm | 2 +- src/lib/Hydra/Helper/AddBuilds.pm | 2 +- src/script/hydra-build | 4 ++-- 8 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/lib/Hydra/Controller/Admin.pm b/src/lib/Hydra/Controller/Admin.pm index d533ed5c..f2a34459 100644 --- a/src/lib/Hydra/Controller/Admin.pm +++ b/src/lib/Hydra/Controller/Admin.pm @@ -53,19 +53,11 @@ sub clearfailedcache : Chained('admin') PathPart('clear-failed-cache') Args(0) { sub clearvcscache : Chained('admin') PathPart('clear-vcs-cache') Args(0) { my ($self, $c) = @_; - - print STDERR "Clearing path cache\n"; - $c->model('DB::CachedPathInputs')->delete_all; - - print STDERR "Clearing git cache\n"; - $c->model('DB::CachedGitInputs')->delete_all; - - print STDERR "Clearing subversion cache\n"; - $c->model('DB::CachedSubversionInputs')->delete_all; - - print STDERR "Clearing bazaar cache\n"; - $c->model('DB::CachedBazaarInputs')->delete_all; - + $c->model('DB::CachedPathInputs')->delete; + $c->model('DB::CachedGitInputs')->delete; + $c->model('DB::CachedSubversionInputs')->delete; + $c->model('DB::CachedBazaarInputs')->delete; + $c->flash->{successMsg} = "VCS caches have been cleared."; $c->res->redirect($c->request->referer // "/admin"); } diff --git a/src/lib/Hydra/Controller/Jobset.pm b/src/lib/Hydra/Controller/Jobset.pm index 0dfa824b..e0d5ee83 100644 --- a/src/lib/Hydra/Controller/Jobset.pm +++ b/src/lib/Hydra/Controller/Jobset.pm @@ -78,8 +78,8 @@ sub jobset_DELETE { requireProjectOwner($c, $c->stash->{project}); txn_do($c->model('DB')->schema, sub { - $c->stash->{jobset}->jobsetevals->delete_all; - $c->stash->{jobset}->builds->delete_all; + $c->stash->{jobset}->jobsetevals->delete; + $c->stash->{jobset}->builds->delete; $c->stash->{jobset}->delete; }); diff --git a/src/lib/Hydra/Controller/Project.pm b/src/lib/Hydra/Controller/Project.pm index 09d9bb7e..8a7f39da 100644 --- a/src/lib/Hydra/Controller/Project.pm +++ b/src/lib/Hydra/Controller/Project.pm @@ -88,8 +88,8 @@ sub project_DELETE { requireProjectOwner($c, $c->stash->{project}); txn_do($c->model('DB')->schema, sub { - $c->stash->{project}->jobsetevals->delete_all; - $c->stash->{project}->builds->delete_all; + $c->stash->{project}->jobsetevals->delete; + $c->stash->{project}->builds->delete; $c->stash->{project}->delete; }); diff --git a/src/lib/Hydra/Controller/Release.pm b/src/lib/Hydra/Controller/Release.pm index 5007fdec..a4fb61c9 100644 --- a/src/lib/Hydra/Controller/Release.pm +++ b/src/lib/Hydra/Controller/Release.pm @@ -38,7 +38,7 @@ sub updateRelease { , description => trim $c->request->params->{description} }); - $release->releasemembers->delete_all; + $release->releasemembers->delete; foreach my $param (keys %{$c->request->params}) { next unless $param =~ /^member-(\d+)-description$/; my $buildId = $1; diff --git a/src/lib/Hydra/Controller/User.pm b/src/lib/Hydra/Controller/User.pm index 84ec88df..e4f0ecc4 100644 --- a/src/lib/Hydra/Controller/User.pm +++ b/src/lib/Hydra/Controller/User.pm @@ -254,7 +254,7 @@ sub edit_POST { } if (isAdmin($c)) { - $user->userroles->delete_all; + $user->userroles->delete; $user->userroles->create({ role => $_}) foreach paramToList($c, "roles"); } diff --git a/src/lib/Hydra/Controller/View.pm b/src/lib/Hydra/Controller/View.pm index 1f8f7847..88e78cb5 100644 --- a/src/lib/Hydra/Controller/View.pm +++ b/src/lib/Hydra/Controller/View.pm @@ -41,7 +41,7 @@ sub updateView { { name => $viewName , description => trim $c->request->params->{description} }); - $view->viewjobs->delete_all; + $view->viewjobs->delete; foreach my $param (keys %{$c->request->params}) { next unless $param =~ /^job-(\d+)-name$/; diff --git a/src/lib/Hydra/Helper/AddBuilds.pm b/src/lib/Hydra/Helper/AddBuilds.pm index b9d7a297..1a575e66 100644 --- a/src/lib/Hydra/Helper/AddBuilds.pm +++ b/src/lib/Hydra/Helper/AddBuilds.pm @@ -570,7 +570,7 @@ sub restartBuild { , iscachedbuild => 0 }); - $build->buildproducts->delete_all; + $build->buildproducts->delete; # Reset the stats for the evals to which this build belongs. # !!! Should do this in a trigger. diff --git a/src/script/hydra-build b/src/script/hydra-build index 72968930..edd0d121 100755 --- a/src/script/hydra-build +++ b/src/script/hydra-build @@ -354,8 +354,8 @@ txn_do($db, sub { die "build $buildId is already being built"; } $build->update({busy => 1, locker => $$}); - $build->buildsteps->search({busy => 1})->delete_all; - $build->buildproducts->delete_all; + $build->buildsteps->search({busy => 1})->delete; + $build->buildproducts->delete; }); die unless $build; From 383bc628039f9752715a7544ee7605aae77c4f1c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 3 Oct 2013 19:43:21 +0200 Subject: [PATCH 160/215] Restore link to clearvcscache --- src/root/topbar.tt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/root/topbar.tt b/src/root/topbar.tt index 9899a37b..af539ffa 100644 --- a/src/root/topbar.tt +++ b/src/root/topbar.tt @@ -94,6 +94,11 @@ title = "Clear all non-running old builds from queue" confirmmsg = "Are you sure you want to clear the queue?" class = "" %] + [% INCLUDE menuItem + uri = c.uri_for(c.controller('Admin').action_for('clearvcscache')) + title = "Clear VCS caches" + confirmmsg = "Are you sure you want to clear the VCS cache?" + class = "" %] [% END %] [% END %] From 550bf210fe111d49187b0d3ccdc6218ab337e1f5 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 3 Oct 2013 19:54:22 +0200 Subject: [PATCH 161/215] Use more flash messages --- src/lib/Hydra/Controller/Jobset.pm | 6 +++++- src/lib/Hydra/Controller/Project.pm | 4 ++++ src/root/layout.tt | 6 +++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/lib/Hydra/Controller/Jobset.pm b/src/lib/Hydra/Controller/Jobset.pm index e0d5ee83..b8a149d2 100644 --- a/src/lib/Hydra/Controller/Jobset.pm +++ b/src/lib/Hydra/Controller/Jobset.pm @@ -53,6 +53,8 @@ sub jobset_PUT { my $uri = $c->uri_for($self->action_for("jobset"), [$c->stash->{project}->name, $c->stash->{jobset}->name]) . "#tabs-configuration"; $self->status_ok($c, entity => { redirect => "$uri" }); + + $c->flash->{successMsg} = "The jobset configuration has been updated."; } else { @@ -85,6 +87,8 @@ sub jobset_DELETE { my $uri = $c->uri_for($c->controller('Project')->action_for("project"), [$c->stash->{project}->name]); $self->status_ok($c, entity => { redirect => "$uri" }); + + $c->flash->{successMsg} = "The jobset has been deleted."; } @@ -107,7 +111,7 @@ sub jobs_tab : Chained('jobsetChain') PathPart('jobs-tab') Args(0) { { columns => ['id', 'job', 'finished', 'buildstatus'] }); foreach my $b (@builds) { my $jobName = $b->get_column('job'); - $evals->{$eval->id}->{$jobName} = + $evals->{$eval->id}->{$jobName} = { id => $b->id, finished => $b->finished, buildstatus => $b->buildstatus }; $jobs{$jobName} = 1; $nrBuilds++; diff --git a/src/lib/Hydra/Controller/Project.pm b/src/lib/Hydra/Controller/Project.pm index 8a7f39da..f2cede92 100644 --- a/src/lib/Hydra/Controller/Project.pm +++ b/src/lib/Hydra/Controller/Project.pm @@ -59,6 +59,8 @@ sub project_PUT { my $uri = $c->uri_for($self->action_for("project"), [$c->stash->{project}->name]) . "#tabs-configuration"; $self->status_ok($c, entity => { redirect => "$uri" }); + + $c->flash->{successMsg} = "The project configuration has been updated."; } else { @@ -95,6 +97,8 @@ sub project_DELETE { my $uri = $c->res->redirect($c->uri_for("/")); $self->status_ok($c, entity => { redirect => "$uri" }); + + $c->flash->{successMsg} = "The project has been deleted."; } diff --git a/src/root/layout.tt b/src/root/layout.tt index cf58d27b..b2b76910 100644 --- a/src/root/layout.tt +++ b/src/root/layout.tt @@ -67,17 +67,17 @@ [% IF flashMsg %]
    -

    [% flashMsg %]

    +
    [% flashMsg %]
    [% END %] [% IF successMsg %]
    -

    [% successMsg %]

    +
    [% successMsg %]
    [% END %] [% IF errorMsg %]
    -

    Error: [% errorMsg %]

    +
    Error: [% errorMsg %]
    [% END %] [% IF !hideHeader %] From ee5b65553593e4a5c9e2b9abe201d17119790002 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 3 Oct 2013 20:03:57 +0200 Subject: [PATCH 162/215] Maintain the order of the input alternatives --- src/root/edit-jobset.tt | 2 +- src/root/jobset.tt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/root/edit-jobset.tt b/src/root/edit-jobset.tt index 0cc48c89..c299f60f 100644 --- a/src/root/edit-jobset.tt +++ b/src/root/edit-jobset.tt @@ -18,7 +18,7 @@ [% INCLUDE renderSelection curValue=input.type param="$baseName-type" options=inputTypes %] - [% FOREACH alt IN input.jobsetinputalts %] + [% FOREACH alt IN input.search_related('jobsetinputalts', {}, { order_by => 'altnr' }) %] [% INCLUDE renderJobsetInputAlt alt=alt name="$baseName-values" %] diff --git a/src/root/jobset.tt b/src/root/jobset.tt index 8bffd227..9a45d3cd 100644 --- a/src/root/jobset.tt +++ b/src/root/jobset.tt @@ -12,7 +12,7 @@ [% INCLUDE renderSelection curValue=input.type param="$baseName-type" options=inputTypes %] - [% FOREACH alt IN input.jobsetinputalts %] + [% FOREACH alt IN input.search_related('jobsetinputalts', {}, { order_by => 'altnr' }) %] [% IF input.type == "string" %] "[% HTML.escape(alt.value) %]" From 3e54f0a6abe70a8a850823e512675744b26bac85 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 4 Oct 2013 14:47:30 +0200 Subject: [PATCH 163/215] Fix an uninitialized value warning --- src/lib/Hydra/Controller/JobsetEval.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Hydra/Controller/JobsetEval.pm b/src/lib/Hydra/Controller/JobsetEval.pm index 8b152394..9fa99dd8 100644 --- a/src/lib/Hydra/Controller/JobsetEval.pm +++ b/src/lib/Hydra/Controller/JobsetEval.pm @@ -91,7 +91,7 @@ sub view : Chained('eval') PathPart('') Args(0) { if ($d == 0) { $n++; $found = 1; - if ($build->buildstatus == 3 || $build->buildstatus == 4) { + if ($build->finished != 0 && ($build->buildstatus == 3 || $build->buildstatus == 4)) { push @{$c->stash->{aborted}}, $build; } elsif ($build->finished == 0 || $build2->finished == 0) { push @{$c->stash->{unfinished}}, $build; From 7818bb75ede7512fb503887df9cb58debecfb8cd Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 4 Oct 2013 15:40:43 +0200 Subject: [PATCH 164/215] Add an action to cancel all builds in a jobset eval --- src/lib/Hydra/Controller/Admin.pm | 8 +++--- src/lib/Hydra/Controller/Build.pm | 34 ++++++-------------------- src/lib/Hydra/Controller/JobsetEval.pm | 9 +++++++ src/lib/Hydra/Helper/Nix.pm | 25 ++++++++++++++++--- src/root/jobset-eval.tt | 1 + src/root/topbar.tt | 2 +- 6 files changed, 44 insertions(+), 35 deletions(-) diff --git a/src/lib/Hydra/Controller/Admin.pm b/src/lib/Hydra/Controller/Admin.pm index f2a34459..a73762dc 100644 --- a/src/lib/Hydra/Controller/Admin.pm +++ b/src/lib/Hydra/Controller/Admin.pm @@ -34,12 +34,12 @@ sub machines : Chained('admin') PathPart('machines') Args(0) { sub clear_queue_non_current : Chained('admin') PathPart('clear-queue-non-current') Args(0) { my ($self, $c) = @_; - my $time = time(); - $c->model('DB::Builds')->search( + my $builds = $c->model('DB::Builds')->search( { finished => 0, busy => 0 , id => { -not_in => \ "select build from JobsetEvalMembers where eval in (select max(id) from JobsetEvals where hasNewBuilds = 1 group by project, jobset)" } - }, {}) - ->update({ finished => 1, buildstatus => 4, starttime => $time, stoptime => $time }); + }); + my $n = cancelBuilds($c->model('DB')->schema, $builds); + $c->flash->{successMsg} = "$n builds have been cancelled."; $c->res->redirect($c->request->referer // "/admin"); } diff --git a/src/lib/Hydra/Controller/Build.pm b/src/lib/Hydra/Controller/Build.pm index f0524aa4..7d62ebba 100644 --- a/src/lib/Hydra/Controller/Build.pm +++ b/src/lib/Hydra/Controller/Build.pm @@ -60,7 +60,6 @@ sub build_GET { $c->stash->{template} = 'build.tt'; $c->stash->{available} = all { isValidPath($_->path) } $build->buildoutputs->all; $c->stash->{drvAvailable} = isValidPath $build->drvpath; - $c->stash->{flashMsg} = $c->flash->{buildMsg}; if (!$build->finished && $build->busy) { $c->stash->{logtext} = read_file($build->logfile, err_mode => 'quiet') // ""; @@ -448,7 +447,7 @@ sub restart : Chained('buildChain') PathPart Args(0) { restartBuild($c->model('DB')->schema, $build); - $c->flash->{buildMsg} = "Build has been restarted."; + $c->flash->{successMsg} = "Build has been restarted."; $c->res->redirect($c->uri_for($self->action_for("build"), $c->req->captures)); } @@ -456,30 +455,11 @@ sub restart : Chained('buildChain') PathPart Args(0) { sub cancel : Chained('buildChain') PathPart Args(0) { my ($self, $c) = @_; - my $build = $c->stash->{build}; - requireProjectOwner($c, $build->project); - - txn_do($c->model('DB')->schema, sub { - error($c, "This build cannot be cancelled.") - if $build->finished || $build->busy; - - # !!! Actually, it would be nice to be able to cancel busy - # builds as well, but we would have to send a signal or - # something to the build process. - - my $time = time(); - $build->update( - { finished => 1, busy => 0 - , iscachedbuild => 0, buildstatus => 4 # = cancelled - , starttime => $time - , stoptime => $time - }); - }); - - $c->flash->{buildMsg} = "Build has been cancelled."; - + my $n = cancelBuilds($c->model('DB')->schema, $c->model('DB::Builds')->search({ id => $build->id })); + error($c, "This build cannot be cancelled.") if $n != 1; + $c->flash->{successMsg} = "Build has been cancelled."; $c->res->redirect($c->uri_for($self->action_for("build"), $c->req->captures)); } @@ -500,7 +480,7 @@ sub keep : Chained('buildChain') PathPart Args(1) { $build->update({keep => $keep}); }); - $c->flash->{buildMsg} = + $c->flash->{successMsg} = $keep ? "Build will be kept." : "Build will not be kept."; $c->res->redirect($c->uri_for($self->action_for("build"), $c->req->captures)); @@ -530,7 +510,7 @@ sub add_to_release : Chained('buildChain') PathPart('add-to-release') Args(0) { $release->releasemembers->create({build => $build->id, description => $build->description}); - $c->flash->{buildMsg} = "Build added to project $releaseName."; + $c->flash->{successMsg} = "Build added to project $releaseName."; $c->res->redirect($c->uri_for($self->action_for("build"), $c->req->captures)); } @@ -607,7 +587,7 @@ sub clone_submit : Chained('buildChain') PathPart('clone/submit') Args(0) { error($c, "This build has already been performed.") unless $newBuild; - $c->flash->{buildMsg} = "Build " . $newBuild->id . " added to the queue."; + $c->flash->{successMsg} = "Build " . $newBuild->id . " added to the queue."; $c->res->redirect($c->uri_for($c->controller('Root')->action_for('queue'))); } diff --git a/src/lib/Hydra/Controller/JobsetEval.pm b/src/lib/Hydra/Controller/JobsetEval.pm index 9fa99dd8..b3291166 100644 --- a/src/lib/Hydra/Controller/JobsetEval.pm +++ b/src/lib/Hydra/Controller/JobsetEval.pm @@ -152,6 +152,15 @@ sub release : Chained('eval') PathPart('release') Args(0) { } +sub cancel : Chained('eval') PathPart('cancel') Args(0) { + my ($self, $c) = @_; + requireProjectOwner($c, $c->stash->{eval}->project); + my $n = cancelBuilds($c->model('DB')->schema, $c->stash->{eval}->builds); + $c->flash->{successMsg} = "$n builds have been cancelled."; + $c->res->redirect($c->uri_for($c->controller('JobsetEval')->action_for('view'), $c->req->captures)); +} + + # Hydra::Base::Controller::NixChannel needs this. sub nix : Chained('eval') PathPart('channel') CaptureArgs(0) { my ($self, $c) = @_; diff --git a/src/lib/Hydra/Helper/Nix.pm b/src/lib/Hydra/Helper/Nix.pm index d12c569c..f5d62c7a 100644 --- a/src/lib/Hydra/Helper/Nix.pm +++ b/src/lib/Hydra/Helper/Nix.pm @@ -21,7 +21,8 @@ our @EXPORT = qw( getEvals getMachines pathIsInsidePrefix captureStdoutStderr run grab - getTotalShares); + getTotalShares + cancelBuilds); sub getHydraHome { @@ -43,11 +44,12 @@ sub getHydraConfig { # doesn't work. sub txn_do { my ($db, $coderef) = @_; + my $res; while (1) { eval { - $db->txn_do($coderef); + $res = $db->txn_do($coderef); }; - last if !$@; + return $res if !$@; die $@ unless $@ =~ "database is locked"; } } @@ -542,4 +544,21 @@ sub getTotalShares { } +sub cancelBuilds($$) { + my ($db, $builds) = @_; + return txn_do($db, sub { + $builds = $builds->search({ finished => 0, busy => 0 }); + my $n = $builds->count; + my $time = time(); + $builds->update( + { finished => 1, + , iscachedbuild => 0, buildstatus => 4 # = cancelled + , starttime => $time + , stoptime => $time + }); + return $n; + }); +} + + 1; diff --git a/src/root/jobset-eval.tt b/src/root/jobset-eval.tt index e6d579ef..81044b0a 100644 --- a/src/root/jobset-eval.tt +++ b/src/root/jobset-eval.tt @@ -42,6 +42,7 @@ c.uri_for(c.controller('JobsetEval').action_for('view'), [% END %] diff --git a/src/root/topbar.tt b/src/root/topbar.tt index af539ffa..f3a8a89d 100644 --- a/src/root/topbar.tt +++ b/src/root/topbar.tt @@ -91,7 +91,7 @@ class = "" %] [% INCLUDE menuItem uri = c.uri_for(c.controller('Admin').action_for('clear_queue_non_current')) - title = "Clear all non-running old builds from queue" + title = "Clear scheduled non-current builds from queue" confirmmsg = "Are you sure you want to clear the queue?" class = "" %] [% INCLUDE menuItem From aa49b128a8a2bbbad548dc6716a5ebb183cdce41 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 4 Oct 2013 15:43:51 +0200 Subject: [PATCH 165/215] Remove the "clone build" feature --- src/lib/Hydra/Controller/Build.pm | 77 ------------------------------- src/root/build.tt | 1 - src/root/clone-build.tt | 51 -------------------- src/sql/hydra.sql | 2 +- 4 files changed, 1 insertion(+), 130 deletions(-) delete mode 100644 src/root/clone-build.tt diff --git a/src/lib/Hydra/Controller/Build.pm b/src/lib/Hydra/Controller/Build.pm index 7d62ebba..a134a307 100644 --- a/src/lib/Hydra/Controller/Build.pm +++ b/src/lib/Hydra/Controller/Build.pm @@ -516,83 +516,6 @@ sub add_to_release : Chained('buildChain') PathPart('add-to-release') Args(0) { } -sub clone : Chained('buildChain') PathPart('clone') Args(0) { - my ($self, $c) = @_; - - my $build = $c->stash->{build}; - - requireProjectOwner($c, $build->project); - - $c->stash->{template} = 'clone-build.tt'; -} - - -sub clone_submit : Chained('buildChain') PathPart('clone/submit') Args(0) { - my ($self, $c) = @_; - - my $build = $c->stash->{build}; - - requireProjectOwner($c, $build->project); - - my ($nixExprPath, $nixExprInputName) = Hydra::Controller::Jobset::nixExprPathFromParams $c; - - # When the expression is in a .scm file, assume it's a Guile + Guix - # build expression. - my $exprType = - $c->request->params->{"nixexprpath"} =~ /.scm$/ ? "guile" : "nix"; - - my $jobName = trim $c->request->params->{"jobname"}; - error($c, "Invalid job name: $jobName") if $jobName !~ /^$jobNameRE$/; - - my $inputInfo = {}; - - foreach my $param (keys %{$c->request->params}) { - next unless $param =~ /^input-(\w+)-name$/; - my $baseName = $1; - my ($inputName, $inputType) = - Hydra::Controller::Jobset::checkInput($c, $baseName); - my $inputValue = Hydra::Controller::Jobset::checkInputValue( - $c, $inputType, $c->request->params->{"input-$baseName-value"}); - eval { - # !!! fetchInput can take a long time, which might cause - # the current HTTP request to time out. So maybe this - # should be done asynchronously. But then error reporting - # becomes harder. - my $info = fetchInput( - $c->hydra_plugins, $c->model('DB'), $build->project, $build->jobset, - $inputName, $inputType, $inputValue); - push @{$$inputInfo{$inputName}}, $info if defined $info; - }; - error($c, $@) if $@; - } - - my ($jobs, $nixExprInput) = evalJobs($inputInfo, $exprType, $nixExprInputName, $nixExprPath); - - my $job; - foreach my $j (@{$jobs->{job}}) { - print STDERR $j->{jobName}, "\n"; - if ($j->{jobName} eq $jobName) { - error($c, "Nix expression returned multiple builds for job $jobName.") - if $job; - $job = $j; - } - } - - error($c, "Nix expression did not return a job named $jobName.") unless $job; - - my %currentBuilds; - my $newBuild = checkBuild( - $c->model('DB'), $build->jobset, - $inputInfo, $nixExprInput, $job, \%currentBuilds, undef, {}, $c->hydra_plugins); - - error($c, "This build has already been performed.") unless $newBuild; - - $c->flash->{successMsg} = "Build " . $newBuild->id . " added to the queue."; - - $c->res->redirect($c->uri_for($c->controller('Root')->action_for('queue'))); -} - - sub evals : Chained('buildChain') PathPart('evals') Args(0) { my ($self, $c) = @_; diff --git a/src/root/build.tt b/src/root/build.tt index edd28b5a..9eff2d65 100644 --- a/src/root/build.tt +++ b/src/root/build.tt @@ -78,7 +78,6 @@
  • Reproduce locally
  • [% END %] [% IF c.user_exists %] -
  • Clone
  • [% IF available %] [% IF build.keep %]
  • Unkeep
  • diff --git a/src/root/clone-build.tt b/src/root/clone-build.tt deleted file mode 100644 index 588e9574..00000000 --- a/src/root/clone-build.tt +++ /dev/null @@ -1,51 +0,0 @@ -[% WRAPPER layout.tt title="Clone build ${build.id}" %] -[% PROCESS common.tt %] -[% USE HTML %] -[% edit=1 %] - -

    Cloning allows you to perform a build with modified inputs.

    - -
    - -

    Nix expression

    - -

    Evaluate job build.job.name) %] - /> in Nix expression build.nixexprpath) %] - /> in input build.nixexprinput) - %] />.

    - -

    Build inputs

    - - - - - - - [% FOREACH input IN build.inputs %] - - - - - - [% END %] - -
    NameTypeValue
    [% input.name %] "input-$input.name-name" value => input.name) %] /> - [% INCLUDE renderSelection curValue=input.type param="input-$input.name-type" options=inputTypes %] - - build.project.name _ ':' _ build.jobset.name _ ':' _ build.job.name _ '[id="'_ build.id _ '"]' ) %] - [% ELSE %] - [% HTML.attributes(value => input.value || input.uri) %] - [% END %] /> -
    - -

    - -
    - -[% END %] diff --git a/src/sql/hydra.sql b/src/sql/hydra.sql index 07700222..23fc7925 100644 --- a/src/sql/hydra.sql +++ b/src/sql/hydra.sql @@ -142,7 +142,7 @@ create table Builds ( isCurrent integer default 0, -- Copy of the nixExprInput/nixExprPath fields of the jobset that - -- instantiated this build. Needed if we want to clone this + -- instantiated this build. Needed if we want to reproduce this -- build. nixExprInput text, nixExprPath text, From e334ff541d2cb069648dbf343720d666ae026c09 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 4 Oct 2013 16:35:56 +0200 Subject: [PATCH 166/215] Jobset eval page: Show the number of builds in each tab --- src/root/jobset-eval.tt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/root/jobset-eval.tt b/src/root/jobset-eval.tt index 81044b0a..41755f9f 100644 --- a/src/root/jobset-eval.tt +++ b/src/root/jobset-eval.tt @@ -48,28 +48,28 @@ c.uri_for(c.controller('JobsetEval').action_for('view'), [% END %] [% IF aborted.size > 0 %] -
  • Aborted jobs
  • +
  • Aborted jobs ([% aborted.size %])
  • [% END %] [% IF nowFail.size > 0 %] -
  • Newly-failing jobs
  • +
  • Newly-failing jobs ([% nowFail.size %])
  • [% END %] [% IF nowSucceed.size > 0 %] -
  • Newly-succeeding jobs
  • +
  • Newly-succeeding jobs ([% nowSucceed.size %])
  • [% END %] [% IF new.size > 0 %] -
  • New jobs
  • +
  • New jobs ([% new.size %])
  • [% END %] [% IF removed.size > 0 %] -
  • Removed jobs
  • +
  • Removed jobs ([% removed.size %])
  • [% END %] [% IF stillFail.size > 0 %] -
  • Still-failing jobs
  • +
  • Still-failing jobs ([% stillFail.size %])
  • [% END %] [% IF stillSucceed.size > 0 %] -
  • Still-succeeding jobs
  • +
  • Still-succeeding jobs ([% stillSucceed.size %])
  • [% END %] [% IF unfinished.size > 0 %] -
  • Queued jobs
  • +
  • Queued jobs ([% unfinished.size %])
  • [% END %]
  • Inputs
  • From 85d51074b988d5236e444a3c03498ec97088a55b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 4 Oct 2013 16:36:22 +0200 Subject: [PATCH 167/215] Jobset eval page: Show all aborted builds under the "aborted jobs" tab Previously some might be included under the "new jobs" tab --- src/lib/Hydra/Controller/JobsetEval.pm | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/Hydra/Controller/JobsetEval.pm b/src/lib/Hydra/Controller/JobsetEval.pm index b3291166..cc06ba5a 100644 --- a/src/lib/Hydra/Controller/JobsetEval.pm +++ b/src/lib/Hydra/Controller/JobsetEval.pm @@ -82,6 +82,10 @@ sub view : Chained('eval') PathPart('') Args(0) { my $n = 0; foreach my $build (@builds) { + if ($build->finished != 0 && ($build->buildstatus == 3 || $build->buildstatus == 4)) { + push @{$c->stash->{aborted}}, $build; + next; + } my $d; my $found = 0; while ($n < scalar(@builds2)) { @@ -91,9 +95,7 @@ sub view : Chained('eval') PathPart('') Args(0) { if ($d == 0) { $n++; $found = 1; - if ($build->finished != 0 && ($build->buildstatus == 3 || $build->buildstatus == 4)) { - push @{$c->stash->{aborted}}, $build; - } elsif ($build->finished == 0 || $build2->finished == 0) { + if ($build->finished == 0 || $build2->finished == 0) { push @{$c->stash->{unfinished}}, $build; } elsif ($build->buildstatus == 0 && $build2->buildstatus == 0) { push @{$c->stash->{stillSucceed}}, $build; From 052bab169daee14002e9753ee2f3446bbaecc392 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 4 Oct 2013 17:01:47 +0200 Subject: [PATCH 168/215] Add a jobset eval action to restart all aborted/cancelled builds --- src/lib/Hydra/Controller/Build.pm | 11 ++----- src/lib/Hydra/Controller/JobsetEval.pm | 10 +++++++ src/lib/Hydra/Helper/AddBuilds.pm | 27 +---------------- src/lib/Hydra/Helper/Nix.pm | 40 ++++++++++++++++++++++++-- src/root/jobset-eval.tt | 1 + 5 files changed, 52 insertions(+), 37 deletions(-) diff --git a/src/lib/Hydra/Controller/Build.pm b/src/lib/Hydra/Controller/Build.pm index a134a307..068a0ccc 100644 --- a/src/lib/Hydra/Controller/Build.pm +++ b/src/lib/Hydra/Controller/Build.pm @@ -437,18 +437,11 @@ sub nix : Chained('buildChain') PathPart('nix') CaptureArgs(0) { sub restart : Chained('buildChain') PathPart Args(0) { my ($self, $c) = @_; - my $build = $c->stash->{build}; - requireProjectOwner($c, $build->project); - - error($c, "This build cannot be restarted.") - unless $build->finished && -f $build->drvpath; - - restartBuild($c->model('DB')->schema, $build); - + my $n = restartBuilds($c->model('DB')->schema, $c->model('DB::Builds')->search({ id => $build->id })); + error($c, "This build cannot be restarted.") if $n != 1; $c->flash->{successMsg} = "Build has been restarted."; - $c->res->redirect($c->uri_for($self->action_for("build"), $c->req->captures)); } diff --git a/src/lib/Hydra/Controller/JobsetEval.pm b/src/lib/Hydra/Controller/JobsetEval.pm index cc06ba5a..40b52cb3 100644 --- a/src/lib/Hydra/Controller/JobsetEval.pm +++ b/src/lib/Hydra/Controller/JobsetEval.pm @@ -163,6 +163,16 @@ sub cancel : Chained('eval') PathPart('cancel') Args(0) { } +sub restart_aborted : Chained('eval') PathPart('restart-aborted') Args(0) { + my ($self, $c) = @_; + requireProjectOwner($c, $c->stash->{eval}->project); + my $builds = $c->stash->{eval}->builds->search({ finished => 1, buildstatus => { -in => [3, 4] } }); + my $n = restartBuilds($c->model('DB')->schema, $builds); + $c->flash->{successMsg} = "$n builds have been restarted."; + $c->res->redirect($c->uri_for($c->controller('JobsetEval')->action_for('view'), $c->req->captures)); +} + + # Hydra::Base::Controller::NixChannel needs this. sub nix : Chained('eval') PathPart('channel') CaptureArgs(0) { my ($self, $c) = @_; diff --git a/src/lib/Hydra/Helper/AddBuilds.pm b/src/lib/Hydra/Helper/AddBuilds.pm index 1a575e66..e0196a81 100644 --- a/src/lib/Hydra/Helper/AddBuilds.pm +++ b/src/lib/Hydra/Helper/AddBuilds.pm @@ -553,29 +553,4 @@ sub checkBuild { }; -sub restartBuild { - my ($db, $build) = @_; - - txn_do($db, sub { - my @paths; - push @paths, $build->drvpath; - push @paths, $_->drvpath foreach $build->buildsteps; - - my $r = `nix-store --clear-failed-paths @paths`; - - $build->update( - { finished => 0 - , busy => 0 - , locker => "" - , iscachedbuild => 0 - }); - - $build->buildproducts->delete; - - # Reset the stats for the evals to which this build belongs. - # !!! Should do this in a trigger. - foreach my $m ($build->jobsetevalmembers->all) { - $m->eval->update({nrsucceeded => undef}); - } - }); -} +1; diff --git a/src/lib/Hydra/Helper/Nix.pm b/src/lib/Hydra/Helper/Nix.pm index f5d62c7a..26742625 100644 --- a/src/lib/Hydra/Helper/Nix.pm +++ b/src/lib/Hydra/Helper/Nix.pm @@ -7,6 +7,7 @@ use File::Basename; use Config::General; use Hydra::Helper::CatalystUtils; use Hydra::Model::DB; +use Nix::Store; our @ISA = qw(Exporter); our @EXPORT = qw( @@ -22,7 +23,7 @@ our @EXPORT = qw( pathIsInsidePrefix captureStdoutStderr run grab getTotalShares - cancelBuilds); + cancelBuilds restartBuilds); sub getHydraHome { @@ -274,7 +275,7 @@ sub findLog { my $logPath = getDrvLogPath($drvPath); return $logPath if defined $logPath; } - + return undef if scalar @outPaths == 0; my @steps = $c->model('DB::BuildSteps')->search( @@ -561,4 +562,39 @@ sub cancelBuilds($$) { } +sub restartBuilds($$) { + my ($db, $builds) = @_; + my $n = 0; + + txn_do($db, sub { + my @paths; + + $builds = $builds->search({ finished => 1 }); + foreach my $build ($builds->all) { + next if !isValidPath($build->drvpath); + push @paths, $build->drvpath; + push @paths, $_->drvpath foreach $build->buildsteps; + + $build->update( + { finished => 0 + , busy => 0 + , locker => "" + , iscachedbuild => 0 + }); + $n++; + + # Reset the stats for the evals to which this build belongs. + # !!! Should do this in a trigger. + $build->jobsetevals->update({nrsucceeded => undef}); + } + + # Clear Nix's negative failure cache. + # FIXME: Add this to the API. + system("nix-store", "--clear-failed-paths", @paths); + }); + + return $n; +} + + 1; diff --git a/src/root/jobset-eval.tt b/src/root/jobset-eval.tt index 41755f9f..73ef4828 100644 --- a/src/root/jobset-eval.tt +++ b/src/root/jobset-eval.tt @@ -43,6 +43,7 @@ c.uri_for(c.controller('JobsetEval').action_for('view'), [% END %] From 5294a0a8a012bb577d075db73f60703b043ce56f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 4 Oct 2013 17:11:42 +0200 Subject: [PATCH 169/215] Register restarted derivations as GC roots --- src/lib/Hydra/Helper/Nix.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/Hydra/Helper/Nix.pm b/src/lib/Hydra/Helper/Nix.pm index 26742625..3b6afbf9 100644 --- a/src/lib/Hydra/Helper/Nix.pm +++ b/src/lib/Hydra/Helper/Nix.pm @@ -575,6 +575,8 @@ sub restartBuilds($$) { push @paths, $build->drvpath; push @paths, $_->drvpath foreach $build->buildsteps; + registerRoot $build->drvpath; + $build->update( { finished => 0 , busy => 0 From 5ccff14f6b1e9ce8546d7773dfb741129ad67f85 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 7 Oct 2013 14:53:27 +0200 Subject: [PATCH 170/215] In Hydra channels, show only packages matching the user's system type Fixes NixOS/nix#169. --- src/lib/Hydra/View/NixExprs.pm | 47 +++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/src/lib/Hydra/View/NixExprs.pm b/src/lib/Hydra/View/NixExprs.pm index a88a0e0f..1faf9e25 100644 --- a/src/lib/Hydra/View/NixExprs.pm +++ b/src/lib/Hydra/View/NixExprs.pm @@ -19,31 +19,48 @@ sub escape { sub process { my ($self, $c) = @_; - my $res = "[\n"; + my %perSystem; foreach my $pkg (@{$c->stash->{nixPkgs}}) { my $build = $pkg->{build}; - $res .= " # $pkg->{name}\n"; - $res .= " { type = \"derivation\";\n"; - $res .= " name = " . escape ($build->get_column("releasename") or $build->nixname) . ";\n"; - $res .= " system = " . (escape $build->system) . ";\n"; - $res .= " outPath = " . (escape $pkg->{outPath}) . ";\n"; - $res .= " meta = {\n"; - $res .= " description = " . (escape $build->description) . ";\n" + my $s = ""; + $s .= " # $pkg->{name}\n"; + $s .= " ${\escape $build->get_column('job')} = {\n"; + $s .= " type = \"derivation\";\n"; + $s .= " name = ${\escape ($build->get_column('releasename') or $build->nixname)};\n"; + $s .= " system = ${\escape $build->system};\n"; + $s .= " outPath = ${\escape $pkg->{outPath}};\n"; + $s .= " meta = {\n"; + $s .= " description = ${\escape $build->description};\n" if $build->description; - $res .= " longDescription = " . (escape $build->longdescription) . ";\n" + $s .= " longDescription = ${\escape $build->longdescription};\n" if $build->longdescription; - $res .= " license = " . (escape $build->license) . ";\n" + $s .= " license = ${\escape $build->license};\n" if $build->license; - $res .= " };\n"; - $res .= " }\n"; + $s .= " maintainers = ${\escape $build->maintainers};\n" + if $build->maintainers; + $s .= " };\n"; + $s .= " };\n\n"; + $perSystem{$build->system} .= $s; } - $res .= "]\n"; + my $res = "{ system ? builtins.currentSystem }:\n\n"; + + my $first = 1; + foreach my $system (keys %perSystem) { + $res .= "else " if !$first; + $res .= "if system == ${\escape $system} then {\n\n"; + $res .= $perSystem{$system}; + $res .= "}\n\n"; + $first = 0; + } + + $res .= "else " if !$first; + $res .= "{}\n"; my $tar = Archive::Tar->new; - $tar->add_data("channel/channel-name", ($c->stash->{channelName} or "unnamed-channel"), {mtime => 0}); - $tar->add_data("channel/default.nix", $res, {mtime => 0}); + $tar->add_data("channel/channel-name", ($c->stash->{channelName} or "unnamed-channel"), {mtime => 1}); + $tar->add_data("channel/default.nix", $res, {mtime => 1}); my $tardata = $tar->write; my $bzip2data; From 0ec03aa0f497f776afbd875f3f36100921f7c660 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 7 Oct 2013 17:06:17 +0200 Subject: [PATCH 171/215] Handle builds with multiple outputs correctly in Hydra channels --- src/lib/Hydra/Base/Controller/ListBuilds.pm | 1 + src/lib/Hydra/Base/Controller/NixChannel.pm | 44 ++++++++---- src/lib/Hydra/Controller/JobsetEval.pm | 1 + src/lib/Hydra/View/NixExprs.pm | 77 +++++++++++++++------ src/root/channel-contents.tt | 1 - 5 files changed, 88 insertions(+), 36 deletions(-) diff --git a/src/lib/Hydra/Base/Controller/ListBuilds.pm b/src/lib/Hydra/Base/Controller/ListBuilds.pm index 89a22ce4..b971d5e8 100644 --- a/src/lib/Hydra/Base/Controller/ListBuilds.pm +++ b/src/lib/Hydra/Base/Controller/ListBuilds.pm @@ -41,6 +41,7 @@ sub nix : Chained('get_builds') PathPart('channel') CaptureArgs(1) { ->search_literal("exists (select 1 from buildproducts where build = me.id and type = 'nix-build')") ->search({}, { columns => [@buildListColumns, 'drvpath', 'description', 'homepage'] , join => ["buildoutputs"] + , order_by => ["me.id", "buildoutputs.name"] , '+select' => ['buildoutputs.path', 'buildoutputs.name'], '+as' => ['outpath', 'outname'] }); } else { diff --git a/src/lib/Hydra/Base/Controller/NixChannel.pm b/src/lib/Hydra/Base/Controller/NixChannel.pm index 40aedaa0..6452f279 100644 --- a/src/lib/Hydra/Base/Controller/NixChannel.pm +++ b/src/lib/Hydra/Base/Controller/NixChannel.pm @@ -14,20 +14,36 @@ sub getChannelData { my @storePaths = (); $c->stash->{nixPkgs} = []; - foreach my $build ($c->stash->{channelBuilds}->all) { - my $outPath = $build->get_column("outpath"); - my $outName = $build->get_column("outname"); - next if $checkValidity && !isValidPath($outPath); - push @storePaths, $outPath; - my $pkgName = $build->nixname . "-" . $build->system . "-" . $build->id . ($outName ne "out" ? "-" . $outName : ""); - push @{$c->stash->{nixPkgs}}, { build => $build, name => $pkgName, outPath => $outPath, outName => $outName }; - # Put the system type in the manifest (for top-level paths) as - # a hint to the binary patch generator. (It shouldn't try to - # generate patches between builds for different systems.) It - # would be nice if Nix stored this info for every path but it - # doesn't. - $c->stash->{systemForPath}->{$outPath} = $build->system; - }; + + my @builds = $c->stash->{channelBuilds}->all; + + for (my $n = 0; $n < scalar @builds; ) { + # Since channelData is a join of Builds and BuildOutputs, we + # need to gather the rows that belong to a single build. + my $build = $builds[$n++]; + my @outputs = ($build); + push @outputs, $builds[$n++] while $n < scalar @builds && $builds[$n]->id == $build->id; + @outputs = grep { $_->get_column("outpath") } @outputs; + + my $outputs = {}; + foreach my $output (@outputs) { + my $outPath = $output->get_column("outpath"); + next if $checkValidity && !isValidPath($outPath); + $outputs->{$output->get_column("outname")} = $outPath; + push @storePaths, $outPath; + # Put the system type in the manifest (for top-level + # paths) as a hint to the binary patch generator. (It + # shouldn't try to generate patches between builds for + # different systems.) It would be nice if Nix stored this + # info for every path but it doesn't. + $c->stash->{systemForPath}->{$outPath} = $build->system; + } + + next if !%$outputs; + + my $pkgName = $build->nixname . "-" . $build->system . "-" . $build->id; + push @{$c->stash->{nixPkgs}}, { build => $build, name => $pkgName, outputs => $outputs }; + } $c->stash->{storePaths} = [@storePaths]; } diff --git a/src/lib/Hydra/Controller/JobsetEval.pm b/src/lib/Hydra/Controller/JobsetEval.pm index 40b52cb3..74ab3bce 100644 --- a/src/lib/Hydra/Controller/JobsetEval.pm +++ b/src/lib/Hydra/Controller/JobsetEval.pm @@ -182,6 +182,7 @@ sub nix : Chained('eval') PathPart('channel') CaptureArgs(0) { ->search({ finished => 1, buildstatus => 0 }, { columns => [@buildListColumns, 'drvpath', 'description', 'homepage'] , join => ["buildoutputs"] + , order_by => ["build.id", "buildoutputs.name"] , '+select' => ['buildoutputs.path', 'buildoutputs.name'], '+as' => ['outpath', 'outname'] }); } diff --git a/src/lib/Hydra/View/NixExprs.pm b/src/lib/Hydra/View/NixExprs.pm index 1faf9e25..25c06b2e 100644 --- a/src/lib/Hydra/View/NixExprs.pm +++ b/src/lib/Hydra/View/NixExprs.pm @@ -23,34 +23,69 @@ sub process { foreach my $pkg (@{$c->stash->{nixPkgs}}) { my $build = $pkg->{build}; - my $s = ""; - $s .= " # $pkg->{name}\n"; - $s .= " ${\escape $build->get_column('job')} = {\n"; - $s .= " type = \"derivation\";\n"; - $s .= " name = ${\escape ($build->get_column('releasename') or $build->nixname)};\n"; - $s .= " system = ${\escape $build->system};\n"; - $s .= " outPath = ${\escape $pkg->{outPath}};\n"; - $s .= " meta = {\n"; - $s .= " description = ${\escape $build->description};\n" - if $build->description; - $s .= " longDescription = ${\escape $build->longdescription};\n" - if $build->longdescription; - $s .= " license = ${\escape $build->license};\n" - if $build->license; - $s .= " maintainers = ${\escape $build->maintainers};\n" - if $build->maintainers; - $s .= " };\n"; - $s .= " };\n\n"; - $perSystem{$build->system} .= $s; + $perSystem{$build->system}->{$build->get_column('job')} = $pkg; } - my $res = "{ system ? builtins.currentSystem }:\n\n"; + my $res = <{$job}; + my $build = $pkg->{build}; + $res .= " # Hydra build ${\$build->id}\n"; + my $attr = $build->get_column('job'); + $attr =~ s/\./-/g; + $res .= " ${\escape $attr} = (mkFakeDerivation {\n"; + $res .= " type = \"derivation\";\n"; + $res .= " name = ${\escape ($build->get_column('releasename') or $build->nixname)};\n"; + $res .= " system = ${\escape $build->system};\n"; + $res .= " meta = {\n"; + $res .= " description = ${\escape $build->description};\n" + if $build->description; + $res .= " longDescription = ${\escape $build->longdescription};\n" + if $build->longdescription; + $res .= " license = ${\escape $build->license};\n" + if $build->license; + $res .= " maintainers = ${\escape $build->maintainers};\n" + if $build->maintainers; + $res .= " };\n"; + $res .= " } {\n"; + my @outputNames = sort (keys $pkg->{outputs}); + $res .= " ${\escape $_} = ${\escape $pkg->{outputs}->{$_}};\n" foreach @outputNames; + my $out = defined $pkg->{outputs}->{"out"} ? "out" : $outputNames[0]; + $res .= " }).$out;\n\n"; + } + $res .= "}\n\n"; $first = 0; } diff --git a/src/root/channel-contents.tt b/src/root/channel-contents.tt index ad040d67..b5f3d269 100644 --- a/src/root/channel-contents.tt +++ b/src/root/channel-contents.tt @@ -60,7 +60,6 @@ install the package simply by clicking on the packages below.

    [% ELSE %] [% HTML.escape(b.description) %] [% END %] - [% IF pkg.outName != 'out' %] [[% pkg.outName %]][% END %] From 20f1bf215ac44f8d177ea92286bde519676f7e42 Mon Sep 17 00:00:00 2001 From: Rob Vermaas Date: Tue, 8 Oct 2013 13:32:46 +0200 Subject: [PATCH 172/215] Make actions dropdown easier to find by making the dropdown title bold --- src/root/build.tt | 2 +- src/root/static/css/hydra.css | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/root/build.tt b/src/root/build.tt index 9eff2d65..dde0e631 100644 --- a/src/root/build.tt +++ b/src/root/build.tt @@ -69,7 +69,7 @@
    +
    +
    + +
    +
    +
    From 58ad3b4b6ce565a269dc77cb0ac703942d70b00d Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Mon, 7 Oct 2013 10:46:10 -0400 Subject: [PATCH 178/215] Enable setting checkresponsible in the edit jobset form Signed-off-by: Shea Levy --- src/lib/Hydra/Controller/Jobset.pm | 6 +++++- src/root/edit-jobset.tt | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/lib/Hydra/Controller/Jobset.pm b/src/lib/Hydra/Controller/Jobset.pm index 6a2090b2..0fa18717 100644 --- a/src/lib/Hydra/Controller/Jobset.pm +++ b/src/lib/Hydra/Controller/Jobset.pm @@ -229,7 +229,11 @@ sub updateJobset { error($c, "Invalid input name ‘$name’.") unless $name =~ /^[[:alpha:]][\w-]*$/; error($c, "Invalid input type ‘$type’.") unless defined $c->stash->{inputTypes}->{$type}; - my $input = $jobset->jobsetinputs->create({ name => $name, type => $type }); + my $input = $jobset->jobsetinputs->create({ + name => $name, + type => $type, + checkresponsible => $c->stash->{params}->{"input-$baseName-checkresponsible"} + }); # Set the values for this input. my @values = ref($values) eq 'ARRAY' ? @{$values} : ($values); diff --git a/src/root/edit-jobset.tt b/src/root/edit-jobset.tt index bf7e94d3..47213f11 100644 --- a/src/root/edit-jobset.tt +++ b/src/root/edit-jobset.tt @@ -25,20 +25,23 @@ [% END %] [% IF edit %][% END %] + + + [% END %] [% BLOCK renderJobsetInputs %] - + [% FOREACH input IN jobset.jobsetinputs %] [% INCLUDE renderJobsetInput input=input baseName="input-$input.name" %] [% END %] - +
    Input nameTypeValues
    Input nameTypeValuesCheck for responsible commits?
    @@ -164,6 +167,7 @@ var x = $("#input-template").clone(true).attr("id", "").insertBefore($(this).parents("tr")).show(); $("#input-template-name", x).attr("name", newid + "-name"); $("#input-template-type", x).attr("name", newid + "-type"); + $("#input-template-checkresponsible", x).attr("name", newid + "-checkresponsible"); $("#input-template", x).attr("id", newid); return false; }); From 2c90857689428cc870a7899117aab453c4a51568 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Mon, 7 Oct 2013 10:47:22 -0400 Subject: [PATCH 179/215] getResponsibleAuthors: Respect checkResponsible Signed-off-by: Shea Levy --- src/lib/Hydra/Helper/CatalystUtils.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Hydra/Helper/CatalystUtils.pm b/src/lib/Hydra/Helper/CatalystUtils.pm index b8a35aaf..08ca5587 100644 --- a/src/lib/Hydra/Helper/CatalystUtils.pm +++ b/src/lib/Hydra/Helper/CatalystUtils.pm @@ -262,7 +262,7 @@ sub getResponsibleAuthors { if ($prevBuild) { foreach my $curInput ($build->buildinputs_builds) { - next unless ($curInput->type eq "git" || $curInput->type eq "hg"); + next unless (($curInput->type eq "git" || $curInput->type eq "hg") && $curInput->checkresponsible); my $prevInput = $prevBuild->buildinputs_builds->find({ name => $curInput->name }); next unless defined $prevInput; From 272d9e235da03ad1c7744459338378d401165d30 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Mon, 7 Oct 2013 10:48:51 -0400 Subject: [PATCH 180/215] Remove unused assignment Signed-off-by: Shea Levy --- src/lib/Hydra/Plugin/HipChatNotification.pm | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/lib/Hydra/Plugin/HipChatNotification.pm b/src/lib/Hydra/Plugin/HipChatNotification.pm index 59350ed3..702d6bfb 100644 --- a/src/lib/Hydra/Plugin/HipChatNotification.pm +++ b/src/lib/Hydra/Plugin/HipChatNotification.pm @@ -37,9 +37,6 @@ sub buildFinished { return if scalar keys %rooms == 0; - # Determine who broke/fixed the build. - my $prevBuild = getPreviousBuild($build); - my ($authors, $nrCommits) = getResponsibleAuthors($build, $self->{plugins}); # Send a message to each room. From f8b80c99c21938d224c7f14082ac5c902b165d38 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Mon, 7 Oct 2013 11:13:37 -0400 Subject: [PATCH 181/215] Include who-broke-the-build information in notification emails Signed-off-by: Shea Levy --- src/lib/Hydra/Plugin/EmailNotification.pm | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/lib/Hydra/Plugin/EmailNotification.pm b/src/lib/Hydra/Plugin/EmailNotification.pm index 1e484483..87e1fb4b 100644 --- a/src/lib/Hydra/Plugin/EmailNotification.pm +++ b/src/lib/Hydra/Plugin/EmailNotification.pm @@ -29,6 +29,11 @@ The following dependent jobs also failed: [% END -%] [% END -%] + +[% IF nrCommits > 0 -%] +This is likely due to [% IF nrCommits > 1 -%][% nrCommits %] commits by [% END -%][% authorList %]. +[% END -%] + [% IF build.buildstatus == 0 -%] Yay! [% ELSE -%] @@ -74,6 +79,13 @@ sub buildFinished { } } + my ($authors, $nrCommits) = getResponsibleAuthors($build, $self->{plugins}); + my $authorList; + if (scalar keys %{authors} > 0) { + my @x = map { "$_ <$authors->{$_}>" } (sort keys %{$authors}); + $authorList = join(" or ", scalar @x > 1 ? join(", ", @[0..scalar @x - 2]): (), $x[-1]); + } + # Send an email to each interested address. # !!! should use the Template Toolkit here. @@ -89,6 +101,8 @@ sub buildFinished { , baseurl => $self->{config}->{'base_uri'} || "http://localhost:3000" , showJobName => \&showJobName, showStatus => \&showStatus , showSystem => index($build->job->name, $build->system) == -1 + , nrCommits => $nrCommits + , authorList => $authorList }; my $body; From 804617f0752d5aadd5aa1c88ecc866d3928b98c9 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Mon, 7 Oct 2013 11:21:16 -0400 Subject: [PATCH 182/215] Email responsible authors if requested Signed-off-by: Shea Levy --- src/lib/Hydra/Plugin/EmailNotification.pm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/Hydra/Plugin/EmailNotification.pm b/src/lib/Hydra/Plugin/EmailNotification.pm index 87e1fb4b..f18a6569 100644 --- a/src/lib/Hydra/Plugin/EmailNotification.pm +++ b/src/lib/Hydra/Plugin/EmailNotification.pm @@ -84,6 +84,9 @@ sub buildFinished { if (scalar keys %{authors} > 0) { my @x = map { "$_ <$authors->{$_}>" } (sort keys %{$authors}); $authorList = join(" or ", scalar @x > 1 ? join(", ", @[0..scalar @x - 2]): (), $x[-1]); + if ($build->jobset->emailresponsible) { + $addresses{$authors->{$_}} = { builds => [ $build ] } foreach (keys %{$authors}); + } } # Send an email to each interested address. From 26470f1656e0fda30761a0ce96a46753c740f391 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Tue, 8 Oct 2013 14:47:24 -0400 Subject: [PATCH 183/215] Check all inputs for blame but only email selected inputs Signed-off-by: Shea Levy --- src/lib/Hydra/Controller/Jobset.pm | 3 +-- src/lib/Hydra/Helper/AddBuilds.pm | 6 +++--- src/lib/Hydra/Helper/CatalystUtils.pm | 6 ++++-- src/lib/Hydra/Plugin/EmailNotification.pm | 6 ++---- src/lib/Hydra/Schema/BuildInputs.pm | 8 ++++---- src/lib/Hydra/Schema/JobsetInputs.pm | 8 ++++---- src/lib/Hydra/Schema/Jobsets.pm | 12 ++---------- src/root/edit-jobset.tt | 14 +++----------- src/script/hydra-evaluator | 2 +- src/sql/hydra.sql | 5 ++--- src/sql/upgrade-23.sql | 5 ++--- 11 files changed, 28 insertions(+), 47 deletions(-) diff --git a/src/lib/Hydra/Controller/Jobset.pm b/src/lib/Hydra/Controller/Jobset.pm index 0fa18717..75f1738a 100644 --- a/src/lib/Hydra/Controller/Jobset.pm +++ b/src/lib/Hydra/Controller/Jobset.pm @@ -206,7 +206,6 @@ sub updateJobset { , nixexprinput => $nixExprInput , enabled => $enabled ? 1 : 0 , enableemail => defined $c->stash->{params}->{enableemail} ? 1 : 0 - , emailresponsible => defined $c->stash->{params}->{emailresponsible} ? 1 : 0 , emailoverride => trim($c->stash->{params}->{emailoverride}) || "" , hidden => defined $c->stash->{params}->{visible} ? 0 : 1 , keepnr => int(trim($c->stash->{params}->{keepnr})) @@ -232,7 +231,7 @@ sub updateJobset { my $input = $jobset->jobsetinputs->create({ name => $name, type => $type, - checkresponsible => $c->stash->{params}->{"input-$baseName-checkresponsible"} + emailresponsible => defined $c->stash->{params}->{"input-$baseName-emailresponsible"} ? 1 : 0 }); # Set the values for this input. diff --git a/src/lib/Hydra/Helper/AddBuilds.pm b/src/lib/Hydra/Helper/AddBuilds.pm index d8b1af8b..99f1b85e 100644 --- a/src/lib/Hydra/Helper/AddBuilds.pm +++ b/src/lib/Hydra/Helper/AddBuilds.pm @@ -148,7 +148,7 @@ sub fetchInputSystemBuild { } sub fetchInput { - my ($plugins, $db, $project, $jobset, $name, $type, $value, $checkresponsbile) = @_; + my ($plugins, $db, $project, $jobset, $name, $type, $value, $emailresponsible) = @_; my @inputs; if ($type eq "build") { @@ -179,7 +179,7 @@ sub fetchInput { foreach my $input (@inputs) { $input->{type} = $type; - $input->{checkresponsible} = $checkresponsible; + $input->{emailresponsible} = $emailresponsible; } return @inputs; @@ -545,7 +545,7 @@ sub checkBuild { , uri => $input->{uri} , revision => $input->{revision} , value => $input->{value} - , checkresponsible => $input->{checkresponsible} + , emailresponsible => $input->{emailresponsible} , dependency => $input->{id} , path => $input->{storePath} || "" # !!! temporary hack , sha256hash => $input->{sha256hash} diff --git a/src/lib/Hydra/Helper/CatalystUtils.pm b/src/lib/Hydra/Helper/CatalystUtils.pm index 08ca5587..296f3118 100644 --- a/src/lib/Hydra/Helper/CatalystUtils.pm +++ b/src/lib/Hydra/Helper/CatalystUtils.pm @@ -259,10 +259,11 @@ sub getResponsibleAuthors { my $nrCommits = 0; my %authors; + my @emailable_authors; if ($prevBuild) { foreach my $curInput ($build->buildinputs_builds) { - next unless (($curInput->type eq "git" || $curInput->type eq "hg") && $curInput->checkresponsible); + next unless ($curInput->type eq "git" || $curInput->type eq "hg"); my $prevInput = $prevBuild->buildinputs_builds->find({ name => $curInput->name }); next unless defined $prevInput; @@ -278,12 +279,13 @@ sub getResponsibleAuthors { foreach my $commit (@commits) { #print STDERR "$commit->{revision} by $commit->{author}\n"; $authors{$commit->{author}} = $commit->{email}; + push @emailable_authors, $commit->{email} if $curInput->emailresponsible; $nrCommits++; } } } - return (\%authors, $nrCommits); + return (\%authors, $nrCommits, \@emailable_authors); } diff --git a/src/lib/Hydra/Plugin/EmailNotification.pm b/src/lib/Hydra/Plugin/EmailNotification.pm index f18a6569..e30f48af 100644 --- a/src/lib/Hydra/Plugin/EmailNotification.pm +++ b/src/lib/Hydra/Plugin/EmailNotification.pm @@ -79,14 +79,12 @@ sub buildFinished { } } - my ($authors, $nrCommits) = getResponsibleAuthors($build, $self->{plugins}); + my ($authors, $nrCommits, $emailable_authors) = getResponsibleAuthors($build, $self->{plugins}); my $authorList; if (scalar keys %{authors} > 0) { my @x = map { "$_ <$authors->{$_}>" } (sort keys %{$authors}); $authorList = join(" or ", scalar @x > 1 ? join(", ", @[0..scalar @x - 2]): (), $x[-1]); - if ($build->jobset->emailresponsible) { - $addresses{$authors->{$_}} = { builds => [ $build ] } foreach (keys %{$authors}); - } + $addresses{$_} = { builds => [ $build ] } foreach (@{$emailable_authors}); } # Send an email to each interested address. diff --git a/src/lib/Hydra/Schema/BuildInputs.pm b/src/lib/Hydra/Schema/BuildInputs.pm index 3c2bb0fb..dafae860 100644 --- a/src/lib/Hydra/Schema/BuildInputs.pm +++ b/src/lib/Hydra/Schema/BuildInputs.pm @@ -72,7 +72,7 @@ __PACKAGE__->table("BuildInputs"); data_type: 'text' is_nullable: 1 -=head2 checkresponsible +=head2 emailresponsible data_type: 'integer' default_value: 0 @@ -111,7 +111,7 @@ __PACKAGE__->add_columns( { data_type => "text", is_nullable => 1 }, "value", { data_type => "text", is_nullable => 1 }, - "checkresponsible", + "emailresponsible", { data_type => "integer", default_value => 0, is_nullable => 0 }, "dependency", { data_type => "integer", is_foreign_key => 1, is_nullable => 1 }, @@ -176,7 +176,7 @@ __PACKAGE__->belongs_to( ); -# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-10-07 14:04:49 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ks8PxHXTwtG+Zco0CAzECg +# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-10-08 13:08:15 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:OaJPzRM+8XGsu3eIkqeYEw 1; diff --git a/src/lib/Hydra/Schema/JobsetInputs.pm b/src/lib/Hydra/Schema/JobsetInputs.pm index f1773693..c6dafde1 100644 --- a/src/lib/Hydra/Schema/JobsetInputs.pm +++ b/src/lib/Hydra/Schema/JobsetInputs.pm @@ -57,7 +57,7 @@ __PACKAGE__->table("JobsetInputs"); data_type: 'text' is_nullable: 0 -=head2 checkresponsible +=head2 emailresponsible data_type: 'integer' default_value: 0 @@ -74,7 +74,7 @@ __PACKAGE__->add_columns( { data_type => "text", is_nullable => 0 }, "type", { data_type => "text", is_nullable => 0 }, - "checkresponsible", + "emailresponsible", { data_type => "integer", default_value => 0, is_nullable => 0 }, ); @@ -150,7 +150,7 @@ __PACKAGE__->has_many( ); -# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-10-07 14:04:49 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:OvSrNdXWqco666sy+rvsKw +# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-10-08 13:06:15 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:+mZZqLjQNwblb/EWW1alLQ 1; diff --git a/src/lib/Hydra/Schema/Jobsets.pm b/src/lib/Hydra/Schema/Jobsets.pm index 2953b063..8ca9d174 100644 --- a/src/lib/Hydra/Schema/Jobsets.pm +++ b/src/lib/Hydra/Schema/Jobsets.pm @@ -95,12 +95,6 @@ __PACKAGE__->table("Jobsets"); default_value: 1 is_nullable: 0 -=head2 emailresponsible - - data_type: 'integer' - default_value: 0 - is_nullable: 0 - =head2 hidden data_type: 'integer' @@ -160,8 +154,6 @@ __PACKAGE__->add_columns( { data_type => "integer", default_value => 1, is_nullable => 0 }, "enableemail", { data_type => "integer", default_value => 1, is_nullable => 0 }, - "emailresponsible", - { data_type => "integer", default_value => 0, is_nullable => 0 }, "hidden", { data_type => "integer", default_value => 0, is_nullable => 0 }, "emailoverride", @@ -295,7 +287,7 @@ __PACKAGE__->belongs_to( ); -# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-10-07 14:04:49 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:hJ41oHEb9PjzluvL7f/ypw +# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-10-08 13:06:15 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:BjT60mlrN7bnljqCMHbPEw 1; diff --git a/src/root/edit-jobset.tt b/src/root/edit-jobset.tt index 47213f11..383bbea9 100644 --- a/src/root/edit-jobset.tt +++ b/src/root/edit-jobset.tt @@ -26,7 +26,7 @@ [% IF edit %][% END %] - + [% END %] @@ -34,7 +34,7 @@ [% BLOCK renderJobsetInputs %] - + [% FOREACH input IN jobset.jobsetinputs %] @@ -118,14 +118,6 @@ -
    -
    - -
    -
    -
    @@ -167,7 +159,7 @@ var x = $("#input-template").clone(true).attr("id", "").insertBefore($(this).parents("tr")).show(); $("#input-template-name", x).attr("name", newid + "-name"); $("#input-template-type", x).attr("name", newid + "-type"); - $("#input-template-checkresponsible", x).attr("name", newid + "-checkresponsible"); + $("#input-template-emailresponsible", x).attr("name", newid + "-emailresponsible"); $("#input-template", x).attr("id", newid); return false; }); diff --git a/src/script/hydra-evaluator b/src/script/hydra-evaluator index 1fb0461c..baf7162e 100755 --- a/src/script/hydra-evaluator +++ b/src/script/hydra-evaluator @@ -34,7 +34,7 @@ sub fetchInputs { foreach my $input ($jobset->jobsetinputs->all) { foreach my $alt ($input->jobsetinputalts->all) { push @{$$inputInfo{$input->name}}, $_ - foreach fetchInput($plugins, $db, $project, $jobset, $input->name, $input->type, $alt->value, $input->checkresponsible); + foreach fetchInput($plugins, $db, $project, $jobset, $input->name, $input->type, $alt->value, $input->emailresponsible); } } } diff --git a/src/sql/hydra.sql b/src/sql/hydra.sql index 2df03620..8717614a 100644 --- a/src/sql/hydra.sql +++ b/src/sql/hydra.sql @@ -57,7 +57,6 @@ create table Jobsets ( triggerTime integer, -- set if we were triggered by a push event enabled integer not null default 1, enableEmail integer not null default 1, - emailResponsible integer not null default 0, -- whether to email committers responsible for a build change hidden integer not null default 0, emailOverride text not null, keepnr integer not null default 3, @@ -78,7 +77,7 @@ create table JobsetInputs ( jobset text not null, name text not null, type text not null, -- "svn", "path", "uri", "string", "boolean", "nix" - checkResponsible integer not null default 0, -- whether this input should be checked for responsbile commits + emailResponsible integer not null default 0, -- whether to email committers to this input who change a build primary key (project, jobset, name), foreign key (project, jobset) references Jobsets(project, name) on delete cascade on update cascade ); @@ -259,7 +258,7 @@ create table BuildInputs ( uri text, revision text, value text, - checkResponsible integer not null default 0, + emailResponsible integer not null default 0, dependency integer, -- build ID of the input, for type == 'build' path text, diff --git a/src/sql/upgrade-23.sql b/src/sql/upgrade-23.sql index 2a53fe85..585711e6 100644 --- a/src/sql/upgrade-23.sql +++ b/src/sql/upgrade-23.sql @@ -1,3 +1,2 @@ -alter table Jobsets add column emailResponsible integer not null default 0; -alter table JobsetInputs add column checkResponsible integer not null default 0; -alter table BuildInputs add column checkResponsible integer not null default 0; +alter table JobsetInputs add column emailResponsible integer not null default 0; +alter table BuildInputs add column emailResponsible integer not null default 0; From a49457b2fd2e9eddad4ede95bf53e063c243e1e3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 10 Oct 2013 12:43:27 +0200 Subject: [PATCH 184/215] Don't break inside durations --- src/root/common.tt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/root/common.tt b/src/root/common.tt index 5107190e..9b2b9dcf 100644 --- a/src/root/common.tt +++ b/src/root/common.tt @@ -40,9 +40,9 @@ END; BLOCK renderDuration; - IF duration >= 24 * 60 * 60; duration div (24 * 60 * 60) %]d [% END; - IF duration >= 60 * 60; duration div (60 * 60) % 24 %]h [% END; - IF duration >= 60; duration div 60 % 60 %]m [% END; + IF duration >= 24 * 60 * 60; duration div (24 * 60 * 60) %]d [% END; + IF duration >= 60 * 60; duration div (60 * 60) % 24 %]h [% END; + IF duration >= 60; duration div 60 % 60 %]m [% END; duration % 60 %]s[% END; From f592ce0026e58e6c1d3f0ce268497c0c2ac0f340 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 11 Oct 2013 10:46:40 +0200 Subject: [PATCH 185/215] Fix extreme slowness in hydra-queue-runner If there are builds in the queue that depend on another scheduled build, then hydra-queue-runner will start the dependency first and block the dependent builds. This is implemented in findBuildDependencyInQueue. However, if there are tens of thousands of such dependent builds, since each call to findBuildDependencyInQueue may take a second or so, hydra-queue-runner will spend hours just deciding which builds *not* to do. Thus very little progress is made. So now, when a build is started, we immediately check which builds are "blocked" by it (i.e. depend on it), and remove such builds from consideration. --- src/script/hydra-queue-runner | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/script/hydra-queue-runner b/src/script/hydra-queue-runner index 4dbb88db..f67d8cc2 100755 --- a/src/script/hydra-queue-runner +++ b/src/script/hydra-queue-runner @@ -9,6 +9,7 @@ use Hydra::Helper::Nix; use Hydra::Model::DB; use IO::Handle; use Nix::Store; +use Set::Scalar; chdir Hydra::Model::DB::getHydraPath or die; my $db = Hydra::Model::DB->new(); @@ -52,14 +53,25 @@ sub findBuildDependencyInQueue { my @deps = grep { /\.drv$/ && $_ ne $build->drvpath } computeFSClosure(0, 0, $build->drvpath); return unless scalar @deps > 0; foreach my $d (@deps) { - my $b = $buildsByDrv->{$d}; - next unless defined $b; - return $db->resultset('Builds')->find($b); + my $bs = $buildsByDrv->{$d}; + next unless defined $bs; + return $db->resultset('Builds')->find((@$bs)[0]); } return undef; } +sub blockBuilds { + my ($buildsByDrv, $blockedBuilds, $build) = @_; + my @rdeps = grep { /\.drv$/ && $_ ne $build->drvpath } computeFSClosure(1, 0, $build->drvpath); + foreach my $drv (@rdeps) { + my $bs = $buildsByDrv->{$drv}; + next if !defined $bs; + $blockedBuilds->insert($_) foreach @$bs; + } +} + + sub checkBuilds { # print "looking for runnable builds...\n"; @@ -80,8 +92,13 @@ sub checkBuilds { # Cache scheduled builds by derivation path to speed up # findBuildDependencyInQueue. my $buildsByDrv = {}; - $buildsByDrv->{$_->drvpath} = $_->id - foreach $db->resultset('Builds')->search({ finished => 0 }, { join => ['project'] }); + push @{$buildsByDrv->{$_->drvpath}}, $_->id + foreach $db->resultset('Builds')->search({ finished => 0 }); + + # Builds in the queue of which a dependency is already building. + my $blockedBuilds = Set::Scalar->new(); + blockBuilds($buildsByDrv, $blockedBuilds, $_) + foreach $db->resultset('Builds')->search({ finished => 0, busy => 1 }); # Get the system types for the runnable builds. my @systemTypes = $db->resultset('Builds')->search( @@ -162,6 +179,8 @@ sub checkBuilds { { order_by => ["priority DESC", "id"] }); foreach my $build (@builds) { + next if $blockedBuilds->has($build->id); + # Find a dependency of $build that has no queued # dependencies itself. This isn't strictly necessary, # but it ensures that Nix builds are done as part of @@ -187,6 +206,8 @@ sub checkBuilds { $timeSpentPerJobset->{$jobset->get_column('project')}->{$jobset->name} += $costPerBuild; + blockBuilds($buildsByDrv, $blockedBuilds, $build); + next j; } } From 0babdf3532303cfb3f4198bf0eb29d15681d0522 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 11 Oct 2013 10:58:25 +0200 Subject: [PATCH 186/215] Adjust to the NixOS/Nixpkgs merge --- release.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/release.nix b/release.nix index d8ccc1cd..eb6867fe 100644 --- a/release.nix +++ b/release.nix @@ -149,7 +149,7 @@ in rec { tests.install = genAttrs' (system: - with import { inherit system; }; + with import { inherit system; }; let hydra = builtins.getAttr system build; in # build.${system} simpleTest { machine = @@ -177,7 +177,7 @@ in rec { }); tests.api = genAttrs' (system: - with import { inherit system; }; + with import { inherit system; }; let hydra = builtins.getAttr system build; in # build."${system}" simpleTest { machine = @@ -215,7 +215,7 @@ in rec { }); tests.s3backup = genAttrs' (system: - with import { inherit system; }; + with import { inherit system; }; let hydra = builtins.getAttr system build; in # build."${system}" simpleTest { machine = From c4e39d476937a384abe259a7509f6fcf1039b7a4 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 11 Oct 2013 12:01:52 +0200 Subject: [PATCH 187/215] Add one-shot jobsets There are jobsets that are evaluated only once, that is, after they've been evaluated, they're disabled automatically. This is primarily useful for doing releases: for instance, doing an evaluation with "officialRelease" set to "true" should be done only once. --- src/lib/Hydra/Controller/Job.pm | 6 +----- src/lib/Hydra/Controller/Jobset.pm | 5 +++-- src/lib/Hydra/Controller/Root.pm | 2 +- src/lib/Hydra/Helper/Nix.pm | 2 +- src/root/edit-jobset.tt | 13 ++++++++++--- src/root/jobset.tt | 8 ++++---- src/root/static/js/common.js | 12 +++++++++++- src/script/hydra-evaluator | 5 ++++- src/sql/hydra.sql | 2 +- 9 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/lib/Hydra/Controller/Job.pm b/src/lib/Hydra/Controller/Job.pm index fc37972d..66a12e5f 100644 --- a/src/lib/Hydra/Controller/Job.pm +++ b/src/lib/Hydra/Controller/Job.pm @@ -31,11 +31,7 @@ sub overview : Chained('job') PathPart('') Args(0) { $c->stash->{queuedBuilds} = [ $job->builds->search( { finished => 0 }, - { join => ['project'] - , order_by => ["priority DESC", "id"] - , '+select' => ['project.enabled'] - , '+as' => ['enabled'] - } + { order_by => ["priority DESC", "id"] } ) ]; # If this is an aggregate job, then get its constituents. diff --git a/src/lib/Hydra/Controller/Jobset.pm b/src/lib/Hydra/Controller/Jobset.pm index b8a149d2..a7c88c2a 100644 --- a/src/lib/Hydra/Controller/Jobset.pm +++ b/src/lib/Hydra/Controller/Jobset.pm @@ -197,14 +197,15 @@ sub updateJobset { my ($nixExprPath, $nixExprInput) = nixExprPathFromParams $c; - my $enabled = defined $c->stash->{params}->{enabled}; + my $enabled = int($c->stash->{params}->{enabled}); + die if $enabled < 0 || $enabled > 2; $jobset->update( { name => $jobsetName , description => trim($c->stash->{params}->{"description"}) , nixexprpath => $nixExprPath , nixexprinput => $nixExprInput - , enabled => $enabled ? 1 : 0 + , enabled => $enabled , enableemail => defined $c->stash->{params}->{enableemail} ? 1 : 0 , emailoverride => trim($c->stash->{params}->{emailoverride}) || "" , hidden => defined $c->stash->{params}->{visible} ? 0 : 1 diff --git a/src/lib/Hydra/Controller/Root.pm b/src/lib/Hydra/Controller/Root.pm index 6a2d3619..281cc90c 100644 --- a/src/lib/Hydra/Controller/Root.pm +++ b/src/lib/Hydra/Controller/Root.pm @@ -74,7 +74,7 @@ sub queue_GET { $self->status_ok( $c, entity => [$c->model('DB::Builds')->search( - {finished => 0}, { join => ['project'], order_by => ["priority DESC", "id"], columns => [@buildListColumns], '+select' => ['project.enabled'], '+as' => ['enabled'] })] + {finished => 0}, { order_by => ["priority DESC", "id"], columns => [@buildListColumns] })] ); } diff --git a/src/lib/Hydra/Helper/Nix.pm b/src/lib/Hydra/Helper/Nix.pm index 3b6afbf9..fe506750 100644 --- a/src/lib/Hydra/Helper/Nix.pm +++ b/src/lib/Hydra/Helper/Nix.pm @@ -540,7 +540,7 @@ sub grab { sub getTotalShares { my ($db) = @_; return $db->resultset('Jobsets')->search( - { 'project.enabled' => 1, 'me.enabled' => 1 }, + { 'project.enabled' => 1, 'me.enabled' => { '!=' => 0 } }, { join => 'project', select => { sum => 'schedulingshares' }, as => 'sum' })->single->get_column('sum'); } diff --git a/src/root/edit-jobset.tt b/src/root/edit-jobset.tt index c299f60f..8c2e9b87 100644 --- a/src/root/edit-jobset.tt +++ b/src/root/edit-jobset.tt @@ -49,11 +49,18 @@
    +
    - +
    + + + + +
    +
    + +
    Input nameTypeValuesCheck for responsible commits?
    Input nameTypeValuesNotify committers
    + + + + @@ -127,10 +131,6 @@ [% HTML.escape(jobset.nixexprinput) %] - - - - diff --git a/src/root/static/js/common.js b/src/root/static/js/common.js index 707c0a53..c304d658 100644 --- a/src/root/static/js/common.js +++ b/src/root/static/js/common.js @@ -69,7 +69,17 @@ $(document).ready(function() { var id = e.target.toString().match(pattern)[0]; history.replaceState(null, "", id); }); - }) + }); + + /* Automatically set Bootstrap radio buttons from hidden form controls. */ + $('div[data-toggle="buttons-radio"] input[type="hidden"]').map(function(){ + $('button[value="' + $(this).val() + '"]', $(this).parent()).addClass('active'); + }); + + /* Automatically update hidden form controls from Bootstrap radio buttons. */ + $('div[data-toggle="buttons-radio"] .btn').click(function(){ + $('input', $(this).parent()).val($(this).val()); + }); }); var tabsLoaded = {}; diff --git a/src/script/hydra-evaluator b/src/script/hydra-evaluator index 0fe15ccf..2f93e9f0 100755 --- a/src/script/hydra-evaluator +++ b/src/script/hydra-evaluator @@ -246,6 +246,9 @@ sub checkJobsetWrapped { print STDERR " created cached eval ", $ev->id, "\n"; $prevEval->builds->update({iscurrent => 1}) if defined $prevEval; } + + # If this is a one-shot jobset, disable it now. + $jobset->update({ enabled => 0 }) if $jobset->enabled == 2; }); # Store the error messages for jobs that failed to evaluate. @@ -305,7 +308,7 @@ sub checkSomeJobset { # longest time (but don't check more often than the jobset's # minimal check interval). ($jobset) = $db->resultset('Jobsets')->search( - { 'project.enabled' => 1, 'me.enabled' => 1, + { 'project.enabled' => 1, 'me.enabled' => { '!=' => 0 }, , 'checkinterval' => { '!=', 0 } , -or => [ 'lastcheckedtime' => undef, 'lastcheckedtime' => { '<', \ (time() . " - me.checkinterval") } ] }, { join => 'project', order_by => [ 'lastcheckedtime nulls first' ], rows => 1 }) diff --git a/src/sql/hydra.sql b/src/sql/hydra.sql index 23fc7925..87666dad 100644 --- a/src/sql/hydra.sql +++ b/src/sql/hydra.sql @@ -55,7 +55,7 @@ create table Jobsets ( errorTime integer, -- timestamp associated with errorMsg lastCheckedTime integer, -- last time the evaluator looked at this jobset triggerTime integer, -- set if we were triggered by a push event - enabled integer not null default 1, + enabled integer not null default 1, -- 0 = disabled, 1 = enabled, 2 = one-shot enableEmail integer not null default 1, hidden integer not null default 0, emailOverride text not null, From 854d419b2c4d17d79bd4f30a7996551f57884a75 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 14 Oct 2013 17:35:14 +0200 Subject: [PATCH 188/215] Use redirectJSON --- src/root/edit-jobset.tt | 7 ++----- src/root/edit-project.tt | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/root/edit-jobset.tt b/src/root/edit-jobset.tt index 8c2e9b87..5dac7396 100644 --- a/src/root/edit-jobset.tt +++ b/src/root/edit-jobset.tt @@ -174,17 +174,14 @@ }); $("#submit-jobset").click(function() { - requestJSON({ + redirectJSON({ [% IF create || clone %] url: "[% c.uri_for('/jobset' project.name '.new') %]", [% ELSE %] url: "[% c.uri_for('/jobset' project.name jobset.name) %]", [% END %] data: $(this).parents("form").serialize(), - type: 'PUT', - success: function(data) { - window.location = data.redirect; - }, + type: 'PUT' }); return false; }); diff --git a/src/root/edit-project.tt b/src/root/edit-project.tt index 58fcfecf..b8d9bacb 100644 --- a/src/root/edit-project.tt +++ b/src/root/edit-project.tt @@ -66,17 +66,14 @@
    State:[% IF jobset.enabled == 0; "Disabled"; ELSIF jobset.enabled == 1; "Enabled"; ELSIF jobset.enabled == 2; "One-shot"; END %]
    Description: [% HTML.escape(jobset.description) %]
    Enabled:[% jobset.enabled ? "Yes" : "No" %]
    Check interval: [% jobset.checkinterval || "disabled" %]