Permalink
Browse files

Add installation plugin capabilities

Allows for INSTALL phase plugins. A 3rd party plugin might be
Zef::Service::InstallPM5 for installing perl5 modules.
  • Loading branch information...
ugexe committed Dec 15, 2018
1 parent 05ca712 commit 8086456e8550bf7b6ba01db897b3d28d43daf14b
Showing with 138 additions and 65 deletions.
  1. +2 −0 META6.json
  2. +1 −0 README.pod
  3. +5 −0 lib/Zef.pm6
  4. +4 −3 lib/Zef/CLI.pm6
  5. +67 −57 lib/Zef/Client.pm6
  6. +33 −0 lib/Zef/Install.pm6
  7. +11 −0 lib/Zef/Service/InstallPM6.pm6
  8. +7 −0 resources/config.json
  9. +7 −4 t/00-load.t
  10. +1 −1 xt/install.t
@@ -18,6 +18,7 @@
"Zef::Config" : "lib/Zef/Config.pm6",
"Zef::Extract" : "lib/Zef/Extract.pm6",
"Zef::Identity" : "lib/Zef/Identity.pm6",
"Zef::Install" : "lib/Zef/Install.pm6",
"Zef::Test" : "lib/Zef/Test.pm6",
"Zef::Fetch" : "lib/Zef/Fetch.pm6",
"Zef::Report" : "lib/Zef/Report.pm6",
@@ -31,6 +32,7 @@
"Zef::Distribution::DependencySpecification" : "lib/Zef/Distribution/DependencySpecification.pm6",
"Zef::Distribution::Local" : "lib/Zef/Distribution/Local.pm6",

"Zef::Service::InstallPM6" : "lib/Zef/Service/InstallPM6.pm6",
"Zef::Service::FetchPath" : "lib/Zef/Service/FetchPath.pm6",
"Zef::Service::TAP" : "lib/Zef/Service/TAP.pm6",
"Zef::Service::P6CReporter" : "lib/Zef/Service/P6CReporter.pm6",
@@ -133,6 +133,7 @@ B<Options>
--extract-timeout=3600
--build-timeout=3600
--test-timeout=3600
--install-timeout=3600

# or set the default to all unset --*-timeout flags to 0
--timeout=0
@@ -58,6 +58,11 @@ role Builder {
method build-matcher($path) { ... }
}

role Installer {
method install($dist, :$cur, :$force) { ... }
method install-matcher($dist) { ... }
}

