2008-11-25 11:01:42 +00:00
package Hydra::Helper::Nix ;
2008-11-18 14:48:40 +00:00
use strict ;
2008-11-28 14:36:04 +00:00
use Exporter ;
2009-02-06 21:01:20 +00:00
use File::Path ;
2009-01-13 14:02:07 +00:00
use File::Basename ;
2010-09-03 09:17:52 +00:00
use Hydra::Helper::CatalystUtils ;
2008-11-28 14:36:04 +00:00
our @ ISA = qw( Exporter ) ;
2009-02-06 15:02:49 +00:00
our @ EXPORT = qw(
2012-02-28 20:16:16 +01:00
getHydraPath getHydraHome getHydraDBPath openHydraDB getHydraConf txn_do
2009-03-15 11:56:11 +00:00
registerRoot getGCRootsDir gcRootFor
2011-04-18 08:10:10 +00:00
getPrimaryBuildsForView
2009-10-15 11:13:36 +00:00
getPrimaryBuildTotal
2011-06-10 09:53:15 +00:00
getViewResult getLatestSuccessfulViewResult jobsetOverview removeAsciiEscapes ) ;
2008-11-18 14:48:40 +00:00
2008-11-28 14:36:04 +00:00
sub getHydraPath {
2011-03-07 12:30:27 +00:00
my $ dir = $ ENV { "HYDRA_DATA" } || "/var/lib/hydra" ;
2011-03-07 15:06:32 +00:00
die "The HYDRA_DATA directory ($dir) does not exist!\n" unless - d $ dir ;
2008-11-28 14:36:04 +00:00
return $ dir ;
}
2012-02-28 20:16:16 +01:00
sub getHydraHome {
my $ dir = $ ENV { "HYDRA_HOME" } or die "The HYDRA_HOME directory does not exist!\n" ;
return $ dir ;
}
2011-03-07 15:06:32 +00:00
sub getHydraConf {
2011-03-16 13:08:12 +00:00
my $ conf = $ ENV { "HYDRA_CONFIG" } || ( getHydraPath . "/hydra.conf" ) ;
die "The HYDRA_CONFIG file ($conf) does not exist!\n" unless - f $ conf ;
2011-03-07 15:06:32 +00:00
return $ conf ;
}
2008-11-28 14:36:04 +00:00
sub getHydraDBPath {
2009-04-29 11:07:46 +00:00
my $ db = $ ENV { "HYDRA_DBI" } ;
2009-05-09 16:00:08 +00:00
if ( defined $ db ) {
2009-04-29 11:07:46 +00:00
return $ db ;
}
else {
my $ path = getHydraPath . '/hydra.sqlite' ;
die "The Hydra database ($path) not exist!\n" unless - f $ path ;
return "dbi:SQLite:$path" ;
}
2008-11-28 14:36:04 +00:00
}
2008-11-18 14:48:40 +00:00
2008-11-28 14:36:04 +00:00
sub openHydraDB {
my $ db = Hydra::Schema - > connect ( getHydraDBPath , "" , "" , { } ) ;
2009-04-25 07:48:30 +00:00
$ db - > storage - > dbh - > do ( "PRAGMA synchronous = OFF;" )
if defined $ ENV { 'HYDRA_NO_FSYNC' } ;
2008-11-28 14:36:04 +00:00
return $ db ;
}
2009-04-22 22:43:04 +00:00
# Awful hack to handle timeouts in SQLite: just retry the transaction.
# DBD::SQLite *has* a 30 second retry window, but apparently it
# doesn't work.
sub txn_do {
my ( $ db , $ coderef ) = @ _ ;
while ( 1 ) {
eval {
$ db - > txn_do ( $ coderef ) ;
} ;
last if ! $@ ;
die $@ unless $@ =~ "database is locked" ;
}
}
2009-02-06 21:01:20 +00:00
sub getGCRootsDir {
die unless defined $ ENV { LOGNAME } ;
2011-04-18 08:10:10 +00:00
my $ dir = ( $ ENV { NIX_STATE_DIR } || "/nix/var/nix" ) . "/gcroots/per-user/$ENV{LOGNAME}/hydra-roots" ;
2009-03-15 11:56:11 +00:00
mkpath $ dir if ! - e $ dir ;
return $ dir ;
2009-02-06 21:01:20 +00:00
}
2009-03-15 11:56:11 +00:00
sub gcRootFor {
2009-02-06 21:01:20 +00:00
my ( $ path ) = @ _ ;
2009-03-31 15:59:47 +00:00
return getGCRootsDir . "/" . basename $ path ;
2009-03-15 11:56:11 +00:00
}
2009-02-06 21:01:20 +00:00
2009-03-15 11:56:11 +00:00
sub registerRoot {
my ( $ path ) = @ _ ;
2011-04-18 08:10:10 +00:00
2009-03-15 11:56:11 +00:00
my $ link = gcRootFor $ path ;
2011-04-18 08:10:10 +00:00
2009-02-06 21:01:20 +00:00
if ( ! - l $ link ) {
symlink ( $ path , $ link )
2009-03-15 11:56:11 +00:00
or die "cannot create GC root `$link' to `$path'" ;
2009-02-06 21:01:20 +00:00
}
}
2009-02-06 15:02:49 +00:00
sub attrsToSQL {
my ( $ attrs , $ id ) = @ _ ;
my @ attrs = split / / , $ attrs ;
my $ query = "1 = 1" ;
foreach my $ attr ( @ attrs ) {
2009-10-20 12:35:01 +00:00
$ attr =~ /^([\w-]+)=([\w-]*)$/ or die "invalid attribute in view: $attr" ;
2009-02-06 15:02:49 +00:00
my $ name = $ 1 ;
my $ value = $ 2 ;
# !!! Yes, this is horribly injection-prone... (though
# name/value are filtered above). Should use SQL::Abstract,
# but it can't deal with subqueries. At least we should use
# placeholders.
2009-03-20 14:50:09 +00:00
$ query . = " and exists (select 1 from buildinputs where build = $id and name = '$name' and value = '$value')" ;
2009-02-06 15:02:49 +00:00
}
return $ query ;
}
2009-10-15 11:13:36 +00:00
sub allPrimaryBuilds {
2009-10-20 12:35:01 +00:00
my ( $ project , $ primaryJob ) = @ _ ;
my $ allPrimaryBuilds = $ project - > builds - > search (
{ jobset = > $ primaryJob - > get_column ( 'jobset' ) , job = > $ primaryJob - > get_column ( 'job' ) , finished = > 1 } ,
{ join = > 'resultInfo' , order_by = > "timestamp DESC"
, '+select' = > [ "resultInfo.releasename" , "resultInfo.buildstatus" ]
, '+as' = > [ "releasename" , "buildstatus" ]
, where = > \ attrsToSQL ( $ primaryJob - > attrs , "me.id" )
} ) ;
2011-04-18 08:10:10 +00:00
return $ allPrimaryBuilds ;
2009-10-15 11:13:36 +00:00
}
2009-02-06 15:02:49 +00:00
2009-10-20 12:35:01 +00:00
2009-10-15 11:13:36 +00:00
sub getPrimaryBuildTotal {
2009-02-06 15:02:49 +00:00
my ( $ project , $ primaryJob ) = @ _ ;
2009-10-20 12:35:01 +00:00
return scalar ( allPrimaryBuilds ( $ project , $ primaryJob ) ) ;
2009-10-15 11:13:36 +00:00
}
2009-10-20 12:35:01 +00:00
sub getPrimaryBuildsForView {
2009-10-15 11:13:36 +00:00
my ( $ project , $ primaryJob , $ page , $ resultsPerPage ) = @ _ ;
$ page = ( defined $ page ? int ( $ page ) : 1 ) || 1 ;
$ resultsPerPage = ( defined $ resultsPerPage ? int ( $ resultsPerPage ) : 20 ) || 20 ;
my @ primaryBuilds = allPrimaryBuilds ( $ project , $ primaryJob ) - > search ( { } ,
{ rows = > $ resultsPerPage
, page = > $ page
2009-02-06 15:02:49 +00:00
} ) ;
2009-10-15 11:13:36 +00:00
2009-02-06 15:02:49 +00:00
return @ primaryBuilds ;
}
2009-10-20 12:35:01 +00:00
2009-10-15 11:13:36 +00:00
sub findLastJobForBuilds {
2010-03-05 17:20:04 +00:00
my ( $ ev , $ depBuilds , $ job ) = @ _ ;
2009-10-15 11:13:36 +00:00
my $ thisBuild ;
2010-03-05 17:20:04 +00:00
my $ project = $ job - > get_column ( 'project' ) ;
my $ jobset = $ job - > get_column ( 'jobset' ) ;
# If the job is in the same jobset as the primary build, then
# search for a build of the job among the members of the jobset
# evaluation ($ev) that produced the primary build.
if ( defined $ ev && $ project eq $ ev - > get_column ( 'project' )
&& $ jobset eq $ ev - > get_column ( 'jobset' ) )
{
$ thisBuild = $ ev - > builds - > find (
{ job = > $ job - > get_column ( 'job' ) , finished = > 1 } ,
{ join = > 'resultInfo' , rows = > 1
2011-04-20 08:57:55 +00:00
, order_by = > [ "build.id" ]
2010-03-05 17:20:04 +00:00
, where = > \ attrsToSQL ( $ job - > attrs , "build.id" )
, '+select' = > [ "resultInfo.buildstatus" ] , '+as' = > [ "buildstatus" ]
} ) ;
}
# As backwards compatibility, find a build of this job that had
# the primary build as input. If there are multiple, prefer
# successful ones, and then oldest. !!! order_by buildstatus is
# hacky
$ thisBuild = $ depBuilds - > find (
{ project = > $ project , jobset = > $ jobset
2011-04-18 08:10:10 +00:00
, job = > $ job - > get_column ( 'job' ) , finished = > 1
2010-03-05 16:37:24 +00:00
} ,
{ join = > 'resultInfo' , rows = > 1
, order_by = > [ "buildstatus" , "timestamp" ]
, where = > \ attrsToSQL ( $ job - > attrs , "build.id" )
, '+select' = > [ "resultInfo.buildstatus" ] , '+as' = > [ "buildstatus" ]
2010-03-05 17:20:04 +00:00
} )
unless defined $ thisBuild ;
2011-04-18 08:10:10 +00:00
2009-10-15 11:13:36 +00:00
return $ thisBuild ;
}
2009-02-06 15:02:49 +00:00
2010-09-03 09:17:52 +00:00
sub jobsetOverview {
2011-08-19 15:13:34 +00:00
my ( $ c , $ project ) = @ _ ;
return $ project - > jobsets - > search ( isProjectOwner ( $ c , $ project ) ? { } : { hidden = > 0 } ,
{ order_by = > "name"
, "+select" = >
2012-02-29 02:22:49 +01:00
[ "(select count(*) from Builds as a where a.finished = 0 and me.project = a.project and me.name = a.jobset and a.isCurrent = 1)"
, "(select count(*) from Builds as a join BuildResultInfo r using (id) where me.project = a.project and me.name = a.jobset and buildstatus <> 0 and a.isCurrent = 1)"
, "(select count(*) from Builds as a join BuildResultInfo r using (id) where me.project = a.project and me.name = a.jobset and buildstatus = 0 and a.isCurrent = 1)"
, "(select count(*) from Builds as a where me.project = a.project and me.name = a.jobset and a.isCurrent = 1)"
2011-08-19 15:13:34 +00:00
]
2012-02-29 02:22:49 +01:00
, "+as" = > [ "nrscheduled" , "nrfailed" , "nrsucceeded" , "nrtotal" ]
} ) ;
2010-09-03 09:17:52 +00:00
}
2009-10-20 12:35:01 +00:00
sub getViewResult {
2009-02-06 15:02:49 +00:00
my ( $ primaryBuild , $ jobs ) = @ _ ;
2011-04-18 08:10:10 +00:00
2009-02-06 15:02:49 +00:00
my @ jobs = ( ) ;
my $ status = 0 ; # = okay
2010-03-05 17:20:04 +00:00
# Get the jobset evaluation of which the primary build is a
# member. If there are multiple, pick the oldest one (i.e. the
# lowest id). (Note that for old builds in the database there
# might not be a evaluation record, so $ev may be undefined.)
my $ ev = $ primaryBuild - > jobsetevalmembers - > find ( { } , { rows = > 1 , order_by = > "eval" } ) ;
$ ev = $ ev - > eval if defined $ ev ;
2011-04-18 08:10:10 +00:00
2009-10-20 12:35:01 +00:00
# The timestamp of the view result is the highest timestamp of all
2009-02-06 15:02:49 +00:00
# constitutent builds.
my $ timestamp = 0 ;
2011-04-18 08:10:10 +00:00
2009-02-06 15:02:49 +00:00
foreach my $ job ( @ { $ jobs } ) {
2010-03-05 16:37:24 +00:00
my $ thisBuild = $ job - > isprimary
? $ primaryBuild
2010-03-05 17:20:04 +00:00
: findLastJobForBuilds ( $ ev , scalar $ primaryBuild - > dependentBuilds , $ job ) ;
2009-02-06 15:02:49 +00:00
2009-10-15 21:35:19 +00:00
if ( ! defined $ thisBuild ) {
$ status = 2 if $ status == 0 ; # = unfinished
} elsif ( $ thisBuild - > get_column ( 'buildstatus' ) != 0 ) {
$ status = 1 ; # = failed
2009-02-06 15:02:49 +00:00
}
$ timestamp = $ thisBuild - > timestamp
if defined $ thisBuild && $ thisBuild - > timestamp > $ timestamp ;
push @ jobs , { build = > $ thisBuild , job = > $ job } ;
}
return
{ id = > $ primaryBuild - > id
, releasename = > $ primaryBuild - > get_column ( 'releasename' )
, jobs = > [ @ jobs ]
, status = > $ status
, timestamp = > $ timestamp
} ;
}
2009-10-20 12:35:01 +00:00
sub getLatestSuccessfulViewResult {
2009-02-06 15:02:49 +00:00
my ( $ project , $ primaryJob , $ jobs ) = @ _ ;
my $ latest ;
2009-10-20 12:35:01 +00:00
foreach my $ build ( getPrimaryBuildsForView ( $ project , $ primaryJob ) ) {
return $ build if getViewResult ( $ build , $ jobs ) - > { status } == 0 ;
2009-02-06 15:02:49 +00:00
}
return undef ;
}
2011-06-10 09:53:15 +00:00
sub removeAsciiEscapes {
my ( $ logtext ) = @ _ ;
2011-06-10 10:53:59 +00:00
$ logtext =~ s/\e\[[0-9]*[A-Za-z]//g ;
2011-06-10 09:53:15 +00:00
return $ logtext ;
}
2011-04-18 08:10:10 +00:00
2008-11-28 14:36:04 +00:00
1 ;