From 4172b5b29081bb8ff0b113bbfa55f7983589c6e0 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 8 May 2019 13:28:02 +0200 Subject: [PATCH 01/73] Add flake.nix --- flake.lock | 9 +++++++++ flake.nix | 21 +++++++++++++++++++++ release.nix | 3 ++- 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..9848cb99 --- /dev/null +++ b/flake.lock @@ -0,0 +1,9 @@ +{ + "nonFlakeRequires": {}, + "requires": { + "nixpkgs": { + "uri": "github:edolstra/nixpkgs/a4d896e89932e873c4117908d558db6210fa3b56" + } + }, + "version": 1 +} \ No newline at end of file diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..1e9bc312 --- /dev/null +++ b/flake.nix @@ -0,0 +1,21 @@ +{ + name = "hydra"; + + description = "A Nix-based continuous build system"; + + epoch = 2019; + + requires = [ "nixpkgs" ]; + + provides = deps: rec { + + hydraJobs = import ./release.nix { + hydraSrc = deps.self; + nixpkgs = deps.nixpkgs; + }; + + packages.hydra = hydraJobs.build.x86_64-linux; + + defaultPackage = packages.hydra; + }; +} diff --git a/release.nix b/release.nix index dd28cef0..9d060d6d 100644 --- a/release.nix +++ b/release.nix @@ -30,7 +30,8 @@ let environment.systemPackages = [ pkgs.perlPackages.LWP pkgs.perlPackages.JSON ]; }; - version = builtins.readFile ./version + "." + toString hydraSrc.revCount + "." + hydraSrc.rev; + # FIXME: use commit date. + version = builtins.readFile ./version + "." + toString hydraSrc.revCount or 0 + "." + hydraSrc.shortRev or "0000000"; in From 2ecc06e5575c01ec929e580d570cb71a1baaf778 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 8 May 2019 18:12:24 +0200 Subject: [PATCH 02/73] Replace shell.nix with a flake devShell attribute --- flake.nix | 7 +++++++ release.nix | 4 +--- shell.nix | 1 - 3 files changed, 8 insertions(+), 4 deletions(-) delete mode 100644 shell.nix diff --git a/flake.nix b/flake.nix index 1e9bc312..15e30151 100644 --- a/flake.nix +++ b/flake.nix @@ -17,5 +17,12 @@ packages.hydra = hydraJobs.build.x86_64-linux; defaultPackage = packages.hydra; + + devShell = (import ./release.nix { + hydraSrc = deps.self; + nixpkgs = deps.nixpkgs; + shell = true; + }).build.x86_64-linux; + }; } diff --git a/release.nix b/release.nix index 9d060d6d..4747b9ba 100644 --- a/release.nix +++ b/release.nix @@ -138,9 +138,7 @@ rec { preConfigure = "autoreconf -vfi"; - NIX_LDFLAGS = [ - "-lpthread" - ]; + NIX_LDFLAGS = [ "-lpthread" ]; enableParallelBuilding = true; diff --git a/shell.nix b/shell.nix deleted file mode 100644 index 454c00bf..00000000 --- a/shell.nix +++ /dev/null @@ -1 +0,0 @@ -(import ./release.nix { shell = true; }).build.x86_64-linux From 950e8bef6cd90befcf14e96826053d1d154e39fe Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 8 May 2019 21:22:52 +0200 Subject: [PATCH 03/73] Disable deprecation warnings --- src/hydra-evaluator/Makefile.am | 2 +- src/hydra-queue-runner/Makefile.am | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hydra-evaluator/Makefile.am b/src/hydra-evaluator/Makefile.am index 161f8567..73638cfe 100644 --- a/src/hydra-evaluator/Makefile.am +++ b/src/hydra-evaluator/Makefile.am @@ -2,4 +2,4 @@ bin_PROGRAMS = hydra-evaluator hydra_evaluator_SOURCES = hydra-evaluator.cc hydra_evaluator_LDADD = $(NIX_LIBS) -lpqxx -hydra_evaluator_CXXFLAGS = $(NIX_CFLAGS) -Wall -I ../libhydra +hydra_evaluator_CXXFLAGS = $(NIX_CFLAGS) -Wall -I ../libhydra -Wno-deprecated-declarations diff --git a/src/hydra-queue-runner/Makefile.am b/src/hydra-queue-runner/Makefile.am index b360faed..1726d0df 100644 --- a/src/hydra-queue-runner/Makefile.am +++ b/src/hydra-queue-runner/Makefile.am @@ -4,4 +4,4 @@ hydra_queue_runner_SOURCES = hydra-queue-runner.cc queue-monitor.cc dispatcher.c builder.cc build-result.cc build-remote.cc \ build-result.hh counter.hh token-server.hh state.hh db.hh hydra_queue_runner_LDADD = $(NIX_LIBS) -lpqxx -hydra_queue_runner_CXXFLAGS = $(NIX_CFLAGS) -Wall -I ../libhydra +hydra_queue_runner_CXXFLAGS = $(NIX_CFLAGS) -Wall -I ../libhydra -Wno-deprecated-declarations From 80fe42e77e15bc12fa4f0ccee39c0c668f1024a1 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 9 May 2019 15:10:16 +0200 Subject: [PATCH 04/73] Update flake.lock --- flake.lock | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flake.lock b/flake.lock index 9848cb99..965d038c 100644 --- a/flake.lock +++ b/flake.lock @@ -2,8 +2,9 @@ "nonFlakeRequires": {}, "requires": { "nixpkgs": { + "contentHash": "sha256-vy2UmXQM66aS/Kn2tCtjt9RwxfBvV+nQVb5tJQFwi8E=", "uri": "github:edolstra/nixpkgs/a4d896e89932e873c4117908d558db6210fa3b56" } }, "version": 1 -} \ No newline at end of file +} From 8f3114960cd4178669c54e7785973ac6198315ab Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 10 May 2019 21:57:19 +0200 Subject: [PATCH 05/73] Remove the hydra-eval-jobs restart hack It's not very effective. --- src/hydra-eval-jobs/hydra-eval-jobs.cc | 136 +++---------------------- 1 file changed, 12 insertions(+), 124 deletions(-) diff --git a/src/hydra-eval-jobs/hydra-eval-jobs.cc b/src/hydra-eval-jobs/hydra-eval-jobs.cc index ce6967b5..40112c72 100644 --- a/src/hydra-eval-jobs/hydra-eval-jobs.cc +++ b/src/hydra-eval-jobs/hydra-eval-jobs.cc @@ -55,41 +55,9 @@ static string queryMetaStrings(EvalState & state, DrvInfo & drv, const string & } -static std::string lastAttrPath; -static bool comma = false; -static size_t maxHeapSize; - - -struct BailOut { }; - - -bool lte(const std::string & s1, const std::string & s2) -{ - size_t p1 = 0, p2 = 0; - - while (true) { - if (p1 == s1.size()) return p2 == s2.size(); - if (p2 == s2.size()) return true; - - auto d1 = s1.find('.', p1); - auto d2 = s2.find('.', p2); - - auto c = s1.compare(p1, d1 - p1, s2, p2, d2 - p2); - - if (c < 0) return true; - if (c > 0) return false; - - p1 = d1 == std::string::npos ? s1.size() : d1 + 1; - p2 = d2 == std::string::npos ? s2.size() : d2 + 1; - } -} - - static void findJobsWrapped(EvalState & state, JSONObject & top, Bindings & autoArgs, Value & vIn, const string & attrPath) { - if (lastAttrPath != "" && lte(attrPath, lastAttrPath)) return; - debug(format("at path `%1%'") % attrPath); checkInterrupt(); @@ -109,8 +77,6 @@ static void findJobsWrapped(EvalState & state, JSONObject & top, if (drv->querySystem() == "unknown") throw EvalError("derivation must have a ‘system’ attribute"); - if (comma) { std::cout << ","; comma = false; } - { auto res = top.object(attrPath); res.attr("nixName", drv->queryName()); @@ -156,15 +122,6 @@ static void findJobsWrapped(EvalState & state, JSONObject & top, res2.attr(j.first, j.second); } - - GC_prof_stats_s gc; - GC_get_prof_stats(&gc, sizeof(gc)); - - if (gc.heapsize_full > maxHeapSize) { - printInfo("restarting hydra-eval-jobs after job '%s' because heap size is at %d bytes", attrPath, gc.heapsize_full); - lastAttrPath = attrPath; - throw BailOut(); - } } else { @@ -200,7 +157,6 @@ static void findJobs(EvalState & state, JSONObject & top, try { findJobsWrapped(state, top, autoArgs, v, attrPath); } catch (EvalError & e) { - if (comma) { std::cout << ","; comma = false; } auto res = top.object(attrPath); res.attr("error", filterANSIEscapes(e.msg(), true)); } @@ -209,14 +165,6 @@ static void findJobs(EvalState & state, JSONObject & top, int main(int argc, char * * argv) { - assert(lte("abc", "def")); - assert(lte("abc", "def.foo")); - assert(!lte("def", "abc")); - assert(lte("nixpkgs.hello", "nixpkgs")); - assert(lte("nixpkgs.hello", "nixpkgs.hellooo")); - assert(lte("gitAndTools.git-annex.x86_64-darwin", "gitAndTools.git-annex.x86_64-linux")); - assert(lte("gitAndTools.git-annex.x86_64-linux", "gitAndTools.git-annex-remote-b2.aarch64-linux")); - /* Prevent undeclared dependencies in the evaluation via $NIX_PATH. */ unsetenv("NIX_PATH"); @@ -229,23 +177,9 @@ int main(int argc, char * * argv) if (initialHeapSize != "") setenv("GC_INITIAL_HEAP_SIZE", initialHeapSize.c_str(), 1); - maxHeapSize = config->getIntOption("evaluator_max_heap_size", 1UL << 30); - initNix(); initGC(); - /* Read the current heap size, which is the initial heap size. */ - GC_prof_stats_s gc; - GC_get_prof_stats(&gc, sizeof(gc)); - auto initialHeapSizeInt = gc.heapsize_full; - - /* Then make sure the maximum heap size will be bigger than the initial heap size. */ - if (initialHeapSizeInt > maxHeapSize) { - printInfo("warning: evaluator_initial_heap_size (%d) bigger than evaluator_max_heap_size (%d).", initialHeapSizeInt, maxHeapSize); - maxHeapSize = initialHeapSizeInt * 1.1; - printInfo(" evaluator_max_heap_size now set to %d.", maxHeapSize); - } - Path releaseExpr; struct MyArgs : LegacyArgs, MixEvalArgs @@ -270,71 +204,25 @@ int main(int argc, char * * argv) JSONObject json(std::cout, true); std::cout.flush(); - do { + /* FIXME: The build hook in conjunction with import-from-derivation is causing "unexpected EOF" during eval */ + settings.builders = ""; - Pipe pipe; - pipe.create(); + /* Prevent access to paths outside of the Nix search path and + to the environment. */ + evalSettings.restrictEval = true; - ProcessOptions options; - options.allowVfork = false; + if (releaseExpr == "") throw UsageError("no expression specified"); - GC_atfork_prepare(); + if (gcRootsDir == "") printMsg(lvlError, "warning: `--gc-roots-dir' not specified"); - auto pid = startProcess([&]() { - pipe.readSide = -1; + EvalState state(myArgs.searchPath, openStore()); - GC_atfork_child(); - GC_start_mark_threads(); + Bindings & autoArgs = *myArgs.getAutoArgs(state); - if (lastAttrPath != "") debug("resuming from '%s'", lastAttrPath); + Value v; + state.evalFile(lookupFileArg(state, releaseExpr), v); - /* FIXME: The build hook in conjunction with import-from-derivation is causing "unexpected EOF" during eval */ - settings.builders = ""; + findJobs(state, json, autoArgs, v, ""); - /* Prevent access to paths outside of the Nix search path and - to the environment. */ - evalSettings.restrictEval = true; - - if (releaseExpr == "") throw UsageError("no expression specified"); - - if (gcRootsDir == "") printMsg(lvlError, "warning: `--gc-roots-dir' not specified"); - - EvalState state(myArgs.searchPath, openStore()); - - Bindings & autoArgs = *myArgs.getAutoArgs(state); - - Value v; - state.evalFile(lookupFileArg(state, releaseExpr), v); - - comma = lastAttrPath != ""; - - try { - findJobs(state, json, autoArgs, v, ""); - lastAttrPath = ""; - } catch (BailOut &) { } - - writeFull(pipe.writeSide.get(), lastAttrPath); - - exit(0); - }, options); - - GC_atfork_parent(); - - pipe.writeSide = -1; - - int status; - while (true) { - checkInterrupt(); - if (waitpid(pid, &status, 0) == pid) break; - if (errno != EINTR) continue; - } - - if (status != 0) - throw Exit(WIFEXITED(status) ? WEXITSTATUS(status) : 99); - - maxHeapSize += 64 * 1024 * 1024; - - lastAttrPath = drainFD(pipe.readSide.get()); - } while (lastAttrPath != ""); }); } From 2c60019910038cfda905db7dc1d6c8ca01df758d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 10 May 2019 22:05:49 +0200 Subject: [PATCH 06/73] hydra-eval-jobs: Modernize argument parser --- src/hydra-eval-jobs/hydra-eval-jobs.cc | 47 ++++++++++++++++---------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/src/hydra-eval-jobs/hydra-eval-jobs.cc b/src/hydra-eval-jobs/hydra-eval-jobs.cc index 40112c72..6b463634 100644 --- a/src/hydra-eval-jobs/hydra-eval-jobs.cc +++ b/src/hydra-eval-jobs/hydra-eval-jobs.cc @@ -180,25 +180,36 @@ int main(int argc, char * * argv) initNix(); initGC(); - Path releaseExpr; - - struct MyArgs : LegacyArgs, MixEvalArgs + struct MyArgs : MixEvalArgs, MixCommonArgs { - using LegacyArgs::LegacyArgs; + Path releaseExpr; + + MyArgs() : MixCommonArgs("hydra-eval-jobs") + { + mkFlag() + .longName("help") + .description("show usage information") + .handler([&]() { + printHelp(programName, std::cout); + throw Exit(); + }); + + mkFlag() + .longName("gc-roots-dir") + .description("garbage collector roots directory") + .labels({"path"}) + .dest(&gcRootsDir); + + mkFlag() + .longName("dry-run") + .description("don't create store derivations") + .set(&settings.readOnlyMode, true); + + expectArg("expr", &releaseExpr); + } }; - MyArgs myArgs(baseNameOf(argv[0]), [&](Strings::iterator & arg, const Strings::iterator & end) { - if (*arg == "--gc-roots-dir") - gcRootsDir = getArg(*arg, arg, end); - else if (*arg == "--dry-run") - settings.readOnlyMode = true; - else if (*arg != "" && arg->at(0) == '-') - return false; - else - releaseExpr = *arg; - return true; - }); - + MyArgs myArgs; myArgs.parseCmdline(argvToStrings(argc, argv)); JSONObject json(std::cout, true); @@ -211,7 +222,7 @@ int main(int argc, char * * argv) to the environment. */ evalSettings.restrictEval = true; - if (releaseExpr == "") throw UsageError("no expression specified"); + if (myArgs.releaseExpr == "") throw UsageError("no expression specified"); if (gcRootsDir == "") printMsg(lvlError, "warning: `--gc-roots-dir' not specified"); @@ -220,7 +231,7 @@ int main(int argc, char * * argv) Bindings & autoArgs = *myArgs.getAutoArgs(state); Value v; - state.evalFile(lookupFileArg(state, releaseExpr), v); + state.evalFile(lookupFileArg(state, myArgs.releaseExpr), v); findJobs(state, json, autoArgs, v, ""); From ddcf05ac2226ae5632f4b6f6c481b30eb6164375 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 10 May 2019 22:18:15 +0200 Subject: [PATCH 07/73] release.nix: Do not rely on currentSystem --- release.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release.nix b/release.nix index 4747b9ba..20e98376 100644 --- a/release.nix +++ b/release.nix @@ -8,7 +8,7 @@ with import (nixpkgs + "/lib"); let - pkgs = import nixpkgs {}; + pkgs = import nixpkgs { system = "x86_64-linux"; }; genAttrs' = genAttrs [ "x86_64-linux" /* "i686-linux" */ ]; From 6ee6ec3bda5c20c4920135bac090ea9af7600719 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 10 May 2019 22:18:37 +0200 Subject: [PATCH 08/73] hydra-eval-jobs: Support flakes E.g. 'hydra-eval-jobs ~/Dev/patchelf' is enough to evaluate patchelf - no need to set up a $NIX_PATH, pass arguments, etc. --- src/hydra-eval-jobs/hydra-eval-jobs.cc | 32 ++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/hydra-eval-jobs/hydra-eval-jobs.cc b/src/hydra-eval-jobs/hydra-eval-jobs.cc index 6b463634..fa5fc176 100644 --- a/src/hydra-eval-jobs/hydra-eval-jobs.cc +++ b/src/hydra-eval-jobs/hydra-eval-jobs.cc @@ -13,6 +13,8 @@ #include "get-drvs.hh" #include "globals.hh" #include "common-eval-args.hh" +#include "flakeref.hh" +#include "flake.hh" #include "hydra-config.hh" @@ -183,6 +185,7 @@ int main(int argc, char * * argv) struct MyArgs : MixEvalArgs, MixCommonArgs { Path releaseExpr; + bool flake = false; MyArgs() : MixCommonArgs("hydra-eval-jobs") { @@ -205,6 +208,11 @@ int main(int argc, char * * argv) .description("don't create store derivations") .set(&settings.readOnlyMode, true); + mkFlag() + .longName("flake") + .description("build a flake") + .set(&flake, true); + expectArg("expr", &releaseExpr); } }; @@ -222,6 +230,10 @@ int main(int argc, char * * argv) to the environment. */ evalSettings.restrictEval = true; + /* When building a flake, use pure evaluation (no access to + 'getEnv', 'currentSystem' etc. */ + evalSettings.pureEval = myArgs.flake; + if (myArgs.releaseExpr == "") throw UsageError("no expression specified"); if (gcRootsDir == "") printMsg(lvlError, "warning: `--gc-roots-dir' not specified"); @@ -231,9 +243,25 @@ int main(int argc, char * * argv) Bindings & autoArgs = *myArgs.getAutoArgs(state); Value v; - state.evalFile(lookupFileArg(state, myArgs.releaseExpr), v); + + if (myArgs.flake) { + FlakeRef flakeRef(myArgs.releaseExpr); + auto vFlake = state.allocValue(); + makeFlakeValue(state, flakeRef, AllowRegistryAtTop, *vFlake); + + auto vProvides = (*vFlake->attrs->get(state.symbols.create("provides")))->value; + state.forceValue(*vProvides); + + auto aHydraJobs = vProvides->attrs->get(state.symbols.create("hydraJobs")); + if (!aHydraJobs) + throw Error("flake '%s' does not provide any Hydra jobs", flakeRef); + + v = *(*aHydraJobs)->value; + + } else { + state.evalFile(lookupFileArg(state, myArgs.releaseExpr), v); + } findJobs(state, json, autoArgs, v, ""); - }); } From f9f595cd21d4fbb11148c2e46f48930bfc2e18b3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 10 May 2019 23:39:55 +0200 Subject: [PATCH 09/73] Add flake configuration to the web interface --- src/lib/Hydra/Controller/Jobset.pm | 49 ++++++++++++++++++------------ src/lib/Hydra/Schema/Builds.pm | 11 +++++-- src/lib/Hydra/Schema/Jobsets.pm | 34 ++++++++++++++++----- src/root/edit-jobset.tt | 35 ++++++++++++++++++++- src/root/jobset.tt | 12 ++++++++ src/sql/hydra.sql | 8 +++-- src/sql/upgrade-57.sql | 6 ++++ 7 files changed, 124 insertions(+), 31 deletions(-) create mode 100644 src/sql/upgrade-57.sql diff --git a/src/lib/Hydra/Controller/Jobset.pm b/src/lib/Hydra/Controller/Jobset.pm index 75284c10..0be73418 100644 --- a/src/lib/Hydra/Controller/Jobset.pm +++ b/src/lib/Hydra/Controller/Jobset.pm @@ -223,12 +223,19 @@ sub updateJobset { error($c, "Cannot rename jobset to ‘$jobsetName’ since that identifier is already taken.") if $jobsetName ne $oldName && defined $c->stash->{project}->jobsets->find({ name => $jobsetName }); - # When the expression is in a .scm file, assume it's a Guile + Guix - # build expression. - my $exprType = - $c->stash->{params}->{"nixexprpath"} =~ /.scm$/ ? "guile" : "nix"; + my $type = int($c->stash->{params}->{"type"}) // 0; - my ($nixExprPath, $nixExprInput) = nixExprPathFromParams $c; + my ($nixExprPath, $nixExprInput); + my $flake; + + if ($type == 0) { + ($nixExprPath, $nixExprInput) = nixExprPathFromParams $c; + } elsif ($type == 1) { + $flake = trim($c->stash->{params}->{"flakeref"}); + error($c, "Invalid flake URI ‘$flake’.") if $flake !~ /^[a-zA-Z]/; + } else { + error($c, "Invalid jobset type."); + } my $enabled = int($c->stash->{params}->{enabled}); die if $enabled < 0 || $enabled > 2; @@ -251,6 +258,8 @@ sub updateJobset { , checkinterval => $checkinterval , triggertime => ($enabled && $checkinterval > 0) ? $jobset->triggertime // time() : undef , schedulingshares => $shares + , type => $type + , flake => $flake }); $jobset->project->jobsetrenames->search({ from_ => $jobsetName })->delete; @@ -260,23 +269,25 @@ sub updateJobset { # Set the inputs of this jobset. $jobset->jobsetinputs->delete; - foreach my $name (keys %{$c->stash->{params}->{inputs}}) { - my $inputData = $c->stash->{params}->{inputs}->{$name}; - my $type = $inputData->{type}; - my $value = $inputData->{value}; - my $emailresponsible = defined $inputData->{emailresponsible} ? 1 : 0; + if ($type == 0) { + foreach my $name (keys %{$c->stash->{params}->{inputs}}) { + my $inputData = $c->stash->{params}->{inputs}->{$name}; + my $type = $inputData->{type}; + my $value = $inputData->{value}; + my $emailresponsible = defined $inputData->{emailresponsible} ? 1 : 0; - 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 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, - emailresponsible => $emailresponsible - }); + my $input = $jobset->jobsetinputs->create( + { name => $name, + type => $type, + emailresponsible => $emailresponsible + }); - $value = checkInputValue($c, $name, $type, $value); - $input->jobsetinputalts->create({altnr => 0, value => $value}); + $value = checkInputValue($c, $name, $type, $value); + $input->jobsetinputalts->create({altnr => 0, value => $value}); + } } } diff --git a/src/lib/Hydra/Schema/Builds.pm b/src/lib/Hydra/Schema/Builds.pm index 1c80f710..a4de7f55 100644 --- a/src/lib/Hydra/Schema/Builds.pm +++ b/src/lib/Hydra/Schema/Builds.pm @@ -191,6 +191,11 @@ __PACKAGE__->table("Builds"); default_value: 0 is_nullable: 0 +=head2 notificationpendingsince + + data_type: 'integer' + is_nullable: 1 + =cut __PACKAGE__->add_columns( @@ -252,6 +257,8 @@ __PACKAGE__->add_columns( { data_type => "text", is_nullable => 1 }, "keep", { data_type => "integer", default_value => 0, is_nullable => 0 }, + "notificationpendingsince", + { data_type => "integer", is_nullable => 1 }, ); =head1 PRIMARY KEY @@ -537,8 +544,8 @@ __PACKAGE__->many_to_many( ); -# Created by DBIx::Class::Schema::Loader v0.07043 @ 2016-02-12 17:20:42 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:/8aVtXu/+o0jmKHnSzwt+g +# Created by DBIx::Class::Schema::Loader v0.07049 @ 2019-05-10 22:30:12 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:YK8Fc+37UAcL0u6ziOc5xQ __PACKAGE__->has_many( "dependents", diff --git a/src/lib/Hydra/Schema/Jobsets.pm b/src/lib/Hydra/Schema/Jobsets.pm index 17b4ab93..3ca67e18 100644 --- a/src/lib/Hydra/Schema/Jobsets.pm +++ b/src/lib/Hydra/Schema/Jobsets.pm @@ -56,12 +56,12 @@ __PACKAGE__->table("Jobsets"); data_type: 'text' is_foreign_key: 1 - is_nullable: 0 + is_nullable: 1 =head2 nixexprpath data_type: 'text' - is_nullable: 0 + is_nullable: 1 =head2 errormsg @@ -139,6 +139,17 @@ __PACKAGE__->table("Jobsets"); data_type: 'integer' is_nullable: 1 +=head2 type + + data_type: 'integer' + default_value: 0 + is_nullable: 0 + +=head2 flake + + data_type: 'text' + is_nullable: 1 + =cut __PACKAGE__->add_columns( @@ -149,9 +160,9 @@ __PACKAGE__->add_columns( "description", { data_type => "text", is_nullable => 1 }, "nixexprinput", - { data_type => "text", is_foreign_key => 1, is_nullable => 0 }, + { data_type => "text", is_foreign_key => 1, is_nullable => 1 }, "nixexprpath", - { data_type => "text", is_nullable => 0 }, + { data_type => "text", is_nullable => 1 }, "errormsg", { data_type => "text", is_nullable => 1 }, "errortime", @@ -180,6 +191,10 @@ __PACKAGE__->add_columns( { data_type => "boolean", is_nullable => 1 }, "starttime", { data_type => "integer", is_nullable => 1 }, + "type", + { data_type => "integer", default_value => 0, is_nullable => 0 }, + "flake", + { data_type => "text", is_nullable => 1 }, ); =head1 PRIMARY KEY @@ -282,7 +297,12 @@ __PACKAGE__->belongs_to( "jobsetinput", "Hydra::Schema::JobsetInputs", { jobset => "name", name => "nixexprinput", project => "project" }, - { is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" }, + { + is_deferrable => 0, + join_type => "LEFT", + on_delete => "NO ACTION", + on_update => "NO ACTION", + }, ); =head2 jobsetinputs @@ -352,8 +372,8 @@ __PACKAGE__->has_many( ); -# Created by DBIx::Class::Schema::Loader v0.07045 @ 2017-03-09 13:03:05 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ivYvsUyhEeaeI4EmRQ0/QQ +# Created by DBIx::Class::Schema::Loader v0.07049 @ 2019-05-11 00:03:52 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:UVG1D59bXaQ1TUEF237tXQ my %hint = ( columns => [ diff --git a/src/root/edit-jobset.tt b/src/root/edit-jobset.tt index 6c380a3a..95b1afaf 100644 --- a/src/root/edit-jobset.tt +++ b/src/root/edit-jobset.tt @@ -42,7 +42,7 @@ [% END %] [% BLOCK renderJobsetInputs %] - +
@@ -96,6 +96,24 @@
+ +
+
+ + + +
+
+
+ +
+ +
+ jobset.flake) %]/> +
+
+ +
jobset.nixexprpath) %]/> @@ -167,6 +185,21 @@ $(document).ready(function() { var id = 0; + function update() { + if ($("#type").val() == 0) { + $(".show-on-legacy").show(); + $(".show-on-flake").hide(); + } else { + $(".show-on-legacy").hide(); + $(".show-on-flake").show(); + } + } + + $("#type-flake").click(function() { update(); }); + $("#type-legacy").click(function() { update(); }); + + update(); + $(".add-input").click(function() { var newid = "input-" + id++; var x = $("#input-template").clone(true).attr("id", "").insertBefore($(this).parents("tr")).show(); diff --git a/src/root/jobset.tt b/src/root/jobset.tt index 9cf1202a..4b891ff6 100644 --- a/src/root/jobset.tt +++ b/src/root/jobset.tt @@ -135,6 +135,15 @@
+ [% IF jobset.type == 1 %] + + + + + [% END %] + [% IF jobset.type == 0 %] + [% END %] @@ -166,7 +176,9 @@
Input nameTypeValueNotify committers
Description: [% HTML.escape(jobset.description) %]
Flake URI: + [% HTML.escape(jobset.flake) %] +
Nix expression: @@ -142,6 +151,7 @@ [% HTML.escape(jobset.nixexprinput) %]
Check interval: [% jobset.checkinterval || "disabled" %]
+ [% IF jobset.type == 0 %] [% INCLUDE renderJobsetInputs %] + [% END %] [% INCLUDE makeLazyTab tabName="tabs-jobs" uri=c.uri_for('/jobset' project.name jobset.name "jobs-tab") %] diff --git a/src/sql/hydra.sql b/src/sql/hydra.sql index 4c8710b8..4b37f6e1 100644 --- a/src/sql/hydra.sql +++ b/src/sql/hydra.sql @@ -54,8 +54,8 @@ create table Jobsets ( name text not null, project text not null, description text, - nixExprInput text not null, -- name of the jobsetInput containing the Nix or Guix expression - nixExprPath text not null, -- relative path of the Nix or Guix expression + nixExprInput text, -- name of the jobsetInput containing the Nix or Guix expression + nixExprPath text, -- relative path of the Nix or Guix expression errorMsg text, -- used to signal the last evaluation error etc. for this jobset errorTime integer, -- timestamp associated with errorMsg lastCheckedTime integer, -- last time the evaluator looked at this jobset @@ -70,7 +70,11 @@ create table Jobsets ( fetchErrorMsg text, forceEval boolean, startTime integer, -- if jobset is currently running + type integer not null default 0, -- 0 == legacy, 1 == flake + flake text, check (schedulingShares > 0), + check ((type = 0) = (nixExprInput is not null and nixExprPath is not null)), + check ((type = 1) = (flake is not null)), primary key (project, name), foreign key (project) references Projects(name) on delete cascade on update cascade #ifdef SQLITE diff --git a/src/sql/upgrade-57.sql b/src/sql/upgrade-57.sql new file mode 100644 index 00000000..15553beb --- /dev/null +++ b/src/sql/upgrade-57.sql @@ -0,0 +1,6 @@ +alter table Jobsets alter column nixExprInput drop not null; +alter table Jobsets alter column nixExprPath drop not null; +alter table Jobsets add column type integer default 0; +alter table Jobsets add column flake text; +alter table Jobsets add check ((type = 0) = (nixExprInput is not null and nixExprPath is not null)); +alter table Jobsets add check ((type = 1) = (flake is not null)); From 842ef9af24f0a595983afb29b69cdc524f17bf27 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sat, 11 May 2019 00:10:46 +0200 Subject: [PATCH 10/73] hydra-eval-jobset: Support flakes --- src/script/hydra-eval-jobset | 37 ++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/script/hydra-eval-jobset b/src/script/hydra-eval-jobset index 9d375c1b..1e5c81e7 100755 --- a/src/script/hydra-eval-jobset +++ b/src/script/hydra-eval-jobset @@ -352,18 +352,27 @@ sub inputsToArgs { sub evalJobs { - my ($inputInfo, $exprType, $nixExprInputName, $nixExprPath) = @_; + my ($inputInfo, $exprType, $nixExprInputName, $nixExprPath, $flakeRef) = @_; - my $nixExprInput = $inputInfo->{$nixExprInputName}->[0] - or die "cannot find the input containing the job expression\n"; + my @cmd; - my $evaluator = ($exprType eq "guile") ? "hydra-eval-guile-jobs" : "hydra-eval-jobs"; + if (defined $flakeRef) { + @cmd = ("hydra-eval-jobs", + "--flake", $flakeRef, + "--gc-roots-dir", getGCRootsDir, + "--max-jobs", 1); + } else { + my $nixExprInput = $inputInfo->{$nixExprInputName}->[0] + or die "cannot find the input containing the job expression\n"; - my @cmd = ($evaluator, - "<" . $nixExprInputName . "/" . $nixExprPath . ">", - "--gc-roots-dir", getGCRootsDir, - "-j", 1, - inputsToArgs($inputInfo, $exprType)); + my $evaluator = ($exprType eq "guile") ? "hydra-eval-guile-jobs" : "hydra-eval-jobs"; + + @cmd = ($evaluator, + "<" . $nixExprInputName . "/" . $nixExprPath . ">", + "--gc-roots-dir", getGCRootsDir, + "--max-jobs", 1, + inputsToArgs($inputInfo, $exprType)); + } if (defined $ENV{'HYDRA_DEBUG'}) { sub escape { @@ -376,13 +385,13 @@ sub evalJobs { } (my $res, my $jobsJSON, my $stderr) = captureStdoutStderr(21600, @cmd); - die "$evaluator returned " . ($res & 127 ? "signal $res" : "exit code " . ($res >> 8)) + die "hydra-eval-jobs returned " . ($res & 127 ? "signal $res" : "exit code " . ($res >> 8)) . ":\n" . ($stderr ? decode("utf-8", $stderr) : "(no output)\n") if $res; print STDERR "$stderr"; - return (decode_json($jobsJSON), $nixExprInput); + return decode_json($jobsJSON); } @@ -400,7 +409,7 @@ sub getPrevJobsetEval { # Check whether to add the build described by $buildInfo. sub checkBuild { - my ($db, $jobset, $inputInfo, $nixExprInput, $buildInfo, $buildMap, $prevEval, $jobOutPathMap, $plugins) = @_; + my ($db, $jobset, $inputInfo, $buildInfo, $buildMap, $prevEval, $jobOutPathMap, $plugins) = @_; my @outputNames = sort keys %{$buildInfo->{outputs}}; die unless scalar @outputNames; @@ -628,7 +637,7 @@ sub checkJobsetWrapped { # Evaluate the job expression. my $evalStart = clock_gettime(CLOCK_MONOTONIC); - my ($jobs, $nixExprInput) = evalJobs($inputInfo, $exprType, $jobset->nixexprinput, $jobset->nixexprpath); + my $jobs = evalJobs($inputInfo, $exprType, $jobset->nixexprinput, $jobset->nixexprpath, $jobset->flake); my $evalStop = clock_gettime(CLOCK_MONOTONIC); if ($jobsetsJobset) { @@ -673,7 +682,7 @@ sub checkJobsetWrapped { foreach my $job (permute(values %{$jobs})) { next if defined $job->{error}; #print STDERR "considering job " . $project->name, ":", $jobset->name, ":", $job->{jobName} . "\n"; - checkBuild($db, $jobset, $inputInfo, $nixExprInput, $job, \%buildMap, $prevEval, $jobOutPathMap, $plugins); + checkBuild($db, $jobset, $inputInfo, $job, \%buildMap, $prevEval, $jobOutPathMap, $plugins); } # Have any builds been added or removed since last time? From 0387787e7edd33efcbb46d1969d4e3868d2b9734 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sat, 11 May 2019 00:14:18 +0200 Subject: [PATCH 11/73] TODO --- src/sql/hydra.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sql/hydra.sql b/src/sql/hydra.sql index 4b37f6e1..8998fd57 100644 --- a/src/sql/hydra.sql +++ b/src/sql/hydra.sql @@ -185,7 +185,8 @@ create table Builds ( -- Copy of the nixExprInput/nixExprPath fields of the jobset that -- instantiated this build. Needed if we want to reproduce this - -- build. + -- build. FIXME: this should be stored in JobsetEvals, storing it + -- here is denormal. nixExprInput text, nixExprPath text, From 30e8fe951b94c0ac0819b14a38d120fc340d2f1c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sat, 11 May 2019 00:40:40 +0200 Subject: [PATCH 12/73] Improve error message --- src/hydra-evaluator/hydra-evaluator.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hydra-evaluator/hydra-evaluator.cc b/src/hydra-evaluator/hydra-evaluator.cc index 86ac5c39..e3004c2e 100644 --- a/src/hydra-evaluator/hydra-evaluator.cc +++ b/src/hydra-evaluator/hydra-evaluator.cc @@ -83,7 +83,7 @@ struct Evaluator } if (evalOne && seen.empty()) { - printError("the specified jobset does not exist"); + printError("the specified jobset does not exist or is disabled"); std::_Exit(1); } From ed00f0b25e5cee10a434f170d18f62a6692681a1 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sat, 11 May 2019 00:41:13 +0200 Subject: [PATCH 13/73] Cache flake-based jobset evaluations --- src/lib/Hydra/Schema/JobsetEvals.pm | 11 +++++++++-- src/script/hydra-eval-jobset | 15 +++++++++++++-- src/sql/hydra.sql | 2 ++ src/sql/upgrade-57.sql | 1 + 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/lib/Hydra/Schema/JobsetEvals.pm b/src/lib/Hydra/Schema/JobsetEvals.pm index 0bd21da2..ef0b1523 100644 --- a/src/lib/Hydra/Schema/JobsetEvals.pm +++ b/src/lib/Hydra/Schema/JobsetEvals.pm @@ -88,6 +88,11 @@ __PACKAGE__->table("JobsetEvals"); data_type: 'integer' is_nullable: 1 +=head2 flake + + data_type: 'text' + is_nullable: 1 + =cut __PACKAGE__->add_columns( @@ -111,6 +116,8 @@ __PACKAGE__->add_columns( { data_type => "integer", is_nullable => 1 }, "nrsucceeded", { data_type => "integer", is_nullable => 1 }, + "flake", + { data_type => "text", is_nullable => 1 }, ); =head1 PRIMARY KEY @@ -188,8 +195,8 @@ __PACKAGE__->belongs_to( ); -# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:SlEiF8oN6FBK262uSiMKiw +# Created by DBIx::Class::Schema::Loader v0.07049 @ 2019-05-11 00:16:10 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:XwlJFCJiS0LHsLg2fFqfUg __PACKAGE__->has_many( "buildIds", diff --git a/src/script/hydra-eval-jobset b/src/script/hydra-eval-jobset index 1e5c81e7..71d24a83 100755 --- a/src/script/hydra-eval-jobset +++ b/src/script/hydra-eval-jobset @@ -607,6 +607,16 @@ sub checkJobsetWrapped { }; my $fetchError = $@; + my $flakeRef = $jobset->flake; + if (defined $flakeRef) { + (my $res, my $json, my $stderr) = captureStdoutStderr( + 600, "nix", "flake", "info", "--tarball-ttl", 0, "--json", "--", $flakeRef); + die "'nix flake info' returned " . ($res & 127 ? "signal $res" : "exit code " . ($res >> 8)) + . ":\n" . ($stderr ? decode("utf-8", $stderr) : "(no output)\n") + if $res; + $flakeRef = decode_json($json)->{'uri'}; + } + Net::Statsd::increment("hydra.evaluator.checkouts"); my $checkoutStop = clock_gettime(CLOCK_MONOTONIC); Net::Statsd::timing("hydra.evaluator.checkout_time", int(($checkoutStop - $checkoutStart) * 1000)); @@ -626,7 +636,7 @@ sub checkJobsetWrapped { my @args = ($jobset->nixexprinput, $jobset->nixexprpath, inputsToArgs($inputInfo, $exprType)); my $argsHash = sha256_hex("@args"); my $prevEval = getPrevJobsetEval($db, $jobset, 0); - if (defined $prevEval && $prevEval->hash eq $argsHash && !$dryRun && !$jobset->forceeval) { + if (defined $prevEval && $prevEval->hash eq $argsHash && !$dryRun && !$jobset->forceeval && $prevEval->flake eq $flakeRef) { print STDERR " jobset is unchanged, skipping\n"; Net::Statsd::increment("hydra.evaluator.unchanged_checkouts"); txn_do($db, sub { @@ -637,7 +647,7 @@ sub checkJobsetWrapped { # Evaluate the job expression. my $evalStart = clock_gettime(CLOCK_MONOTONIC); - my $jobs = evalJobs($inputInfo, $exprType, $jobset->nixexprinput, $jobset->nixexprpath, $jobset->flake); + my $jobs = evalJobs($inputInfo, $exprType, $jobset->nixexprinput, $jobset->nixexprpath, $flakeRef); my $evalStop = clock_gettime(CLOCK_MONOTONIC); if ($jobsetsJobset) { @@ -697,6 +707,7 @@ sub checkJobsetWrapped { , evaltime => abs(int($evalStop - $evalStart)) , hasnewbuilds => $jobsetChanged ? 1 : 0 , nrbuilds => $jobsetChanged ? scalar(keys %buildMap) : undef + , flake => $flakeRef }); if ($jobsetChanged) { diff --git a/src/sql/hydra.sql b/src/sql/hydra.sql index 8998fd57..f81bbad4 100644 --- a/src/sql/hydra.sql +++ b/src/sql/hydra.sql @@ -527,6 +527,8 @@ create table JobsetEvals ( nrBuilds integer, nrSucceeded integer, -- set lazily when all builds are finished + flake text, -- immutable flake reference + foreign key (project) references Projects(name) on delete cascade on update cascade, foreign key (project, jobset) references Jobsets(project, name) on delete cascade on update cascade ); diff --git a/src/sql/upgrade-57.sql b/src/sql/upgrade-57.sql index 15553beb..b59ee949 100644 --- a/src/sql/upgrade-57.sql +++ b/src/sql/upgrade-57.sql @@ -4,3 +4,4 @@ alter table Jobsets add column type integer default 0; alter table Jobsets add column flake text; alter table Jobsets add check ((type = 0) = (nixExprInput is not null and nixExprPath is not null)); alter table Jobsets add check ((type = 1) = (flake is not null)); +alter table JobsetEvals add column flake text; From 4ec51032eedc8fabd108266d5eee08fb3e62f962 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sat, 11 May 2019 00:52:06 +0200 Subject: [PATCH 14/73] Jobset eval page: Show immutable flake URI --- src/root/jobset-eval.tt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/root/jobset-eval.tt b/src/root/jobset-eval.tt index 979d9862..c091ec59 100644 --- a/src/root/jobset-eval.tt +++ b/src/root/jobset-eval.tt @@ -18,7 +18,8 @@ -

This evaluation was performed on [% INCLUDE renderDateTime +

This evaluation was performed [% IF eval.flake %]from the flake +[%HTML.escape(eval.flake)%][%END%] on [% INCLUDE renderDateTime timestamp=eval.timestamp %]. Fetching the dependencies took [% eval.checkouttime %]s and evaluation took [% eval.evaltime %]s.

From f68cb7b57e29370b2de641091ee8be72ff5b6ed2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sat, 11 May 2019 00:52:25 +0200 Subject: [PATCH 15/73] "Reproduce" action: Support flakes No more need for a reproduction script! It just says something like If you have Nix installed, you can reproduce this build on your own machine by running the following command: # nix build github:edolstra/dwarffs/09c823e977946668b63ad6c88ed358b48220f124:hydraJobs.build.x86_64-linux --- src/root/build.tt | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/root/build.tt b/src/root/build.tt index 750ae474..b563ebb1 100644 --- a/src/root/build.tt +++ b/src/root/build.tt @@ -120,7 +120,7 @@ END;