module Zef:ver($?DISTRIBUTION.meta // $?DISTRIBUTION.meta// '*'):api($?DISTRIBUTION.meta // '*'):auth($?DISTRIBUTION.meta // '') { our sub zrun(*@_, *%_) is export { run (|@_).grep(*.?chars), |%_ } our sub zrun-async(*@_, *%_) is export { Proc::Async.new( (|@_).grep(*.?chars), |%_ ) } # rakudo must be able to parse json, so it doesn't # make sense to require a dependency to parse it our sub from-json($text) { ::("Rakudo::Internals::JSON").from-json($text) } our sub to-json(|c) { ::("Rakudo::Internals::JSON").to-json(|c) } enum LEVEL is export ; # We have an entry called STAGING because STAGE was already taken by the enum. We # should improve this situation in the future. enum STAGE is export ; enum PHASE is export ; # Get a resource located at a uri and save it to the local disk role Fetcher is export { method fetch($uri, $save-as) { ... } method fetch-matcher($uri) { ... } } # As a post-hook to the default fetchers we will need to extract zip # files. `git` does this itself, so a git based Fetcher wouldn't need this # although we could possibly add `--no-checkout` to `git`s fetch and treat # Extract as the action of `--checkout $branch` (allowing us to "extract" # a specific version from a commit/tag) role Extractor is export { method extract($archive-file, $target-dir) { ... } method ls-files($archive-file) { ... } method extract-matcher($path) { ... } } # test a single file OR all the files in a directory (recursive optional) role Tester is export { method test($path, :@includes, :$stdout, :$stderr) { ... } method test-matcher($path) { ... } } role Builder is export { method build($dist, :@includes, :$stdout, :$stderr) { ... } method build-matcher($path) { ... } } role Installer is export { method install($dist, :$cur, :$force) { ... } method install-matcher($dist) { ... } } role Reporter is export { method report($dist) { ... } } role Candidate is export { has $.dist; has Str $.as; # Requested as (maybe a url, maybe an identity, maybe a path) has Str() $.from; # Recommended from (::Ecosystems, ::LocalCache) has Str() $.uri is rw; # url, file path, etc has $.build-results is rw; has $.test-results is rw; } role PackageRepository is export { # An identifier like .^name but intended to differentiate between instances of the same class # For instance: ::Ecosystems and ::Ecosystems which would otherwise share the # same .^name of ::Ecosystems method id { $?CLASS.^name.split('+', 2)[0] } # max-results is meant so we can :max-results(1) when we are interested in using it like # `.candidates` (i.e. 1 match per identity) so we can stop iterating search plugins earlier method search(:$max-results, *@identities, *%fields --> Iterable) { ... } # Optional method currently being called after a search/fetch # to assist ::Repository::LocalCache in updating its MANIFEST path cache. # The concept needs more thought, but for instance a GitHub related repositories # could commit changes or push to a remote branch, and (as is now) the cs # ::LocalCache to update MANIFEST so we don't *have* to do a recursive folder search # # method store(*@dists) { } # Optional method for listing available packages. For p6c style repositories # where we have an index file this is easy. For metacpan style where we # make a remote query not so much (maybe it could list the most recent X # modules... or maybe it just doesn't implement it at all) # method available { } # Optional method that tells a repository to 'sync' its database. # Useful for repositories that store the database / file locally. # Not useful for query based resolution e.g. metacpan # method update { } } # Used by the phase's loader (i.e Zef::Fetch) to test that the plugin can # be used. for instance, ::Shell wrappers probe via `cmd --help`. Note # that the result of .probe is cached by each phase loader role Probeable is export { method probe (--> Bool) { ... } } role Pluggable is export { #| Stringified module names to load as a plugin has @.backends; #| All the loaded @.backend objects has $!plugins; sub DEBUG($plugin, $message) { say "[Plugin - {$plugin // $plugin // qq||}] $message"\ if ?%*ENV; } method plugins(*@short-names) { my $all-plugins := self!list-plugins; return $all-plugins unless +@short-names; my @plugins; for $all-plugins -> @plugin-group { if @plugin-group.grep(-> $plugin { $plugin.short-name ~~ any(@short-names) }) -> @filtered-group { push @plugins, @filtered-group; } } return @plugins; } has $!list-plugins-lock = Lock.new; method !list-plugins(@backends = @!backends) { $!list-plugins-lock.protect: { return $!plugins if $!plugins.so; # @backends used to only be an array of hash. However now the ::Repository # section of the config an an array of an array of hash and thus the logic # below was adapted (it wasn't designed this way from the start). my @plugins; for @backends -> $backend { if $backend ~~ Hash { if self!try-load($backend) -> $class { push @plugins, $class; } } else { my @group; for @$backend -> $plugin { if self!try-load($plugin) -> $class { push @group, $class; } } push( @plugins, @group ) if +@group; } } return $!plugins := @plugins } } method !try-load(Hash $plugin) { use Zef::Identity:auth(Zef.^auth):api(Zef.^api):ver(Zef.^ver); my $identity = Zef::Identity.new($plugin); my $short-name = $identity.name; my $plugin-is-core = so $?DISTRIBUTION.meta{$short-name}:exists; my $auth-matcher = $identity.auth || do { $plugin-is-core ?? Zef.^auth !! True }; my $api-matcher = $identity.api || do { $plugin-is-core ?? Zef.^api !! True }; my $version-matcher = $identity.version || do { $plugin-is-core ?? Zef.^ver !! True }; my $dep-spec = CompUnit::DependencySpecification.new(:$short-name, :$auth-matcher, :$api-matcher, :$version-matcher); DEBUG($plugin, "Checking: {$short-name}"); # default to enabled unless `"enabled" : 0` if $plugin:exists && (!$plugin || $plugin eq "0") { DEBUG($plugin, "\t(SKIP) Not enabled"); return; } unless try $*REPO.need($dep-spec) { DEBUG($plugin, "\t(SKIP) Plugin could not be loaded"); return; } DEBUG($plugin, "\t(OK) Plugin loaded successful"); if ::($short-name).^find_method('probe') { unless ::($short-name).probe { DEBUG($plugin, "\t(SKIP) Probing failed"); return; } DEBUG($plugin, "\t(OK) Probing successful") } # add attribute `short-name` here to make filtering by name slightly easier # until a more elegant solution can be integrated into plugins themselves my $class = ::($short-name).new(|($plugin // []))\ but role :: { has $.short-name = $plugin // '' }; # make the class name more human readable for cli output, # i.e. Zef::Service::Shell::curl instead of Zef::Service::Shell::curl+{} $class.^set_name($short-name); unless ?$class { DEBUG($plugin, "(SKIP) Plugin unusable: initialization failure"); return; } DEBUG($plugin, "(OK) Plugin is now usable: {$short-name}"); return $class; } } } class X::Zef::UnsatisfiableDependency is Exception { method message() { 'Failed to resolve some missing dependencies' } }