role Reporter {
method report($dist) { ... }
}
@@ -99,6 +99,7 @@ package Zef::CLI {
Int :$extract-timeout = $timeout,
Int :$build-timeout = $timeout,
Int :$test-timeout = $timeout,
Int :$install-timeout = $timeout,
Bool :$dry,
Bool :$update,
Bool :$upgrade,
@@ -123,7 +124,7 @@ package Zef::CLI {
:$force-resolve, :$force-fetch, :$force-extract,
:$force-build, :$force-test, :$force-install,
:$fetch-timeout, :$extract-timeout, :$build-timeout,
:$test-timeout,
:$test-timeout, :$install-timeout,
);

# LOCAL PATHS
@@ -176,7 +177,7 @@ package Zef::CLI {
my @fetched = grep *.so, |@local, ($client.fetch(@remote).Slip if +@remote && $fetch);

my CompUnit::Repository @to = $install-to.map(*.&str2cur);
my @installed = $client.install( :@to, :$fetch, :$test, :$build, :$upgrade, :$update, :$dry, :$serial, @fetched );
my @installed = $client.make-install( :@to, :$fetch, :$test, :$build, :$upgrade, :$update, :$dry, :$serial, @fetched );
my @fail = @candidates.grep: {.as !~~ any(@installed>>.as)}

say "!!!> Install failures: {@fail.map(*.dist.identity).join(', ')}" if +@fail;
@@ -726,7 +727,7 @@ package Zef::CLI {
--install-to=[name] Short name or spec of CompUnit::Repository to install to
--config-path=[path] Load a specific Zef config file
--[phase]-timeout=[int] Set a timeout (in seconds) for the corresponding phase ( phase: fetch, extract, build, test )
--[phase]-timeout=[int] Set a timeout (in seconds) for the corresponding phase ( phase: fetch, extract, build, test, install )
VERBOSITY LEVEL (from least to most verbose)
--error, --warn, --info (default), --verbose, --debug
@@ -9,6 +9,7 @@ use Zef::Fetch;
use Zef::Extract;
use Zef::Build;
use Zef::Test;
use Zef::Install;
use Zef::Report;

class Zef::Client {
@@ -19,6 +20,7 @@ class Zef::Client {
has $.extractor;
has $.tester;
has $.builder;
has $.installer;
has $.reporter;
has $.config;

@@ -38,6 +40,7 @@ class Zef::Client {
has Int $.extract-timeout is rw = 3600;
has Int $.build-timeout is rw = 3600;
has Int $.test-timeout is rw = 3600;
has Int $.install-timeout is rw = 3600;

has Bool $.depends is rw = True;
has Bool $.build-depends is rw = True;
@@ -48,6 +51,7 @@ class Zef::Client {
:$!fetcher = Zef::Fetch.new(:backends(|$!config<Fetch>)),
:$!extractor = Zef::Extract.new(:backends(|$!config<Extract>)),
:$!builder = Zef::Build.new(:backends(|$!config<Build>)),
:$!installer = Zef::Install.new(:backends(|$!config<Install>)),
:$!tester = Zef::Test.new(:backends(|$!config<Test>)),
:$!reporter = Zef::Report.new(:backends(|$!config<Report>)),
:$!recommendation-manager = Zef::Repository.new(:backends($!config<Repository>.map({ $_<options><cache> = $!cache; $_<options><fetcher> = $!fetcher; $_ }).Slip)),
@@ -370,14 +374,74 @@ class Zef::Client {
@tested
}


# xxx: needs some love
method search(*@identities ($, *@), *%fields, Bool :$strict = False) {
$!recommendation-manager.search(@identities, :$strict, |%fields);
}

method uninstall(CompUnit::Repository :@from!, *@identities) {
my @specs = @identities.map: { Zef::Distribution::DependencySpecification.new($_) }
eager gather for self.list-installed(@from) -> $candi {
my $dist = $candi.dist;
if @specs.first({ $dist.spec-matcher($_) }) {
my $cur = CompUnit::RepositoryRegistry.repository-for-spec("inst#{$candi.from}", :next-repo($*REPO));
$cur.uninstall($dist.compat);
take $candi;
}
}
}

method install(:@curs, *@candidates ($, *@)) {
my @installed = eager gather for @candidates -> $candi {
self.logger.emit({
level => INFO,
stage => INSTALL,
phase => BEFORE,
message => "Installing: {$candi.dist.?identity // $candi.as}",
});

for @curs -> $cur {
KEEP self.logger.emit({
level => VERBOSE,
stage => INSTALL,
phase => AFTER,
message => "Install [OK] for {$candi.dist.?identity // $candi.as}",
});

CATCH {
when /'already installed'/ {
self.logger.emit({
level => INFO,
stage => INSTALL,
phase => AFTER,
message => "Install [SKIP] for {$candi.dist.?identity // $candi.as}: {$_}",
});
}
default {
self.logger.emit({
level => ERROR,
stage => INSTALL,
phase => AFTER,
message => "Install [FAIL] for {$candi.dist.?identity // $candi.as}: {$_}",
});
$_.rethrow;
}
}

method install(
# Previously we put this through the deprecation CURI.install shim no matter what,
# but that doesn't play nicely with relative paths. We want to keep the original meta
# paths for newer rakudos so we must avoid using :absolute for the source paths by
# using the newer CURI.install if available
take $candi if $!installer.install($candi.dist.compat, :$cur, :force($!force-install), :timeout($!install-timeout));
}
}

return @installed;
}

# Unlike test/build/install/etc methods, this organizes multiples phases for multiples candidates.
# Eventually this will move back to a role/task based method of managing such phase dependencies.
method make-install(
CompUnit::Repository :@to!, # target CompUnit::Repository
Bool :$fetch = True, # try fetching whats missing
Bool :$build = True, # run Build.pm (DEPRECATED..?)
@@ -502,49 +566,7 @@ class Zef::Client {
# Install Phase:
# Ideally `--dry` uses a special unique CompUnit::Repository that is meant to be deleted entirely
# and contain only the modules needed for this specific run/plan
my @installed-candidates = ?$dry ?? @tested-candidates !! @tested-candidates.grep: -> $candi {
self.logger.emit({
level => INFO,
stage => INSTALL,
phase => BEFORE,
message => "Installing: {$candi.dist.?identity // $candi.as}",
});

@curs.grep(-> $cur {
KEEP self.logger.emit({
level => VERBOSE,
stage => INSTALL,
phase => AFTER,
message => "Install [OK] for {$candi.dist.?identity // $candi.as}",
});

CATCH {
when /'already installed'/ {
self.logger.emit({
level => INFO,
stage => INSTALL,
phase => AFTER,
message => "Install [SKIP] for {$candi.dist.?identity // $candi.as}: {$_}",
});
}
default {
self.logger.emit({
level => ERROR,
stage => INSTALL,
phase => AFTER,
message => "Install [FAIL] for {$candi.dist.?identity // $candi.as}: {$_}",
});
$_.rethrow;
}
}

# Previously we put this through the deprecation CURI.install shim no matter what,
# but that doesn't play nicely with relative paths. We want to keep the original meta
# paths for newer rakudos so we must avoid using :absolute for the source paths by
# using the newer CURI.install if available
$cur.install($candi.dist.compat, :force($!force-install));
}).Slip
}
my @installed-candidates = ?$dry ?? @tested-candidates !! self.install(:@curs, @tested-candidates);

# Report phase:
# Handle exit codes for various option permutations like --force
@@ -569,18 +591,6 @@ class Zef::Client {
my @installed = ?$serial ?? @linked-candidates.map({ |$installer($_) }) !! $installer(@linked-candidates);
}

method uninstall(CompUnit::Repository :@from!, *@identities) {
my @specs = @identities.map: { Zef::Distribution::DependencySpecification.new($_) }
eager gather for self.list-installed(@from) -> $candi {
my $dist = $candi.dist;
if @specs.first({ $dist.spec-matcher($_) }) {
my $cur = CompUnit::RepositoryRegistry.repository-for-spec("inst#{$candi.from}", :next-repo($*REPO));
$cur.uninstall($dist.compat);
take $candi;
}
}
}

method list-rev-depends($identity, Bool :$indirect) {
my $spec = Zef::Distribution::DependencySpecification.new($identity);
my $dist = self.list-available.first(*.dist.contains-spec($spec)).?dist || return [];
@@ -0,0 +1,33 @@
use Zef;

class Zef::Install does Pluggable {
submethod TWEAK(|) {
@ = self.plugins; # preload plugins
}

method install-matcher($dist) { self.plugins.grep(*.install-matcher($dist)) }

method install($dist, :$cur, :$force, Supplier :$logger, Int :$timeout) {
my $installer = self.install-matcher($dist).first(*.so);
die "No installing backend available" unless ?$installer;

if ?$logger {
$logger.emit({ level => DEBUG, stage => INSTALL, phase => START, message => "Installing with plugin: {$installer.^name}" });
$installer.stdout.Supply.grep(*.defined).act: -> $out { $logger.emit({ level => VERBOSE, stage => INSTALL, phase => LIVE, message => $out }) }
$installer.stderr.Supply.grep(*.defined).act: -> $err { $logger.emit({ level => ERROR, stage => INSTALL, phase => LIVE, message => $err }) }
}

my $todo = start { $installer.install($dist, :$cur, :$force) };
my $time-up = ($timeout ?? Promise.in($timeout) !! Promise.new);
await Promise.anyof: $todo, $time-up;
$logger.emit({ level => DEBUG, stage => INSTALL, phase => LIVE, message => "Installing {$dist.path} timed out" })
if $time-up.so && $todo.not;

my $got = $todo.so ?? $todo.result !! False;

$installer.stdout.done;
$installer.stderr.done;

return $got;
}
}
@@ -0,0 +1,11 @@
use Zef;

class Zef::Service::InstallPM6 does Installer does Messenger {
method install-matcher($dist) { $dist ~~ Distribution }

method probe { True }

method install($dist, :$cur, :$force) {
$cur.install($dist, :$force);
}
}
@@ -8,6 +8,13 @@
"whitelist" : "*",
"blacklist" : []
},
"Install" : [
{
"short-name" : "installpm6",
"enabled" : 1,
"module" : "Zef::Service::InstallPM6"
}
],
"Report" : [
{
"short-name" : "p6ctesters",
@@ -2,7 +2,7 @@ use v6;
use Test;
plan 2;

subtest {
subtest 'Core' => {
use-ok("Zef");
# Just `use Zef::CLI` will make it output usage
# use-ok("Zef::CLI");
@@ -11,6 +11,7 @@ subtest {
use-ok("Zef::Extract");
use-ok("Zef::Identity");
use-ok("Zef::Test");
use-ok("Zef::Install");
use-ok("Zef::Fetch");
use-ok("Zef::Client");

@@ -26,11 +27,13 @@ subtest {
use-ok("Zef::Utils::FileSystem");
use-ok("Zef::Utils::SystemInfo");
use-ok("Zef::Utils::URI");
}, 'Core';
}

subtest {
subtest 'Plugins' => {
use-ok("Zef::Service::FetchPath");
use-ok("Zef::Service::TAP");
use-ok("Zef::Service::InstallPM6");
use-ok("Zef::Service::P6CReporter");
use-ok("Zef::Service::Shell::DistributionBuilder");
use-ok("Zef::Service::Shell::LegacyBuild");
use-ok("Zef::Service::Shell::Test");
@@ -44,4 +47,4 @@ subtest {
use-ok("Zef::Service::Shell::PowerShell");
use-ok("Zef::Service::Shell::PowerShell::download");
use-ok("Zef::Service::Shell::PowerShell::unzip");
}, 'Plugins';
}
@@ -41,7 +41,7 @@ sub test-install($path = $?FILE.IO.parent.parent) {
as => ~$path,
from => ~$?FILE,
);
my @got = |$client.install( :to(@cur), :!test, :!fetch, $candidate );
my @got = |$client.make-install( :to(@cur), :!test, :!fetch, $candidate );
@installed = unique(|@installed, |@got, :as(*.dist.identity));
}

0 comments on commit 8086456

Please sign in to comment.