Skip to content

Commit

Permalink
Support testing build determinism
Browse files Browse the repository at this point in the history
Builds can now specify the attribute "isDeterministic = true" to tell
Hydra to build with build-repeat > 0. If there is a mismatch between
rounds, the step / build fails with a suitable status.

Maybe this should be a meta attribute, but that makes it invisible to
hydra-queue-runner, and it seems reasonable to make a claim of
mandatory determinism part of the derivation (since e.g. enabling this
flag should trigger a rebuild).
  • Loading branch information
edolstra committed Dec 6, 2016
1 parent d0ad3fd commit 8bb36e7
Show file tree
Hide file tree
Showing 6 changed files with 21 additions and 1 deletion.
12 changes: 11 additions & 1 deletion src/hydra-queue-runner/build-remote.cc
Expand Up @@ -169,7 +169,7 @@ void State::buildRemote(ref<Store> destStore,
unsigned int remoteVersion;

try {
to << SERVE_MAGIC_1 << 0x202;
to << SERVE_MAGIC_1 << 0x203;
to.flush();

unsigned int magic = readInt(from);
Expand All @@ -180,6 +180,8 @@ void State::buildRemote(ref<Store> destStore,
throw Error(format("unsupported ‘nix-store --serve’ protocol version on ‘%1%’") % machine->sshName);
if (GET_PROTOCOL_MINOR(remoteVersion) >= 1)
sendDerivation = false;
if (GET_PROTOCOL_MINOR(remoteVersion) < 3 && step->isDeterministic)
throw Error("machine ‘%1%’ does not support deterministic builds; please upgrade it to Nix 1.12", machine->sshName);

} catch (EndOfFile & e) {
child.pid.wait(true);
Expand Down Expand Up @@ -261,6 +263,9 @@ void State::buildRemote(ref<Store> destStore,
to << maxSilentTime << buildTimeout;
if (GET_PROTOCOL_MINOR(remoteVersion) >= 2)
to << 64 * 1024 * 1024; // == maxLogSize
if (GET_PROTOCOL_MINOR(remoteVersion) >= 3)
// FIXME: make the number of repeats configurable.
to << (step->isDeterministic ? 1 : 0);
to.flush();

result.startTime = time(0);
Expand Down Expand Up @@ -325,6 +330,11 @@ void State::buildRemote(ref<Store> destStore,
case BuildResult::LogLimitExceeded:
result.stepStatus = bsLogLimitExceeded;
break;
case BuildResult::NotDeterministic:
result.stepStatus = bsNotDeterministic;
result.canRetry = false;
result.canCache = true;
break;
default:
result.stepStatus = bsAborted;
break;
Expand Down
1 change: 1 addition & 0 deletions src/hydra-queue-runner/queue-monitor.cc
Expand Up @@ -418,6 +418,7 @@ Step::ptr State::createStep(ref<Store> destStore,
step->drv = readDerivation(drvPath);

step->preferLocalBuild = step->drv.willBuildLocally();
step->isDeterministic = get(step->drv.env, "isDetermistic", "0") == "1";

step->systemType = step->drv.platform;
{
Expand Down
2 changes: 2 additions & 0 deletions src/hydra-queue-runner/state.hh
Expand Up @@ -35,6 +35,7 @@ typedef enum {
bsUnsupported = 9,
bsLogLimitExceeded = 10,
bsNarSizeLimitExceeded = 11,
bsNotDeterministic = 12,
bsBusy = 100, // not stored
} BuildStatus;

Expand Down Expand Up @@ -139,6 +140,7 @@ struct Step
nix::Derivation drv;
std::set<std::string> requiredSystemFeatures;
bool preferLocalBuild;
bool isDeterministic;
std::string systemType; // concatenation of drv.platform and requiredSystemFeatures

struct State
Expand Down
2 changes: 2 additions & 0 deletions src/root/build.tt
Expand Up @@ -69,6 +69,8 @@ FOR step IN steps; IF step.busy; busy = 1; END; END;
<span class="error">Log limit exceeded</span>
[% ELSIF step.status == 11 %]
<span class="error">Output limit exceeded</span>
[% ELSIF step.status == 12 %]
<span class="error">Non-deterministic build</span>
[% ELSIF step.errormsg %]
<span class="error">Failed: [% HTML.escape(step.errormsg) %]</span>
[% ELSE %]
Expand Down
4 changes: 4 additions & 0 deletions src/root/common.tt
Expand Up @@ -209,6 +209,8 @@ BLOCK renderBuildStatusIcon;
<img src="[% c.uri_for("/static/images/emojione-red-x-274c.svg") %]" height="[% size %]" width="[% size %]" title="Log limit exceeded" alt="Log limit exceeded" class="build-status" />
[% ELSIF buildstatus == 11 %]
<img src="[% c.uri_for("/static/images/emojione-red-x-274c.svg") %]" height="[% size %]" width="[% size %]" title="Output size limit exceeded" alt="Output size limit exceeded" class="build-status" />
[% ELSIF buildstatus == 12 %]
<img src="[% c.uri_for("/static/images/emojione-red-x-274c.svg") %]" height="[% size %]" width="[% size %]" title="Non-deterministic build" alt="Non-deterministic build" class="build-status" />
[% ELSE %]
<img src="[% c.uri_for("/static/images/emojione-red-x-274c.svg") %]" height="[% size %]" width="[% size %]" title="Failed" alt="Failed" class="build-status" />
[% END;
Expand Down Expand Up @@ -240,6 +242,8 @@ BLOCK renderStatus;
<span class="error">Log limit exceeded</span>
[% ELSIF buildstatus == 11 %]
<span class="error">Output limit exceeded</span>
[% ELSIF buildstatus == 12 %]
<span class="error">Non-deterministic build</span>
[% ELSE %]
<span class="error">Aborted</span>
(Hydra failure; see <a href="#nix-error">below</a>)
Expand Down
1 change: 1 addition & 0 deletions src/sql/hydra.sql
Expand Up @@ -211,6 +211,7 @@ create table Builds (
-- 9 = unsupported system type
-- 10 = log limit exceeded
-- 11 = NAR size limit exceeded
-- 12 = build or step was not deterministic
buildStatus integer,

size bigint,
Expand Down

1 comment on commit 8bb36e7

@copumpkin
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems broader than Hydra, since I could also imagine nix-build automatically running a build twice if it's declared to be deterministic (if configured to do that, at least)

Please sign in to comment.