my %default_exports = (
load_or_skip => \&load_or_skip,
load_or_skip_all => \&load_or_skip_all,
- run_perl => \&run_perl,
skip_all => \&skip_all,
);
my %features = (
- threads => \&init_threads,
- usleep => \&init_usleep,
+ threads => \&init_threads,
+ usleep => \&init_usleep,
+ run_perl => \&init_run_perl,
+ capture => \&init_capture,
);
sub import {
return $loaded;
}
-sub run_perl {
- my $code = shift;
+sub fresh_perl_env (&) {
+ my $handler = shift;
my ($SystemRoot, $PATH) = @ENV{qw<SystemRoot PATH>};
my $ld_name = $Config::Config{ldlibpthname};
my $perl = $^X;
unless (-e $perl and -x $perl) {
$perl = $Config::Config{perlpath};
+ unless (-e $perl and -x $perl) {
+ return undef;
+ }
}
- system { $perl } $perl, '-T', map("-I$_", @INC), '-e', $code;
+ return $handler->($perl, '-T', map("-I$_", @INC));
+}
+
+sub init_run_perl {
+ my $prefix = shift;
+
+ if (defined $prefix) {
+ if (length $prefix and $prefix !~ /_$/) {
+ $prefix .= '_';
+ }
+ } else {
+ $prefix = '';
+ }
+
+ my $p = $prefix;
+
+ return (
+ run_perl => \&run_perl,
+ "${p}RUN_PERL_FAILED" => sub () { 'Could not execute perl subprocess' },
+ );
+}
+
+sub run_perl {
+ my $code = shift;
+
+ if ($code =~ /"/) {
+ die 'Double quotes in evaluated code are not portable';
+ }
+
+ fresh_perl_env {
+ my ($perl, @perl_args) = @_;
+ system { $perl } $perl, @perl_args, '-e', $code;
+ };
+}
+
+sub init_capture {
+ skip_all 'Cannot capture output on VMS' if $^O eq 'VMS';
+
+ load_or_skip_all 'IO::Handle', '0', [ ];
+ load_or_skip_all 'IO::Select', '0', [ ];
+ load_or_skip_all 'IPC::Open3', '0', [ ];
+ if ($^O eq 'MSWin32') {
+ load_or_skip_all 'Socket', '0', [ ];
+ }
+
+ return (
+ capture => \&capture,
+ capture_perl => \&capture_perl,
+ );
+}
+
+# Inspired from IPC::Cmd
+
+sub capture {
+ my @cmd = @_;
+
+ my $want = wantarray;
+
+ my $fail = sub {
+ my $err = $!;
+ my $ext_err = $^O eq 'MSWin32' ? $^E : undef;
+
+ my $syscall = shift;
+ my $args = join ', ', @_;
+
+ my $msg = "$syscall($args) failed: ";
+
+ if (defined $err) {
+ no warnings 'numeric';
+ my ($err_code, $err_str) = (int $err, "$err");
+ $msg .= "$err_str ($err_code)";
+ }
+
+ if (defined $ext_err) {
+ no warnings 'numeric';
+ my ($ext_err_code, $ext_err_str) = (int $ext_err, "$ext_err");
+ $msg .= ", $ext_err_str ($ext_err_code)";
+ }
+
+ die "$msg\n";
+ };
+
+ my ($status, $content_out, $content_err);
+
+ local $@;
+ my $ok = eval {
+ my ($pid, $out, $err);
+
+ if ($^O eq 'MSWin32') {
+ my $pipe = sub {
+ socketpair $_[0], $_[1],
+ &Socket::AF_UNIX, &Socket::SOCK_STREAM, &Socket::PF_UNSPEC
+ or $fail->(qw<socketpair reader writer>);
+ shutdown $_[0], 1 or $fail->(qw<shutdown reader>);
+ shutdown $_[1], 0 or $fail->(qw<shutdown writer>);
+ return 1;
+ };
+ local (*IN_R, *IN_W);
+ local (*OUT_R, *OUT_W);
+ local (*ERR_R, *ERR_W);
+ $pipe->(*IN_R, *IN_W);
+ $pipe->(*OUT_R, *OUT_W);
+ $pipe->(*ERR_R, *ERR_W);
+
+ $pid = IPC::Open3::open3('>&IN_R', '<&OUT_W', '<&ERR_W', @cmd);
+
+ close *IN_W or $fail->(qw<close input>);
+ $out = *OUT_R;
+ $err = *ERR_R;
+ } else {
+ my $in = IO::Handle->new;
+ $out = IO::Handle->new;
+ $out->autoflush(1);
+ $err = IO::Handle->new;
+ $err->autoflush(1);
+
+ $pid = IPC::Open3::open3($in, $out, $err, @cmd);
+
+ close $in;
+ }
+
+ # Forward signals to the child (except SIGKILL)
+ my %sig_handlers;
+ foreach my $s (keys %SIG) {
+ $sig_handlers{$s} = sub {
+ kill "$s" => $pid;
+ $SIG{$s} = $sig_handlers{$s};
+ };
+ }
+ local $SIG{$_} = $sig_handlers{$_} for keys %SIG;
+
+ unless ($want) {
+ close $out or $fail->(qw<close output>);
+ close $err or $fail->(qw<close error>);
+ waitpid $pid, 0;
+ $status = $?;
+ return 1;
+ }
+
+ my $sel = IO::Select->new();
+ $sel->add($out, $err);
+
+ my $fd_out = fileno $out;
+ my $fd_err = fileno $err;
+
+ my %contents;
+ $contents{$fd_out} = '';
+ $contents{$fd_err} = '';
+
+ while (my @ready = $sel->can_read) {
+ for my $fh (@ready) {
+ my $buf;
+ my $bytes_read = sysread $fh, $buf, 4096;
+ if (not defined $bytes_read) {
+ $fail->('sysread', 'fd(' . fileno($fh) . ')');
+ } elsif ($bytes_read) {
+ $contents{fileno($fh)} .= $buf;
+ } else {
+ $sel->remove($fh);
+ close $fh or $fail->('close', 'fd(' . fileno($fh) . ')');
+ last unless $sel->count;
+ }
+ }
+ }
+
+ waitpid $pid, 0;
+ $status = $?;
+
+ if ($^O eq 'MSWin32') {
+ # Manual CRLF translation that couldn't be done with sysread.
+ s/\x0D\x0A/\n/g for values %contents;
+ }
+
+ $content_out = $contents{$fd_out};
+ $content_err = $contents{$fd_err};
+
+ 1;
+ };
+
+ if ($ok) {
+ return ($status, $content_out, $content_err);
+ } else {
+ my $err = $@;
+ chomp $err;
+ return (undef, $err);
+ }
+}
+
+sub capture_perl {
+ my $code = shift;
+
+ if ($code =~ /"/) {
+ die 'Double quotes in evaluated code are not portable';
+ }
+
+ fresh_perl_env {
+ my @perl = @_;
+ capture @perl, '-e', $code;
+ };
}
sub init_threads {