From f4a44db6642aa7e62e8c3b6e01d5ed2567740919 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 9 Nov 2008 00:48:36 +0000 Subject: [PATCH] --- .../lib/HydraFrontend/Controller/Root.pm | 1 + src/HydraFrontend/lib/HydraFrontend/Schema.pm | 4 +- .../lib/HydraFrontend/Schema/Buildlogs.pm | 14 +++---- .../lib/HydraFrontend/Schema/Buildproducts.pm | 14 +++---- .../lib/HydraFrontend/Schema/Builds.pm | 16 ++++---- .../Schema/{Buildinputs.pm => Inputs.pm} | 34 +++++++--------- .../lib/HydraFrontend/Schema/Jobs.pm | 40 +++++++++++++++++++ .../HydraFrontend/Schema/Jobsetinputalts.pm | 4 +- .../lib/HydraFrontend/Schema/Jobsetinputs.pm | 4 +- .../lib/HydraFrontend/Schema/Jobsets.pm | 4 +- .../lib/HydraFrontend/Schema/Projects.pm | 4 +- src/HydraFrontend/root/build.tt | 18 ++++----- src/HydraFrontend/root/index.tt | 8 ++++ src/hydra.sql | 39 ++++++++++-------- src/scheduler.pl | 28 ++++++++----- 15 files changed, 141 insertions(+), 91 deletions(-) rename src/HydraFrontend/lib/HydraFrontend/Schema/{Buildinputs.pm => Inputs.pm} (56%) create mode 100644 src/HydraFrontend/lib/HydraFrontend/Schema/Jobs.pm diff --git a/src/HydraFrontend/lib/HydraFrontend/Controller/Root.pm b/src/HydraFrontend/lib/HydraFrontend/Controller/Root.pm index baf2c1d2..53d2d10f 100644 --- a/src/HydraFrontend/lib/HydraFrontend/Controller/Root.pm +++ b/src/HydraFrontend/lib/HydraFrontend/Controller/Root.pm @@ -29,6 +29,7 @@ sub getBuild { sub index :Path :Args(0) { my ( $self, $c ) = @_; $c->stash->{template} = 'index.tt'; + $c->stash->{projects} = [$c->model('DB::Projects')->all]; $c->stash->{allBuilds} = [$c->model('DB::Builds')->search(undef, {order_by => "timestamp DESC"})]; # Get the latest build for each unique job. # select * from builds as x where timestamp == (select max(timestamp) from builds where jobName == x.jobName); diff --git a/src/HydraFrontend/lib/HydraFrontend/Schema.pm b/src/HydraFrontend/lib/HydraFrontend/Schema.pm index 03f3c4ce..64759718 100644 --- a/src/HydraFrontend/lib/HydraFrontend/Schema.pm +++ b/src/HydraFrontend/lib/HydraFrontend/Schema.pm @@ -8,8 +8,8 @@ use base 'DBIx::Class::Schema'; __PACKAGE__->load_classes; -# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-08 23:34:46 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Gl/KqOOAg3rH0hWZUovhxw +# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-09 01:36:21 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:G17vptu+2rEUXbsqVtoXzQ # You can replace this text with custom content, and it will be preserved on regeneration diff --git a/src/HydraFrontend/lib/HydraFrontend/Schema/Buildlogs.pm b/src/HydraFrontend/lib/HydraFrontend/Schema/Buildlogs.pm index f96ded5e..e3ce113f 100644 --- a/src/HydraFrontend/lib/HydraFrontend/Schema/Buildlogs.pm +++ b/src/HydraFrontend/lib/HydraFrontend/Schema/Buildlogs.pm @@ -8,7 +8,7 @@ use base 'DBIx::Class'; __PACKAGE__->load_components("Core"); __PACKAGE__->table("buildLogs"); __PACKAGE__->add_columns( - "buildid", + "build", { data_type => "integer", is_nullable => 0, size => undef }, "logphase", { data_type => "text", is_nullable => 0, size => undef }, @@ -17,16 +17,12 @@ __PACKAGE__->add_columns( "type", { data_type => "text", is_nullable => 0, size => undef }, ); -__PACKAGE__->set_primary_key("buildid", "logphase"); -__PACKAGE__->belongs_to( - "buildid", - "HydraFrontend::Schema::Builds", - { id => "buildid" }, -); +__PACKAGE__->set_primary_key("build", "logphase"); +__PACKAGE__->belongs_to("build", "HydraFrontend::Schema::Builds", { id => "build" }); -# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-08 23:34:46 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:tlyJLjDbR0vk3Jt/O3M4nw +# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-09 01:36:21 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:xvWlrugDQD11vH+7f91K0A # You can replace this text with custom content, and it will be preserved on regeneration diff --git a/src/HydraFrontend/lib/HydraFrontend/Schema/Buildproducts.pm b/src/HydraFrontend/lib/HydraFrontend/Schema/Buildproducts.pm index 7fb7e2d9..2348498b 100644 --- a/src/HydraFrontend/lib/HydraFrontend/Schema/Buildproducts.pm +++ b/src/HydraFrontend/lib/HydraFrontend/Schema/Buildproducts.pm @@ -8,7 +8,7 @@ use base 'DBIx::Class'; __PACKAGE__->load_components("Core"); __PACKAGE__->table("buildProducts"); __PACKAGE__->add_columns( - "buildid", + "build", { data_type => "integer", is_nullable => 0, size => undef }, "path", { data_type => "text", is_nullable => 0, size => undef }, @@ -17,16 +17,12 @@ __PACKAGE__->add_columns( "subtype", { data_type => "text", is_nullable => 0, size => undef }, ); -__PACKAGE__->set_primary_key("buildid", "path"); -__PACKAGE__->belongs_to( - "buildid", - "HydraFrontend::Schema::Builds", - { id => "buildid" }, -); +__PACKAGE__->set_primary_key("build", "path"); +__PACKAGE__->belongs_to("build", "HydraFrontend::Schema::Builds", { id => "build" }); -# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-08 23:34:46 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:lCdfeZud7izQv/11dVFFVA +# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-09 01:36:21 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:SMsT6htcybeWNHhv82+ilA # You can replace this text with custom content, and it will be preserved on regeneration diff --git a/src/HydraFrontend/lib/HydraFrontend/Schema/Builds.pm b/src/HydraFrontend/lib/HydraFrontend/Schema/Builds.pm index 5893afa9..c370d01c 100644 --- a/src/HydraFrontend/lib/HydraFrontend/Schema/Builds.pm +++ b/src/HydraFrontend/lib/HydraFrontend/Schema/Builds.pm @@ -39,25 +39,25 @@ __PACKAGE__->add_columns( ); __PACKAGE__->set_primary_key("id"); __PACKAGE__->has_many( - "buildinputs", - "HydraFrontend::Schema::Buildinputs", - { "foreign.buildid" => "self.id" }, + "inputs", + "HydraFrontend::Schema::Inputs", + { "foreign.build" => "self.id" }, ); __PACKAGE__->has_many( "buildproducts", "HydraFrontend::Schema::Buildproducts", - { "foreign.buildid" => "self.id" }, + { "foreign.build" => "self.id" }, ); __PACKAGE__->has_many( "buildlogs", "HydraFrontend::Schema::Buildlogs", - { "foreign.buildid" => "self.id" }, + { "foreign.build" => "self.id" }, ); -# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-08 23:34:46 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:JRXGOLW2h+DOY7LZUdkCWQ +# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-09 01:36:21 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:nfVureYYGM1V/NHroQA5Tw -__PACKAGE__->has_many(dependentBuildInputs => 'HydraFrontend::Schema::Buildinputs', 'inputid'); +__PACKAGE__->has_many(dependents => 'HydraFrontend::Schema::Inputs', 'dependency'); 1; diff --git a/src/HydraFrontend/lib/HydraFrontend/Schema/Buildinputs.pm b/src/HydraFrontend/lib/HydraFrontend/Schema/Inputs.pm similarity index 56% rename from src/HydraFrontend/lib/HydraFrontend/Schema/Buildinputs.pm rename to src/HydraFrontend/lib/HydraFrontend/Schema/Inputs.pm index 9b0d789c..3ae7e42b 100644 --- a/src/HydraFrontend/lib/HydraFrontend/Schema/Buildinputs.pm +++ b/src/HydraFrontend/lib/HydraFrontend/Schema/Inputs.pm @@ -1,4 +1,4 @@ -package HydraFrontend::Schema::Buildinputs; +package HydraFrontend::Schema::Inputs; use strict; use warnings; @@ -6,9 +6,13 @@ use warnings; use base 'DBIx::Class'; __PACKAGE__->load_components("Core"); -__PACKAGE__->table("buildInputs"); +__PACKAGE__->table("inputs"); __PACKAGE__->add_columns( - "buildid", + "id", + { data_type => "integer", is_nullable => 0, size => undef }, + "build", + { data_type => "integer", is_nullable => 0, size => undef }, + "job", { data_type => "integer", is_nullable => 0, size => undef }, "name", { data_type => "text", is_nullable => 0, size => undef }, @@ -20,28 +24,20 @@ __PACKAGE__->add_columns( { data_type => "integer", is_nullable => 0, size => undef }, "tag", { data_type => "text", is_nullable => 0, size => undef }, - "inputid", + "value", + { data_type => "text", is_nullable => 0, size => undef }, + "dependency", { data_type => "integer", is_nullable => 0, size => undef }, "path", { data_type => "text", is_nullable => 0, size => undef }, - "value", - { data_type => "text", is_nullable => 0, size => undef }, -); -__PACKAGE__->set_primary_key("buildid", "name"); -__PACKAGE__->belongs_to( - "buildid", - "HydraFrontend::Schema::Builds", - { id => "buildid" }, ); +__PACKAGE__->set_primary_key("id"); +__PACKAGE__->belongs_to("build", "HydraFrontend::Schema::Builds", { id => "build" }); -# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-08 23:34:46 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:2XeATQWeO3i3eSHlquS2QA +# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-09 01:36:21 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:3PAsUD+79bZk4vGeSyyACg -__PACKAGE__->belongs_to( - "build", - "HydraFrontend::Schema::Builds", - { id => "inputid" }, -); +__PACKAGE__->belongs_to("dependency", "HydraFrontend::Schema::Builds", { id => "dependency" }); 1; diff --git a/src/HydraFrontend/lib/HydraFrontend/Schema/Jobs.pm b/src/HydraFrontend/lib/HydraFrontend/Schema/Jobs.pm new file mode 100644 index 00000000..81274320 --- /dev/null +++ b/src/HydraFrontend/lib/HydraFrontend/Schema/Jobs.pm @@ -0,0 +1,40 @@ +package HydraFrontend::Schema::Jobs; + +use strict; +use warnings; + +use base 'DBIx::Class'; + +__PACKAGE__->load_components("Core"); +__PACKAGE__->table("jobs"); +__PACKAGE__->add_columns( + "id", + { data_type => "integer", is_nullable => 0, size => undef }, + "timestamp", + { data_type => "integer", is_nullable => 0, size => undef }, + "priority", + { data_type => "integer", is_nullable => 0, size => undef }, + "project", + { data_type => "text", is_nullable => 0, size => undef }, + "jobset", + { data_type => "text", is_nullable => 0, size => undef }, + "attrname", + { data_type => "text", is_nullable => 0, size => undef }, + "description", + { data_type => "text", is_nullable => 0, size => undef }, + "drvpath", + { data_type => "text", is_nullable => 0, size => undef }, + "outpath", + { data_type => "text", is_nullable => 0, size => undef }, + "system", + { data_type => "text", is_nullable => 0, size => undef }, +); +__PACKAGE__->set_primary_key("id"); + + +# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-09 01:36:21 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:T8O0XTTOZXapWpJbzjKLTw + + +# You can replace this text with custom content, and it will be preserved on regeneration +1; diff --git a/src/HydraFrontend/lib/HydraFrontend/Schema/Jobsetinputalts.pm b/src/HydraFrontend/lib/HydraFrontend/Schema/Jobsetinputalts.pm index e2b2cd20..b29816d2 100644 --- a/src/HydraFrontend/lib/HydraFrontend/Schema/Jobsetinputalts.pm +++ b/src/HydraFrontend/lib/HydraFrontend/Schema/Jobsetinputalts.pm @@ -33,8 +33,8 @@ __PACKAGE__->belongs_to( ); -# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-08 23:34:46 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:m24w17dWVxjIqPlea77G3A +# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-09 01:36:21 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:DzEHCDlnponciGmGASknlg # You can replace this text with custom content, and it will be preserved on regeneration diff --git a/src/HydraFrontend/lib/HydraFrontend/Schema/Jobsetinputs.pm b/src/HydraFrontend/lib/HydraFrontend/Schema/Jobsetinputs.pm index 2cecaa33..6093bde3 100644 --- a/src/HydraFrontend/lib/HydraFrontend/Schema/Jobsetinputs.pm +++ b/src/HydraFrontend/lib/HydraFrontend/Schema/Jobsetinputs.pm @@ -43,8 +43,8 @@ __PACKAGE__->has_many( ); -# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-08 23:34:46 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ufIbhVFTzl7awpRrZofvJQ +# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-09 01:36:21 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Lm2oIWEUSHFICYMX2qmTfw # You can replace this text with custom content, and it will be preserved on regeneration diff --git a/src/HydraFrontend/lib/HydraFrontend/Schema/Jobsets.pm b/src/HydraFrontend/lib/HydraFrontend/Schema/Jobsets.pm index 67bc9f9b..019a1052 100644 --- a/src/HydraFrontend/lib/HydraFrontend/Schema/Jobsets.pm +++ b/src/HydraFrontend/lib/HydraFrontend/Schema/Jobsets.pm @@ -40,8 +40,8 @@ __PACKAGE__->has_many( ); -# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-08 23:34:46 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:7hm28Izo7wCZc07fH1EJRg +# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-09 01:36:21 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:EmATMMeNmMd2AI8lVzcLFA # You can replace this text with custom content, and it will be preserved on regeneration diff --git a/src/HydraFrontend/lib/HydraFrontend/Schema/Projects.pm b/src/HydraFrontend/lib/HydraFrontend/Schema/Projects.pm index ffb748fd..e43a0b0a 100644 --- a/src/HydraFrontend/lib/HydraFrontend/Schema/Projects.pm +++ b/src/HydraFrontend/lib/HydraFrontend/Schema/Projects.pm @@ -19,8 +19,8 @@ __PACKAGE__->has_many( ); -# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-08 23:34:46 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:WCqXnL5vOhpwjYB9/Aw7tg +# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-09 01:36:21 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ZifQocKoHOPRrJQSPggZ+w # You can replace this text with custom content, and it will be preserved on regeneration diff --git a/src/HydraFrontend/root/build.tt b/src/HydraFrontend/root/build.tt index 22197099..211b7a32 100644 --- a/src/HydraFrontend/root/build.tt +++ b/src/HydraFrontend/root/build.tt @@ -33,11 +33,11 @@ Build started: - [% date.format(build.starttime, '%Y-%m-%d %H:%M:%S') %] + [% IF build.starttime %][% date.format(build.starttime, '%Y-%m-%d %H:%M:%S') %][% ELSE %](cached build)[% END %] Build finished: - [% date.format(build.stoptime, '%Y-%m-%d %H:%M:%S') %] + [% IF build.stoptime %][% date.format(build.stoptime, '%Y-%m-%d %H:%M:%S') %][% ELSE %](cached build)[% END %] Duration (seconds): @@ -81,13 +81,13 @@ NameTypeWhatStore path - [% FOREACH input IN build.buildinputs -%] + [% FOREACH input IN build.inputs -%] [% input.name %] [% input.type %] [% IF input.type == "build" %] - Job [% input.build.project %]:[% input.build.attrname %] build [% input.inputid %] + Job [% input.dependency.project %]:[% input.dependency.attrname %] build [% input.dependency.id %] [% ELSIF input.type == "string" %] "[% input.value %]" [% ELSE %] @@ -140,7 +140,7 @@ -[% IF build.dependentBuildInputs %] +[% IF build.dependents %]

Used by

@@ -151,12 +151,12 @@ BuildInput nameSystemTimestamp - [% FOREACH input IN build.dependentBuildInputs -%] + [% FOREACH input IN build.dependents -%] - Job [% input.buildid.project %]:[% input.buildid.attrname %] build [% input.buildid.id %] + Job [% input.build.project %]:[% input.build.attrname %] build [% input.build.id %] [% input.name %] - [% input.buildid.system %] - [% date.format(input.buildid.timestamp, '%Y-%m-%d %H:%M:%S') %] + [% input.build.system %] + [% date.format(input.build.timestamp, '%Y-%m-%d %H:%M:%S') %] [% END -%] diff --git a/src/HydraFrontend/root/index.tt b/src/HydraFrontend/root/index.tt index 39f028e0..483663c1 100644 --- a/src/HydraFrontend/root/index.tt +++ b/src/HydraFrontend/root/index.tt @@ -30,4 +30,12 @@ +

Projects

+ + + [% END %] diff --git a/src/hydra.sql b/src/hydra.sql index 53a23a5d..ae3dfddb 100644 --- a/src/hydra.sql +++ b/src/hydra.sql @@ -20,8 +20,13 @@ create table builds ( ); -create table buildInputs ( - buildId integer not null, +-- Inputs of jobs/builds. +create table inputs ( + id integer primary key autoincrement not null, + + -- Which job or build this input belongs to. Exactly one must be non-null. + build integer, + job integer, -- Copied from the jobSetInputs from which the build was created. name text not null, @@ -30,33 +35,33 @@ create table buildInputs ( revision integer, tag text, value text, - inputId integer, -- build ID of the input, for type == 'build' + dependency integer, -- build ID of the input, for type == 'build' path text, - primary key (buildId, name), - foreign key (buildId) references builds(id) on delete cascade -- ignored by sqlite - foreign key (inputId) references builds(id) -- ignored by sqlite + foreign key (build) references builds(id) -- ignored by sqlite + foreign key (job) references jobs(id) -- ignored by sqlite + foreign key (dependency) references builds(id) -- ignored by sqlite ); create table buildProducts ( - buildId integer not null, + build integer not null, path text not null, type text not null, -- "nix-build", "file", "doc", "report", ... subtype text not null, -- "source-dist", "rpm", ... - primary key (buildId, path), - foreign key (buildId) references builds(id) on delete cascade -- ignored by sqlite + primary key (build, path), + foreign key (build) references builds(id) on delete cascade -- ignored by sqlite ); create table buildLogs ( - buildId integer not null, + build integer not null, logPhase text not null, path text not null, type text not null, - primary key (buildId, logPhase), - foreign key (buildId) references builds(id) on delete cascade -- ignored by sqlite + primary key (build, logPhase), + foreign key (build) references builds(id) on delete cascade -- ignored by sqlite ); @@ -64,9 +69,9 @@ create table buildLogs ( create trigger cascadeBuildDeletion before delete on builds for each row begin - delete from buildInputs where buildId = old.id; - delete from buildLogs where buildId = old.id; - delete from buildProducts where buildId = old.id; + --delete from buildInputs where build = old.id; + delete from buildLogs where build = old.id; + delete from buildProducts where build = old.id; end; @@ -117,9 +122,11 @@ create table jobSetInputAlts ( ); -create table jobQueue ( +create table jobs ( id integer primary key autoincrement not null, timestamp integer not null, -- time this build was added to the db (in Unix time) + + priority integer not null, -- Info about the inputs. project text not null, -- !!! foreign key diff --git a/src/scheduler.pl b/src/scheduler.pl index 94be0b38..66c75082 100644 --- a/src/scheduler.pl +++ b/src/scheduler.pl @@ -70,15 +70,15 @@ sub buildJob { foreach my $inputName (keys %{$usedInputs}) { my $input = $usedInputs->{$inputName}; - $db->resultset('Buildinputs')->create( - { buildid => $build->id + $db->resultset('Inputs')->create( + { build => $build->id , name => $inputName , type => $input->{type} , uri => $input->{uri} #, revision => $input->{orig}->revision #, tag => $input->{orig}->tag , value => $input->{value} - , inputid => $input->{id} + , dependency => $input->{id} , path => ($input->{storePath} or "") # !!! temporary hack }); } @@ -87,7 +87,7 @@ sub buildJob { if (-e $logPath) { print " LOG $logPath\n"; $db->resultset('Buildlogs')->create( - { buildid => $build->id + { build => $build->id , logphase => "full" , path => $logPath , type => "raw" @@ -100,7 +100,7 @@ sub buildJob { foreach my $logPath (glob "$outPath/log/*") { print " LOG $logPath\n"; $db->resultset('Buildlogs')->create( - { buildid => $build->id + { build => $build->id , logphase => basename($logPath) , path => $logPath , type => "raw" @@ -117,7 +117,7 @@ sub buildJob { my $path = $3; die unless -e $path; $db->resultset('Buildproducts')->create( - { buildid => $build->id + { build => $build->id , type => $type , subtype => $subtype , path => $path @@ -126,7 +126,7 @@ sub buildJob { close LIST; } else { $db->resultset('Buildproducts')->create( - { buildid => $build->id + { build => $build->id , type => "nix-build" , subtype => "" , path => $outPath @@ -193,7 +193,10 @@ sub checkJobAlternatives { my ($project, $jobset, $inputInfo, $nixExprPath, $jobName, $jobExpr, $extraArgs, $argsNeeded, $n) = @_; if ($n >= scalar @{$argsNeeded}) { - checkJob($project, $jobset, $inputInfo, $nixExprPath, $jobName, $jobExpr, $extraArgs); + eval { + checkJob($project, $jobset, $inputInfo, $nixExprPath, $jobName, $jobExpr, $extraArgs); + }; + warn $@ if $@; return; } @@ -303,9 +306,12 @@ sub checkJobSet { } } - checkJobAlternatives( - $project, $jobset, {}, $nixExprPath, - $jobName, $jobExpr, "", \@argsNeeded, 0); + eval { + checkJobAlternatives( + $project, $jobset, {}, $nixExprPath, + $jobName, $jobExpr, "", \@argsNeeded, 0); + }; + warn $@ if $@; } }