File Coverage

blib/lib/Genome/Sys.pm
Criterion Covered Total %
statement 69 207 33.3
branch 18 118 15.2
condition 6 42 14.2
subroutine 11 18 61.1
pod 2 11 18.1
total 106 396 26.7


line stmt bran cond sub pod time code
1             package Genome::Sys;
2              
3 1     1   30448 use strict;
  1         2  
  1         47  
4 1     1   6 use warnings;
  1         3  
  1         33  
5 1     1   6 use Genome;
  1         2  
  1         9  
6 1     1   20 use Cwd;
  1         3  
  1         2791  
7              
8             class Genome::Sys {
9             # TODO: remove all cases of inheritance
10             #is => 'UR::Singleton',
11             };
12              
13             sub dbpath {
14 3     3 1 82026 my ($class, $name, $version) = @_;
15 3 50       26 unless ($version) {
16 0         0 die "Genome::Sys dbpath must be called with a database name and a version. Use 'latest' for the latest installed version.";
17             }
18 3   50     28 my $base_dirs = $ENV{"GENOME_DB"} ||= '/var/lib/genome/db';
19 3         29 return $class->_find_in_path($base_dirs, "$name/$version");
20             }
21              
22             sub swpath {
23 0     0 1 0 my ($class, $name, $version) = @_;
24 0 0       0 unless ($version) {
25 0         0 die "Genome::Sys swpath must be called with a database name and a version. Use 'latest' for the latest installed version.";
26             }
27 0   0     0 my $base = $ENV{"GENOME_SW"} ||= '/var/lib/genome/sw';
28 0         0 return join("/",$base,$name,$version);
29             }
30              
31             sub _find_in_path {
32 3     3   7 my ($class, $base_dirs, $subdir) = @_;
33 3         20 my @base_dirs = split(':',$base_dirs);
34 4 50       228 my @dirs =
35 6         15 map { -l $_ ? Cwd::abs_path($_) : ($_) }
36             map {
37 3         15 my $path = join("/",$_,$subdir);
38 6 100       167 (-e $path ? ($path) : ())
39             }
40             @base_dirs;
41 3         16 return $dirs[0];
42             }
43              
44             # temp file management
45              
46             sub _temp_directory_prefix {
47 1     1   2 my $self = shift;
48 1         13 my $base = join("_", map { lc($_) } split('::',$self->class));
  2         12  
49 1         3 return $base;
50             }
51              
52             our $base_temp_directory;
53             sub base_temp_directory {
54 1     1 0 2 my $self = shift;
55 1   33     7 my $class = ref($self) || $self;
56 1         3 my $template = shift;
57              
58 1         1 my $id;
59 1 50       5 if (ref($self)) {
60 0 0       0 return $self->{base_temp_directory} if $self->{base_temp_directory};
61 0         0 $id = $self->id;
62             }
63             else {
64             # work as a class method
65 1 50       3 return $base_temp_directory if $base_temp_directory;
66 1         1 $id = '';
67             }
68              
69 1 50       3 unless ($template) {
70 1         6 my $prefix = $self->_temp_directory_prefix();
71 1   33     4 $prefix ||= $class;
72 1         13 my $time = $self->__context__->now;
73              
74 1         355 $time =~ s/[\s\: ]/_/g;
75 1         5 $template = "/gm-$prefix-$time-$id-XXXX";
76 1         2 $template =~ s/ /-/g;
77             }
78              
79             # See if we're running under LSF and LSF gave us a directory that will be
80             # auto-cleaned up when the job terminates
81 1   50     8 my $tmp_location = $ENV{'TMPDIR'} || "/tmp";
82 1 50       6 if ($ENV{'LSB_JOBID'}) {
83 0         0 my $lsf_possible_tempdir = sprintf("%s/%s.tmpdir", $ENV{'TMPDIR'}, $ENV{'LSB_JOBID'});
84 0 0       0 $tmp_location = $lsf_possible_tempdir if (-d $lsf_possible_tempdir);
85             }
86             # tempdir() thows its own exception if there's a problem
87              
88             # For debugging purposes, allow cleanup to be disabled
89 1         2 my $cleanup = 1;
90 1 50       5 if($ENV{'GENOME_SYS_NO_CLEANUP'}) {
91 0         0 $cleanup = 0;
92             }
93 1         7 my $dir = File::Temp::tempdir($template, DIR=>$tmp_location, CLEANUP => $cleanup);
94              
95 1         591 $self->create_directory($dir);
96              
97 1 50       3 if (ref($self)) {
98 0         0 return $self->{base_temp_directory} = $dir;
99             }
100             else {
101             # work as a class method
102 1         3 return $base_temp_directory = $dir;
103             }
104              
105 0 0       0 unless ($dir) {
106 0         0 Carp::croak("Unable to determine base_temp_directory");
107             }
108              
109 0         0 return $dir;
110             }
111              
112             our $anonymous_temp_file_count = 0;
113             sub create_temp_file_path {
114 1     1 0 2 my $self = shift;
115 1         3 my $name = shift;
116 1 50       3 unless ($name) {
117 0         0 $name = 'anonymous' . $anonymous_temp_file_count++;
118             }
119 1         4 my $dir = $self->base_temp_directory;
120 1         3 my $path = $dir .'/'. $name;
121 1 50       20 if (-e $path) {
122 0         0 Carp::croak "temp path '$path' already exists!";
123             }
124              
125 1 50 33     7 if (!$path or $path eq '/') {
126 0         0 Carp::croak("create_temp_file_path() failed");
127             }
128              
129 1         3 return $path;
130             }
131              
132             sub create_temp_file {
133 0     0 0 0 my $self = shift;
134 0         0 my $path = $self->create_temp_file_path(@_);
135 0         0 my $fh = IO::File->new($path, '>');
136 0 0       0 unless ($fh) {
137 0         0 Carp::croak "Failed to create temp file $path: $!";
138             }
139 0 0       0 return ($fh,$path) if wantarray;
140 0         0 return $fh;
141             }
142              
143             sub create_temp_directory {
144 1     1 0 41 my $self = shift;
145 1         6 my $path = $self->create_temp_file_path(@_);
146 1         4 $self->create_directory($path);
147 1         3 return $path;
148             }
149              
150             sub create_directory {
151 2     2 0 4 my ($self, $directory) = @_;
152              
153 2 50       6 unless ( defined $directory ) {
154 0         0 Carp::croak("Can't create_directory: No path given");
155             }
156              
157             # FIXME do we want to throw an exception here? What if the user expected
158             # the directory to be created, not that it already existed
159 2 100       27 return $directory if -d $directory;
160              
161 1         3 my $errors;
162             # make_path may throw its own exceptions...
163 1         10 File::Path::make_path($directory, { mode => 02775, error => \$errors });
164            
165 1 50 33     220 if ($errors and @$errors) {
166 0         0 my $message = "create_directory for path $directory failed:\n";
167 0         0 foreach my $err ( @$errors ) {
168 0         0 my($path, $err_str) = %$err;
169 0         0 $message .= "Pathname " . $path ."\n".'General error' . ": $err_str\n";
170             }
171 0         0 Carp::croak($message);
172             }
173            
174 1 50       18 unless (-d $directory) {
175 0         0 Carp::croak("No error from 'File::Path::make_path', but failed to create directory ($directory)");
176             }
177              
178 1         2 return $directory;
179             }
180              
181             sub create_symlink {
182 0     0 0   my ($self, $target, $link) = @_;
183              
184 0 0         unless ( defined $target ) {
185 0           Carp::croak("Can't create_symlink: no target given");
186             }
187              
188 0 0         unless ( defined $link ) {
189 0           Carp::croak("Can't create_symlink: no 'link' given");
190             }
191              
192 0 0         unless ( -e $target ) {
193 0           Carp::croak("Cannot create link ($link) to target ($target): target does not exist");
194             }
195            
196 0 0         if ( -e $link ) { # the link exists and points to spmething
197 0           Carp::croak("Link ($link) for target ($target) already exists.");
198             }
199            
200 0 0         if ( -l $link ) { # the link exists, but does not point to something
201 0           Carp::croak("Link ($link) for target ($target) is already a link.");
202             }
203              
204 0 0         unless ( symlink($target, $link) ) {
205 0           Carp::croak("Can't create link ($link) to $target\: $!");
206             }
207            
208 0           return 1;
209             }
210              
211             sub _open_file {
212 0     0     my ($self, $file, $rw) = @_;
213 0 0         if ($file eq '-') {
214 0 0         if ($rw eq 'r') {
    0          
215 0           return 'STDIN';
216             }
217             elsif ($rw eq 'w') {
218 0           return 'STDOUT';
219             }
220             else {
221 0           die "cannot open '-' with access '$rw': r = STDIN, w = STDOUT!!!";
222             }
223             }
224 0           my $fh = IO::File->new($file, $rw);
225 0 0         return $fh if $fh;
226 0           Carp::croak("Can't open file ($file) with access '$rw': $!");
227             }
228              
229             sub validate_file_for_reading {
230 0     0 0   my ($self, $file) = @_;
231              
232 0 0         unless ( defined $file ) {
233 0           Carp::croak("Can't validate_file_for_reading: No file given");
234             }
235              
236 0 0         if ($file eq '-') {
237 0           return 1;
238             }
239              
240 0 0         unless (-e $file ) {
241 0           Carp::croak("File ($file) does not exist");
242             }
243              
244 0 0         unless (-f $file) {
245 0           Carp::croak("File ($file) exists but is not a plain file");
246             }
247              
248 0 0         unless ( -r $file ) {
249 0           Carp::croak("Do not have READ access to file ($file)");
250             }
251              
252 0           return 1;
253             }
254              
255             sub open_file_for_reading {
256 0     0 0   my ($self, $file) = @_;
257              
258 0 0         $self->validate_file_for_reading($file)
259             or return;
260              
261             # _open_file throws its own exception if it doesn't work
262 0           return $self->_open_file($file, 'r');
263             }
264              
265             sub shellcmd {
266             # execute a shell command in a standard way instead of using system()\
267             # verifies inputs and ouputs, and does detailed logging...
268              
269             # TODO: add IPC::Run's w/ timeout but w/o the io redirection...
270              
271 0     0 0   my ($self,%params) = @_;
272 0           my $cmd = delete $params{cmd};
273 0           my $output_files = delete $params{output_files} ;
274 0           my $input_files = delete $params{input_files};
275 0           my $output_directories = delete $params{output_directories} ;
276 0           my $input_directories = delete $params{input_directories};
277 0           my $allow_failed_exit_code = delete $params{allow_failed_exit_code};
278 0           my $allow_zero_size_output_files = delete $params{allow_zero_size_output_files};
279 0           my $skip_if_output_is_present = delete $params{skip_if_output_is_present};
280 0 0         $skip_if_output_is_present = 1 if not defined $skip_if_output_is_present;
281 0 0         if (%params) {
282 0           my @crap = %params;
283 0           Carp::confess("Unknown params passed to shellcmd: @crap");
284             }
285              
286 0 0 0       if ($output_files and @$output_files) {
287 0           my @found_outputs = grep { -e $_ } grep { not -p $_ } @$output_files;
  0            
  0            
288 0 0 0       if ($skip_if_output_is_present
289             and @$output_files == @found_outputs
290             ) {
291 0           $self->status_message(
292             "SKIP RUN (output is present): $cmd\n\t"
293             . join("\n\t",@found_outputs)
294             );
295 0           return 1;
296             }
297             }
298              
299 0 0 0       if ($input_files and @$input_files) {
300 0           my @missing_inputs = grep { not -s $_ } grep { not -p $_ } @$input_files;
  0            
  0            
301 0 0         if (@missing_inputs) {
302 0 0         Carp::croak("CANNOT RUN (missing input files): $cmd\n\t"
303 0           . join("\n\t", map { -e $_ ? "(empty) $_" : $_ } @missing_inputs));
304             }
305             }
306              
307 0 0 0       if ($input_directories and @$input_directories) {
308 0           my @missing_inputs = grep { not -d $_ } @$input_directories;
  0            
309 0 0         if (@missing_inputs) {
310 0           Carp::croak("CANNOT RUN (missing input directories): $cmd\n\t"
311             . join("\n\t", @missing_inputs));
312             }
313             }
314              
315 0           $self->status_message("RUN: $cmd");
316 0           my $exit_code = system($cmd);
317 0 0         if ( $exit_code == -1 ) {
    0          
    0          
318 0           Carp::croak("ERROR RUNNING COMMAND. Failed to execute: $cmd");
319             } elsif ( $exit_code & 127 ) {
320 0           my $signal = $exit_code & 127;
321 0 0         my $withcore = ( $exit_code & 128 ) ? 'with' : 'without';
322              
323 0           Carp::croak("COMMAND KILLED. Signal $signal, $withcore coredump: $cmd");
324             } elsif ($exit_code >> 8 != 0) {
325 0           $exit_code = $exit_code >> 8;
326 0           $DB::single = $DB::stopper;
327 0 0         if ($allow_failed_exit_code) {
328 0           Carp::carp("TOLERATING Exit code $exit_code, msg $! from: $cmd");
329             } else {
330 0           Carp::croak("ERROR RUNNING COMMAND. Exit code $exit_code, msg $! from: $cmd");
331             }
332             }
333              
334 0           my @missing_output_files;
335 0 0 0       if ($output_files and @$output_files) {
336 0           @missing_output_files = grep { not -s $_ } grep { not -p $_ } @$output_files;
  0            
  0            
337             }
338 0 0         if (@missing_output_files) {
339 0 0 0       if ($allow_zero_size_output_files
340             and @$output_files == @missing_output_files
341             ) {
342 0           for my $output_file (@$output_files) {
343 0           Carp::carp("ALLOWING zero size output file '$output_file' for command: $cmd");
344 0           my $fh = $self->open_file_for_writing($output_file);
345 0 0         unless ($fh) {
346 0           Carp::croak("failed to open $output_file for writing to replace missing output file: $!");
347             }
348 0           $fh->close;
349             }
350 0           @missing_output_files = ();
351             }
352             }
353            
354 0           my @missing_output_directories;
355 0 0 0       if ($output_directories and @$output_directories) {
356 0           @missing_output_directories = grep { not -s $_ } grep { not -p $_ } @$output_directories;
  0            
  0            
357             }
358              
359              
360 0 0 0       if (@missing_output_files or @missing_output_directories) {
361 0 0         for (@$output_files) { unlink $_ or Carp::croak("Can't unlink $_: $!"); }
  0            
362 0           Carp::croak("MISSING OUTPUTS! "
363             . join(', ', @missing_output_files)
364             . " "
365             . join(', ', @missing_output_directories));
366             }
367              
368 0           return 1;
369              
370             }
371             1;
372              
373             __END__