clamp the subject line of error emails to be no more than 115 chars
[sgn.git] / lib / SGN / Build.pm
blob4a95a1b37529ba8ae442fab274f223fd26fe4070
1 package SGN::Build;
2 use strict;
3 use warnings;
5 use version;
7 my $HAVE_PARSE_DEB_CONTROL;
9 BEGIN {
10 eval { require Parse::Deb::Control };
11 if( $@ ) {
12 warn "WARNING: Failed to load Parse::Deb::Control, and it is needed to check R dependencies:\n$@\n"
14 $HAVE_PARSE_DEB_CONTROL = !$@;
17 use Module::Build;
18 use base 'Module::Build';
20 my $HAVE_CAPTURE;
21 BEGIN {
22 eval { require Capture::Tiny };
23 $HAVE_CAPTURE = !$@;
26 # we should probably convert this to autodie at some point
28 # build action just runs make on programs
29 sub ACTION_build {
30 my $self = shift;
31 $self->SUPER::ACTION_build(@_);
32 system "make -C programs";
33 if($?) {
34 _handle_errors($?);
35 die "make -C programs failed\!n";
38 $self->check_R
39 or die "R dependency check failed, aborting.\n";
42 # override install to just copy the whole dir into the install_base
43 sub ACTION_install {
44 my $self = shift;
46 # installation is just copying the entire dist into
47 # install_base/sgn
48 require File::Spec;
49 my $tgt_dir = File::Spec->catdir($self->install_base,'sgn');
50 system 'cp', '-rl', '.', $tgt_dir;
51 if($?) {
52 _handle_errors($?);
53 die "SGN site copy ( cp -rl . $tgt_dir ) failed!\n";
57 sub ACTION_clean {
58 shift->SUPER::ACTION_clean(@_);
59 system "make -C programs clean";
60 if($?) {
61 _handle_errors($?);
62 die "make -C programs clean failed!\n";
66 sub ACTION_installdeps {
67 my $self = shift;
69 $self->_R_installdeps;
71 $self->SUPER::ACTION_installdeps( @_ );
74 sub create_build_script {
75 my $self = shift;
77 $self->check_R
78 or warn $self->{R}{check_output};
80 return $self->SUPER::create_build_script(@_);
83 sub check_R {
84 my ( $self, @args ) = @_;
85 if( $HAVE_CAPTURE ) {
86 my $ret;
87 my $out = Capture::Tiny::capture_merged {
88 $ret = $self->_run_R_check( @args );
91 $self->{R}{check_output} = $out;
92 if( !$ret and my ($missing) = $out =~ /required but not available:\s+(\S(?:[^\n]+\n)+)\n/si ) {
93 $self->{R}{missing_packages} = [ split /\s+/, $missing ];
96 return $ret;
97 } else {
98 return $self->_run_R_check( @args );
102 sub _R_installdeps {
103 my ( $self ) = @_;
105 if( $self->check_R ) {
106 print "All R prerequisites satisfied\n";
107 return;
110 my @missing_packages = @{ $self->{R}{missing_packages} || [] };
111 unless( @missing_packages ) {
112 print "No missing R packages detected, cannot installdeps for R.\n";
113 return;
116 my $package_vec = 'c('.join( ',', map qq|"$_"|, @missing_packages ).')';
117 my $cran_mirror = $ENV{CRAN_MIRROR} || "http://lib.stat.cmu.edu/R/CRAN";
119 my $tf = File::Temp->new;
120 $tf->print( <<EOR );
121 userdir <- unlist(strsplit(Sys.getenv("R_LIBS_USER"), .Platform\$path.sep))[1L]
122 if (!file.exists(userdir) && !dir.create(userdir, recursive = TRUE, showWarnings = TRUE))
123 stop("unable to create ", sQuote(userdir))
124 .libPaths(c(userdir, .libPaths()))
125 install.packages( $package_vec, contriburl = contrib.url("$cran_mirror") )
127 $tf->close;
129 # use system so the user will be able to use the R graphical
130 # mirror chooser, and other things
131 system 'R', '--slave', -f => "$tf", '--no-save', '--no-restore';
132 if( $? ) {
133 _handle_errors($?);
134 warn "Failed to automatically install R dependencies\n";
135 } elsif( $self->check_R ) {
136 print "Successfully installed R dependencies.\n";
140 sub _handle_errors {
141 my ($exit_code) = @_;
142 if ($exit_code == -1) {
143 print "Error: failed to execute: $!\n";
144 } elsif ($exit_code & 127) {
145 warn sprintf("Error: child died with signal %d, %s coredump\n",
146 ($exit_code & 127), ($exit_code & 128) ? 'with' : 'without');
147 } else {
148 warn sprintf("Error: child exited with value %d\n",$exit_code >> 8);
152 sub _run_R_check {
153 my $self = shift;
155 print "\nChecking R prerequisites...\n";
157 # check the R version ourself, since R CMD check apparently does
158 # not do it.
159 $self->_check_R_version
160 or return 0;
162 my $no_manual = $self->_R_version_current ge version->new('2.10.0') ? '--no-manual' : '';
164 my $ret = system "R CMD check $no_manual --no-codoc --no-vignettes -o _build R_files";
165 if ( $ret || $? ) {
166 _handle_errors($?);
167 warn "\nR PREREQUISITE CHECK FAILED.\n\n";
168 return 0;
169 } else {
170 print "R prerequisites OK.\n\n";
171 return 1;
175 sub _check_R_version {
176 my $self = shift;
179 unless ($HAVE_PARSE_DEB_CONTROL) {
180 warn "Parse::Deb::Control not present, skipping R configuration";
181 return 0;
183 my ( $cmp, $v ) = $self->_R_version_required;
185 if( eval '$self->_R_version_current'." $cmp version->parse('$v')" ) {
186 return 1;
187 } else {
188 warn "R VERSION CHECK FAILED, we have ".$self->_R_version_current.", but we require $cmp $v.\n";
189 warn "To install R : sudo aptitude install r-base-core\n\n";
190 return 0;
194 # parse and return the R DESCRIPTION file
195 sub _R_desc {
196 return Parse::Deb::Control->new([qw[ R_files DESCRIPTION ]]);
199 # parse and return the version of R we require as string list like
200 # ('>=','2.10.0')
201 sub _R_version_required {
202 my $self = shift;
203 my @k = $self->_R_desc->get_keys('Depends')
204 or return ( '>=', 0 );
205 my ($version) = ${$k[0]->{value}} =~ / \b R \s* \( ([^\)]+) /x
206 or return ( '>=', 0 );
208 my @v = split /\s+/, $version;
209 unshift @v, '==' unless @v > 1;
211 return @v;
214 # parse and return the current R version as a version object
215 sub _R_version_current {
216 my $r = `R --version`;
217 return 0 unless $r;
219 $r =~ /R version ([\d\.]+)/;
221 return version->new($1);