pax_global_header00006660000000000000000000000064142201376200014507gustar00rootroot0000000000000052 comment=7c46ca2d5a1f78c86a5c543c4080774b30770ddb zef-0.13.8/000077500000000000000000000000001422013762000123645ustar00rootroot00000000000000zef-0.13.8/.appveyor.yml000066400000000000000000000060751422013762000150420ustar00rootroot00000000000000os: Visual Studio 2017 platform: x64 install: - '"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"' - appveyor-retry choco install strawberryperl --allow-empty-checksums - SET PATH=C:\strawberry\c\bin;C:\strawberry\perl\site\bin;C:\strawberry\perl\bin;%PATH% - appveyor-retry git clone https://github.com/rakudo/rakudo.git %APPVEYOR_BUILD_FOLDER%\..\rakudo - cd %APPVEYOR_BUILD_FOLDER%\..\rakudo - perl Configure.pl --gen-moar --gen-nqp --backends=moar - nmake install - SET PATH=%APPVEYOR_BUILD_FOLDER%\..\rakudo\install\bin;%PATH% - SET PATH=%APPVEYOR_BUILD_FOLDER%\..\rakudo\install\share\perl6\site\bin;%PATH% - cd %APPVEYOR_BUILD_FOLDER% build: off test_script: - raku -I. bin/zef --version # run xtests - raku -I. xt/repository.t - raku -I. xt/install.t # test explicitly via `prove t/*` and `raku t/foo.t && raku t/bar.t` # both should work, since all our CI envs have prove - raku -I. bin/zef --debug --/tap-harness --/prove --raku-test test . - raku -I. bin/zef --debug --/tap-harness --prove --/raku-test test . # run relative local path test + install - raku -I. bin/zef --debug install . # test uninstall - raku -I. bin/zef uninstall zef # run absolute local path test + install - raku -I. bin/zef install %APPVEYOR_BUILD_FOLDER% # change path to make sure next `zef` commands aren't using any files in cwd or lib/ - cd %APPVEYOR_BUILD_FOLDER%\.. - zef update --debug # test informational commands - zef --version - zef --help - zef locate Zef::CLI - zef locate lib/Zef/CLI.rakumod - zef browse zef bugtracker --/open - zef info zef # test bells and whistles - zef --debug test ./zef - zef --debug search Base64 - zef --debug rdepends Base64 - zef --debug depends Cro::SSL - zef --debug fetch Base64 # test installing from what `fetch` put in ::LocalCache - zef --debug --/fez --/cpan --/p6c --/rea install Base64 - zef --debug --max=10 list - zef --debug --installed list - zef --debug --force-install install Base64 # test tar + upgrade - zef --debug install https://github.com/ugexe/Perl6-PathTools/archive/0434191c56e0f3254ab1d756d90f9191577de5a0.tar.gz - zef --debug upgrade PathTools # test zip - zef --debug install https://github.com/ugexe/Perl6-Text--Table--Simple/archive/v0.0.3.zip # test remote git repo + tag - zef --debug install https://github.com/ugexe/Perl6-Text--Table--Simple.git@v0.0.4 # Test self contained installation - zef install Distribution::Common --/test - zef install Distribution::Common::Remote -to=inst#foo --contained --/test - zef uninstall Distribution::Common - raku -I inst#foo -M Distribution::Common::Remote::Github -e "" - zef --/confirm nuke TempDir StoreDir RootDir - zef update cached --debug # test single repository update; should be 0 after previous nuke - raku -I %APPVEYOR_BUILD_FOLDER% %APPVEYOR_BUILD_FOLDER%/bin/zef --/confirm nuke site home shallow_clone: true zef-0.13.8/.circleci/000077500000000000000000000000001422013762000142175ustar00rootroot00000000000000zef-0.13.8/.circleci/config.yml000066400000000000000000000070471422013762000162170ustar00rootroot00000000000000version: 2 variables: macos: &macos macos: xcode: "10.2.0" linux: &linux machine: true install-rakudo: &install-rakudo run: name: Build and install rakudo command: | git clone https://github.com/rakudo/rakudo.git $HOME/rakudo cd $HOME/rakudo perl Configure.pl --gen-moar --gen-nqp --make-install test-zef: &test-zef run: name: Run tests command: | raku -I. bin/zef --version # run xtests raku -I. xt/repository.t raku -I. xt/install.t # test explicitly via `prove t/*` and `raku t/foo.t && raku t/bar.t` # both should work, since all our CI envs have prove raku -I. bin/zef --debug --/tap-harness --/prove --raku-test test . raku -I. bin/zef --debug --/tap-harness --prove --/raku-test test . # run relative local path test + install raku -I. bin/zef --debug install . # test uninstall raku -I. bin/zef uninstall zef # run absolute local path test + install raku -I. bin/zef install $PWD # change path to make sure next `zef` commands aren't using any files in cwd or lib/ (cd .. && zef update --debug) # test informational commands zef --version zef --help zef locate Zef::CLI zef locate lib/Zef/CLI.rakumod zef browse zef bugtracker --/open zef info zef # test bells and whistles zef --debug test . zef --debug search Base64 zef --debug rdepends Base64 zef --debug depends Cro::SSL zef --debug fetch Base64 # test installing from what `fetch` put in ::LocalCache zef --debug --/fez --/cpan --/p6c --/rea install Base64 zef --debug --max=10 list zef --debug --installed list zef --debug --force-install install Base64 # test tar + upgrade zef --debug install https://github.com/ugexe/Perl6-PathTools/archive/0434191c56e0f3254ab1d756d90f9191577de5a0.tar.gz zef --debug upgrade PathTools # test zip zef --debug install https://github.com/ugexe/Perl6-Text--Table--Simple/archive/v0.0.3.zip # test remote git repo + tag zef --debug install https://github.com/ugexe/Perl6-Text--Table--Simple.git@v0.0.4 # Test self contained installation zef install Distribution::Common --/test zef install Distribution::Common::Remote -to=inst#foo --contained --/test zef uninstall Distribution::Common raku -I inst#foo -M Distribution::Common::Remote::Github -e '' zef --/confirm nuke TempDir StoreDir RootDir zef update cached --debug # test single repository update; should be 0 after previous nuke raku -I /home/circleci/project /home/circleci/project/bin/zef --/confirm nuke site home jobs: test-linux: <<: *linux environment: ZEF_PLUGIN_DEBUG: 1 ZEF_BUILDPM_DEBUG: 1 PATH: /home/circleci/rakudo/install/share/perl6/site/bin:/home/circleci/rakudo/install/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin steps: - checkout - *install-rakudo - *test-zef #test-macos: # <<: *macos # environment: # ZEF_PLUGIN_DEBUG: 1 # ZEF_BUILDPM_DEBUG: 1 # PATH: /Users/circleci/rakudo/install/share/perl6/site/bin:/Users/circleci/rakudo/install/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin # steps: # - checkout # - *install-rakudo # - *test-zef workflows: version: 2 test: jobs: - test-linux #- test-macos zef-0.13.8/.github/000077500000000000000000000000001422013762000137245ustar00rootroot00000000000000zef-0.13.8/.github/FUNDING.yml000066400000000000000000000000201422013762000155310ustar00rootroot00000000000000github: [ugexe] zef-0.13.8/.github/ISSUE_TEMPLATE.md000066400000000000000000000011321422013762000164260ustar00rootroot00000000000000 ## Context ## Expected Behavior ## Actual Behavior ## Steps to Reproduce ## Your Environment * raku -v * zef list --installed zef-0.13.8/.gitignore000066400000000000000000000000631422013762000143530ustar00rootroot00000000000000*.moarvm *.jar tmp/ lib/.precomp/ .precomp/ .idea/ zef-0.13.8/.travis.yml000066400000000000000000000060511422013762000144770ustar00rootroot00000000000000language: perl os: - linux - osx env: - BACKEND=moar # - BACKEND=jvm matrix: allow_failures: - env: BACKEND=jvm fast_finish: true sudo: false before_install: - git clone https://github.com/rakudo/rakudo.git $HOME/rakudo - cd $HOME/rakudo - 'if [[ $BACKEND == "moar" ]]; then export OPTS="--gen-moar --gen-nqp --backends=moar"; fi' - 'if [[ $BACKEND == "jvm" ]]; then export OPTS="--gen-nqp --backends=jvm"; fi' - perl Configure.pl $OPTS - make install - export PATH=$HOME/rakudo/install/bin:$PATH - export ZEF_BUILDPM_DEBUG=1 - export PATH=$HOME/rakudo/install/share/perl6/site/bin:$PATH - cd $TRAVIS_BUILD_DIR install: # need at least 1 statement in 'install' - raku -v script: - raku -I. bin/zef --version # run xtests - raku -I. xt/repository.t - raku -I. xt/install.t # test explicitly via `prove t/*` and `raku t/foo.t && raku t/bar.t` # both should work, since all our CI envs have prove - raku -I. bin/zef --debug --/tap-harness --/prove --raku-test test . - raku -I. bin/zef --debug --/tap-harness --prove --/raku-test test . # run relative local path test + install - raku -I. bin/zef --debug install . # test uninstall - raku -I. bin/zef uninstall zef # run absolute local path test + install - raku -I. bin/zef install $TRAVIS_BUILD_DIR # change path to make sure next `zef` commands aren't using any files in cwd or lib/ - cd $TRAVIS_BUILD_DIR/.. - zef update --debug # test informational commands - zef --version - zef --help - zef locate Zef::CLI - zef locate lib/Zef/CLI.rakumod - zef browse zef bugtracker --/open - zef info zef # test bells and whistles - zef --debug test ./zef - zef --debug search Base64 - zef --debug rdepends Base64 - zef --debug depends Cro::SSL - zef --debug fetch Base64 # test installing from what `fetch` put in ::LocalCache - zef --debug --/fez --/cpan --/p6c --/rea install Base64 - zef --debug --max=10 list - zef --debug --installed list - zef --debug --force-install install Base64 # test tar + upgrade - zef --debug install https://github.com/ugexe/Perl6-PathTools/archive/0434191c56e0f3254ab1d756d90f9191577de5a0.tar.gz - zef --debug upgrade PathTools # test zip - zef --debug install https://github.com/ugexe/Perl6-Text--Table--Simple/archive/v0.0.3.zip # test remote git repo + tag - zef --debug install https://github.com/ugexe/Perl6-Text--Table--Simple.git@v0.0.4 # Test self contained installation - zef install Distribution::Common --/test - zef install Distribution::Common::Remote -to=inst#foo --contained --/test - zef uninstall Distribution::Common - raku -I inst#foo -M Distribution::Common::Remote::Github -e '' - zef --/confirm nuke TempDir StoreDir RootDir - zef update cached --debug # test single repository update; should be 0 after previous nuke - raku -I $TRAVIS_BUILD_DIR $TRAVIS_BUILD_DIR/bin/zef --/confirm nuke site home zef-0.13.8/LICENSE000066400000000000000000000213061422013762000133730ustar00rootroot00000000000000 The Artistic License 2.0 Copyright (c) 2000-2006, The Perl Foundation. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble This license establishes the terms under which a given free software Package may be copied, modified, distributed, and/or redistributed. The intent is that the Copyright Holder maintains some artistic control over the development of that Package while still keeping the Package available as open source and free software. You are always permitted to make arrangements wholly outside of this license directly with the Copyright Holder of a given Package. If the terms of this license do not permit the full use that you propose to make of the Package, you should contact the Copyright Holder and seek a different licensing arrangement. Definitions "Copyright Holder" means the individual(s) or organization(s) named in the copyright notice for the entire Package. "Contributor" means any party that has contributed code or other material to the Package, in accordance with the Copyright Holder's procedures. "You" and "your" means any person who would like to copy, distribute, or modify the Package. "Package" means the collection of files distributed by the Copyright Holder, and derivatives of that collection and/or of those files. A given Package may consist of either the Standard Version, or a Modified Version. "Distribute" means providing a copy of the Package or making it accessible to anyone else, or in the case of a company or organization, to others outside of your company or organization. "Distributor Fee" means any fee that you charge for Distributing this Package or providing support for this Package to another party. It does not mean licensing fees. "Standard Version" refers to the Package if it has not been modified, or has been modified only in ways explicitly requested by the Copyright Holder. "Modified Version" means the Package, if it has been changed, and such changes were not explicitly requested by the Copyright Holder. "Original License" means this Artistic License as Distributed with the Standard Version of the Package, in its current version or as it may be modified by The Perl Foundation in the future. "Source" form means the source code, documentation source, and configuration files for the Package. "Compiled" form means the compiled bytecode, object code, binary, or any other form resulting from mechanical transformation or translation of the Source form. Permission for Use and Modification Without Distribution (1) You are permitted to use the Standard Version and create and use Modified Versions for any purpose without restriction, provided that you do not Distribute the Modified Version. Permissions for Redistribution of the Standard Version (2) You may Distribute verbatim copies of the Source form of the Standard Version of this Package in any medium without restriction, either gratis or for a Distributor Fee, provided that you duplicate all of the original copyright notices and associated disclaimers. At your discretion, such verbatim copies may or may not include a Compiled form of the Package. (3) You may apply any bug fixes, portability changes, and other modifications made available from the Copyright Holder. The resulting Package will still be considered the Standard Version, and as such will be subject to the Original License. Distribution of Modified Versions of the Package as Source (4) You may Distribute your Modified Version as Source (either gratis or for a Distributor Fee, and with or without a Compiled form of the Modified Version) provided that you clearly document how it differs from the Standard Version, including, but not limited to, documenting any non-standard features, executables, or modules, and provided that you do at least ONE of the following: (a) make the Modified Version available to the Copyright Holder of the Standard Version, under the Original License, so that the Copyright Holder may include your modifications in the Standard Version. (b) ensure that installation of your Modified Version does not prevent the user installing or running the Standard Version. In addition, the Modified Version must bear a name that is different from the name of the Standard Version. (c) allow anyone who receives a copy of the Modified Version to make the Source form of the Modified Version available to others under (i) the Original License or (ii) a license that permits the licensee to freely copy, modify and redistribute the Modified Version using the same licensing terms that apply to the copy that the licensee received, and requires that the Source form of the Modified Version, and of any works derived from it, be made freely available in that license fees are prohibited but Distributor Fees are allowed. Distribution of Compiled Forms of the Standard Version or Modified Versions without the Source (5) You may Distribute Compiled forms of the Standard Version without the Source, provided that you include complete instructions on how to get the Source of the Standard Version. Such instructions must be valid at the time of your distribution. If these instructions, at any time while you are carrying out such distribution, become invalid, you must provide new instructions on demand or cease further distribution. If you provide valid instructions or cease distribution within thirty days after you become aware that the instructions are invalid, then you do not forfeit any of your rights under this license. (6) You may Distribute a Modified Version in Compiled form without the Source, provided that you comply with Section 4 with respect to the Source of the Modified Version. Aggregating or Linking the Package (7) You may aggregate the Package (either the Standard Version or Modified Version) with other packages and Distribute the resulting aggregation provided that you do not charge a licensing fee for the Package. Distributor Fees are permitted, and licensing fees for other components in the aggregation are permitted. The terms of this license apply to the use and Distribution of the Standard or Modified Versions as included in the aggregation. (8) You are permitted to link Modified and Standard Versions with other works, to embed the Package in a larger work of your own, or to build stand-alone binary or bytecode versions of applications that include the Package, and Distribute the result without restriction, provided the result does not expose a direct interface to the Package. Items That are Not Considered Part of a Modified Version (9) Works (including, but not limited to, modules and scripts) that merely extend or make use of the Package, do not, by themselves, cause the Package to be a Modified Version. In addition, such works are not considered parts of the Package itself, and are not subject to the terms of this license. General Provisions (10) Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license. (11) If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of this license. (12) This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder. (13) This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement, then this Artistic License to you shall terminate on the date that such litigation is filed. (14) Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. zef-0.13.8/META6.json000066400000000000000000000072711422013762000141020ustar00rootroot00000000000000{ "meta-version" : "0", "perl" : "6.c", "name" : "zef", "api" : "0", "version" : "0.13.8", "auth" : "github:ugexe", "description" : "A Raku / Perl 6 module manager", "license" : "Artistic-2.0", "build-depends" : [ ], "test-depends" : [ "Test" ], "depends" : [ "NativeCall" ], "provides" : { "Zef" : "lib/Zef.rakumod", "Zef::Build" : "lib/Zef/Build.rakumod", "Zef::CLI" : "lib/Zef/CLI.rakumod", "Zef::Client" : "lib/Zef/Client.rakumod", "Zef::Config" : "lib/Zef/Config.rakumod", "Zef::Extract" : "lib/Zef/Extract.rakumod", "Zef::Identity" : "lib/Zef/Identity.rakumod", "Zef::Install" : "lib/Zef/Install.rakumod", "Zef::Test" : "lib/Zef/Test.rakumod", "Zef::Fetch" : "lib/Zef/Fetch.rakumod", "Zef::Report" : "lib/Zef/Report.rakumod", "Zef::Repository" : "lib/Zef/Repository.rakumod", "Zef::Repository::LocalCache" : "lib/Zef/Repository/LocalCache.rakumod", "Zef::Repository::Ecosystems" : "lib/Zef/Repository/Ecosystems.rakumod", "Zef::Distribution" : "lib/Zef/Distribution.rakumod", "Zef::Distribution::DependencySpecification" : "lib/Zef/Distribution/DependencySpecification.rakumod", "Zef::Distribution::Local" : "lib/Zef/Distribution/Local.rakumod", "Zef::Service::FetchPath" : "lib/Zef/Service/FetchPath.rakumod", "Zef::Service::TAP" : "lib/Zef/Service/TAP.rakumod", "Zef::Service::FileReporter" : "lib/Zef/Service/FileReporter.rakumod", "Zef::Service::InstallRakuDistribution" : "lib/Zef/Service/InstallRakuDistribution.rakumod", "Zef::Service::Shell::DistributionBuilder" : "lib/Zef/Service/Shell/DistributionBuilder.rakumod", "Zef::Service::Shell::LegacyBuild" : "lib/Zef/Service/Shell/LegacyBuild.rakumod", "Zef::Service::Shell::Test" : "lib/Zef/Service/Shell/Test.rakumod", "Zef::Service::Shell::prove" : "lib/Zef/Service/Shell/prove.rakumod", "Zef::Service::Shell::unzip" : "lib/Zef/Service/Shell/unzip.rakumod", "Zef::Service::Shell::tar" : "lib/Zef/Service/Shell/tar.rakumod", "Zef::Service::Shell::p5tar" : "lib/Zef/Service/Shell/p5tar.rakumod", "Zef::Service::Shell::curl" : "lib/Zef/Service/Shell/curl.rakumod", "Zef::Service::Shell::git" : "lib/Zef/Service/Shell/git.rakumod", "Zef::Service::Shell::wget" : "lib/Zef/Service/Shell/wget.rakumod", "Zef::Service::Shell::PowerShell" : "lib/Zef/Service/Shell/PowerShell.rakumod", "Zef::Service::Shell::PowerShell::download" : "lib/Zef/Service/Shell/PowerShell/download.rakumod", "Zef::Service::Shell::PowerShell::unzip" : "lib/Zef/Service/Shell/PowerShell/unzip.rakumod", "Zef::Utils::FileSystem" : "lib/Zef/Utils/FileSystem.rakumod", "Zef::Utils::SystemQuery" : "lib/Zef/Utils/SystemQuery.rakumod", "Zef::Utils::URI" : "lib/Zef/Utils/URI.rakumod" }, "resources" : [ "config.json", "scripts/perl5tar.pl", "scripts/win32http.ps1", "scripts/win32unzip.ps1" ], "authors" : [ "Nick Logan", "Tony O'Dell" ], "support" : { "bugtracker" : "https://github.com/ugexe/zef/issues", "source" : "https://github.com/ugexe/zef.git" }, "tags" : [ "package-manager", "module-installer", "meta-search", "distribution", "ecosystem", "cpan", "toolchain" ] } zef-0.13.8/README.md000066400000000000000000000441121422013762000136450ustar00rootroot00000000000000## Zef Raku / Perl6 Module Management # Installation #### Manual $ git clone https://github.com/ugexe/zef.git $ cd zef $ raku -I. bin/zef install . #### Rakubrew To install via rakubrew, please use the following command: $ rakubrew build-zef # USAGE zef --help zef --version # install the CSV::Parser distribution zef install CSV::Parser # search for distribution names matching `CSV` zef search CSV # detailed information for a matching distribution zef info CSV::Parser # list all available distributions zef list # list reverse dependencies of an identity zef rdepends HTTP::UserAgent # test project in current directory zef test . # fetch a specific module only zef fetch CSV::Parser # fetch a module, then shell into its local path zef look CSV::Parser # smoke test modules from all repositories zef smoke # run Build.pm if one exists in given path zef build . # update Repository package lists zef update # upgrade all distributions (BETA) zef upgrade # upgrade specific distribution (BETA) zef upgrade CSV::Parser # lookup module info by name/path/sha1 zef --sha1 locate 9FA0AC28824EE9E5A9C0F99951CA870148AE378E # launch browser to named support urls from meta data zef browse zef bugtracker ## More CLI #### **install** \[\*@identities\] Note: The install process does not install anything until all phases have completed. So, if the user requested to `install A`, and A required module B: both would be downloaded, potentially built, tested, and installed -- but only if both passed all their tests. For example: if module A failed its tests, then module B would not be installed (even if it passed its own tests) unless forced. \[`@identities`\] can take the form of a file path (starting with **.** or **/**), URLs, paths, or identities: # IDENTITY zef install CSV::Parser zef install "CSV::Parser:auth:ver<0.1.2>" zef install "CSV::Parser:ver<0.1.2>" # PATH zef install ./Perl6-Net--HTTP # URL zef -v install https://github.com/ugexe/zef.git zef -v install https://github.com/ugexe/zef/archive/master.tar.gz zef -v install https://github.com/ugexe/zef.git@v0.1.22 A request may contain any number and combination of these. Paths and URLs will be resolved first so they are available to fulfill any dependencies of other requested identities. **Options** # Install to a custom locations --install-to= # site/home/vendor/perl, or -to= # inst#/home/some/path/custom # Install all transitive and direct dependencies # even if they are already installed globally (BETA) --contained # Load a specific Zef config file --config-path=/some/path/config.json # Install only the dependency chains of the requested distributions --deps-only # Ignore errors occuring during the corresponding phase --force-resolve --force-fetch --force-extract --force-build --force-test --force-install # or set the default to all unset --force-* flags to True --force # Set the timeout for corresponding phases --fetch-timeout=600 --extract-timeout=3600 --build-timeout=3600 --test-timeout=3600 --install-timeout=3600 # Number of simultaneous distributions/jobs to process for the corresponding phases --fetch-degree=5 --test-degree=1 # or set the default to all unset --*-timeout flags to 0 --timeout=0 # Do everything except the actual installations --dry # Build/Test/Install each dependency serially before proceeding to Build/Test/Install the next --serial # Disable testing --/test # Disable build phase --/build # Disable fetching dependencies --/depends --/build-depends --/test-depends # Force a refresh for all module index indexes --update # Force a refresh for a specific ecosystem module index --update=[ecosystem] # Skip refreshing all module index indexes --/update # Skip refreshing for a specific ecosystem module index --/update=[ecosystem] **ENV Options** # Number of simultaneous distributions/jobs to process for the corresponding phases (see: --[phase]-degree options) ZEF_FETCH_DEGREE=5 ZEF_TEST_DEGREE=1 # Set the timeout for corresponding phases (see: --[phase]-timeout options) ZEF_FETCH_TIMEOUT=600 ZEF_EXTRACT_TIMEOUT=3600 ZEF_BUILD_TIMEOUT=3600 ZEF_TEST_TIMEOUT=3600 ZEF_INSTALL_TIMEOUT=3600 # Path to config file (see: --config-path option) ZEF_CONFIG_PATH=$PWD/resources/config.json #### **uninstall** \[\*@identities\] Uninstall the specified distributions Note: Requires a bleeding edge rakudo (not available in 6.c) #### **update** Update the package indexes for all `Repository` backends Note: Some `Repository` backends, like the default Ecosystems, have an `auto-update` option in `resources/config.json` that can be enabled. This should be the number of hours until it should auto update based on the file system last modified time of the ecosystem json file location. #### **upgrade** \[\*@identities\] _BETA_ Upgrade specified identities. If no identities are provided, zef attempts to upgrade all installed distributions. #### **search** \[$identity\] How these are handled depends on the `Repository` engine used, which by default is `Zef::Repository::Ecosystems>p6c<` $ zef -v search URI ===> Found 4 results ------------------------------------------------------------------------- ID|From |Package |Description ------------------------------------------------------------------------- 1 |Zef::Repository::LocalCache |URI:ver<0.1.1> |A URI impleme... 2 |Zef::Repository::Ecosystems |URI:ver<0.1.1> |A URI impleme... 3 |Zef::Repository::Ecosystems |URI:ver<0.1.1> |A URI impleme... 4 |Zef::Repository::Ecosystems |URI:ver<0.000.001>|A URI impleme... ------------------------------------------------------------------------- #### **info** \[$identity\] View meta information of a distribution $ zef -v info HTTP::UserAgent - Info for: HTTP::UserAgent - Identity: HTTP::UserAgent:ver<1.1.16>:auth - Recommended By: Zef::Repository::LocalCache Author: github:sergot Description: Web user agent Source-url: https://github.com/sergot/http-useragent.git Provides: 11 modules # HTTP::Cookie # HTTP::Header # HTTP::Cookies # HTTP::Message # HTTP::Request # HTTP::Response # HTTP::MediaType # HTTP::UserAgent # HTTP::Header::Field # HTTP::Request::Common # HTTP::UserAgent::Common Depends: 7 items --------------------------------- ID|Identity |Installed? --------------------------------- 1 |HTTP::Status |✓ 2 |File::Temp |✓ 3 |DateTime::Parse |✓ 4 |Encode |✓ 5 |MIME::Base64 |✓ 6 |URI |✓ 7 |IO::Capture::Simple|✓ --------------------------------- **Options** # Extra details (eg, list dependencies and which ones are installed) -v #### **list** \[\*@from\] List known available distributions $ zef --installed list ===> Found via /home/foo/.rakubrew/moar-master/install/share/perl6/site CSV::Parser:ver<0.1.2>:auth Zef:auth ===> Found via /home/foo/.rakubrew/moar-master/install/share/perl6 CORE:ver<6.c>:auth Note that not every Repository may provide such a list, and such lists may only be a subset. For example: We may not be able to get a list of every distribution on cpan, but we \*can\* get the $x most recent additions (we use 100 for now). \[`@from`\] allows you to show results from specific repositories only: zef --installed list perl # Only list modules installed by rakudo itself zef list cpan # Only show available modules from the repository zef list p6c # with a name field matching the arguments to `list` zef list cached # (be sure the repository is enabled in config) Otherwise results from all enabled repositories will be returned. **Options** # Only list installed distributions --installed # Additionally list the modules of discovered distributions -v #### **depends** \[$identity\] List direct and transitive dependencies to the first successful build graph for `$identity` $ zef depends Cro::SSL Cro::Core:ver<0.7> IO::Socket::Async::SSL:ver<0.3> OpenSSL:ver<0.1.14>:auth #### **rdepends** \[$identity\] List available distributions that directly depend on `$identity` $ zef rdepends Net::HTTP Minecraft-Tools:ver<0.1.0> LendingClub:ver<0.1.0> #### **fetch** \[\*@identities\] Fetches candidates for given identities #### **test** \[\*@paths\] Run tests on each distribution located at \[`@paths`\] #### **build** \[\*@paths\] Run the Build.pm file located in the given \[`@paths`\] If you want to create a build hook, put the following dependency-free boilerplate in a file named `Build.pm` at the root of your distribution: class Build { method build($dist-path) { # do build stuff to your module # which is located at $dist-path } } Set the env variable **ZEF\_BUILDPM\_DEBUG=1** or use the _--debug_ flag for additional debugging information. _Note: In the future, a more appropriate hooking solution will replace this._ #### **look** \[$identity\] Fetches the requested distribution and any dependencies (if requested), changes the directory to that of the fetched distribution, and then stops program execution. This allows you modify or look at the source code before manually continuing the install via `zef install .` Note that the path to any dependencies that needed to be fetched will be set in env at **RAKULIB**, so you should be able to run any build scripts, tests, or complete a manual install without having to specify their locations. #### **browse** $identity \[bugtracker | homepage | source\] **Options** # disables launching a browser window (just shows url) --/open Output the url and launch a browser to open it. # also opens browser $ zef browse Net::HTTP bugtracker https://github.com/ugexe/Perl6-Net--HTTP/issues # only outputs the url $ zef browse Net::HTTP bugtracker --/open https://github.com/ugexe/Perl6-Net--HTTP/issues #### **locate** \[$identity, $name-path, $sha1-id\] **Options** # The argument is a sha1-id (otherwise assumed to be an identity or name-path) --sha1 Lookup a locally installed module by $identity, $name-path, or $sha1-id $ zef --sha1 locate A9948E7371E0EB9AFDF1EEEB07B52A1B75537C31 ===> From Distribution: zef:ver<*>:auth:api<> lib/Zef/CLI.rakumod => ~/rakudo/install/share/perl6/site/sources/A9948E7371E0EB9AFDF1EEEB07B52A1B75537C31 $ zef locate Zef::CLI ===> From Distribution: zef:ver<*>:auth:api<> lib/Zef/CLI.rakumod => ~/rakudo/install/share/perl6/site/sources/A9948E7371E0EB9AFDF1EEEB07B52A1B75537C31 $ zef locate lib/Zef/CLI.rakumod ===> From Distribution: zef:ver<*>:auth:api<> Zef::CLI => ~/rakudo/install/share/perl6/site/sources/A9948E7371E0EB9AFDF1EEEB07B52A1B75537C31 #### **nuke** \[RootDir | TempDir | StoreDir\] Deletes all paths in the specific configuration directory #### **nuke** \[site | home\] Deletes all paths that are rooted in the prefix of the matching CompUnit::Repository name # uninstall all modules $ zef nuke site home ## Output Verbosity You can control the logging level using the following flags: # More/less detailed output --error, --warn, --info (default), --verbose (-v), --debug # Global Configuration ### Finding the configuration file You can always see the configuration file that will be used by running: $ zef --help In most cases the default configuration combined with command line options should be enough for most users. If you are most users (e.g. not: power users, packagers, zef plugin developers) you hopefully don't care about this section! ### How the configuration file is chosen The configuration file will be chosen at runtime from one of two (technically four) locations, and one can affect the others (this is not really a design decision and suggestions and PRs are welcome). First, and the most precise way, is to specify the config file by passing `--config-path="..."` to any zef command. Second, third, and fourth we look at the path pointed to by `%?RESOURCES`. This will point to `$zef-dir/resources/config.json`, where `$zef-dir` will be either: - The prefix of a common configuration directory, such as `$XDG_CONFIG_HOME` or `$HOME/.config`. - The prefix of a rakudo installation location - This is the case if the modules loaded for bin/zef come from an installation CompUnit::Repository. - The current working directory `$*CWD` - This is the case when modules loaded for bin/zef come from a non-installation CompUnit::Repository (such as `-I $dist-path`). To understand how this is chosen, consider: # Modules not loaded from an ::Installation, # so %?RESOURCES is $*CWD/resources $ raku -I. bin/zef --help ... CONFIGURATION /home/user/raku/zef/resources/config.json ... # Installed zef script loads modules from an ::Installation, # so %?RESOURCES is $raku-share-dir/site/resources $ zef --help ... CONFIGURATION /home/user/raku/install/share/perl6/site/resources/EE5DBAABF07682ECBE72BEE98E6B95E5D08675DE.json ... This config is loaded, but it is not yet the chosen config! We check that temporary config's `%config` for valid json in a file named `config.json` (i.e. `%config/config.json`). This can be confusing (so it may go away or be refined - PRs welcome) but for most cases it just means `$*HOME/.zef/config.json` will override an installed zef configuration file. To summarize: - You can edit the `resources/config.json` file before you install zef. When you `raku -I. bin/zef install .` that configuration file be be used to install zef and will also be installed with zef such that it will be the default. - You can create a `%config/config.json` file. Where `%config` comes from the previously mentioned `%?RESOURCES`'s `RootDir` field (`$*HOME/.zef` in most cases), to allow overriding zef config behavior on a per user basis (allows setting different `--install-to` targets for, say, a root user and a regular user). Since this new config file could have a different `RootDir` than the default config (used to find the new one in the first place) this behavior may be changed in the future to be less confusing. - You can override both of the previous entries by passing `zef --config-path="$path" ` ### Configuration fields #### Basic Settings - **RootDir** - Where zef will look for a custom config.json file - **TempDir** - A staging area for items that have been fetched and need to be extracted/moved - **StoreDir** - Where zef caches distributions, package lists, etc after they've been fetched and extracted - **DefaultCUR** - This sets the default value for `--install-to="..."`. The default value of `auto` means it will first try installing to rakudo's installation prefix, and if its not writable by the current user it will install to `$*HOME/.raku`. These directories are not chosen by zef - they are actually represented by the magic strings `site` and `home` (which, like `auto`, are valid values despite not being paths along with `vendor` and `perl`) #### Phases / Plugins Settings These consist of an array of hashes that describe how to instantiate some class that fulfills the appropriate interface from _Zef.pm_ (`Repository` `Fetcher` `Extractor` `Builder` `Tester` `Reporter`) The descriptions follow this format: { "short-name" : "p6c", "enabled" : 1, "module" : "Zef::Repository::Ecosystems", "options" : { } } and are instantiated via ::($hash).new(|($hash) - **short-name** - This adds an enable and disable flag by the same name to the CLI (e.g. `--p6c` and `--/p6c`) and is used when referencing which object took some action. - **enabled** - Set to 0 to skip over the object during consideration (it will never be loaded). If omitted or if the value is non 0 then it will be enabled for use. - **module** - The name of the class to instantiate. While it doesn't technically have to be a module it _does_ need to be a known namespace to `require`. - **options** - These are passed to the objects `new` method and may not be consistent between modules as they are free to implement their own requirements. See the configuration file in [resources/config.json](https://github.com/ugexe/zef/blob/master/resources/config.json) for a little more information on how plugins are invoked. You can see debug output related to chosing and loading plugins by setting the env variable **ZEF\_PLUGIN\_DEBUG=1** # FAQ ### Proxy support? All the default fetching plugins have proxy support, but you'll need to refer to the backend program's (wget, curl, git, etc) docs. You may need to set an _ENV_ variable, or you may need to add a command line option for that specific plugin in _resources/config.json_ ### Custom installation locations? Pass a path to the _-to_ / _--install-to_ option and prefix the path with `inst#` (unless you know what you're doing) $ zef -to="inst#/home/raku/custom" install Text::Table::Simple ===> Searching for: Text::Table::Simple ===> Testing: Text::Table::Simple:ver<0.0.3>:auth ===> Testing [OK] for Text::Table::Simple:ver<0.0.3>:auth ===> Installing: Text::Table::Simple:ver<0.0.3>:auth To make the custom location discoverable: # Set the RAKULIB env: $ RAKULIB="inst#/home/raku/custom" raku -e "use Text::Table::Simple; say 'ok'" ok # or simply include it as needed $ raku -Iinst#/home/raku/custom -e "use Text::Table::Simple; say 'ok'" ok zef-0.13.8/bin/000077500000000000000000000000001422013762000131345ustar00rootroot00000000000000zef-0.13.8/bin/zef000077500000000000000000000000441422013762000136440ustar00rootroot00000000000000#!/usr/bin/env perl6 use Zef::CLI; zef-0.13.8/lib/000077500000000000000000000000001422013762000131325ustar00rootroot00000000000000zef-0.13.8/lib/Zef.rakumod000066400000000000000000000175711422013762000152550ustar00rootroot00000000000000package Zef { 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 ; enum STAGE is export ; enum PHASE is export ; # A way to avoid printing everything to make --quiet option more universal between plugins # Need to create a messaging format to include the phase, file, verbosity level, progress, # etc we may or may not display as necessary. It's current usage is not finalized and # any suggestions for this are well taken role Messenger is export { has $.stdout = Supplier.new; has $.stderr = Supplier.new; } # 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) { ... } method test-matcher($path) { ... } } role Builder is export { method build($dist, :@includes) { ... } 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 $.as; # Requested as (maybe a url, maybe an identity, maybe a path) has $.from; # Recommended from (::Ecosystems, ::MetaCPAN, ::LocalCache) has $.uri is rw; # url, file path, etc has Bool $.is-dependency is rw; # I don't think we use/need this anymore... 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 { has $!plugins; has @.backends; 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 { dd $plugin.short-name; $plugin.short-name ~~ any(@short-names) }) -> @filtered-group { push @plugins, @filtered-group; } } return @plugins; } method !list-plugins(@backends = @!backends) { # @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; } method !try-load(Hash $plugin) { my $module = $plugin; DEBUG($plugin, "Checking: {$module}"); # default to enabled unless `"enabled" : 0` if $plugin:exists && (!$plugin || $plugin eq "0") { DEBUG($plugin, "\t(SKIP) Not enabled"); return; } if (try require ::($ = $module)) ~~ Nil { DEBUG($plugin, "\t(SKIP) Plugin could not be loaded"); return; } DEBUG($plugin, "\t(OK) Plugin loaded successful"); if ::($ = $module).^find_method('probe') { unless ::($ = $module).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 = ::($ = $module).new(|($plugin // []))\ but role :: { has $.short-name = $plugin // '' }; unless ?$class { DEBUG($plugin, "(SKIP) Plugin unusable: initialization failure"); return; } DEBUG($plugin, "(OK) Plugin is now usable: {$module}"); return $class; } } } class X::Zef::UnsatisfiableDependency is Exception { method message() { 'Failed to resolve some missing dependencies' } } zef-0.13.8/lib/Zef/000077500000000000000000000000001422013762000136565ustar00rootroot00000000000000zef-0.13.8/lib/Zef/Build.rakumod000066400000000000000000000102441422013762000163020ustar00rootroot00000000000000use Zef; use Zef::Distribution; class Zef::Build does Builder does Pluggable { =begin pod =title class Zef::Build =subtitle A configurable implementation of the Builder interface =head1 Synopsis =begin code :lang use Zef; use Zef::Build; use Zef::Distribution::Local; # Setup with a single builder backend my $builder = Zef::Build.new( backends => [ { module => "Zef::Service::Shell::LegacyBuild" }, ], ); # Assuming our current directory is a raku distribution with a Build.rakumod file... my $dist-to-build = Zef::Distribution::Local.new($*CWD); my $candidate = Candidate.new(dist => $dist-to-build); my $logger = Supplier.new andthen *.Supply.tap: -> $m { say $m. } my $build-ok = so all $builder.build($candidate, :$logger); say $build-ok ?? "Build OK" !! "Something went wrong..."; =end code =head1 Description A C that uses 1 or more other C instances as backends. It abstracts the logic to do 'build this distribution with the first backend that supports the given distribution'. =head1 Methods =head2 method build-matcher method build-matcher(Zef::Distribution $dist --> Bool:D) Returns C if any of the probeable C know how to build C<$dist>. =head2 method build method build(Candidate $candi, Str :@includes, Supplier :$logger, Int :$timeout, :$meta --> Array[Bool]) Builds the distribution for C<$candi>. For more info see C and C since the build step process is coupled tightly to the backend used. An optional C<:$logger> can be supplied to receive events about what is occuring. An optional C<:$timeout> can be passed to denote the number of seconds after which we'll assume failure. Returns an C with some number of C (which depends on the backend used). If there are no C items in the returned C then we assume success. =end pod submethod TWEAK(|) { @ = self.plugins; # preload plugins } #| Returns true if any of the backends 'build-matcher' understand the given uri/path method build-matcher(Zef::Distribution $dist --> Bool:D) { return so self!build-matcher($dist) } #| Returns the backends that understand the given uri based on their build-matcher result method !build-matcher(Zef::Distribution $dist --> Array[Builder]) { my @matching-backends = self.plugins.grep(*.build-matcher($dist)); my Builder @results = @matching-backends; return @results; } #| Build the given path using any provided @includes #| Will return results from the first Builder backend that supports the given $candi.dist (distribution) method build(Candidate $candi, Str :@includes, Supplier :$logger, Int :$timeout --> Array[Bool]) { my $dist := $candi.dist; die "Can't build non-existent path: {$dist.path}" unless $dist.path.IO.e; my $builder = self!build-matcher($dist).first(*.so); die "No building backend available" unless ?$builder; if ?$logger { $logger.emit({ level => DEBUG, stage => BUILD, phase => START, candi => $candi, message => "Building with plugin: {$builder.^name}" }); $builder.stdout.Supply.grep(*.defined).act: -> $out { $logger.emit({ level => VERBOSE, stage => BUILD, phase => LIVE, candi => $candi, message => $out }) } $builder.stderr.Supply.grep(*.defined).act: -> $err { $logger.emit({ level => ERROR, stage => BUILD, phase => LIVE, candi => $candi, message => $err }) } } my $todo = start { try $builder.build($dist, :@includes) }; my $time-up = ($timeout ?? Promise.in($timeout) !! Promise.new); await Promise.anyof: $todo, $time-up; $logger.emit({ level => DEBUG, stage => BUILD, phase => LIVE, candi => $candi, message => "Building {$dist.path} timed out" }) if ?$logger && $time-up.so && $todo.not; my Bool @results = $todo.so ?? $todo.result !! False; return @results; } } zef-0.13.8/lib/Zef/CLI.rakumod000066400000000000000000001622121422013762000156550ustar00rootroot00000000000000use Zef; use Zef::Client; use Zef::Config; use Zef::Utils::FileSystem; use Zef::Identity; use Zef::Distribution; use Zef::Utils::URI; use nqp; package Zef::CLI { =begin pod =title package Zef::CLI =subtitle The zef command line interface =head1 Synopsis =begin code :lang zef --help raku -e 'use Zef::CLI' install ./my-dist =end code =head1 Description Acts as a script-in-module-form (for precompilation reasons). As such the following two commands are the same: C, C. See `zef --help` or `raku -I. bin/zef --help` for high level information on the various commands and options. =head1 Subroutines =head2 sub MAIN(:$version) multi sub MAIN(Bool :version($) where .so) { Show the version of zef being used. =head2 sub MAIN(:$help) multi sub MAIN(Bool :h(:help($))) Show the usage message. =head2 sub MAIN('fetch', ...) sub MAIN( 'fetch', Bool :force(:$force-fetch), Int :timeout(:$fetch-timeout) = %*ENV // 600, Int :degree(:$fetch-degree) = %*ENV || 5, # default different from Zef::Client, :$update, # Not Yet Implemented *@identities ($, *@) ) Download and extract the best matching distribution found for each C<@identities>. Will exit 0 on success. If C<$force-fetch> is C then a failure to fetch something won't throw an exception. If C<$fetch-timeout> is set to a non-zero c zef will wait for that many seconds when fetching something before attempting to try the next matching fetching adapter. If C<$fetch-degree> is set zef will download up to that many distributions in parallel. =head2 sub MAIN('test', ...) sub MAIN( 'test', Bool :force(:$force-test), Int :timeout(:$test-timeout) = %*ENV || 3600, Int :degree(:$test-degree) = %*ENV || 1, *@paths ($, *@) ) Test the distributions from the provided C<@paths>. Will exit 0 on success. If C<$force-test> is C then a test failure won't throw an exception. If C<$test-timeout> is set to a non-zero c zef will wait for that many seconds when testing a distribution before aborting. If C<$test-degree> is set zef will test up to that many distributions in parallel. =head2 sub MAIN('build', ...) sub MAIN( 'build', Bool :force(:$force-build), Int :timeout(:$build-timeout) = %*ENV || 3600, *@paths ($, *@) ) Build the distributions from the provided C<@paths>. Will exit 0 on success. If C<$force-build> is C then a test failure won't throw an exception. If C<$build-timeout> is set to a non-zero c zef will wait for that many seconds when building a distribution before aborting. =head2 sub MAIN('install', ...) sub MAIN( 'install', Bool :$fetch = True, Bool :$build = True, Bool :$test = True, Bool :$depends = True, Bool :$build-depends = $build, Bool :$test-depends = $test, Bool :$force, Bool :$force-resolve = $force, Bool :$force-fetch = $force, Bool :$force-extract = $force, Bool :$force-build = $force, Bool :$force-test = $force, Bool :$force-install = $force, Int :$timeout, Int :$fetch-timeout = %*ENV // $timeout // 600, Int :$extract-timeout = %*ENV // $timeout // 3600, Int :$build-timeout = %*ENV // $timeout // 3600, Int :$test-timeout = %*ENV // $timeout // 3600, Int :$install-timeout = %*ENV // $timeout // 3600, Int :$degree, Int :$fetch-degree = %*ENV || $degree || 5, # default different from Zef::Client Int :$test-degree = %*ENV || $degree || 1, Bool :$dry, Bool :$upgrade,# Not Yet Implemented Bool :$deps-only, Bool :$serial, Bool :$contained, :$update, :$exclude, :to(:$install-to) = $CONFIG, *@wants ($, *@) ) Fetch, extract, build, test, and install C<@wanted> distributions and their prerequisites. If C<$fetch> is set to C then the fetch phase will be skipped. If C<$build> is set to C then the build phase will be skipped. If C<$test> is set to C then the test phase will be skipped. If C<$depends> is set to C then runtime dependencies will be ignored. If C<$build-depends> is set to C then build dependencies will be ignored. If C<$test-depends> is set to C then test dependencies will be ignored. If C<$force> is set to C then zef will treat any failures as successes, potentially allowing progress to continue on failure. If C<$force-resolve> is set to C then zef will treat any name resolution failures as successes, potentially allowing progress to continue on failure. If C<$force-fetch> is set to C then zef will treat any fetch failures as successes, potentially allowing progress to continue on failure. If C<$force-extract> is set to C then zef will treat any extract failures as successes, potentially allowing progress to continue on failure. If C<$force-build> is set to C then zef will treat any build failures as successes, potentially allowing progress to continue on failure. If C<$force-test> is set to C then zef will treat any test failures as successes, potentially allowing progress to continue on failure. If C<$force-install> is set to C then zef will treat any install failures as successes, potentially allowing progress to continue on failure. If C<$timeout> is set to a non-zero c zef will wait for that many seconds when executing a given action for a specific distribution before aborting. If C<$fetch-timeout> is set to a non-zero c zef will wait for that many seconds when fetching something before attempting to try the next matching fetching adapter. If C<$build-timeout> is set to a non-zero c zef will wait for that many seconds when building a distribution before aborting. If C<$test-timeout> is set to a non-zero c zef will wait for that many seconds when testing a distribution before aborting. If C<$install-timeout> is set to a non-zero c zef will wait for that many seconds when installing a distribution before aborting. If C<$fetch-degree> is set zef will download up to that many distributions in parallel. If C<$test-degree> is set zef will test up to that many distributions in parallel. If C<$dry> is set to C then the final step of actually installing the distribution will be skipped and considered a success. If C<$deps-only> is set to C then only the dependencies of the requested identities will be processed. If C<$serial> is set to C then zef will install each distribution after it passes its own tests. By default this is C and means nothing will be installed unless all distributions passed their tests. If C<$contained> is set to C (and combined with C<:$install-to>) zef will install all distributions to a location, including those that might otherwise already be installed and visible in another location (BETA) If C<$update> is set to C it will force an update of all ecosystem / repository indexes. If C<$update> is set to a C or C it will only update the repositories that match the given names. If C<$exclude> If C<$exclude> is set to a C or C then any matching dependency will be ignored. If C<$to>/C<:$install-to> is set to a C or C then distributions will be installed to all of those C. The short names C C C can be used to reference their associated repository, otherwise a file path can be given to use a custom location. By default this is set to the zef-specific value C which makes zef use the C repository if the user has write access and the C otherwise. =head2 sub MAIN('uninstall', ...) sub MAIN( 'uninstall', :from(:$uninstall-from), *@identities ($, *@) ) Uninstall distributions matching C<@identities> from any visible repository. If C<$uninstall-from> is set to a C or C then the uninstall will apply to those repositories instead of all visible ones. =head2 sub MAIN('search', ...) multi sub MAIN('search', Int :$wrap = False, :$update, *@terms ($, *@)) Does a basic substring search for any of C<@terms> and returns any matching distributions. If C<$wrap> is set to C then the generated table won't wrap (nyi -- need to detect terminal width again), and if set to an C limit the generated table to that many characters wide. If C<$update> is set to C it will force an update of all ecosystem / repository indexes. If C<$update> is set to a C or C it will only update the repository that matches the given names. =head2 sub MAIN('list', ...) multi sub MAIN('list', Int :$max?, :$update, Bool :i(:$installed), *@at) List all distributions from all known repositories. If C<$max> is set then only that many results will be returned from each repository. If C<$update> is set to C it will force an update of all ecosystem / repository indexes. If C<$update> is set to a C or C it will only update the repositories that match the given names. If C<$i>/C<$installed> is set to C then only installed distributions will be listed. If C<@at> is set to a C it will only list distributions from the repositories that match the given names. =head2 sub MAIN('depends', ...) multi sub MAIN( 'depends', $identity, Bool :$depends = True, Bool :$test-depends = True, Bool :$build-depends = True, ) List the dependencies of the given C<$identity>. If C<$depends> is set to C then runtime dependencies will be ignored. If C<$build-depends> is set to C then build dependencies will be ignored. If C<$test-depends> is set to C then test dependencies will be ignored. =head2 sub MAIN('rdepends', ...) multi sub MAIN( 'rdepends', $identity, Bool :$depends = True, Bool :$test-depends = True, Bool :$build-depends = True, ) List the reverse dependencies of the given C<$identity>. If C<$depends> is set to C then runtime dependencies will be ignored. If C<$build-depends> is set to C then build dependencies will be ignored. If C<$test-depends> is set to C then test dependencies will be ignored. =head2 sub MAIN('info', ...) multi sub MAIN('info', $identity, :$update, Int :$wrap = False) List detailed information about the best match for C<$identity>. If C<$wrap> is set to C then the generated table won't wrap (nyi -- need to detect terminal width again), and if set to an C limit the generated table to that many characters wide. If C<$update> is set to C it will force an update of all ecosystem / repository indexes. If C<$update> is set to a C or C it will only update the repositories that match the given names. =head2 sub MAIN('browse', ...) multi sub MAIN('browse', $identity, $url-type where * ~~ any(), Bool :$open = True) Open a browser to the C<$url-type> resource of the best matching distribution for C<$identity>. If C<$open> is set to C then a browser window won't be opened, instead just outputting the url to the terminal. =head2 sub MAIN('update', ...) multi sub MAIN('update', *@names) Update all ecosystem / repository indexes. If C<@names> is set to a C or C it will only update the repositories that match the given names. =head2 sub MAIN('nuke', ...) multi sub MAIN('nuke', Bool :$confirm, *@names ($, *@)) Delete everything for C<@names>, where C<@names> can be a core repository name (C C C), or a config directory reference (C C). If C<$confirm> is set to C then zef will not prompt to confirm deletion. =end pod my $verbosity = preprocess-args-verbosity-mutate(@*ARGS); %*ENV = $verbosity >= DEBUG; %*ENV = $verbosity >= DEBUG; my $CONFIG = preprocess-args-config-mutate(@*ARGS); my $VERSION = try EVAL q[$?DISTRIBUTION.meta.first(*.so)]; # TODO: deprecate usage of --depsonly @*ARGS = @*ARGS.map: { $_ eq '--depsonly' ?? '--deps-only' !! $_ } proto MAIN(|) is export { # Suppress backtrace CATCH { default { try { ::("Rakudo::Internals").?LL-EXCEPTION } ?? .rethrow !! .message.¬e; &*EXIT(1) } } {*} } #| Download specific distributions multi sub MAIN( 'fetch', Bool :force(:$force-fetch), Int :timeout(:$fetch-timeout) = %*ENV // 600, Int :degree(:$fetch-degree) = %*ENV || 5, # default different from Zef::Client, :$update, *@identities ($, *@) ) { my $client = get-client(:config($CONFIG), :$force-fetch, :$update, :$fetch-timeout, :$fetch-degree); my @candidates = $client.find-candidates(@identities.map(*.&str2identity)); abort "Failed to resolve any candidates. No reason to proceed" unless +@candidates; my @fetched = $client.fetch(@candidates); my @fail = @candidates.grep: {.as !~~ any(@fetched>>.as)} say "!!!> Fetch failed: {.as}{?($verbosity >= VERBOSE)??' at '~.dist.path!!''}" for @fail; exit +@fetched && +@fetched == +@candidates && +@fail == 0 ?? 0 !! 1; } #| Run tests multi sub MAIN( 'test', Bool :force(:$force-test), Int :timeout(:$test-timeout) = %*ENV || 3600, Int :degree(:$test-degree) = %*ENV || 1, *@paths ($, *@) ) { my $client = get-client(:config($CONFIG), :$force-test, :$test-timeout, :$test-degree); my @candidates = $client.link-candidates( @paths.map(*.&path2candidate) ); abort "Failed to resolve any candidates. No reason to proceed" unless +@candidates; my @tested = $client.test(@candidates); my (:@test-pass, :@test-fail) := @tested.classify: {.test-results.grep(*.so) ?? !! } say "!!!> Testing failed: {.as}{?($verbosity >= VERBOSE)??' at '~.dist.path!!''}" for @test-fail; exit ?@test-fail ?? 1 !! ?@test-pass ?? 0 !! 255; } #| Run the build process multi sub MAIN( 'build', Bool :force(:$force-build), Int :timeout(:$build-timeout) = %*ENV || 3600, *@paths ($, *@) ) { my $client = get-client(:config($CONFIG), :$force-build, |(:$build-timeout with $build-timeout),); my @candidates = $client.link-candidates( @paths.map(*.&path2candidate) ); abort "Failed to resolve any candidates. No reason to proceed" unless +@candidates; my @built = $client.build(@candidates); my (:@pass, :@fail) := @built.classify: {.?build-results.grep(*.so).elems ?? !! } say "!!!> Build failure: {.as}{?($verbosity >= VERBOSE)??' at '~.dist.path!!''}" for @fail; exit ?@fail ?? 1 !! ?@pass ?? 0 !! 255; } #| Fetch, extract, build, test and install distributions multi sub MAIN( 'install', Bool :$fetch = True, Bool :$build = True, Bool :$test = True, Bool :$depends = True, Bool :$build-depends = $build, Bool :$test-depends = $test, Bool :$force, Bool :$force-resolve = $force, Bool :$force-fetch = $force, Bool :$force-extract = $force, Bool :$force-build = $force, Bool :$force-test = $force, Bool :$force-install = $force, Int :$timeout, Int :$fetch-timeout = %*ENV // $timeout // 600, Int :$extract-timeout = %*ENV // $timeout // 3600, Int :$build-timeout = %*ENV // $timeout // 3600, Int :$test-timeout = %*ENV // $timeout // 3600, Int :$install-timeout = %*ENV // $timeout // 3600, Int :$degree, Int :$fetch-degree = %*ENV || $degree || 5, # default different from Zef::Client Int :$test-degree = %*ENV || $degree || 1, Bool :$dry, Bool :$upgrade, Bool :$deps-only, Bool :$serial, Bool :$contained, :$update, :$exclude, :to(:$install-to) = $CONFIG, *@wants ($, *@) ) { @wants .= map: *.&str2identity; my (:@paths, :@uris, :@identities) := @wants.classify: -> $wanted { $wanted ~~ /^[\. | \/]/ ?? !! ($wanted ~~ /^\w+:/ && $wanted.IO.is-absolute) ?? # for the rare e.g. C:\foo type paths !! ?Zef::Identity.new($wanted) ?? !! (my $uri = uri($wanted) and !$uri.is-relative) ?? !! abort("Don't understand identity: {$wanted}"); } my $client = get-client( :config($CONFIG), :$update, :exclude($exclude.grep(*.defined).map({ Zef::Distribution::DependencySpecification.new($_) })), :$depends, :$test-depends, :$build-depends, :$force-resolve, :$force-fetch, :$force-extract, :$force-build, :$force-test, :$force-install, :$fetch-timeout, :$extract-timeout, :$build-timeout, :$test-timeout, :$install-timeout, :$fetch-degree, :$test-degree, ); my CompUnit::Repository @at = $install-to.map(*.&str2cur); # LOCAL PATHS abort "The following were recognized as file paths but don't exist as such - {@paths.grep(!*.IO.e)}" if +@paths.grep(!*.IO.e); my (:@wanted-paths, :@skip-paths) := @paths\ .classify: {$client.is-installed(Zef::Distribution::Local.new($_).identity, :@at) ?? !! } say "The following local path candidates are already installed: {@skip-paths.join(', ')}"\ if ($verbosity >= VERBOSE) && +@skip-paths; my @requested-paths = ?$force-install ?? @paths !! @wanted-paths; my @path-candidates = @requested-paths.map(*.&path2candidate); # URIS my @uri-candidates-to-check = $client.fetch( @uris.map({ Candidate.new(:as($_), :uri($_)) }) ) if +@uris; abort "No candidates found matching uri: {@uri-candidates-to-check.join(', ')}" if +@uris && +@uri-candidates-to-check == 0; my (:@wanted-uris, :@skip-uris) := @uri-candidates-to-check\ .classify: {$client.is-installed($_.dist.identity, :@at) ?? !! } say "The following uri candidates are already installed: {@skip-uris.map(*.as).join(', ')}"\ if ($verbosity >= VERBOSE) && +@skip-uris; my @requested-uris = (?$force-install ?? @uri-candidates-to-check !! @wanted-uris)\ .grep: { $_ ~~ none(@path-candidates.map(*.dist.identity)) } my @uri-candidates = @requested-uris; # IDENTITIES my (:@wanted-identities, :@skip-identities) := @identities\ .classify: {$client.is-installed($_, :@at) ?? !! } say "The following candidates are already installed: {@skip-identities.join(', ')}"\ if ($verbosity >= VERBOSE) && +@skip-identities; my @requested-identities = (?$force-install ?? @identities !! @wanted-identities)\ .grep: { $_ ~~ none(@uri-candidates.map(*.dist.identity)) } my @requested = $client.find-candidates(:$upgrade, @requested-identities) if +@requested-identities; abort "No candidates found matching identity: {@requested-identities.join(', ')}"\ if +@requested-identities && +@requested == 0; my @prereqs = $client.find-prereq-candidates(:skip-installed(not $contained), |@path-candidates, |@uri-candidates, |@requested)\ if +@path-candidates || +@uri-candidates || +@requested; my @candidates = grep *.defined, ?$deps-only ?? @prereqs !! (|@path-candidates, |@uri-candidates, |@requested, |@prereqs); unless +@candidates { note("All candidates are currently installed"); exit(0) if $deps-only; abort("No reason to proceed. Use --force-install to continue anyway", 0) unless $force-install; } my (:@local, :@remote) := @candidates.classify: {.dist ~~ Zef::Distribution::Local ?? !! } my @fetched = grep *.so, |@local, ($client.fetch(@remote).Slip if +@remote && $fetch); my CompUnit::Repository @to = $install-to.map(*.&str2cur); 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; exit +@installed && +@installed == +@candidates && +@fail == 0 ?? 0 !! 1; } #| Uninstall distributions multi sub MAIN( 'uninstall', :from(:$uninstall-from), *@identities ($, *@) ) { my $client = get-client(:config($CONFIG)); my CompUnit::Repository @from = $uninstall-from.grep(*.defined).map(*.&str2cur); my @uninstalled = $client.uninstall( :@from, @identities.map(*.&str2identity) ); my @fail = @identities.grep(* !~~ any(@uninstalled.map(*.as))); if +@uninstalled == 0 && +@fail { note("!!!> Found no matching candidates to uninstall"); exit 1; } for @uninstalled.classify(*.from).kv -> $from, $candidates { say "===> Uninstalled from $from"; say "$_" for |$candidates>>.dist>>.identity; } say "!!!> Failed to uninstall distributions: {@fail.join('. ')}" if +@fail; exit +@fail ?? 1 !! 0; } #| Get a list of possible distribution candidates for the given terms multi sub MAIN('search', Int :$wrap = False, :$update, *@terms ($, *@)) { my $client = get-client(:config($CONFIG), :$update); my @results = $client.search(@terms).unique(:as({ .from ~ ' ' ~ .dist.identity })); say "===> Found " ~ +@results ~ " results"; my @rows = eager gather for @results -> $candi { FIRST { take [] } take [ $++, $candi.from, $candi.dist.identity, ($candi.dist.meta // '') ]; } print-table(@rows, :$wrap); exit 0; } #| A list of available modules from enabled repositories multi sub MAIN('list', Int :$max, :$update, Bool :i(:$installed), *@at) { my $client = get-client(:config($CONFIG), :$update); my $found := ?$installed ?? $client.list-installed(@at.map(*.&str2cur)) !! $client.list-available(@at); my $range := defined($max) ?? 0..+$max !! *; my %locations = $found[$range].classify: -> $candi { $candi.from } for %locations.kv -> $from, $candis { note "===> Found via {$from}"; for $candis.sort(*.dist.identity) -> $candi { say "{$candi.dist.identity}"; say "#\t{$_}" for @($candi.dist.provides.keys.sort if ?($verbosity >= VERBOSE)); } } exit 0; } #| Upgrade installed distributions (BETA) multi sub MAIN( 'upgrade', Bool :$fetch = True, Bool :$build = True, Bool :$test = True, Bool :$depends = True, Bool :$test-depends = $test, Bool :$build-depends = $build, Bool :$force, Bool :$force-resolve = $force, Bool :$force-fetch = $force, Bool :$force-extract = $force, Bool :$force-build = $force, Bool :$force-test = $force, Bool :$force-install = $force, Int :$timeout, Int :$fetch-timeout = %*ENV // $timeout // 600, Int :$extract-timeout = %*ENV // $timeout // 3600, Int :$build-timeout = %*ENV // $timeout // 3600, Int :$test-timeout = %*ENV // $timeout // 3600, Int :$install-timeout = %*ENV // $timeout // 3600, Int :$degree, Int :$fetch-degree = %*ENV || $degree || 5, # default different from Zef::Client, Int :$test-degree = %*ENV || $degree || 1, Bool :$dry, Bool :$update, Bool :$serial, :$exclude, :to(:$install-to) = $CONFIG, *@identities ) { # XXX: This is a very inefficient prototype. Not sure how to handle an 'upgrade' when # multiple versions are already installed, so for now an 'upgrade' always means we # leave the previous version installed. my $client = get-client( :config($CONFIG), :exclude($exclude.grep(*.defined).map({ Zef::Distribution::DependencySpecification.new($_) })), :$depends, :$test-depends, :$build-depends, :$force-resolve, :$force-fetch, :$force-extract, :$force-build, :$force-test, :$force-install, :$fetch-timeout, :$extract-timeout, :$build-timeout, :$test-timeout, :$install-timeout, :$fetch-degree, :$test-degree ); my @missing = @identities.grep: { not $client.is-installed($_) }; abort "Can't upgrade identities that aren't installed: {@missing.join(', ')}" if +@missing; my @installed = $client.list-installed($install-to.map(*.&str2cur))\ .sort(*.dist.ver).sort(*.dist.api).reverse\ .unique(:as({"{.dist.name}:auth<{.dist.auth-matcher}>"})); my @requested = +@identities ?? $client.find-candidates(@identities.map(*.&str2identity)) !! $client.find-candidates(@installed.map(*.dist.clone(ver => "*")).map(*.identity).unique); my (:@upgradable, :@current, :@unknown) := @requested.classify: -> $candi { my $latest-installed = @installed.grep({ .dist.name eq $candi.dist.name })\ .sort({ .dist.auth-matcher ne $candi.dist.auth-matcher }).head; # this is to handle auths that changed. need to find a better way... !$latest-installed ?? !! (($latest-installed.dist.ver <=> $candi.dist.ver) === Order::Less) ?? !! ; } note "Unsure of how to handle the following distributions: {@unknown.map(*.dist.identity),join(',')}" if +@unknown; abort("All requested distributions are already at their latest versions", 0) unless +@upgradable; say "The following distributions will be upgraded: {@upgradable.map(*.dist.identity).join(', ')}"; my &installer = &MAIN.assuming( :$depends, :$test-depends, :$build-depends, :$test, :$fetch, :$build, :$update, :$exclude, :$install-to, :$force-resolve, :$force-fetch, :$force-build, :$force-test, :$force-install, :$fetch-timeout, :$extract-timeout, :$build-timeout, :$test-timeout, :$fetch-degree, :$test-degree, :$dry, :$serial, ); # Sort these ahead of time so they can be installed individually by passing # the .uri instead of the identities (which would require another search) my @sorted-candidates = $client.sort-candidates(@upgradable); say "===> Updating: " ~ @sorted-candidates.map(*.dist.identity).join(', '); my (:@upgraded, :@failed) := @sorted-candidates.map(*.uri).classify: -> $uri { my &*EXIT = sub ($code) { return $code == 0 ?? True !! False }; try { &installer('install', $uri) } ?? !! ; } abort "!!!> Failed upgrading *all* modules" unless +@upgraded; say "!!!> Some modules failed to update: {@failed.map(*.dist.identity).join(', ')}" if +@failed; exit +@upgraded < +@upgradable ?? 1 !! 0; } #| View dependencies of a distribution multi sub MAIN( 'depends', $identity, Bool :$depends = True, Bool :$test-depends = True, Bool :$build-depends = True, ) { # TODO: refactor this stuff which was copied from 'install' # So really we just need a function to handle separating the different identity types # and optionally delivering a message for each section. my @wants = ($identity,).map: *.&str2identity; my (:@paths, :@uris, :@identities) := @wants.classify: -> $wanted { $wanted ~~ /^[\. | \/]/ ?? !! ?Zef::Identity.new($wanted) ?? !! (my $uri = uri($wanted) and !$uri.is-relative) ?? !! abort("Don't understand identity: {$wanted}"); } my $client = Zef::Client.new(:config($CONFIG), :$depends, :$test-depends, :$build-depends,); abort "The following were recognized as file paths but don't exist as such - {@paths.grep(!*.IO.e)}" if +@paths.grep(!*.IO.e); my @path-candidates = @paths.map(*.&path2candidate); my @uri-candidates-to-check = $client.fetch( @uris.map({ Candidate.new(:as($_), :uri($_)) }) ) if +@uris; abort "No candidates found matching uri: {@uri-candidates-to-check.join(', ')}" if +@uris && +@uri-candidates-to-check == 0; my @uri-candidates = @uri-candidates-to-check.grep: { $_ ~~ none(@path-candidates.map(*.dist.identity)) } my @requested-identities = @identities.grep: { $_ ~~ none(@uri-candidates.map(*.dist.identity)) } my @requested = $client.find-candidates(@requested-identities) if +@requested-identities; abort "No candidates found matching identity: {@requested-identities.join(', ')}"\ if +@requested-identities && +@requested == 0; my @prereqs = $client.find-prereq-candidates(:!skip-installed, |@path-candidates, |@uri-candidates, |@requested)\ if +@path-candidates || +@uri-candidates || +@requested; .say for @prereqs.map(*.dist.identity); } #| View direct reverse dependencies of a distribution multi sub MAIN( 'rdepends', $identity, Bool :$depends = True, Bool :$test-depends = True, Bool :$build-depends = True, ) { my $client = get-client(:config($CONFIG), :$depends, :$test-depends, :$build-depends); .dist.identity.say for $client.list-rev-depends($identity); exit 0; } #| Lookup locally installed distributions by short-name, name-path, or sha1 id multi sub MAIN('locate', $identity, Bool :$sha1) { my $client = get-client(:config($CONFIG)); if !$sha1 { if $identity.ends-with('.rakumod' | '.pm6' | '.pm') { my @candis = $client.list-installed.grep({ .dist.meta.values.grep({parse-value($_) eq $identity}).so; }); for @candis -> $candi { LAST exit 0; NEXT say ''; if $candi { # This is relying on implementation details for compatibility purposes. It will # use something more appropriate sometime in 2019. my %meta = $candi.dist.meta; %meta = %meta.map({ $_.key => parse-value($_.value) }).hash; my $lib = %meta.hash.antipairs.hash.{$identity}; my $lib-sha1 = nqp::sha1($lib ~ CompUnit::Repository::Distribution.new($candi.dist).id); say "===> From Distribution: {~$candi.dist}"; say "{$lib} => {$candi.from.prefix.child('sources').child($lib-sha1)}"; } } } elsif $identity.starts-with('bin/' | 'resources/') { my @candis = $client.list-installed.grep({ .dist.meta.first({.key eq $identity}).so }); for @candis -> $candi { LAST exit 0; NEXT say ''; if $candi { my $libs = $candi.dist.meta; my $lib = $libs.first({.key eq $identity}); say "===> From Distribution: {~$candi.dist}"; say "{$identity} => {$candi.from.prefix.child('resources').child($lib.value)}"; } } } elsif $client.resolve($identity) -> @candis { for @candis -> $candi { LAST exit 0; NEXT say ''; say "===> From Distribution: {~$candi.dist}"; my $source-prefix = $candi.from.prefix.child('sources'); my $source-path = $source-prefix.child(nqp::sha1($identity ~ CompUnit::Repository::Distribution.new($candi.dist).id)); say "{$identity} => {$source-path}" if $source-path.IO.f; } } } else { my @candis = $client.list-installed.grep(-> $candi { # This is relying on implementation details for compatibility purposes. It will # use something more appropriate sometime in 2019. my %meta = $candi.dist.meta; %meta = %meta.map({ $_.key => parse-value($_.value) }).hash; my @source_files = %meta.map({ nqp::sha1($_.key ~ CompUnit::Repository::Distribution.new($candi.dist).id) }); my @resource_files = %meta.values.first({$_ eq $identity}); $identity ~~ any(grep *.defined, flat @source_files, @resource_files); }); for @candis -> $candi { LAST exit 0; NEXT say ''; if $candi { my %meta = $candi.dist.meta; %meta = %meta.map({ $_.key => parse-value($_.value) }).hash; my %sources = %meta.map({ $_.key => nqp::sha1($_.key ~ CompUnit::Repository::Distribution.new($candi.dist).id) }).hash; say "===> From Distribution: {~$candi.dist}"; $identity ~~ any(%sources.values) ?? (say "{$_} => {$candi.from.prefix.child('sources').child($identity)}" for %sources.antipairs.hash{$identity}) !! (say "{.key} => {.value}" for $candi.dist.meta.first({.value eq $identity})); } } } say "!!!> Nothing located"; exit 1; } #| Detailed distribution information multi sub MAIN('info', $identity, :$update, Int :$wrap = False) { my $client = get-client(:config($CONFIG), :$update); my $latest-installed-candi = $client.resolve($identity).head; my @remote-candis = $client.search($identity, :strict, :max-results(1)); abort "!!!> Found no candidates matching identity: {$identity}" unless $latest-installed-candi || +@remote-candis; my $candi := ($latest-installed-candi, |@remote-candis).grep(*.defined).sort(*.dist.ver).sort(*.dist.api).tail; my $dist := $candi.dist; say "- Info for: $identity"; say "- Identity: {$dist.identity}"; say "- Recommended By: {$candi.from}"; say "- Installed: {$latest-installed-candi??$latest-installed-candi.dist.identity eq $dist.identity??qq|Yes|!!qq|Yes, as $latest-installed-candi.dist.identity()|!!'No'}"; say "Author:\t {$dist.author}" if $dist.author; say "Description:\t {$dist.description}" if $dist.description; say "License:\t {$dist.meta}" if $dist.meta; say "Source-url:\t {$dist.source-url}" if $dist.source-url; my @provides = $dist.provides.sort(*.key.chars); say "Provides: {@provides.elems} modules"; if ?($verbosity >= VERBOSE) { my @rows = eager gather for @provides -> $lib { FIRST { take [] } my $module-name = $lib.key; my $name-path = parse-value($lib.value); take [ $module-name, $name-path ]; } print-table(@rows, :$wrap); } if $dist.meta { say "Support:"; for $dist.meta.kv -> $k, $v { say "# $k:\t$v"; } } my @deps = (|$dist.depends-specs, |$dist.test-depends-specs, |$dist.build-depends-specs).grep(*.defined).unique; say "Depends: {@deps.elems} items"; if ?($verbosity >= VERBOSE) { my @rows = eager gather for @deps -> $spec { FIRST { take [] } my $row = [ ++$, $spec.name, ($client.is-installed($spec) ?? '✓' !! '')]; take $row; } print-table(@rows, :$wrap); } exit 0; } #| Browse a distribution's available support urls (homepage, bugtracker, source) multi sub MAIN('browse', $identity, $url-type where * ~~ any(), Bool :$open = True) { my $client = get-client(:config($CONFIG)); my $candi = $client.resolve($identity).head || $client.search($identity, :strict, :max-results(1))[0]\ || abort "!!!> Found no candidates matching identity: {$identity}"; my %support = $candi.dist.meta; my $url = %support{$url-type}; my @has-urls = grep { %support{$_} }, ; unless $url && $url.starts-with('http://' | 'https://') { say "'browse' urls supported by $identity: {+@has-urls??@has-urls.join(',')!!'none'}"; exit 255; } say $url; my @cmd = $*DISTRO.is-win ?? !! $*VM.osname eq 'darwin' ?? !! ; run( |@cmd, $url ) if $open; } #| Download a single module and change into its directory multi sub MAIN('look', $identity) { my $client = get-client(:config($CONFIG)); my @candidates = $client.find-candidates( str2identity($identity) ); abort "Failed to resolve any candidates. No reason to proceed" unless +@candidates; my (:@remote, :@local) := @candidates.classify: {.dist !~~ Zef::Distribution::Local ?? !! } my $fetched = @local[0] || $client.fetch(@remote[0])[0] || abort "Failed to fetch candidate: $identity"; my $dist-path = $fetched.dist.path; say "===> Shelling into directory: {$dist-path}"; exit so shell(%*ENV // %*ENV // %*ENV, :cwd($dist-path)) ?? 0 !! 1; } #| Smoke test multi sub MAIN( 'smoke', Bool :$fetch = True, Bool :$build = True, Bool :$test = True, Bool :$depends = True, Bool :$test-depends = $test, Bool :$build-depends = $build, Bool :$force, Bool :$force-resolve = $force, Bool :$force-fetch = $force, Bool :$force-extract = $force, Bool :$force-build = $force, Bool :$force-test = $force, Bool :$force-install = $force, Int :$timeout, Int :$fetch-timeout = %*ENV // $timeout // 600, Int :$extract-timeout = %*ENV // $timeout // 3600, Int :$build-timeout = %*ENV // $timeout // 3600, Int :$test-timeout = %*ENV // $timeout // 3600, Int :$install-timeout = %*ENV // $timeout // 3600, Int :$degree, Int :$fetch-degree = %*ENV || $degree || 5, # default different from Zef::Client, Int :$test-degree = %*ENV || $degree || 1, Bool :$update, Bool :$upgrade, Bool :$dry, Bool :$serial, :$exclude, :to(:$install-to) = $CONFIG, ) { my $client = get-client( :config($CONFIG), :exclude($exclude.grep(*.defined).map({ Zef::Distribution::DependencySpecification.new($_) })), :$depends, :$test-depends, :$build-depends, :$force-resolve, :$force-fetch, :$force-extract, :$force-build, :$force-test, :$force-install, :$fetch-timeout, :$extract-timeout, :$build-timeout, :$test-timeout, :$install-timeout, :$fetch-degree, :$test-degree, ); my @identities = $client.list-available.map(*.dist.identity).unique; say "===> Smoke testing with {+@identities} distributions..."; my &installer = &MAIN.assuming( 'install', :$depends, :$test-depends, :$build-depends, :$test, :$fetch, :$build, :$update, :$upgrade, :$exclude, :$install-to, :$force-resolve, :$force-fetch, :$force-build, :$force-test, :$force-install, :$fetch-timeout, :$extract-timeout, :$build-timeout, :$test-timeout, :$fetch-degree, :$test-degree, :$dry, :$serial, ); for @identities -> $identity { my &*EXIT = sub ($code) { return $code == 0 ?? True !! False }; my $result = try installer($identity); say "===> Smoke result for {$identity}: {?$result??'OK'!!'NOT OK'}"; } exit 0; } #| Update package indexes multi sub MAIN('update', *@names) { my $client = get-client(:config($CONFIG), :update(@names || Bool::True)); if $verbosity >= VERBOSE { my $plugins := $client.recommendation-manager.plugins(|@names).map(*.Slip).grep(*.defined); my $rows := $plugins.map({ [.id, .available.elems] }); print-table( [["Content Storage", "Distribution Count"], |$rows], wrap => True ); } exit 0; } #| Nuke module installations (site, home) and repositories from config (RootDir, StoreDir, TempDir) multi sub MAIN('nuke', Bool :$confirm, *@names ($, *@)) { my sub dir-delete($dir) { my @deleted = grep *.defined, try delete-paths($dir, :f, :d, :r); say "Deleted " ~ +@deleted ~ " paths from $dir/*"; } my sub confirm-delete(*@dirs) { for @dirs -> $dir { next() R, say "$dir does not exist. Skipping..." unless $dir.IO.e; given prompt("Delete {$dir.IO.absolute}/* [y/n]: ") { when any() { dir-delete($dir) } when any() { say "Skipping..." } default { say "Invalid entry (enter Y or N)"; redo } } } } my @config-keys = ; my @config-dirs = $CONFIG<<{@names (&) @config-keys}>>.map(*.IO.absolute).sort; my @curli-dirs = @names\ .grep(* !~~ any(@config-keys))\ .map(*.&str2cur)\ .grep(*.?can-install)\ .map(*.prefix.absolute); my @delete = |@curli-dirs, |@config-dirs; $confirm === False ?? @delete.map(*.&dir-delete) !! confirm-delete( @delete ); exit 0; } #| Detailed version information multi sub MAIN(Bool :version($) where .so) { say $VERSION // 'unknown'; exit 0; } multi sub MAIN(Bool :h(:help($))) { note qq:to/END_USAGE/ Zef - Raku / Perl6 Module Management USAGE zef [flags|options] command [args] zef --version COMMANDS install Install specific dependencies by name or path uninstall Uninstall specified distributions test Run tests on a given module's path fetch Fetch and extract module's source build Run the Build.pm in a given module's path look Fetch followed by shelling into the module's path update Update package indexes for repositories upgrade (BETA) Upgrade specific distributions (or all if no arguments) search Show a list of possible distribution candidates for the given terms info Show detailed distribution information browse Open browser to various support urls (homepage, bugtracker, source) list List known available distributions, or installed distributions with `--installed` depends List all direct and transitive dependencies for a given identity rdepends List all distributions directly depending on a given identity locate Lookup installed module information by short-name, name-path, or sha1 (with --sha1 flag) smoke Run smoke testing on available modules nuke Delete directory/prefix containing matching configuration path or CURLI name OPTIONS --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, install ) --[phase]-degree=[int] Number of simultaneous distributions/jobs to process for the corresponding phase ( phase : fetch, test ) --update Force a refresh for all module indexes --update=[ecosystem] Force a refresh for a specific ecosystem module index --/update Skip refreshing all module indexes --/update=[ecosystem] Skip refreshing for a specific ecosystem module index ENV OPTIONS ZEF_[phase]_TIMEOUT See --[phase]-timeout ( phases: FETCH, BUILD, TEST, INSTALL ) ZEF_[phase]_DEGREE See --[phase]-degree ( phases: FETCH, TEST ) VERBOSITY LEVEL (from least to most verbose) --error, --warn, --info (default), --verbose, --debug FLAGS --deps-only Install only the dependency chains of the requested distributions --dry Run all phases except the actual installations --serial Install each dependency after passing testing and before building/testing the next dependency --contained (BETA) Install all transitive and direct dependencies regardless if they are already installed globally --/test Skip the testing phase --/build Skip the building phase --/depends Do not fetch runtime dependencies --/test-depends Do not fetch test dependencies --/build-depends Do not fetch build dependencies FORCE FLAGS Ignore errors occuring during the corresponding phase: --force-resolve --force-fetch --force-extract --force-build --force-test --force-install CONFIGURATION {$CONFIG.IO.absolute} Enable or disable plugins that match the configuration that has field `short-name` that matches -- # `--fez` Enable plugin with short-name `fez` --/ # `--/fez` Disable plugin with short-name `fez` END_USAGE } proto sub abort(|) {*} multi sub abort(Int $exit-code, Str $str) { samewith($str, $exit-code) } multi sub abort(Str $str, Int $exit-code = 255) { say $str; exit $exit-code } # Filter/mutate out verbosity flags from @*ARGS and return a verbosity level my sub preprocess-args-verbosity-mutate(*@_) { my (:@log-level, :@filtered-args) := @_.classify: { $_ ~~ any(<--fatal --error --warn --info -v --verbose --debug --trace>) ?? !! ; } @*ARGS = @filtered-args; do given any(@log-level) { when '--fatal' { FATAL } when '--error' { ERROR } when '--warn' { WARN } when '--info' { INFO } when '--verbose' { VERBOSE } when '-v' { VERBOSE } when '--debug' { DEBUG } when '--trace' { TRACE } default { INFO } } } # Second crack at cli config modification # Currently only uses Bools `--name` and `--/name` to enable and disable a plugin # Note that `name` can match the config plugin key `short-name` or `module` # * Now also removes --config-path $path parameters # TODO: Turn this into a more general getopts my sub preprocess-args-config-mutate(*@args) { # get/remove --config-path=xxx # MUTATES @*ARGS my Str $config-path-from-args; for |@args.flatmap(*.split(/\=/, 2)).rotor(2 => -1, :partial) { $config-path-from-args = ~$_[1] if $_[0] eq '--config-path' && $_[1]; LAST { @*ARGS = eager gather for |@args.kv -> $key, $value { take($value) unless $value.starts-with('--config-path') || ($key > 0 && @args[$key - 1] eq '--config-path') } } } my $chosen-config-file = $config-path-from-args // %*ENV // Zef::Config::guess-path(); # Keep track of the original path so we can show it on the --help usage :-/ my $config = do { # The .Str.IO thing is due to a weird rakudo bug I can't figure out . # A bare .IO will complain that its being called on a type Any (not true) my $IO = $chosen-config-file.Str.IO; my %hash = Zef::Config::parse-file($chosen-config-file).hash; class :: { has $.IO; has %.hash handles ; }.new(:%hash, :$IO); } # - Move named options to start of @*ARGS so the git familiar style of options after positional parameters works # - get/remove --$short-name and --/$short-name where $short-name is a value in the config file my $plugin-lookup := Zef::Config::plugin-lookup($config.hash); for @*ARGS -> $arg { state @positional; state @named; LAST { @*ARGS = flat @named, @positional; } my $arg-as = $arg.subst(/^["--" | "--\/"]/, ''); my $enabled = $arg.starts-with('--/') ?? 0 !! 1; $arg.starts-with('-') ?? $arg-as ~~ any($plugin-lookup.keys) ?? (for |$plugin-lookup{$arg-as} -> $p { $p = $enabled }) !! @named.append($arg) !! @positional.append($arg); } $config; } my sub get-client(*%_) { my $client = Zef::Client.new(|%_); my $logger = $client.logger; my $stdout = $logger.Supply.grep({ . <= $verbosity }); my $reporter = $logger.Supply.grep({ (. == TEST && . == AFTER) || (. == ERROR && . == AFTER) || (. == FATAL && . == AFTER) }); $stdout.tap: -> $m { given $m. { when BEFORE { say "===> {$m.}" } when AFTER { say "===> {$m.}" } default { # Prefix output with a name that references its source since # lines may be coming from many sources at once. my $line-prefix = ((.dist??.dist.meta!!Nil) // .as) with $m.; say($line-prefix ?? "[$line-prefix] $_" !! $_) for $m..lines; } } } $reporter.grep({ . }).tap: { $client.reporter.report(., :$logger); }; with %_ { my @plugins = $client.recommendation-manager.plugins.map(*.Slip).grep(*.defined); if %_ === Bool::False { @plugins.map({ try .auto-update = False }); } elsif %_ === Bool::True { @plugins.race(:batch(1)).map(*.?update); } else { @plugins.grep({.short-name ~~ any(%_.grep(*.not))}).map({ try .auto-update = False }); @plugins.grep({.short-name ~~ any(%_.grep(*.so))}).race(:batch(1)).map(*.?update); } } $client; } # maybe its a name, maybe its a spec/path. either way Zef::App methods take a CURs, not strings my sub str2cur($target) { my $named-repo = CompUnit::RepositoryRegistry.repository-for-name($target); return $named-repo if $named-repo; # first try 'site', then try 'home' if $target eq 'auto' { state $cur = first { .can-install() }, map { CompUnit::RepositoryRegistry.repository-for-name($_) }, ; return $cur if $cur; } # Technically a path without any short-id# is a CURFS, but now it needs to be explicitly declared file# # so that the more common case can be used without the prefix (inst#). This only applies when the path # exists, so that short-names (site, home) that don't exist still throw errors instead of creating a directory. my $spec-target = $target ~~ m/^\w+\#.*?[\. | \/]/ ?? $target !! $target.IO.e ?? "inst#{$target}" !! $target; return CompUnit::RepositoryRegistry.repository-for-spec(~$spec-target, :next-repo($*REPO)); } my sub path2candidate($path) { Candidate.new( as => $path, uri => $path.IO.absolute, dist => Zef::Distribution::Local.new($path), ) } # prints a table with rows and columns. expects a header row. # automatically adjusts column widths, as well as `yada`ing # any characters on a line past $max-width my sub print-table(@rows, Int :$wrap = 120) { # this ugly thing is so users can pass in Bool or Int as a MAIN argument my $max-width = $wrap === Bool::True ?? 0 !! $wrap; # returns formatted row my sub _row2str (@widths, @cells, Int :$max) { my $format = @widths.map({"%-{$_}s"}).join('|'); my $str = sprintf( $format, @cells.map({ $_ // '' }) ); return $str unless ?$max && $str.chars > $max; my $cutoff = $str.substr(0, $max || $str.chars); return $cutoff unless $cutoff.chars > 3; return ($cutoff.substr(0,*-3) ~ '...') if $cutoff.substr(*-3,3) ~~ /\S\S\S/; return ($cutoff.substr(0,*-2) ~ '..') if $cutoff.substr(*-2,2) ~~ /\S\S/; return ($cutoff.substr(0,*-1) ~ '.') if $cutoff.substr(*-1,1) ~~ /\S/; return $cutoff; } # Iterate over ([1,2,3],[2,3,4,5],[33,4,3,2]) to find the longest string in each column my sub _get_column_widths ( *@rows ) { return @rows[0].keys.map: { @rows>>[$_]>>.chars.max } } my @widths = _get_column_widths(@rows); my @fixed-rows = @rows.map: { _row2str(@widths, @$_, :max($max-width)) } if +@fixed-rows { my $width = [+] _get_column_widths(@fixed-rows); my $sep = '-' x $width; say "{$sep}\n{@fixed-rows[0]}\n{$sep}"; .say for @fixed-rows[1..*]; say $sep; } } my sub parse-value($str-or-kv) { do given $str-or-kv { when Str { $_ } when Hash { $_.keys[0] } when Pair { $_.key } } } } zef-0.13.8/lib/Zef/Client.rakumod000066400000000000000000001500651422013762000164670ustar00rootroot00000000000000use Zef; use Zef::Distribution; use Zef::Distribution::Local; use Zef::Distribution::DependencySpecification; use Zef::Repository; use Zef::Utils::FileSystem; use Zef::Fetch; use Zef::Extract; use Zef::Build; use Zef::Test; use Zef::Install; use Zef::Report; class Zef::Client { =begin pod =title class Zef::Client =subtitle Task coordinator for raku distribution installation workflows =head1 Synopsis =begin code :lang use Zef::Client; use Zef::Config; # Get default config (see resources/config.json for more details on config options) my $config-file = Zef::Config::guess-path(); my $config = Zef::Config::parse-file($config-file); # Create a client my $client = Zef::Client.new(:$config); # Add some basic logging so there is output to see my $logger = $client.logger.Supply; $logger.tap: -> $m { say $m. } # Use the client to resolve the requested candidates my @requested-candidates = $client.find-candidates('Distribution::Common::Remote'); my @dependencies-candidates = $client.find-prereq-candidates(|@requested-candidates); my @candidates = |@requested-candidates, |@dependencies-candidates; say "Found " ~ @candidates.elems ~ " candidates..."; # Use the client to fetch/build/test/install candidates to the default raku repository my CompUnit::Repository @install-to = CompUnit::RepositoryRegistry.repository-for-name('site'); $client.make-install(|@candidates, :to(@install-to)); say "Installed candidates!"; =end code =head1 Description A class that coordinates the various stages of a raku distribution installation workflow based on various configuration parameters. Additionally it provides slightly higher level facilities for fetching, extracting, etc, than the e.g. C, C, etc modules it uses underneath. For example C may run an extraction step unlike C, since the former is in the context of a distribution (i.e. we want the distribution at the specific commit/tag, not the HEAD immediately after fetching). =head1 Methods =head2 method find-candidates method find-candidates(*@identities ($, *@) --> Array[Candidate]) Searches all repositories via C and returns a matching C / distribution for each supplied identity. Generally this is used to find the top level distributions requested, such as C in C. =head2 method find-candidates method find-prereq-candidates(Bool :$skip-installed = True, *@candis ($, *@) --> Array[Candidate]) Similar to C but returns matching a matching C for each dependency of the supplied identities. Generally this is used to recursively discover and determine the dependencies of the identities requested. If C<$skip-installed> is set to C it will potentially install a newer version of an already installed matching dependency (without uninstalling the previous version). It also skips any identity matching of C<@.ignore>, which allows getting past an unresolvable dependency ala `zef install Inline::Perl5 --ignore="perl"`. Returns an C of C that fulfill the dependency requirements of C<@identities>. =head2 method search method search(*@identities ($, *@), *%fields, Bool :$strict = False --> Array[Candidate]) Resolves each identity in C<@identities> to all of its matching C from all backends via C (with C<$max-results> applying to each individual backend). If C<$strict> is C then it will consider partial matches on module short-names (i.e. 'zef search HTTP' will get results for e.g. C). =head2 method fetch method fetch(*@candidates ($, *@) --> Array[Candidate]) Fetches a distribution from some location, and unpacks/extracts it to a temporary location to be used be cached, tested, installed, etc. It effective combines the functionality of C and C into a single method as there isn't yet a useful reason to have workflows that work with compressed archives/packages. Fetches up to C<$.fetch-degree> different C<@candidates> in parallel. Anytime a distribution is fetched it will call C<.store(@distributions)> on any C that supports it (usually just C). File are saved to the C setting in C, and extracted to the C<$.cache> directory (the C setting in C). Returns an C of C containing the successfully fetched results. =head2 method build method build(*@candidates ($, *@) --> Array[Candidate]) Runs the build process on each C<@candidates> that the backends for C know how to process. Builds up to C<$.build-degree> different C<@candidates> in parallel. Returns an C of C with each C<.build-results> set appropriately. =head2 method test method test(*@candidates ($, *@) --> Array[Candidate]) Runs the test process on each C<@candidates> via the backends of C. Tests up to C<$.test-degree> different C<@candidates> in parallel. Returns an C of C with each C<.test-results> set appropriately. =head2 method uninstall method uninstall(CompUnit::Repository :@from!, *@identities --> Array[Candidate]) Searches each C in C<@from> for each C<@identities> and uninstalls any matching distributions. For instance uninstalling C could potentially uninstall multiple versions, whereas uninstall C would only uninstall that specific version. Returns an C containing each uninstalled C. =head2 method install method install(:@curs, *@candidates ($, *@) --> Array[Candidate]) Install a C containing a C to each C in C<@curs>. Returns an C containing each successfully installed C. =head2 method make-install method make-install(CompUnit::Repository :@to!, Bool :$fetch = True, Bool :$build = True, Bool :$test = True, Bool :$dry, Bool :$serial, *@candidates ($, *@), *%_) The 'do everything but resolve dependencies' method. You essentially figure out all the C you need to install (dependencies, etc) and pass them to this method. Its similar to C except it also handles calling C (if C<$fetch> is C), C (if C<$build> is C), and C (if <$test> is C). If C<$dry> is C then the final step of calling C (which moves the modules to where C will see them) will be skipped. If <$serial> is C then each C will be installed after it passes its own tests (instead of the default behavior of only installing if all C, including dependencies, pass their tests). =head2 method list-rev-depends method list-rev-depends($identity, Bool :$indirect --> Array[Candidate]) Return an C of C of all distribution that directly depend on C<$identity>. If C<$indirect> is C then it additionally returns distributions that indirectly / transitively depend on C<$identity> =head2 method list-available method list-available(*@recommendation-manager-names --> Array[Candidate]) Returns an C of C for every distribution from every repository / recommendation-manager with a name (as set in C) matching any of those in C<@recommendation-manager-names> (or all repositories if no names are supplied). Note some non-standard repositories may not support listing all available distributions. =head2 method list-installed method list-installed(*@curis --> Array[Candidate]) Returns an C of C for each Raku distribution installed to each C C<@curis> (or all known C if no C<@curis> are supplied). =head2 method list-leaves method list-leaves(--> Array[Candidate]) Returns an C of C for each installed distributions that nothing else appears to depend on. =head2 method list-dependencies method list-dependencies(*@candis --> Array[DependencySpecification]) Returns an C of C and // or C for each C<@candis> distributions various dependency requirements. If C<$.depends> is set to C then runtime dependencies will be ignored. If C<$.test-depends> is set to C then test dependencies will be ignored. If C<$.build-depends> is set to C then build dependencies will be ignored. =head2 method resolve method resolve($spec, CompUnit::Repository :@at --> Array[Candidate]) Returns the best matching distributions from installed sources for the given C<$spec>, in preferred order (highest api version and highest version) from each C in C<@at> (or all known C if C<@at> is not set). C<$spec> should be either a C or C. =head2 method is-installed multi method is-installed(Str $spec, |c --> Bool:D) multi method is-installed(Zef::Distribution::DependencySpecification::Any $spec, |c --> Bool:D) multi method is-installed(Zef::Distribution::DependencySpecification $spec, |c --> Bool:D) Returns C if the requested C<$spec> is installed. The logic it uses to decide if something is installed is based on the C<$spec.from-matcher>: C> will search C<$PATH> for C, C> will check if C can see an e.g. C or C, and everything else will be looked up as a C raku module. =head2 method sort-candidates method sort-candidates(@candis --> Array[Candidate]) Does a topological sort of C<@candis> based on their various dependency fields and C<$.depends>/C<$.test-depends>/C<$.build-depends>. =end pod #| Where zef will cache index databases (p6c.json, etc) and distributions has IO::Path $.cache; #| Repository abstraction used to query for distributions has Zef::Repository $.recommendation-manager; # todo: rename this? #| Fetcher abstraction used to fetch distributions, ecosystem databases, etc has Zef::Fetch $.fetcher; #| Extractor abstraction used to e.g. extract or checkout data sources has Zef::Extract $.extractor; #| Builder abstraction used to handle running the build phase of a distribution has Zef::Build $.builder; #| Tester abstraction used to handle running the test phase of a distribution has Zef::Test $.tester; #| Installer abstraction used to handle the install phase of a distribution #| (we theoretically could install Perl modules with an adapter for instance) has Zef::Install $.installer; #| Reporter abstraction to to handle the report phase of a distribution has Zef::Report $.reporter; #| The config data (see resources/config.json) has %.config; #| Supplier where logging events originate #| For example to get 'test' related event you might use: #| $client.logger.Supply.grep({ . eq "TEST" }) has Supplier $.logger = Supplier.new; #| Internal use store for keeping track of module names to skip has @!ignore; # # NOTE: All attributes below this point have CLI equivalents # #| User supplied module names that will be skipped #| For example to skip a native perl dependency like perl:from: #| :exclude("perl"); #| or from the command line: #| --exclude=perl has @.exclude; #| Continue resolving dependencies even if there is an error in doing so has Bool $.force-resolve is rw = False; #| Continue fetching dependencies even if there is an error in doing so #| (I don't think there isn't a good reason to ever set this to True) has Bool $.force-fetch is rw = False; #| Continue extracting dependencies even if there is an error in doing so #| (I don't think there isn't a good reason to ever set this to True) has Bool $.force-extract is rw = False; #| Continue building dependencies even if there is an error in doing so has Bool $.force-build is rw = False; #| Continue testing dependencies even if there is an error in doing so has Bool $.force-test is rw = False; #| Continue installing dependencies even if there is an error in doing so has Bool $.force-install is rw = False; #| The max number of items to fetch concurrently has Int $.fetch-degree is rw = 1; #| The max number of distributions to test concurrently has Int $.test-degree is rw = 1; #| The number of seconds to wait before aborting a fetching task has Int $.fetch-timeout is rw = 600; #| The number of seconds to wait before aborting a extracting task has Int $.extract-timeout is rw = 3600; #| The number of seconds to wait before aborting a building task has Int $.build-timeout is rw = 3600; #| The number of seconds to wait before aborting a testing task has Int $.test-timeout is rw = 3600; #| The number of seconds to wait before aborting a installing task has Int $.install-timeout is rw = 3600; #| If run time dependencies should be considered when processing distributions has Bool $.depends is rw = True; #| If build time dependencies should be considered when building distributions has Bool $.build-depends is rw = True; #| If test time dependencies should be considered when building distributions has Bool $.test-depends is rw = True; submethod TWEAK( :$!cache = %!config.IO, :$!fetcher = Zef::Fetch.new(:backends(|%!config)), :$!extractor = Zef::Extract.new(:backends(|%!config)), :$!builder = Zef::Build.new(:backends(|%!config)), :$!installer = Zef::Install.new(:backends(|%!config)), :$!tester = Zef::Test.new(:backends(|%!config)), :$!reporter = Zef::Report.new(:backends(|%!config)), :$!recommendation-manager = Zef::Repository.new(:backends(%!config.tree({$_}, *.map({ $_ //= $!cache; $_ = $!fetcher; $_ })).Array)), ) { mkdir $!cache unless $!cache.IO.e; # Ignore CORE modules to speed up searches and to avoid dual-life issues until CORE is more strictly versioned @!ignore = CompUnit::RepositoryRegistry .repository-for-name('core') .candidates('CORE') .map(*.meta.keys.Slip) .unique .map({ Zef::Distribution::DependencySpecification.new($_) }) ; } #| Return a matching candidate/distribution for each supplied identity method find-candidates(Bool :$upgrade, *@identities ($, *@) --> Array[Candidate]) { self.logger.emit({ level => INFO, stage => RESOLVE, phase => BEFORE, message => "Searching for: {@identities.join(', ')}", }); my Candidate @candidates = self!find-candidates(:$upgrade, @identities); for @candidates.classify({.from}).kv -> $from, $found { self.logger.emit({ level => VERBOSE, stage => RESOLVE, phase => AFTER, message => "Found: {$found.map(*.dist.identity).join(', ')} [via {$from}]", }) } return @candidates; } #| Similar to self.find-candidates, but this can be called recursively. Notably #| it allows the message for the call to .find-candidates(...) to differentiate #| between later calls to .find-prereq-candidates(...) (which calls !find-candidates #| so it doesn't send the aforementioned logging message for a top level request). method !find-candidates(Bool :$upgrade, *@identities ($, *@) --> Array[Candidate]) { my Candidate @candidates = $!recommendation-manager.candidates(@identities, :$upgrade)\ .grep(-> $candi { not @!exclude.first({$candi.dist.contains-spec($_)}) })\ .grep(-> $candi { not @!ignore.first({$candi.dist.contains-spec($_)}) })\ .unique(:as(*.dist.identity)); return @candidates; } #| Return matching candidates that fulfill the dependencies (including transitive) for each supplied candidate/distribution method find-prereq-candidates(Bool :$skip-installed = True, Bool :$upgrade, *@candis ($, *@) --> Array[Candidate]) { my Candidate @results = self!find-prereq-candidates(:$skip-installed, :$upgrade, |@candis); return @results; } #| Similar .find-prereq-candidates this has an additional non-public api parameter :@certain used during recursion method !find-prereq-candidates(Bool :$skip-installed = True, Bool :$upgrade, :@certain, *@candis ($, *@) --> Array[Candidate]) { my @skip = @candis.map(*.dist); my $prereqs := gather { my @specs = self.list-dependencies(@candis); while @specs.splice -> @specs-batch { self.logger.emit({ level => DEBUG, stage => RESOLVE, phase => BEFORE, message => "Dependencies: {@specs-batch.map(*.name).unique.join(', ')}", }); next unless my @needed = @specs-batch\ # The current set of specs .grep({ not @skip.first(*.contains-spec($_)) })\ # Dists in @skip are not needed .grep(-> $spec { not @!exclude.first({ $_.spec-matcher($spec) }) })\ .grep(-> $spec { not @!ignore.first({ $_.spec-matcher($spec) }) })\ .grep({ $skip-installed ?? self.is-installed($_).not !! True }); my %needed = @needed.classify: { $_.isa(Zef::Distribution::DependencySpecification::Any) ?? "alternative" !! "certain" }; my @identities = %needed.map(*.identity) if %needed; self.logger.emit({ level => INFO, stage => RESOLVE, phase => BEFORE, message => "Searching for missing dependencies: {@needed.map(*.identity).join(', ')}", }); my @prereq-candidates = self!find-candidates(:$upgrade, @identities) if @identities; my @alt-identities = gather for %needed.list -> $needed { next if any(|@certain, |@prereq-candidates).dist.contains-spec($needed); my @candidates; if $needed.specs.first({ CATCH { when X::Zef::UnsatisfiableDependency { @candidates = (); } } @candidates = self!find-candidates(:$upgrade, $_.identity); if @candidates { my Candidate @new-candidates = self!find-prereq-candidates( :$upgrade, :certain(|@certain, |@prereq-candidates), @candidates, ); @candidates.append: @new-candidates; } @candidates }) -> $ { @prereq-candidates.append(@candidates); } else { take $needed.identity; } } if %needed; @prereq-candidates.append: self!find-candidates(:$upgrade, @alt-identities) if @alt-identities; my @not-found = @needed.grep({ not @prereq-candidates.first(*.dist.contains-spec($_)) }); # The failing part of this should ideally be handled in Zef::CLI I think if +@prereq-candidates == +@needed || @not-found.cache.elems == 0 { for @prereq-candidates.classify({.from}).kv -> $from, $found { self.logger.emit({ level => VERBOSE, stage => RESOLVE, phase => AFTER, message => "Found dependencies: {$found.map(*.dist.identity).join(', ')} [via {$from}]", }) } } else { self.logger.emit({ level => ERROR, stage => RESOLVE, phase => AFTER, message => "Failed to find dependencies: {@not-found.map(*.identity).join(', ')}", }); $!force-resolve ?? $!logger.emit({ level => ERROR, stage => RESOLVE, phase => LIVE, message => 'Failed to resolve missing dependencies, but continuing with --force-resolve', }) !! die X::Zef::UnsatisfiableDependency.new but role :: { method message { X::Zef::UnsatisfiableDependency.message ~ qq| (use e.g. --exclude="{@not-found.head.name}" to skip)|; } }; }; @skip.append: @prereq-candidates.map(*.dist); @specs = self.list-dependencies(@prereq-candidates); for @prereq-candidates -> $prereq { $prereq.is-dependency = True; take $prereq; } } } # check $prereqs to see if we have any unneeded depends my Candidate @results = $prereqs.unique(:as(*.dist.identity)); return @results; } method fetch(*@candidates ($, *@) --> Array[Candidate]) { my @fetched = self!fetch(@candidates); my @extracted = self!extract(@fetched); my Candidate @local-candis = @extracted.map: -> $candi { my $dist = Zef::Distribution::Local.new(~$candi.uri); $candi.clone(:$dist); } $!recommendation-manager.store(@local-candis.map(*.dist)); return @local-candis; } method !fetch(*@candidates ($, *@) --> Array[Candidate]) { my Candidate @fetched = @candidates.hyper(:batch(1), :degree($!fetch-degree || 5)).map: -> $candi { self.logger.emit({ level => DEBUG, stage => FETCH, phase => BEFORE, candi => $candi, message => "Fetching: {$candi.as}", }); die "Cannot determine a uri to fetch {$candi.as} from. Perhaps it's META6.json is missing an e.g. source-url" unless $candi.uri; my $tmp = %!config.IO.child("{time}.{$*PID}.{(^10000).rand}"); my $stage-at = $tmp.child($candi.uri.IO.basename); die "failed to create directory: {$tmp.absolute}" unless ($tmp.IO.e || mkdir($tmp)); # $candi.uri will always point to where $candi.dist should be copied from. # It could be a file or url; $dist.source-url contains where the source was # originally located but we may want to use a local copy (while retaining # the original source-url for some other purpose like updating) my $save-to = $!fetcher.fetch($candi, $stage-at, :$!logger, :timeout($!fetch-timeout)); if !$save-to { self.logger.emit({ level => ERROR, stage => FETCH, phase => AFTER, candi => $candi, message => "Fetching [FAIL]: {$candi.dist.?identity // $candi.as} from {$candi.uri}", }); $!force-fetch ?? $!logger.emit({ level => ERROR, stage => FETCH, phase => LIVE, candi => $candi, message => 'Failed to fetch, but continuing with --force-fetch', }) !! die("Aborting due to fetch failure: {$candi.dist.?identity // $candi.uri} (use --force-fetch to override)"); } else { self.logger.emit({ level => VERBOSE, stage => FETCH, phase => AFTER, candi => $candi, message => "Fetching [OK]: {$candi.dist.?identity // $candi.as} to $save-to", }); } $candi.uri = $save-to; $candi; }; return @fetched; } method !extract(*@candidates ($, *@) --> Array[Candidate]) { my Candidate @extracted = eager gather for @candidates -> $candi { self.logger.emit({ level => DEBUG, stage => EXTRACT, phase => BEFORE, candi => $candi, message => "Extracting: {$candi.as}", }); my $tmp = $candi.uri.parent; my $stage-at = $candi.uri; my $relpath = $stage-at.relative($tmp); my $extract-to = %!config.IO.child($relpath); die "failed to create directory: {$tmp.absolute}" unless ($tmp.IO.e || mkdir($tmp)); my $meta6-prefix = '' R// $!extractor.ls-files($candi).sort.first({ .IO.basename eq 'META6.json' }); self.logger.emit({ level => WARN, stage => EXTRACT, phase => BEFORE, candi => $candi, message => "Extraction: Failed to find a META6.json file for {$candi.dist.?identity // $candi.as} -- failure is likely", }) unless $meta6-prefix; my $extracted-to = $!extractor.extract($candi, $extract-to, :$!logger, :timeout($!extract-timeout)); if !$extracted-to { self.logger.emit({ level => ERROR, stage => EXTRACT, phase => AFTER, candi => $candi, message => "Extraction [FAIL]: {$candi.dist.?identity // $candi.as} from {$candi.uri}", }); $!force-extract ?? $!logger.emit({ level => ERROR, stage => EXTRACT, phase => LIVE, candi => $candi, message => 'Failed to extract, but continuing with --force-extract', }) !! die("Aborting due to extract failure: {$candi.dist.?identity // $candi.uri} (use --force-extract to override)"); } else { try { delete-paths($tmp) } # Remove this when META.info support can finally be removed if !$meta6-prefix and my $meta-info = $extracted-to.IO.add('META.info') and $meta-info.e { self.logger.emit({ level => WARN, stage => EXTRACT, phase => AFTER, candi => $candi, message => "Extraction: Failed to find a META6.json file for {$candi.dist.?identity // $candi.as} -- creating it from deprecated META.info file", }); try { $meta-info.copy($meta-info.parent.add('META6.json')) } } self.logger.emit({ level => VERBOSE, stage => EXTRACT, phase => AFTER, candi => $candi, message => "Extraction [OK]: {$candi.as} to {$extract-to}", }); } $candi.uri = $extracted-to.child($meta6-prefix); take $candi; } return @extracted; } # xxx: needs some love. also an entire specification method build(*@candidates ($, *@) --> Array[Candidate]) { my Candidate @built = eager gather for @candidates -> $candi { my $dist := $candi.dist; unless $!builder.build-matcher($dist) { self.logger.emit({ level => DEBUG, stage => BUILD, phase => BEFORE, candi => $candi, message => "# SKIP: No need to build {$candi.dist.?identity // $candi.as}", }); take $candi; next(); } $!logger.emit({ level => INFO, stage => BUILD, phase => BEFORE, candi => $candi, message => "Building: {$candi.dist.?identity // $candi.as}", }); my $result := $!builder.build($candi, :includes($candi.dist.metainfo // []), :$!logger, :timeout($!build-timeout)).cache; $candi.build-results = $result; if $result.grep(*.not).elems { self.logger.emit({ level => ERROR, stage => BUILD, phase => AFTER, candi => $candi, message => "Building [FAIL]: {$candi.dist.?identity // $candi.as}", }); $!force-build ?? $!logger.emit({ level => ERROR, stage => BUILD, phase => LIVE, candi => $candi, message => 'Failed to build, but continuing with --force-build', }) !! die("Aborting due to build failure: {$candi.dist.?identity // $candi.uri} (use --force-build to override)"); } else { self.logger.emit({ level => INFO, stage => BUILD, phase => AFTER, candi => $candi, message => "Building [OK] for {$candi.dist.?identity // $candi.as}", }); } take $candi; } @built } # xxx: needs some love method test(*@candidates ($, *@) --> Array[Candidate]) { my Candidate @tested = @candidates.hyper(:batch(1), :degree($!test-degree || 1)).map: -> $candi { self.logger.emit({ level => INFO, stage => TEST, phase => BEFORE, candi => $candi, message => "Testing: {$candi.dist.?identity // $candi.as}", }); my $result := $!tester.test($candi, :includes($candi.dist.metainfo // []), :$!logger, :timeout($!test-timeout)).cache; $candi.test-results = $result; if $result.grep(*.not).elems { self.logger.emit({ level => ERROR, stage => TEST, phase => AFTER, candi => $candi, message => "Testing [FAIL]: {$candi.dist.?identity // $candi.as}", }); $!force-test ?? $!logger.emit({ level => ERROR, stage => TEST, phase => LIVE, candi => $candi, message => 'Failed to get passing tests, but continuing with --force-test', }) !! die("Aborting due to test failure: {$candi.dist.?identity // $candi.uri} (use --force-test to override)"); } else { self.logger.emit({ level => INFO, stage => TEST, phase => AFTER, candi => $candi, message => "Testing [OK] for {$candi.dist.?identity // $candi.as}", }); } $candi; } return @tested } #| Search for identities from the various repository backends and returns the matching distributions method search(*@identities ($, *@), *%fields, Bool :$strict = False --> Array[Candidate]) { my Candidate @results = $!recommendation-manager.search(@identities, :$strict, |%fields); return @results; } #| Uninstall a distribution from a given repository method uninstall(CompUnit::Repository :@from!, *@identities --> Array[Candidate]) { my @specs = @identities.map: { Zef::Distribution::DependencySpecification.new($_) } my Candidate @results = 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); take $candi; } } return @results; } #| Install a distribution to a given repository method install(:@curs, *@candidates ($, *@) --> Array[Candidate]) { my Candidate @installed = eager gather for @candidates -> $candi { self.logger.emit({ level => INFO, stage => INSTALL, phase => BEFORE, candi => $candi, message => "Installing: {$candi.dist.?identity // $candi.as}", }); for @curs -> $cur { KEEP self.logger.emit({ level => VERBOSE, stage => INSTALL, phase => AFTER, candi => $candi, message => "Install [OK] for {$candi.dist.?identity // $candi.as}", }); CATCH { when /'already installed'/ { self.logger.emit({ level => INFO, stage => INSTALL, phase => AFTER, candi => $candi, message => "Install [SKIP] for {$candi.dist.?identity // $candi.as}: {$_}", }); } default { self.logger.emit({ level => ERROR, stage => INSTALL, phase => AFTER, candi => $candi, message => "Install [FAIL] for {$candi.dist.?identity // $candi.as}: {$_}", }); $_.rethrow; } } take $candi if $!installer.install($candi, :$cur, :force($!force-install), :$!logger, :timeout($!install-timeout)); } } return @installed; } #| This organizes and executes multiples phases for multiples candidates (test/build/install/etc) method make-install( CompUnit::Repository :@to!, # target CompUnit::Repository Bool :$fetch = True, # try fetching whats missing Bool :$build = True, # run Build.pm (DEPRECATED..?) Bool :$test = True, # run tests Bool :$dry, # do everything *but* actually install Bool :$serial, *@candidates ($, *@), *%_ ) { my @curs = @to.grep: -> $cur { UNDO { self.logger.emit({ level => WARN, stage => INSTALL, phase => BEFORE, message => "CompUnit::Repository install target is not writeable/installable: {$cur}" }); } KEEP { self.logger.emit({ level => TRACE, stage => INSTALL, phase => BEFORE, message => "CompUnit::Repository install target is valid: {$cur}" }); } $cur.?can-install || next(); } die "Need a valid installation target to continue" unless ?$dry || +@curs; # XXX: Each loop block below essentially represents a phase, so they will probably # be moved into their own method/module related directly to their phase. For now # lumping them here allows us to easily move functionality between phases until we # find the perfect balance/structure. die "Must specify something to install" unless +@candidates; # Fetch Stage: # Use the results from searching each available Repository and download/fetch the distributions they point at my @fetched-candidates = eager gather for @candidates -> $store { # Note that this method of not fetching Zef::Distribution::Local means we cannot # show fetching messages that would be fired in self.fetch(|) ( such as the download uri ). # The reason it doesn't just fetch regardless is because it avoids caching local dev dists # ala `zef install .` from polluting the name/auth/api/ver namespace of the local cache. # TODO: Find a solution for the issues noted above which will resolve GH#261 "zef install should tell user where the install was from" take $_ for ($store.dist.^name.contains('Zef::Distribution::Local') || !$fetch) ?? $store !! self.fetch($store, |%_); } die "Failed to fetch any candidates. No reason to proceed" unless +@fetched-candidates; # Filter Stage: # Handle stuff like removing distributions that are already installed, that don't have # an allowable license, etc. It faces the same "fetch an alternative if available on failure" # problem outlined below under `Sort Phase` (a depends on [A, B] where A gets filtered out # below because it has the wrong license means we don't need anything that depends on A but # *do* need to replace those items with things depended on by B [which replaces A]) my @filtered-candidates = @fetched-candidates.grep: -> $candi { my $*error; self.logger.emit({ level => DEBUG, stage => FILTER, phase => BEFORE, candi => $candi, message => "Filtering: {$candi.dist.identity}", }); KEEP $!logger.emit({ level => DEBUG, stage => FILTER, phase => AFTER, candi => $candi, message => "Filtering [OK] for {$candi.dist.?identity // $candi.as}", }); UNDO $!logger.emit({ level => ERROR, stage => FILTER, phase => AFTER, candi => $candi, message => "Filtering [FAIL] for {$candi.dist.?identity // $candi.as}: {$*error}", }); $*error = do given %!config { when ..?chars && any(|.) ~~ any('*', $candi.dist.meta // '') { "License blacklist configuration exists and matches {$candi.dist.meta // 'n/a'} for {$candi.dist.name}"; } when ..?chars && any(|.) ~~ none('*', $candi.dist.meta // '') { "License whitelist configuration exists and does not match {$candi.dist.meta // 'n/a'} for {$candi.dist.name}"; } } $*error.?chars; } die "All candidates have been filtered out. No reason to proceed" unless +@filtered-candidates; # Sort Phase: # This ideally also handles creating alternate build orders when a `depends` includes # alternative dependencies. Then if the first build order fails it can try to fall back # to the next possible build order. However such functionality may not be useful this late # as at this point we expect to have already fetched/filtered the distributions... so either # we fetch all alternatives (most of which would probably would not use) or do this in a way # that allows us to return to a previous state in our plan (xxx: Zef::Plan is planned) my @sorted-candidates = self.sort-candidates(@filtered-candidates, |%_); die "Something went terribly wrong determining the build order" unless +@sorted-candidates; # Setup(?) Phase: # Attach appropriate metadata so we can do --dry runs using -I/some/dep/path # and can install after we know they pass any required tests my @linked-candidates = self.link-candidates(@sorted-candidates); die "Something went terribly wrong linking the distributions" unless +@linked-candidates; my $installer = sub (*@_) { # Build Phase: my @built-candidates = ?$build ?? self.build(@_) !! @_; die "No installable candidates remain after `build` failures" unless +@built-candidates; # Test Phase: my @tested-candidates = !$test ?? @built-candidates !! self.test(@built-candidates).grep({ $!force-test || .test-results.grep(!*.so).elems == 0 }); # actually we *do* want to proceed here later so that the Report phase can know about the failed tests/build die "All candidates failed building and/or testing. No reason to proceed" unless +@tested-candidates; # 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 !! self.install(:@curs, @tested-candidates); # Report phase: # Handle exit codes for various option permutations like --force # Inform user of what was tested/built/installed and what failed # Optionally report to any cpan testers type service (testers.perl6.org) unless $dry { # Get the name of the bin scripts my sub bin-names($dist) { $dist.meta.hash.keys.grep(*.starts-with("bin/")).map(*.substr(4)) }; if @installed-candidates.map(*.dist).map(*.&bin-names.Slip).unique -> @bins { my $msg = "\n{+@bins} bin/ script{+@bins>1??'s'!!''}{+@bins??' ['~@bins~']'!!''} installed to:" ~ "\n" ~ @curs.map(*.prefix.child('bin')).join("\n"); self.logger.emit({ level => INFO, stage => REPORT, phase => LIVE, message => $msg, }); } } @installed-candidates; } # sub installer my Candidate @installed = ?$serial ?? @linked-candidates.map({ |$installer($_) }) !! $installer(@linked-candidates); return @installed; } #| Return distributions that depend on the given identity method list-rev-depends($identity, Bool :indirect($) --> Array[Candidate]) { my $spec = Zef::Distribution::DependencySpecification.new($identity); my $dist = self.list-available.first(*.dist.contains-spec($spec)).?dist || self.list-installed.first(*.dist.contains-spec($spec)).?dist; return Array[Candidate].new unless $dist; my $rev-deps := gather for self.list-available -> $candi { my $specs := self.list-dependencies($candi); take $candi if $specs.first({ $dist.contains-spec($_, :strict) }); } my Candidate @results = $rev-deps.unique(:as(*.dist.identity)); return @results; } #| Return all distributions from all configured repositories method list-available(*@recommendation-manager-names --> Array[Candidate]) { my Candidate @available = $!recommendation-manager.available(@recommendation-manager-names); return @available; } #| Return all distributions in known CompUnit::Repository::Installation repositories method list-installed(*@repos --> Array[Candidate]) { my @curs = +@repos ?? @repos !! $*REPO.repo-chain; my @curis = @curs.grep(CompUnit::Repository::Installation); my @curi-dists = @curis.map(-> $curi { Hash.new({ :$curi, :dists($curi.installed) }) }).grep({ $_.defined }); my Candidate @dists = gather for @curi-dists -> % [:$curi, :@dists] { for @dists -> $curi-dist { if try { Zef::Distribution.new( |%($curi-dist.meta) ) } -> $dist { take Candidate.new( :$dist, :from($curi), :uri($curi.path-spec) ); } } } return @dists; } method list-leaves(--> Array[Candidate]) { my @installed = self.list-installed; my @dep-specs = self.list-dependencies(@installed); my Candidate @leaves = gather for @installed -> $candi { my $dist := $candi.dist; take $candi unless @dep-specs.first: { $dist.contains-spec($_) } } return @leaves; } #| Return distributions that are direct dependencies of the supplied distributions method list-dependencies(*@candis --> Array[DependencySpecification]) { my $deps := gather for @candis -> $candi { take $_ for grep *.defined, ($candi.dist.depends-specs if ?$!depends).Slip, ($candi.dist.test-depends-specs if ?$!test-depends).Slip, ($candi.dist.build-depends-specs if ?$!build-depends).Slip; } # This returns both Zef::Distribution::DependencySpecification and Zef::Distribution::DependencySpecification::Any #my Zef::Distribution::DependencySpecification @results = $deps.unique(:as(*.identity)); my DependencySpecification @results = $deps.unique(:as(*.identity)); return @results; } #| Returns the best matching distributions from installed sources, in preferred order, similar to $*REPO.resolve method resolve($spec, CompUnit::Repository :@at --> Array[Candidate]) { my $candis := self.list-installed(@at).grep(*.dist.contains-spec($spec)); my Candidate @results = $candis.sort(*.dist.ver).sort(*.dist.api).reverse; return @results; } #| Return true if the requested dependency is already installed multi method is-installed(Str $spec, |c --> Bool:D) { return self.is-installed(Zef::Distribution::DependencySpecification.new($spec)); } #| Return true of one-or-more of the requested dependencies are already installed multi method is-installed(Zef::Distribution::DependencySpecification::Any $spec, |c --> Bool:D) { return so $spec.specs.first({ self.is-installed($_, |c) }); } #| Return true if the requested dependency is already installed multi method is-installed(Zef::Distribution::DependencySpecification $spec, |c --> Bool:D) { return do given $spec.?from-matcher { when 'bin' { so Zef::Utils::FileSystem::which($spec.name) } when 'native' { so self!native-library-is-installed($spec) } default { so self.resolve($spec, |c).so } } } #| Return true of a native library can be seen by NativeCall method !native-library-is-installed($spec --> Bool) { use MONKEY-SEE-NO-EVAL; my $lib = "'$spec.name()'"; $lib = "$lib, v$spec.ver()" if $spec.ver; try { EVAL qq[use NativeCall; sub native_library_is_installed is native($lib) \{ !!! \}; native_library_is_installed(); ]; CATCH { default { return False if .payload.starts-with("Cannot locate native library") } } } return True; } #| Topological sort used to determine which dependency can be processed next in a given phase method sort-candidates(@candis --> Array[Candidate]) { my Candidate @tree; my $visit = sub ($candi) { return if ($candi.dist.metainfo // 0) == 1; if ($candi.dist.metainfo // 0) == 0 { $candi.dist.metainfo = 1; my @deps = |self.list-dependencies($candi); for @deps -> $m { for @candis.grep(*.dist.contains-spec($m)) -> $m2 { $visit($m2); } } @tree.append($candi); } }; for @candis -> $candi { $visit($candi) if ($candi.dist.metainfo // 0) == 0; } .dist.metainfo = Nil for @tree; return @tree; } #| Adds appropriate include (-I / PERL6LIB) paths for dependencies proto method link-candidates(|) {*} multi method link-candidates(Bool :recursive($)! where *.so, *@candidates) { # :recursive # Given Foo::XXX that depends on Bar::YYY that depends on Baz::ZZZ # - Foo::XXX -> -I/Foo/XXX -I/Bar/YYY -I/Baz/ZZZ # - Bar::YYY -> -I/Bar/YYY -I/Baz/ZZZ # - Baz::ZZZ -> -I/Baz/ZZZ # XXX: Need to change this so it only add indirect dependencies # instead of just using recursion on the array in order. Otherwise there # can be distributions that are part of a different dependency # chain will end up with some extra includes my @linked = self.link-candidates(@candidates); @ = @linked.map: -> $candi { # can probably use rotor instead of doing the `@a[$index + 1..*]` dance my @direct-includes = $candi.dist.metainfo.grep(*.so); my @recursive-includes = try @linked[(++$)..*]\ .map(*.dist.metainfo).flatmap(*.flat); my @unique-includes = unique(@direct-includes, @recursive-includes); my Str @results = @unique-includes.grep(*.so); $candi.dist.metainfo = @results; $candi; } } multi method link-candidates(Bool :inclusive($)! where *.so, *@candidates) { # :inclusive # Given Foo::XXX that depends on Bar::YYY that depends on Baz::ZZZ # - Foo::XXX -> -I/Foo/XXX -I/Bar/YYY -I/Baz/ZZZ # - Bar::YYY -> -I/Foo/XXX -I/Bar/YYY -I/Baz/ZZZ # - Baz::ZZZ -> -I/Foo/XXX -I/Bar/YYY -I/Baz/ZZZ my @linked = self.link-candidates(@candidates); @ = @linked.map(*.dist.metainfo).flatmap(*.flat).unique; } multi method link-candidates(*@candidates) { # Default # Given Foo::XXX that depends on Bar::YYY that depends on Baz::ZZZ # - Foo::XXX -> -I/Foo/XXX -I/Bar/YYY # - Bar::YYY -> -I/Bar/YYY -I/Baz/ZZZ # - Baz::ZZZ -> -I/Baz/ZZZ @ = @candidates.map: -> $candi { my @dep-specs = |self.list-dependencies($candi); # this could probably be done in the topological-sort itself my $includes := eager gather for @dep-specs -> $spec { CANDIDATE: for @candidates -> $fcandi { my $fdist := $fcandi.dist; if $fdist.contains-spec($spec) { take $fdist.IO.absolute; take $_ for |$fdist.metainfo.grep(*.so); last CANDIDATE; } } } my Str @results = $includes.unique; $candi.dist.metainfo = @results; $candi; } } } zef-0.13.8/lib/Zef/Config.rakumod000066400000000000000000000036441422013762000164560ustar00rootroot00000000000000use Zef; module Zef::Config { our sub parse-file($path) { my %config = %(Zef::from-json( $path.IO.slurp )); %config{$_.key} = $_.value.subst(/'{$*HOME}' || '$*HOME'/, $*HOME // $*TMPDIR, :g)\ for %config.grep(*.key.ends-with('Dir')); %config //= 'auto'; # XXX: config upgrade - just remove this in future when no one is looking %config //= %config:delete; %config; } our sub guess-path { my %default-conf; my IO::Path $local-conf-path; my @path-candidates = ( (%*ENV // "$*HOME/.config").IO.child('/zef/config.json'), %?RESOURCES.IO, ); for @path-candidates -> $path { if $path.e { %default-conf = try { parse-file($path) } // Hash.new; die "Failed to parse the zef config file '$path'" if !%default-conf; $local-conf-path = $path; last; } } die "Failed to find the zef config file at: {@path-candidates.join(', ')}" unless $local-conf-path.defined and $local-conf-path.e; die "Failed to parse a zef config file at $local-conf-path" if !%default-conf; return $local-conf-path; } our sub plugin-lookup($config) { my $lookup; my sub do-lookup($node) { if $node ~~ Hash { for @$node -> $sub-node { if $sub-node.value ~~ Str | Int && $sub-node.key eq any() { $lookup{$sub-node.value}.push($node); next; } do-lookup($sub-node.value); } } elsif $node ~~ Array { do-lookup($_) for $node.cache; } } do-lookup($config); $lookup; } } zef-0.13.8/lib/Zef/Distribution.rakumod000066400000000000000000000223611422013762000177250ustar00rootroot00000000000000use Zef; use Zef::Distribution::DependencySpecification; use Zef::Utils::SystemQuery; class Zef::Distribution does Distribution is Zef::Distribution::DependencySpecification { =begin pod =title class Zef::Distribution =subtitle A generic Distribution implementation =head1 Synopsis =begin code :lang use Zef::Distribution; use JSON::Fast; my %meta = from-json("META6.json".IO.slurp); my $dist = Zef::Distribution.new(|%meta); # Show the meta data say $dist.meta.perl; # Output if the $dist contains a namespace matching Foo::Bar:ver<1> say $dist.contains-spec("Foo::Bar:ver<1>"); =end code =head1 Description A C implementation that is used to represent not-yet-downloaded distributions. Generally you should use this class using the C interface and not as a struct representation of META6 data -- i.e. use C<$dist.meta.hash{"version"}> instead of <$dist.ver>. These variations are a wart included mostly for backwards compatibility purposes (or just leftover from years of changes to e.g. C and friends). When using this class "best practice" would be to consider the following methods are the public api: C and C (the C interface methods), C, C, C, C, C, C, C =head1 Methods =head2 method meta method meta(--> Hash:D) Returns the meta data that represents the distribution. =head2 method content method content() Will always throw an exception. This class is primarily used to represent a distribution when all we have it meta data; use a class like C (which subclasses this class) if or when you need access to the content of files besides C. =head2 method depends-specs method depends-specs(--> Array[Zef::Distribution::DependencySpecification]) Return an C of C for the runtime dependencies of the distribution. =head2 method test-depends-specs method test-depends-specs(--> Array[Zef::Distribution::DependencySpecification]) Return an C of C for the test dependencies of the distribution. =head2 method depends-specs method build-depends-specs(--> Array[Zef::Distribution::DependencySpecification]) Return an C of C for the build dependencies of the distribution. =head2 method provides-specs method provides-specs(--> Array[Zef::Distribution::DependencySpecification]) Return an C of C for the namespaces in the distributions C. =head2 method provides-spec-matcher method provides-spec-matcher(Zef::Distribution::DependencySpecification $spec, :$strict --> Bool:D) { self.provides-specs.first({ ?$_.spec-matcher($spec, :$strict) }) } Returns C if C<$spec> matches any namespaces this distribution provides (but not the name of the distribution itself). If C<$strict> is C then partial name matches will be allowed (i.e. C matching C). =head2 method contains-spec multi method contains-spec(Str $spec, |c --> Bool:D) multi method contains-spec(Zef::Distribution::DependencySpecification $spec, Bool :$strict = True --> Bool:D) multi method contains-spec(Zef::Distribution::DependencySpecification::Any $spec, Bool :$strict = True --> Bool:D) Returns C if C<$spec> matches any namespace this distribution provides, including the name of the distribution itself. If C<$strict> is C then partial name matches will be allowed (i.e. C matching C). When given a C C<$spec> the C<$spec> will be turned into a C. =head2 method Str method Str(--> Str) Returns the explicit full name of the distribution, i.e. C -> C:auth<>:api<>> =head2 method id method id(--> Str) Returns a file system safe unique string identifier for the distribution. This is generally meant for internal use only. Note: This should not publicly be relied on for matching any C implementation details this may appear to be emulating. =end pod has $.meta-version; has $.name; has $.auth; has $.author; has $.authority; has $.api; has $.ver; has $.version; has $.description; has $.depends; has %.provides; has %.files; has $.source-url; has $.license; has $.build-depends; has $.test-depends; has @.resources; has %.support; has $.builder; has %!meta; # attach arbitrary data, like for topological sort, that won't be saved on install has %.metainfo is rw; method new(*%_) { self.bless(|%_, :meta(%_)) } submethod TWEAK(:%!meta, :@!resources --> Nil) { @!resources = @!resources.flatmap(*.flat); } method auth { with $!auth { .Str } else { Nil } } method ver { with $!ver // $!version { $!ver ~~ Version ?? $_ !! $!ver = Version.new($_ // 0) } } method api { with $!api { $!api ~~ Version ?? $_ !! $!api = Version.new($_ // 0) } } # 'new-depends' refers to the hash form of `depends` has $!new-depends-cache; method !new-depends($type) { return Empty unless $.depends ~~ Hash; $!new-depends-cache := system-collapse($.depends) unless $!new-depends-cache.defined; return system-collapse($.depends){$type}.grep(*.defined).grep(*.).map(*.).map(*.Slip).Slip; } method !depends2specs(*@depends --> Array[DependencySpecification]) { my $depends := @depends.map({$_ ~~ List ?? $_.Slip !! $_ }).grep(*.defined); my DependencySpecification @depends-specs = $depends.map({ Zef::Distribution::DependencySpecification.new($_) }).grep(*.name); return @depends-specs; } method depends-specs(--> Array[DependencySpecification]) { my $depends := system-collapse($.depends); my $deps := $.depends ~~ Hash ?? self!new-depends('runtime') !! $depends; return self!depends2specs($deps); } method build-depends-specs(--> Array[DependencySpecification]) { my $orig-build-depends := system-collapse($.build-depends); my $new-build-depends := self!new-depends('build'); return self!depends2specs(|$orig-build-depends, $new-build-depends); } method test-depends-specs(--> Array[DependencySpecification]) { my $orig-test-depends := system-collapse($.test-depends); my $new-test-depends := self!new-depends('test'); return self!depends2specs(|$orig-test-depends, $new-test-depends); } # make locating a module that is part of a distribution (ex. URI::Escape of URI) easier. # it doesn't need to be a hash mapping as its just for matching has @!provides-specs; method provides-specs(--> Array[DependencySpecification]) { return @!provides-specs if @!provides-specs.elems; my DependencySpecification @provides-specs = self.meta.grep(*.defined).map({ # if $spec.name is not defined then .key (the module name of the current provides) # is not a valid module name (according to Zef::Identity grammar anyway). I ran into # this problem with `NativeCall::Errno` where one of the provides was: `X:NativeCall::Errorno` # The single colon cannot just be fixed to DWIM because that could just as easily denote # an identity part (identity parts are separated by a *single* colon; double colon is left alone) my $spec = Zef::Distribution::DependencySpecification.new(self!long-name(.key)); next unless defined($spec.name); $spec; }).grep(*.defined).Slip; return @!provides-specs := @provides-specs; } method provides-spec-matcher(DependencySpecification $spec, :$strict --> Bool:D) { return so self.provides-specs.first({ ?$_.spec-matcher($spec, :$strict) }) } proto method contains-spec(|) {*} multi method contains-spec(Str $spec, |c --> Bool:D) { samewith( Zef::Distribution::DependencySpecification.new($spec, |c) ) } multi method contains-spec(Zef::Distribution::DependencySpecification $spec, Bool :$strict = True --> Bool:D) { return so self.spec-matcher($spec, :$strict) || self.provides-spec-matcher($spec, :$strict) } multi method contains-spec(Zef::Distribution::DependencySpecification::Any $spec, Bool :$strict = True --> Bool:D) { return so self.contains-spec(any($spec.specs), :$strict) } method Str(--> Str) { return self!long-name($!name); } method !long-name($name --> Str) { return sprintf '%s:ver<%s>:auth<%s>:api<%s>', $name, (self.ver // ''), (self.auth // '').trans(['<', '>'] => ['\<', '\>']), (self.api // ''), ; } method id(--> Str) { use nqp; return nqp::sha1(self.Str); } method meta(--> Hash:D) { return %!meta } method content(|) { die "this method must be subclassed by something that can read from a content store"; } } zef-0.13.8/lib/Zef/Distribution/000077500000000000000000000000001422013762000163355ustar00rootroot00000000000000zef-0.13.8/lib/Zef/Distribution/DependencySpecification.rakumod000066400000000000000000000102151422013762000244770ustar00rootroot00000000000000use Zef; use Zef::Identity; role DependencySpecification { method name(--> Str) { ... } method identity(--> Str) { ... } method spec-matcher($spec --> Bool:D) { ... } } class Zef::Distribution::DependencySpecification::Any does DependencySpecification { has @.specs; method name { "any({@.specs.map(*.name).join(', ')})" } method identity { "any({@.specs.map(*.identity).join(',')})" } method spec-matcher($spec --> Bool:D) { return so @!specs.first(*.spec-matcher($spec)); } } class Zef::Distribution::DependencySpecification does DependencySpecification { has $!ident; has $.spec; submethod TWEAK(:$!spec, :$!ident) { } multi submethod new(Zef::Identity $ident) { self.bless(:$ident) } multi submethod new(Str $spec) { self.bless(:$spec) } multi submethod new(Hash $spec) { self.bless(:$spec) } multi submethod new(Hash $spec where {$_.keys == 1 and $_.keys[0] eq 'any'}) { Zef::Distribution::DependencySpecification::Any.new: :specs( $spec.values[0].map: {self.new($_)} ) } multi submethod new($spec) { die "Invalid dependency specification: $spec.gist()"; } method identity { my $hash = %(:name($.name), :ver($.version-matcher), :auth($.auth-matcher), :api($.api-matcher), :from($.from-matcher)); my $identity = hash2identity( $hash ); $identity; } method clone(|) { $!ident = Nil; nextsame(); } method spec-parts(Zef::Distribution::DependencySpecification:_: $spec = self!spec) { # Need to find a way to break this cache when a distribution gets cloned with a different version $!ident //= Zef::Identity.new(|$spec); $!ident.?hash; } method name { self.spec-parts } method ver { self.spec-parts } method version-matcher { self.spec-parts // '*' } method auth-matcher { self.spec-parts // '' } method api-matcher { self.spec-parts // '*' } method from-matcher { self.spec-parts // '' } method !spec { $.spec || self.Str } multi method spec-matcher(Zef::Distribution::DependencySpecification::Any $spec, Bool :$strict = True) { self.spec-matcher(any($spec.specs), :$strict) } multi method spec-matcher($spec, Bool :$strict = True) { return False unless $spec.name.?chars && self.name.?chars; if $strict { return False unless $spec.name eq self.name; } else { my $name = $spec.name; return False unless self.name ~~ /[:i $name]/; } if $spec.auth-matcher.chars { return False unless $.auth-matcher.chars && $spec.auth-matcher eq $.auth-matcher; } if $spec.version-matcher.chars && $spec.version-matcher ne '*' && $.version-matcher ne '*' { my $spec-version = Version.new($spec.version-matcher); my $self-version = Version.new($.version-matcher); return False unless self!version-matcher(:$spec-version, :$self-version); } if $spec.api-matcher.chars && $spec.api-matcher ne '*' && $.api-matcher ne '*' { my $spec-version = Version.new($spec.api-matcher); my $self-version = Version.new($.api-matcher); return False unless self!version-matcher(:$spec-version, :$self-version); } return True; } method !version-matcher(Version :$self-version is copy, Version :$spec-version is copy) { # Normalize the parts between version so that Version ~~ Version works in the way we need # Example: for `0.1 ~~ 0.1.1` we want `0.1.0` ~~ `0.1.1` my $self-add-parts = $spec-version.parts.elems - $self-version.parts.elems; $self-version = Version.new( (|$self-version.parts, |(0 xx $self-add-parts), ("+" if $self-version.plus)).join('.') ) if $self-add-parts > 0; my $spec-add-parts = $self-version.parts.elems - $spec-version.parts.elems; $spec-version = Version.new( (|$spec-version.parts, |(0 xx $spec-add-parts), ("+" if $spec-version.plus)).join('.') ) if $spec-add-parts; return $self-version ~~ $spec-version; } } zef-0.13.8/lib/Zef/Distribution/Local.rakumod000066400000000000000000000154101422013762000207540ustar00rootroot00000000000000use Zef; use Zef::Distribution; class Zef::Distribution::Local is Zef::Distribution { =begin pod =title class Zef::Distribution::Local =subtitle A local file system Distribution implementation =head1 Synopsis =begin code :lang use Zef::Distribution::Local; my $dist = Zef::Distribution::Local.new($*CWD); # Show the meta data say $dist.meta.perl; # Output the content of the first item in provides with $dist.meta.hash.values.head -> $name-path { say $dist.content($name-path).open.slurp; } # Output if the $dist contains a namespace matching Foo::Bar:ver<1> say $dist.contains-spec("Foo::Bar:ver<1>"); =end code =head1 Description A C implementation that is used to represent locally downloaded and extracted distributions. =head1 Methods =head2 method new method new(IO() $path) Create a C from a local distribution via its C file. If C<$path> is a directory then it will assume there is a C file it can use. If C<$path> is a file it will assume it a json file containing meta data (formatted like C). =head2 method meta method meta(--> Hash:D) Returns the meta data that represents the distribution. =head2 method content method content($name-path --> IO::Handle:D) Returns an unopened C that can be used to get the content of the C<$name-path>, where C<$name-path> is a value of the distributions C e.g. C, C<$dist.content($dist.meta{"Foo"})>. =end pod has $.path; has $.IO; #| Create a distribution from $path. #| If $path = dir/meta6.json, $.path is set to dir. #| If $path = dir/, $.path is set to the first meta file (if any) thats found. method new(IO() $path) { die "Cannot create a Zef::Distribution from non-existent path: {$path}" unless $path.e; my $meta-path = self!find-meta($path) || die "No meta file? Path: {$path}"; my $abspath = $meta-path.parent.absolute; my %meta = try { %(Zef::from-json($meta-path.slurp)) } || die "Invalid json? File: {$meta-path}"; my $IO = $abspath.IO; self.bless(:path($abspath), :$IO, |%(%meta.grep(?*.value.elems)), :meta(%meta)); } has %!meta-cache; #| Get the meta data this distribution provides method meta(--> Hash:D) { return %!meta-cache if %!meta-cache; my %hash = self.Zef::Distribution::meta; # These are required for installation, but not part of META6 spec # Eventually there needs to be a spec for authors to declare their bin scripts, # and CUR should probably handle the resources file mapping itself (since all # data needed to calculate it exists under the 'resources' field). %hash{"resources/" ~ .key} = .value for self!resources(:meta(%hash)).list; %hash{"bin/" ~ .key} = .value for self!scripts.list; return %!meta-cache := %hash; } #| Get a handle used to read/slurp data from files this distribution contains method content($name-path --> IO::Handle:D) { my $handle = IO::Handle.new: path => IO::Path.new($name-path, :CWD(self.IO)); return $handle // $handle.throw; } #| Given a path that might be a file or directory it makes a best guess at what the implied META6.json is. method !find-meta(Zef::Distribution::Local: $path? is copy --> IO::Path) { my $dir = $path ~~ IO::Path # Purpose: Turn whatever the user gives us to a IO::Path if possible ?? $path # - Already IO::Path !! $path.?chars # - If $path is Any it won't have .chars (hence .?chars) ?? $path.IO # - A string with at least 1 char is needed to call `.IO` !! self.IO; # - Assume its meant to be called on itself (todo: check $path.defined) # If a file was passed in then we assume its a metafile. Normally you'd pass # in a directory containing the meta file, but for convience we'll do this for files return $dir if !$dir || $dir.IO.f; # META.info and META6.info are not spec, but are still in use. # The windows path size check is for windows symlink wonkiness. # "12" is the minimum size required for a valid meta that # rakudos internal json parser can understand (and is longer than # what the symlink issue noted above usually involves) my $meta-variants = .map: { $ = $dir.child($_) } my $chosen-meta = $meta-variants.grep(*.IO.e).first: -> $file { so ($file.e && ($*DISTRO.is-win ?? ((try $file.s) > 12) !! $file.f)); } || IO::Path; return $chosen-meta; } #| Get all files in resources/ directory and map them into a hash CURI.install understands. method !resources(:%meta, Bool :$absolute --> Hash:D) { my $res-path = self.IO.child('resources'); # resources/libraries is treated differently than everything else. # It uses the internal platform-library-name method to apply an # automatic platform naming scheme to the paths. It maps the original # path to this new path so that CURI.install can understand it. # Example: # META FILE: 'resources/libraries/mylib' # GENERATED: 'resources/libraries/mylib' => 'resources/libaries/libmylib.so' # or 'resources/libraries/mylib' => 'resources/libaries/mylib.dll' # Note that it does not add the "lib" prefix on Windows. Whether the generated file has the "lib" prefix is platform dependent. my $lib-path = $res-path.child('libraries'); return %meta.grep(*.defined).map(-> $resource { my $resource-path = $resource ~~ m/^libraries\/(.*)/ ?? $lib-path.child($*VM.platform-library-name(IO::Path.new($0, :CWD($!path)))) !! $res-path.child($resource); $resource => $resource-path.IO.is-relative ?? ( ?$absolute ?? $resource-path.IO.absolute($!path) !! $resource-path ) !! ( !$absolute ?? $resource-path.IO.relative($!path) !! $resource-path ); }).hash; } #| Get all files in bin/ directory and map them into a hash CURI.install understands. method !scripts(Bool :$absolute --> Hash:D) { do with $.IO.child('bin') -> $bin { return $bin.dir.grep(*.IO.f).map({ $_.IO.basename => $_.IO.is-relative ?? ( ?$absolute ?? $_.IO.absolute($!path) !! $_ ) !! ( !$absolute ?? $_.IO.relative($!path) !! $_ ) }).hash if $bin.IO.d } return {}; } } zef-0.13.8/lib/Zef/Extract.rakumod000066400000000000000000000155511422013762000166630ustar00rootroot00000000000000use Zef; use Zef::Utils::FileSystem; class Zef::Extract does Extractor does Pluggable { =begin pod =title class Zef::Extract =subtitle A configurable implementation of the Extractor interface =head1 Synopsis =begin code :lang use Zef; use Zef::Extract; # Setup with a single extractor backend my $extractor = Zef::Extract.new( backends => [ { module => "Zef::Service::Shell::tar" }, ], ); # Save the content of $uri to $save-to my $tar-file = $*CWD.add("zef-v0.9.4.tar.gz"); my $candidate = Candidate.new(uri => $tar-file); my $extract-to = $*CWD.add("my-extract-dir"); # Show what files an archive contains say "About to extract the following paths:"; say "\t{$_}" for $extractor.ls-files($candidate); # Extract the archive my $extracted-to = $extractor.extract($candidate, $extract-to); say $extracted-to ?? "Done" !! "Something went wrong..."; =end code =head1 Description An C that uses 1 or more other C instances as backends. It abstracts the logic to do 'extract this path with the first backend that supports the given path'. =head1 Methods =head2 method extract-matcher method extract-matcher($path --> Bool:D) Returns C if any of the probeable C know how to extract C<$path>. =head2 method extract method extract(Candidate $candi, IO() $extract-to, Supplier :$logger, Int :$timeout --> IO::Path) Extracts the files for C<$candi> (usually as C<$candi.uri>) to C<$extract-to>. If a backend fails to extract for some reason (such as going over its C<:$timeout>) the next matching backend will be used. Failure occurs when no backend was able to extract the C<$candi>. An optional C<:$logger> can be supplied to receive events about what is occurring. An optional C<:$timeout> can be passed to denote the number of seconds after which we'll assume failure. On success it returns the C where the data was actually extracted to. On failure it returns C. Note this differs from other 'Extractor' adapters C (i.e. the extractors this uses as backends) which take a C as the first parameter, not a C. =head2 method ls-files method ls-files(IO() $archive-file --> Array[Str]) On success it returns an C of relative paths that are available to be extracted from C<$archive-file>. =end pod submethod TWEAK(|) { @ = self.plugins; # preload plugins } #| Returns true if any of the backends 'extract-matcher' understand the given uri/path method extract-matcher($path --> Bool:D) { return so self!extract-matcher($path) } #| Returns the backends that understand the given uri based on their extract-matcher result method !extract-matcher($path --> Array[Extractor]) { my @matching-backends = self.plugins.grep(*.extract-matcher($path)); my Extractor @results = @matching-backends; return @results; } #| A helper method to deliver the 'missing backends' suggestions for extractors method !extractors($path --> Array[Extractor]) { my @extractors = self!extract-matcher($path).cache; unless +@extractors { my @report_enabled = self.plugins.map(*.short-name); my @report_disabled = self.backends.map(*.).grep({ $_ ~~ none(@report_enabled) }); die "Enabled extracting backends [{@report_enabled}] don't understand $path\n" ~ "You may need to configure one of the following backends, or install its underlying software - [{@report_disabled}]"; } my Extractor @results = @extractors; return @results; } #| Will return the first successful result while attempting to extract the given $candi.uri method extract(Candidate $candi, IO() $extract-to, Supplier :$logger, Int :$timeout --> IO::Path) { my $path := $candi.uri; die "Can't extract non-existent path: {$path}" unless $path.IO.e; die "Can't extract to non-existent path: {$extract-to}" unless $extract-to.e || $extract-to.mkdir; my $extractors = self!extractors($path).map(-> $extractor { if ?$logger { $logger.emit({ level => DEBUG, stage => EXTRACT, phase => START, candi => $candi, message => "Extracting with plugin: {$extractor.^name}" }); $extractor.stdout.Supply.act: -> $out { $logger.emit({ level => VERBOSE, stage => EXTRACT, phase => LIVE, candi => $candi, message => $out }) } $extractor.stderr.Supply.act: -> $err { $logger.emit({ level => ERROR, stage => EXTRACT, phase => LIVE, candi => $candi, message => $err }) } } my $out = lock-file-protect("{$extract-to}.lock", -> { my $todo = start { try $extractor.extract($path, $extract-to) }; my $time-up = ($timeout ?? Promise.in($timeout) !! Promise.new); await Promise.anyof: $todo, $time-up; $logger.emit({ level => DEBUG, stage => EXTRACT, phase => LIVE, candi => $candi, message => "Extracting $path timed out" }) if ?$logger && $time-up.so && $todo.not; $todo.so ?? $todo.result !! Nil }); # really just saving $extractor for an error message later on. should do away with it later $extractor => $out; }); # gnu tar on windows doesn't always work as I expect, so try another plugin if extraction fails my $extracted-to = $extractors.grep({ $logger.emit({ level => WARN, stage => EXTRACT, phase => LIVE, candi => $candi, message => "Extracting with plugin {.key.^name} aborted." }) if ?$logger && !(.value.defined && .value.IO.e); .value.defined && .value.IO.e; }).map(*.value).head; die "something went wrong extracting {$path} to {$extract-to} with {$.plugins.join(',')}" unless $extracted-to.IO.e; my IO::Path $result = $extracted-to.IO; return $result; } #| Will return the results first successful extraction, where the results are an array of strings, where #| each string is a relative path representing a file that can be extracted from the given $candi.uri #| Note this differs from other 'Extract' adapters .extract() which take a $uri as the first #| parameter, not a $candi method ls-files($candi --> Array[Str]) { my $path := $candi.uri; my $extractors := self!extractors($path); my $name-paths := $extractors.map(*.ls-files($path)).first(*.defined).map(*.IO); my @files = $name-paths.map({ .is-absolute ?? $path.child(.relative($path)).cleanup.relative($path) !! $_ }); my Str @results = @files.map(*.Str); return @results; } } zef-0.13.8/lib/Zef/Fetch.rakumod000066400000000000000000000111401422013762000162700ustar00rootroot00000000000000use Zef; use Zef::Utils::FileSystem; class Zef::Fetch does Fetcher does Pluggable { =begin pod =title class Zef::Fetch =subtitle A configurable implementation of the Fetcher interface =head1 Synopsis =begin code :lang use Zef; use Zef::Fetch; # Setup with a single fetcher backend my $fetcher = Zef::Fetch.new( backends => [ { module => "Zef::Service::Shell::curl" }, { module => "Zef::Service::Shell::PowerShell::download" }, ], ); # Save the content of $uri to $save-to my $uri = "https://httpbin.org/ip"; my $save-to = $*CWD.child("output.txt"); my $saved-to = $fetcher.fetch(Candidate.new(:$uri), $save-to); say $saved-to ?? $saved-to.slurp !! "Failed to download and save"; =end code =head1 Description A C class that uses 1 or more other C instances as backends. It abstracts the logic to do 'grab this uri with the first backend that supports the given uri'. =head1 Methods =head2 method fetch-matcher method fetch-matcher($path --> Bool:D) Returns C if any of the probeable C know how to fetch C<$path>. =head2 method fetch method fetch(Candidate $candi, IO() $save-to, Supplier :$logger, Int :$timeout --> IO::Path) Fetches the files for C<$candi> (usually as C<$candi.uri>) to C<$save-to>. If a backend fails to fetch for some reason (such as going over its C<:$timeout>) the next matching backend will be used. Failure occurs when no backend was able to fetch the C<$candi>. An optional C<:$logger> can be supplied to receive events about what is occurring. An optional C<:$timeout> can be passed to denote the number of seconds after which we'll assume failure. On success it returns the C where the data was actually fetched to. On failure it returns C. Note this differs from other 'Fetcher' adapters C (i.e. the fetchers this uses as backends) which take a C as the first parameter, not a C. =end pod submethod TWEAK(|) { @ = self.plugins; # preload plugins } #| Returns true if any of the backends 'fetch-matcher' understand the given uri/path method fetch-matcher($uri --> Bool:D) { return so self!fetch-matcher($uri) } #| Returns the backends that understand the given uri based on their fetch-matcher result method !fetch-matcher($uri --> Array[Fetcher]) { my @matching-backends = self.plugins.grep(*.fetch-matcher($uri)); my Fetcher @results = @matching-backends; return @results; } #| Fetch the given url. #| Will return the first successful result while attempting to fetch the given $candi. method fetch(Candidate $candi, IO() $save-to, Supplier :$logger, Int :$timeout --> IO::Path) { my $uri = $candi.uri; my @fetchers = self!fetch-matcher($uri).cache; unless +@fetchers { my @report_enabled = self.plugins.map(*.short-name); my @report_disabled = self.backends.map(*.).grep({ $_ ~~ none(@report_enabled) }); die "Enabled fetching backends [{@report_enabled}] don't understand $uri\n" ~ "You may need to configure one of the following backends, or install its underlying software - [{@report_disabled}]"; } my $got := @fetchers.map: -> $fetcher { if ?$logger { $logger.emit({ level => DEBUG, stage => FETCH, phase => START, candi => $candi, message => "Fetching $uri with plugin: {$fetcher.^name}" }); $fetcher.stdout.Supply.act: -> $out { $logger.emit({ level => VERBOSE, stage => FETCH, phase => LIVE, candi => $candi, message => $out }) } $fetcher.stderr.Supply.act: -> $err { $logger.emit({ level => ERROR, stage => FETCH, phase => LIVE, candi => $candi, message => $err }) } } my $ret = lock-file-protect("{$save-to}.lock", -> { my $todo = start { try $fetcher.fetch($uri, $save-to) }; my $time-up = ($timeout ?? Promise.in($timeout) !! Promise.new); await Promise.anyof: $todo, $time-up; $logger.emit({ level => DEBUG, stage => FETCH, phase => LIVE, candi => $candi, message => "Fetching $uri timed out" }) if ?$logger && $time-up.so && $todo.not; $todo.so ?? $todo.result !! Nil; }); $ret; } my IO::Path $result = $got.grep(*.so).map(*.IO).head; return $result; } } zef-0.13.8/lib/Zef/Identity.rakumod000066400000000000000000000054111422013762000170340ustar00rootroot00000000000000class Zef::Identity { has $.name; has $.version; has $.auth; has $.api; has $.from; method CALL-ME($id) { once { note 'Zef::Identity(...) is deprecated. Use Zef::Identity.new(...) instead' } try self.new(|$id) } my grammar REQUIRE { regex TOP { ^^ [':' ]* $$ } regex name { <-restricted +name-sep>+ } token key { <-restricted>+ } regex value { '<' ~ '>' [<( [[ |\<|\\> . ]+?]* %% ['\\' . ]+ )>] } token restricted { [':' | '<' | '>' | '(' | ')'] } token name-sep { < :: > } } my class REQUIRE::Actions { method TOP($/) { make %('name'=> $/.made, %($/ Z=> $/>>.ast)) if $/ } method name($/) { make $/.Str } method key($/) { my $str = make $/.Str; ($str eq 'ver') ?? 'version' !! $str } method value($/) { make $/.Str } } proto method new(|) {*} multi method new(Str :$name!, :ver(:$version), :$auth, :$api, :$from) { self.bless(:$name, :$version, :$auth, :$api, :$from); } multi method new(Str $id) { if $id.starts-with('.' | '/') { self.bless( name => $id, version => '', auth => '', api => '', from => '', ); } elsif REQUIRE.parse($id, :actions(REQUIRE::Actions)).ast -> $ident { self.bless( name => ~($ident // ''), version => ~($ident.first(*.defined) // ''), auth => ~($ident // '').trans(['\<', '\>'] => ['<', '>']), api => ~($ident // ''), from => ~($ident || 'Perl6'), ); } } # Acme::Foo::SomeModule:auth:ver('1.0') method identity { $!name ~ (($!version // '' ) ne ('*' | '') ?? ":ver<" ~ $!version ~ ">" !! '') ~ (($!auth // '' ) ne ('*' | '') ?? ":auth<" ~ $!auth ~ ">" !! '') ~ (($!api // '' ) ne ('*' | '') ?? ":api<" ~ $!api ~ ">" !! '') ~ (($!from // '' ) ne ('Perl6' | '') ?? ":from<" ~ $!from ~ ">" !! ''); } method hash { my %hash; %hash = $!name // ''; %hash = $!version // ''; %hash = $!auth // ''; %hash = $!api // ''; %hash = $!from // ''; %hash; } } sub str2identity($str) is export { # todo: when $str is a path Zef::Identity.new($str).?identity // $str; } sub identity2hash($identity) is export { Zef::Identity.new($identity).?hash; } sub hash2identity($hash) is export { Zef::Identity.new(|$hash).?identity; } zef-0.13.8/lib/Zef/Install.rakumod000066400000000000000000000103431422013762000166510ustar00rootroot00000000000000use Zef; use Zef::Distribution; class Zef::Install does Installer does Pluggable { =begin pod =title class Zef::Install =subtitle A configurable implementation of the Installer interface =head1 Synopsis =begin code :lang use Zef; use Zef::Install; use Zef::Distribution::Local; # Setup with a single installer backend my $installer = Zef::Install.new( backends => [ { module => "Zef::Service::InstallRakuDistribution" }, ], ); # Assuming our current directory is a raku distribution... my $dist-to-install = Zef::Distribution::Local.new($*CWD); my $candidate = Candidate.new(dist => $dist-to-install); my $install-to-repo = CompUnit::RepositoryRegistry.repository-for-name("site"); # ...install the distribution using the first available backend my $installed = so $installer.install($candidate, :cur($install-to-repo)); say $installed ?? 'Install OK' !! 'Something went wrong...'; =end code =head1 Description An C class that uses 1 or more other C instances as backends. It abstracts the logic to do 'install this distribution with the first backend that supports the given distribution'. =head1 Methods =head2 method install-matcher method install-matcher(Zef::Distribution $dist --> Bool:D) Returns C if any of the probeable C know how to install C<$dist>. =head2 method install method install(Candidate $candi, CompUnit::Repository :$cur!, Bool :$force, Supplier :$logger, Int :$timeout --> Bool:D) Installs the distribution C<$candi.dist> to C<$cur> (see synopsis). Set C<$force> to C to allow installing a distribution that is already installed. An optional C<:$logger> can be supplied to receive events about what is occurring. An optional C<:$timeout> can be passed to denote the number of seconds after which we'll assume failure. Returns C if the installation succeeded. Note In the future this might have backends allowing installation of e.g. Python modules for things using C. =end pod submethod TWEAK(|) { @ = self.plugins; # preload plugins } #| Returns true if any of the backends 'build-matcher' understand the given uri/path method install-matcher(Zef::Distribution $dist --> Bool:D) { return so self!install-matcher($dist) } #| Returns the backends that understand the given uri based on their build-matcher result method !install-matcher(Zef::Distribution $dist --> Array[Installer]) { my @matching-backends = self.plugins.grep(*.install-matcher($dist)); my Installer @results = @matching-backends; return @results; } #| Install the distribution in $candi.dist to the $cur CompUnit::Repository. #| Use :force to install over an existing distribution using the same name/auth/ver/api method install(Candidate $candi, CompUnit::Repository :$cur!, Bool :$force, Supplier :$logger, Int :$timeout --> Bool:D) { my $dist = $candi.dist; 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, candi => $candi, message => "Installing with plugin: {$installer.^name}" }); $installer.stdout.Supply.grep(*.defined).act: -> $out { $logger.emit({ level => VERBOSE, stage => INSTALL, phase => LIVE, candi => $candi, message => $out }) } $installer.stderr.Supply.grep(*.defined).act: -> $err { $logger.emit({ level => ERROR, stage => INSTALL, phase => LIVE, candi => $candi, 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, candi => $candi, message => "Installing {$dist.path} timed out" }) if ?$logger && $time-up.so && $todo.not; my $got = $todo.so ?? $todo.result !! False; return $got; } } zef-0.13.8/lib/Zef/Report.rakumod000066400000000000000000000051301422013762000165140ustar00rootroot00000000000000use Zef; class Zef::Report does Pluggable does Reporter { =begin pod =title class Zef::Report =subtitle A configurable implementation of the Reporter interface =head1 Synopsis =begin code :lang use Zef; use Zef::Report; use Zef::Distribution::Local; # Setup with a single installer backend my $reporter = Zef::Report.new( backends => [ { module => "Zef::Service::FileReporter" }, ], ); # Assuming our current directory is a raku distribution... my $dist-to-report = Zef::Distribution::Local.new($*CWD); my $candidate = Candidate.new(dist => $dist-to-report); my $logger = Supplier.new andthen *.Supply.tap: -> $m { say $m. } # ...report the distribution using the all available backends my $reported = so $reporter.report($candidate, :$logger); say $reported ?? 'Reported OK' !! 'Something went wrong...'; =end code =head1 Description A C class that uses 1 or more other C instances as backends. It abstracts the logic to do 'report this distribution with every backend that supports the given distribution'. =head1 Methods =head2 method report method report(Candidate $candi, Supplier :$logger) Reports information about the distribution C<$candi.dist> to a temporary file (the file can be discovered from the output message emitted). An optional C<:$logger> can be supplied to receive events about what is occurring. Returns C if the reporting succeeded. =end pod submethod TWEAK(|) { @ = self.plugins; # preload plugins } #| Report basic information about this Candidate to a temp file method report(Candidate $candi, Supplier :$logger) { my $reporters := self.plugins.grep(*.so).cache; my @reports = $reporters.map: -> $reporter { if ?$logger { $logger.emit({ level => DEBUG, stage => REPORT, phase => START, candi => $candi, message => "Reporting with plugin: {$reporter.^name}" }); $reporter.stdout.Supply.grep(*.defined).act: -> $out { $logger.emit({ level => VERBOSE, stage => REPORT, phase => LIVE, candi => $candi, message => $out }) } $reporter.stderr.Supply.grep(*.defined).act: -> $err { $logger.emit({ level => ERROR, stage => REPORT, phase => LIVE, candi => $candi, message => $err }) } } my $report = $reporter.report($candi); $report; } return @reports.grep(*.defined); } } zef-0.13.8/lib/Zef/Repository.rakumod000066400000000000000000000247041422013762000174300ustar00rootroot00000000000000use Zef; class Zef::Repository does PackageRepository does Pluggable { =begin pod =title class Zef::Repository =subtitle A configurable implementation of the Repository interface =head1 Synopsis =begin code :lang use Zef::Fetch; use Zef::Repository; # Need a fetcher and cache for the backend repository to fetch and save to my @fetching_backends = [ { module => "Zef::Service::Shell::curl" }, { module => "Zef::Service::Shell::PowerShell::download" }, ]; my $fetcher = Zef::Fetch.new(:backends(@fetching_backends)); my $cache = $*TMPDIR.child("{time}") andthen { mkdir $_ unless $_.IO.e }; # Create repo using a backend that is essentially just: Zef::Repository::Ecosystems.new(|%options-shown-below) # Note usually options are all Str so they can be set in the config file, but for the time being the cache # and fetcher objects need to be passed in here as well (currently auto-magically done in Zef::Client but could # be done in Zef::Repository) my $repo = Zef::Repository.new( backends => [ [ { module => "Zef::Repository::Ecosystems", options => { cache => $cache, fetcher => $fetcher, name => "cpan", auto-update => 1, mirrors => ["https://raw.githubusercontent.com/ugexe/Perl6-ecosystems/11efd9077b398df3766eaa7cf8e6a9519f63c272/cpan.json"] } }, ], ] ); # Print out all available distributions from all supplied backend repositories say $_.dist.identity for $repo.available; # Get the best match for 'Zef' my @matches = $repo.candidates("Zef"); say "Best match: " ~ @matches.head.dist.identity; =end code =head1 Description A C that uses 1 or more other C instances as backends. It abstracts the logic for e.g. sorting by version from multiple repositories. Each C (including this one and whatever backends this may use) can be thought of as recommendation managers, where C gives a recommendation based on recommendations it gets from its backends (i.e. fez, p6c, cpan, cached). =head1 Methods =head2 method candidates method candidates(*@identities ($, *@) --> Array[Candidate]) Resolves each identity in C<@identities> to its best matching C, where the "best match" is generally what is most consistent with how C would otherwise load modules. Each repository makes its own recommendations, and only then will the results be considered to make a single recommendation for each of C<@identities> from. For instance one ecosystem might return a C and C, and another a C for an identity of C -- this module emulates C internal module resolution logic and will choose the best match (in this case C. This module purposely does not combine multiple ecosystems into a single list to make a recommendation from; by design it a recommendation manager for the results of recommendation managers. This allows more consistent resolution of dependencies and integration with MetaCPAN like services (which may not just provide an entire list of modules it has -- i.e. it makes it own recommendation for a name) One difference in how recommendations are made from raku is that repos are grouped. Given pseudo backends C<[[Eco1,Eco2],[Eco3]]> we see three repository backends in two different groups -- ecosystems in later groups are only searched if previous groups found no matches. This allows users to avoid dependency confusion attacks by allowing custom ecosystems to be the preferred source for whatever namespaces it provides regardless if another ecosystem later provides a module by the same name but higher version number. Returns an C of C, where each C matches exactly one of the provided C<@identities> (and each C<@identities> matches zero or one of the C). method search method search(:$max-results, Bool :$strict, *@identities ($, *@), *%fields --> Array[Candidate]) Resolves each identity in C<@identities> to all of its matching C from all backends (with C<$max-results> applying to each individual backend). If C<$strict> is C then it will consider partial matches on module short-names (i.e. 'zef search HTTP' will get results for e.g. C). method store method store(*@dists --> Nil) Attempts to store/save/cache each C<@dist> to each backend repository that provides a C method. Generally this is used when a module is fetched from e.g. fez so that C can cache it for next time. Note distributions fetched from local paths (i.e. `zef install .`) do not generally get passed to this method. method available method available(*@plugins --> Array[Candidate]) Returns an C of all C provided by all backend repositories that support the C method (http-query-per-request repositories may not be able to provide this) and have a 'name' (as defined in its entry in C) matching any of those in C<@names> (i.e. C will only show stuff from the 'fez' backend). method update method update(*@plugins --> Hash) Updates each ecosystem backend (generally downloading a p6c.json or cpan.json file, or updating the 'cached' index). =end pod submethod TWEAK(|) { @ = self.plugins; # preload plugins } #| This is what is used to resolve dependencies. #| Similar to .search(...), except it only returns 1 result (the best match) for each identity provided #| i.e .search for Foo:ver<*> may return multiple versions, but .candidates would only ever return 1 even #| if it exists in multiple repositories/backends. method candidates(*@identities ($, *@) --> Array[Candidate]) { # todo: have a `file` identity in Zef::Identity my %searchable = @identities.grep({ not $_.starts-with("." | "/") }).map({ $_ => 1 }); my @unsorted-candis = eager gather GROUP: for self!plugins -> @repo-group { my @look-for = %searchable.grep(*.value).hash.keys.sort; my @group-results = @repo-group.hyper(:batch(1)).map: -> $repo { my @search-for = $repo.id eq 'Zef::Repository::LocalCache' ?? @identities !! @look-for; $repo.search(@search-for, :strict).Slip; } for @group-results -> $dist { %searchable{$dist.as} = 0; take $dist; } last GROUP unless %searchable.values.grep(*.so).so; } my @unsorted-grouped-candis = @unsorted-candis.grep(*.defined).categorize({.as}).values; # Take the distribution with the highest version out of all matching distributions from each repository my @sorted-candis = @unsorted-grouped-candis.map: -> $candis { my @presorted = $candis.sort(*.dist.ver).sort(*.dist.api); my $api = @presorted.tail.dist.api; my $version = @presorted.tail.dist.ver; my @sorted = @presorted.grep({ .dist.ver eq $version }).grep({ .dist.api eq $api }); @sorted.tail; } # dedupe things the earlier `.as` categorization won't group right, like Foo:ver<1.0+> and Foo:ver<1.1+> my Candidate @results = @sorted-candis.unique(:as(*.dist.identity)); return @results; } #| This is what is used to search for identities. #| Similar to .candidates(...), except it will return more than one result per identity as appropriate. method search(:$max-results, Bool :$strict, *@identities ($, *@), *%fields --> Array[Candidate]) { return Nil unless @identities || %fields; my @searchable = @identities.grep({ not $_.starts-with("." | "/") }); my @unsorted-candis = eager gather GROUP: for self!plugins -> @repo-group { my @group-results = @repo-group.hyper(:batch(1)).map: -> $repo { $repo.search(@searchable, :$strict).Slip; } if @group-results.elems { take $_ for @group-results; } } my Candidate @results = @unsorted-candis; return @results; } #| Call 'store' on any Repository that provides that interface (by default just 'cached') method store(*@dists --> Nil) { for self!plugins.map(*.Slip).grep(*.^can('store')) -> $storage { $storage.?store(@dists); } } #| Get all candidates/distributions from each backend method available(*@plugins --> Array[Candidate]) { my @can-available = self!plugins(@plugins).map(*.Slip).grep: -> $plugin { note "Plugin '{$plugin.short-name}' does not support `.available` -- Skipping" unless $plugin.can('available'); # UNDO doesn't work here yet $plugin.can('available'); } my @available = @can-available.hyper(:batch(1)).map({ $_.available.Slip }); my Candidate @results = @available; return @results; } #| Update each Repository / backend method update(*@plugins --> Nil) { my @can-update = self!plugins(@plugins).grep: -> $plugin { note "Plugin '{$plugin.short-name}' does not support `.update` -- Skipping" unless $plugin.can('update'); # UNDO doesn't work here yet $plugin.can('update'); } @can-update.race(:batch(1)).map({ $_.update }); } #| Like self.plugins this returns a list of plugins that Pluggable + @.backends provides, but also allows #| filtering which plugins are used by short-name (short-name is set in the config per Repository) so that #| things like `--update=fez` or `--/update=cpan` work. method !plugins(*@short-names) { my $all-plugins := self.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; } } zef-0.13.8/lib/Zef/Repository/000077500000000000000000000000001422013762000160355ustar00rootroot00000000000000zef-0.13.8/lib/Zef/Repository/Ecosystems.rakumod000066400000000000000000000237561422013762000215740ustar00rootroot00000000000000use Zef; use Zef::Utils::FileSystem; use Zef::Distribution; use Zef::Distribution::DependencySpecification; class Zef::Repository::Ecosystems does PackageRepository { =begin pod =title class Zef::Repository::Ecosystems =subtitle A simple json database based implementation of the Repository interface =head1 Synopsis =begin code :lang use Zef::Fetch; use Zef::Repository::Ecosystems; my @fetching_backends = [ { module => "Zef::Service::Shell::curl" }, { module => "Zef::Service::Shell::PowerShell::download" }, ]; my @mirrors = 'https://raw.githubusercontent.com/ugexe/Perl6-ecosystems/11efd9077b398df3766eaa7cf8e6a9519f63c272/cpan.json'; my $fetcher = Zef::Fetch.new(:backends(@fetching_backends)); my $cache = $*HOME.child(".zef/store") andthen { mkdir $_ unless $_.IO.e }; my $repo = Zef::Repository::Ecosystems.new(name => "cpan", :$fetcher, :$cache, :@mirrors); # Print out all available distributions from this repository say $_.dist.identity for $repo.available; =end code =head1 Description A basic C that uses a file (containing an array of hash / META6 json) as a database. It is used for the default 'fez', 'p6c', and 'cpan' ecosystems, and is also a good choice for ad-hoc darkpans by passing it your own mirrors in the config. =head1 Methods =head2 method search method search(Bool :$strict, *@identities ($, *@), *%fields --> Array[Candidate]) Resolves each identity in C<@identities> to all of its matching C. If C<$strict> is C then it will consider partial matches on module short-names (i.e. 'zef search HTTP' will get results for e.g. C). =head2 method available method available(*@plugins --> Array[Candidate]) Returns an C of all C provided by this repository instance (i.e. all distributions in the cpan ecosystem). =head2 method update method update(--> Nil) Attempts to update the local file / database using the first of C<@.mirrors> that successfully fetches. =end pod #| A name for the repository/ecosystem to be referenced (i.e. '===> Updated myname mirror: ...') has Str $.name; #| One or more URIs containing an ecosystem 'array-of-hash' database. URI types that work #| are whatever the supplied $!fetcher supports (so generally local files and https) has List $.mirrors; #| Int - the db will be lazily updated when it is $!auto-update hours old. #| Bool True - the db will be lazily updated regardless of how old the db is. #| Bool False - do not update the db. has $.auto-update is rw; #| Bool True - will use the meta as the source url #| Bool False - will not check meta as the source url has Bool $.uses-path is rw = False; #| Where we will save/stage the db file we fetch has IO::Path $.cache; #| Used to get data from a URI. Generally uses Zef::Fetcher, which itself uses multiple backends to allow #| fetching local paths, https, and git by default has Fetcher $.fetcher; #| A array of distributions found in the ecosystem db. Lazily populated as soon as the db is referenced has Zef::Distribution @!distributions; #| Similar to @!distributions, but indexes by short name i.e. { "Foo::Bar" => ($dist1, $dist2), "Baz" => ($dist1) } has Array[Distribution] %!short-name-lookup; #| see role Repository in lib/Zef.pm6 method id(--> Str) { $?CLASS.^name.split('+', 2)[0] ~ "<{$!name}>" } #| see role Repository in lib/Zef.pm6 method available(--> Array[Candidate]) { self!populate-distributions; my @candidates = @!distributions.map: -> $dist { Candidate.new( dist => $dist, uri => ($dist.source-url || $dist.meta), from => self.id, as => $dist.identity, ); } my Candidate @results = @candidates; return @results; } #| Iterate over mirrors until we successfully fetch and save one #| see role Repository in lib/Zef.pm6 has Int $!update-counter; # Keep track if we already did an update during this runtime method update(--> Nil) { $!update-counter++; $!mirrors.first: -> $uri { # TODO: use the logger to send these as events note "===> Updating $!name mirror: $uri"; UNDO note "!!!> Failed to update $!name mirror: $uri"; KEEP note "===> Updated $!name mirror: $uri"; my $save-as = $!cache.IO.child($uri.IO.basename); my $saved-as = try { CATCH { default { .note; } } $!fetcher.fetch(Candidate.new(:$uri), $save-as, :timeout(180)); } next unless $saved-as.defined && $saved-as.?chars && $saved-as.IO.e; # this is kinda odd, but if $path is a file, then its fetching via http from p6c.org # and if its a directory its pulling from my ecosystems repo (this hides the difference for now) $saved-as .= child("{$!name}.json") if $saved-as.d; next unless $saved-as.e; lock-file-protect("{$saved-as}.lock", -> { self!spurt-package-list($saved-as.slurp(:bin)) }); } } #| see role Repository in lib/Zef.pm6 method search(Bool :$strict, *@identities, *%fields --> Array[Candidate]) { return Nil unless @identities || %fields; my %specs = @identities.map: { $_ => Zef::Distribution::DependencySpecification.new($_) } my @searchable-identities = %specs.classify({ .value.from-matcher }).grep(*.defined).hash.keys; return Nil unless @searchable-identities; # populate %!short-name-lookup self!populate-distributions; my $grouped-results := @searchable-identities.map: -> $searchable-identity { my $wanted-spec := %specs{$searchable-identity}; my $wanted-short-name := $wanted-spec.name; my $dists-to-search := $strict ?? (%!short-name-lookup{$wanted-short-name} // Nil).grep(*.so) !! %!short-name-lookup{%!short-name-lookup.keys.grep(*.contains($wanted-short-name))}.map(*.Slip).grep(*.so); my $matching-candidates := $dists-to-search.grep(*.contains-spec($wanted-spec, :$strict)).map({ my $uri; if $_.meta && $.uses-path { $uri = $_.meta; $uri ~~ s/^repo\///; $uri = $.mirrors.first ~ $uri; } Candidate.new( dist => $_, uri => ($uri || $_.source-url || $_.meta), as => $searchable-identity, from => self.id, ); }); $matching-candidates; } # ((A_Match_1, A_Match_2), (B_Match_1)) -> ( A_Match_1, A_Match_2, B_Match_1) my Candidate @results = $grouped-results.map(*.Slip); return @results; } #| Location of db file has IO::Path $!package-list-path; method !package-list-path(--> IO::Path) { unless $!package-list-path { my $dir = $!cache.IO.child($!name); $dir.mkdir unless $dir.e; $!package-list-path = $dir.child($!name ~ '.json'); } return $!package-list-path; } #| Read our package db method !slurp-package-list(--> List) { return [ ] unless self!package-list-path.e; do given self!package-list-path.open(:r) { LEAVE {.close} .lock: :shared; try |Zef::from-json(.slurp); } } #| Write our package db method !spurt-package-list($content --> Bool) { do given self!package-list-path.open(:w) { LEAVE {.close} .lock; try .spurt($content); } } #| Check if our package list should be updated method !is-package-list-stale(--> Bool:D) { return so !self!package-list-path.e || ($!auto-update && self!package-list-path.modified < now.DateTime.earlier(:hours($!auto-update)).Instant); } #| Populate @!distributions and %!short-name-lookup, essentially initializing the data as late as possible has $!populate-distributions-lock = Lock.new; method !populate-distributions(--> Nil) { $!populate-distributions-lock.protect: { self.update if !$!update-counter && self!is-package-list-stale; return if +@!distributions; for self!slurp-package-list -> $meta { with try Zef::Distribution.new(|%($meta)) -> $dist { # Keep track of out namespaces we are going to index later my @short-names-to-index; # Take the dist identity push @short-names-to-index, $dist.name; # Take the identity of each module in provides # * The fast path doesn't work with provides entries that are long names (i.e. Foo:ver<1>) # * The slow path results in parsing the module names in every distributions provides even though # long names don't work in rakudo (yet) # * ...So maintain future correctness while getting the fast path in 99% of cases by doing a # cheap check for '<' and parsing only if needed append @short-names-to-index, $dist.meta.keys.first(*.contains('<')) ?? $dist.provides-specs.map(*.name) # slow path !! $dist.meta.keys; # fast path # Index the short name to the distribution. Make sure entries are # unique since dist name and one module name will usually match. push %!short-name-lookup{$_}, $dist for @short-names-to-index.unique; push @!distributions, $dist; } } } } } zef-0.13.8/lib/Zef/Repository/LocalCache.rakumod000066400000000000000000000227701422013762000214070ustar00rootroot00000000000000use Zef; use Zef::Distribution::Local; use Zef::Distribution::DependencySpecification; use Zef::Utils::FileSystem; class Zef::Repository::LocalCache does PackageRepository { =begin pod =title class Zef::Repository::LocalCache =subtitle A local caching implementation of the Repository interface =head1 Synopsis =begin code :lang use Zef::Fetch; use Zef::Repository::LocalCache; # Point cache at default zef cache so there are likely some distributions to see my $cache = $*HOME.child(".zef/store"); my $repo = Zef::Repository::LocalCache.new(:$cache); # Print out all available distributions from this repository say $_.dist.identity for $repo.available; =end code =head1 Description The C zef uses for its local cache. It is intended to keep track of contents of a directory full of raku distributions. It provides the optional C method C which allows it to save/copy any modules downloaded by other repositories. Note: distributions installed from local file paths (i.e. C) will not be cached since local development of modules often occurs without immediately bumping versions (and thus a stale version would soon get cached). Note: THIS IS PROBABLY NOT ANY MORE EFFICIENT THAN ::Ecosystems BASED REPOSITORIES At one time json parsing/writing was slow enough that parts of this implementation were faster. Now it is mostly just useful for dynamically generating the MANIFEST.zef from the directory structure this repository expects instead of fetching a file like C. =head1 Methods =head2 method search method search(Bool :$strict, *@identities ($, *@), *%fields --> Array[Candidate]) Resolves each identity in C<@identities> to all of its matching C. If C<$strict> is C then it will consider partial matches on module short-names (i.e. 'zef search HTTP' will get results for e.g. C). =head2 method available method available(*@plugins --> Array[Candidate]) Returns an C of all C provided by this repository instance (i.e. all distributions in the local cache). =head2 method update method update(--> Nil) Attempts to update the local file / database using the first of C<@.mirrors> that successfully fetches. =head2 method store method store(*@dists --> Nil) Attempts to store/save/cache each C<@dist>. Generally this is called when a module is fetched from e.g. cpan so that this module can cache it locally for next time. Note distributions fetched from local paths (i.e. `zef install .`) do not generally get passed to this method. =end pod #| One or more URIs containing an ecosystem 'array-of-hash' database. URI types that work #| are whatever the supplied $!fetcher supports (so generally local files and https) has List $.mirrors; #| Int - the db will be lazily updated when it is $!auto-update hours old. #| Bool True - the db will be lazily updated regardless of how old the db is. #| Bool False - do not update the db. has $.auto-update is rw; #| Where we will save/stage the db file we fetch has IO::Path $.cache; #| A array of distributions found in the ecosystem db. Lazily populated as soon as the db is referenced has Zef::Distribution @!distributions; #| Similar to @!distributions, but indexes by short name i.e. { "Foo::Bar" => ($dist1, $dist2), "Baz" => ($dist1) } has Array[Distribution] %!short-name-lookup; #| see role Repository in lib/Zef.pm6 method available(--> Array[Candidate]) { self!populate-distributions; my Candidate @candidates = @!distributions.map: -> $dist { Candidate.new( dist => $dist, uri => ($dist.source-url || $dist.meta), from => self.id, as => $dist.identity, ); } my Candidate @results = @candidates; return @results; } #| Rebuild the manifest/index by recursively searching for META files method update(--> Nil) { LEAVE { self.store(@!distributions) } self!update; self!populate-distributions; } #| Method to allow self.store() call the equivalent of self.update() without infinite recursion method !update(--> Bool:D) { # $.cache/level1/level2/ # dirs containing dist files my @dirs = $!cache.IO.dir.grep(*.d).map(*.dir.Slip).grep(*.d); my @dists = grep { .defined }, map { try Zef::Distribution::Local.new($_) }, @dirs; my $content = join "\n", @dists.map: { join "\0", (.identity, .path) } so $content ?? self!spurt-package-list($content) !! False; } #| see role Repository in lib/Zef.pm6 method search(Bool :$strict, *@identities, *%fields --> Array[Candidate]) { return Nil unless @identities || %fields; my %specs = @identities.map: { $_ => Zef::Distribution::DependencySpecification.new($_) } my @searchable-identities = %specs.classify({ .value.from-matcher }).grep(*.defined).hash.keys; return Nil unless @searchable-identities; # populate %!short-name-lookup self!populate-distributions; my $grouped-results := @searchable-identities.map: -> $searchable-identity { my $wanted-spec := %specs{$searchable-identity}; my $wanted-short-name := $wanted-spec.name; my $dists-to-search := $strict ?? (%!short-name-lookup{$wanted-short-name} // Nil).grep(*.so) !! %!short-name-lookup{%!short-name-lookup.keys.grep(*.contains($wanted-short-name))}.map(*.Slip).grep(*.so); my $matching-candidates := $dists-to-search.grep(*.contains-spec($wanted-spec, :$strict)).map({ Candidate.new( dist => $_, uri => ($_.source-url || $_.meta), as => $searchable-identity, from => self.id, ); }); $matching-candidates; } # ((A_Match_1, A_Match_2), (B_Match_1)) -> ( A_Match_1, A_Match_2, B_Match_1) my Candidate @results = $grouped-results.map(*.Slip); return @results; } #| After the `fetch` phase an app can call `.store` on any Repository that #| provides it, allowing each Repository to do things like keep a simple list of #| identities installed, keep a cache of anything installed (how its used here), etc method store(*@dists --> Bool) { for @dists.grep({ not self.search($_.identity).elems }) -> $dist { my $from = $dist.IO; my $to = $.cache.IO.child($from.basename).child($dist.id); try copy-paths( $from, $to ) } self!update; } #| Location of db file has IO::Path $!package-list-path; method !package-list-path(--> IO::Path) { unless $!package-list-path { my $dir = $!cache.IO; $dir.mkdir unless $dir.e; $!package-list-path = $dir.child('MANIFEST.zef'); } return $!package-list-path; } #| Read our package db method !slurp-package-list(--> List) { return [ ] unless self!package-list-path.e; do given self!package-list-path.open(:r) { LEAVE {.close} .lock: :shared; .slurp.lines.map({.split("\0")[1]}).cache; } } #| Write our package db method !spurt-package-list($content --> Bool) { do given self!package-list-path.open(:w) { LEAVE {.close} .lock; try .spurt($content); } } #| Populate @!distributions and %!short-name-lookup, essentially initializing the data as late as possible has $!populate-distributions-lock = Lock.new; method !populate-distributions(--> Nil) { $!populate-distributions-lock.protect: { self!update if $.auto-update || !self!package-list-path.e; return if +@!distributions; for self!slurp-package-list -> $path { with try Zef::Distribution::Local.new($path) -> $dist { # Keep track of out namespaces we are going to index later my @short-names-to-index; # Take the dist identity push @short-names-to-index, $dist.name; # Take the identity of each module in provides # * The fast path doesn't work with provides entries that are long names (i.e. Foo:ver<1>) # * The slow path results in parsing the module names in every distributions provides even though # long names don't work in rakudo (yet) # * ...So maintain future correctness while getting the fast path in 99% of cases by doing a # cheap check for '<' and parsing only if needed append @short-names-to-index, $dist.meta.keys.first(*.contains('<')) ?? $dist.provides-specs.map(*.name) # slow path !! $dist.meta.keys; # fast path # Index the short name to the distribution. Make sure entries are # unique since dist name and one module name will usually match. push %!short-name-lookup{$_}, $dist for @short-names-to-index.unique; push @!distributions, $dist; } } } } } zef-0.13.8/lib/Zef/Service/000077500000000000000000000000001422013762000152565ustar00rootroot00000000000000zef-0.13.8/lib/Zef/Service/FetchPath.rakumod000066400000000000000000000125041422013762000205120ustar00rootroot00000000000000use Zef; use Zef::Utils::FileSystem; class Zef::Service::FetchPath does Fetcher does Extractor does Messenger { =begin pod =title class Zef::Service::FetchPath =subtitle A file system based implementation of the Fetcher and Extractor interfaces =head1 Synopsis =begin code :lang use Zef; use Zef::Service::FetchPath; my $fetch-path = Zef::Service::FetchPath.new; # Copy the content of the current directory to ./backup_dir/$random/* my $source = $*CWD; my $save-to = $*CWD.child("backup_dir"); my $saved-to = $fetch-path.fetch($source, $save-to); die "Failed to copy paths" unless $saved-to; say "The following top level paths now exist:"; say "\t{$_.Str}" for $saved-to.dir; my $extract-to = $*CWD.child("extracted_backup_dir"); my $extracted-to = $fetch-path.extract($saved-to, $extract-to); die "Failed to extract paths" unless $extracted-to; say "The following top level paths now exist:"; say "\t{$_.Str}" for $extracted-to.dir; =end code =head1 Description C and C class for handling local file paths. You probably never want to use this unless its indirectly through C or C; handling files will generally be easier using core language functionality. This class exists to provide the means for fetching local paths using the C and C interfaces that the e.g. git/http/tar fetching/extracting adapters use. =head1 Methods =head2 method probe method probe(--> Bool:D) Returns C if this module believes all run time prerequisites are met. Since the only prerequisite is a file system this always returns C =head2 method fetch-matcher method fetch-matcher(Str() $uri --> Bool:D) Returns C if this module knows how to fetch C<$uri>, which it decides based on if C<$uri> looks like a file path (i.e. C<$uri> starts with a C<.> or C) and if that file path exists. =head2 method extract-matcher method extract-matcher(Str() $uri --> Bool:D) Returns C if this module knows how to extract C<$uri>, which it decides based on if C<$uri> looks like a file path (i.e. C<$uri> starts with a C<.> or C) and if that file path exists as a directory. =head2 method fetch method fetch(IO() $source-path, IO() $save-to --> IO::Path) Fetches the given C<$source-path> from the file system and copies it to C<$save-to> (+ timestamp if C<$source-path> is a directory) directory. On success it returns the C where the data was actually saved to (usually a subdirectory under the passed-in C<$save-to>). On failure it returns C. =head2 method extract method extract(IO() $source-path, IO() $save-to --> IO::Path) Extracts the given C<$source-path> from the file system and copies it to C<$save-to>. On success it returns the C where the data was actually extracted to. On failure it returns C. =head2 method ls-files method ls-files(IO() $path --> Array[Str]) On success it returns an C of relative paths that are available to be extracted from C<$path>. =end pod #| Always return true since a file system is required method probe(--> Bool:D) { return True } #| Return true if this Fetcher understands the given uri/path method fetch-matcher(Str() $uri --> Bool:D) { # .is-absolute lets the app pass around absolute paths on windows and still work as expected my $is-pathy = so <. />.first({ $uri.starts-with($_) }) || $uri.IO.is-absolute; return so $is-pathy && $uri.IO.e; } #| Return true if this Extractor understands the given uri/path method extract-matcher(Str() $uri --> Bool:D) { # .is-absolute lets the app pass around absolute paths on windows and still work as expected my $is-pathy = so <. />.first({ $uri.starts-with($_) }) || $uri.IO.is-absolute; return so $is-pathy && $uri.IO.d; } #| Fetch (copy) the given source path to the $save-to (+ timestamp if source-path is a directory) directory method fetch(IO() $source-path, IO() $save-to --> IO::Path) { return Nil if !$source-path.e; return $source-path if $source-path.absolute eq $save-to.absolute; # fakes a fetch my $dest-path = $source-path.d ?? $save-to.child("{$source-path.IO.basename}_{time}") !! $save-to; mkdir($dest-path) if $source-path.d && !$save-to.e; return $dest-path if copy-paths($source-path, $dest-path).elems; return Nil; } #| Extract (copy) the files located in $source-path directory to $save-to directory. #| This is mostly the same as fetch, and essentially allows the workflow to treat #| any uri type (including paths) as if they can be extracted. method extract(IO() $source-path, IO() $save-to --> IO::Path) { my $extracted-to = $save-to.child($source-path.basename); my @extracted = copy-paths($source-path, $extracted-to); return +@extracted ?? $extracted-to !! Nil; } #| List all files and directories, recursively, for the given path method ls-files(IO() $path --> Array[Str]) { my Str @results = list-paths($path, :f, :!d, :r).map(*.Str); return @results; } } zef-0.13.8/lib/Zef/Service/FileReporter.rakumod000066400000000000000000000075731422013762000212600ustar00rootroot00000000000000use Zef; class Zef::Service::FileReporter does Messenger does Reporter { =begin pod =title class Zef::Service::FileReporter =subtitle A basic save-to-file based implementation of the Reporter interface =head1 Synopsis =begin code :lang use Zef; use Zef::Distribution::Local; use Zef::Service::FileReporter; my $reporter = Zef::Service::FileReporter.new; # Add logging if we want to see output $reporter.stdout.Supply.tap: { say $_ }; $reporter.stderr.Supply.tap: { note $_ }; # Assuming our current directory is a raku distribution my $dist = Zef::Distribution::Local.new($*CWD); my $candidate = Candidate.new(:$dist); my $reported = so $reporter.report($candidate); say $reported ?? "Report Success" !! "Report Failure"; =end code =head1 Description C class that serves as an example of a reporter. Note this doesn't yet save e.g. test output in a way that can be recorded, such as attaching it to C or to a temp file linked to that C. =head1 Methods =head2 method probe method probe(--> Bool:D) Always returns C since this is backed by C. =head2 method report method report(Candidate $candi --> Bool:D) Given C<$candi> it will save various information including the distribution meta data, system information, if the tests passed, and (in the future, so nyi) test output. Returns C if the report data was saved successfully. =end pod method probe(--> Bool:D) { return True } method report(Candidate $candi) { my $report-json = Zef::to-json(:pretty, { :name($candi.dist.name), :version(first *.defined, $candi.dist.meta), :dependencies($candi.dist.meta), :metainfo($candi.dist.meta.hash), :build-passed($candi.build-results.map(*.not).none.so), :test-passed($candi.test-results.map(*.not).none.so), :distro({ :name($*DISTRO.name), :version($*DISTRO.version.Str), :auth($*DISTRO.auth), :release($*DISTRO.release), }), :kernel({ :name($*KERNEL.name), :version($*KERNEL.version.Str), :auth($*KERNEL.auth), :release($*KERNEL.release), :hardware($*KERNEL.hardware), :arch($*KERNEL.arch), :bits($*KERNEL.bits), }), :perl({ :name($*RAKU.name), :version($*RAKU.version.Str), :auth($*RAKU.auth), :compiler({ :name($*RAKU.compiler.name), :version($*RAKU.compiler.version.Str), :auth($*RAKU.compiler.auth), :release($*RAKU.compiler.release), :codename($*RAKU.compiler.codename), }), }), :vm({ :name($*VM.name), :version($*VM.version.Str), :auth($*VM.auth), :config($*VM.config), :properties($*VM.?properties), :precomp-ext($*VM.precomp-ext), :precomp-target($*VM.precomp-target), :prefix($*VM.prefix.Str), }), }); my $out-file = $*TMPDIR.add("zef-report_{rand}"); try { CATCH { default { $.stderr.emit("Encountered problems sending test report for {$candi.dist.identity}"); return False; } } $out-file.spurt: $report-json; $.stdout.emit("Report for {$candi.dist.identity} will be available at {$out-file.absolute}"); } return $out-file.e; } } zef-0.13.8/lib/Zef/Service/InstallRakuDistribution.rakumod000066400000000000000000000051501422013762000234740ustar00rootroot00000000000000use Zef; class Zef::Service::InstallRakuDistribution does Installer does Messenger { =begin pod =title class Zef::Service::InstallRakuDistribution =subtitle A raku CompUnit::Repository based implementation of the Installer interface =head1 Synopsis =begin code :lang use Zef; use Zef::Service::InstallRakuDistribution; my $installer = Zef::Service::InstallRakuDistribution.new; # Assuming our current directory is a raku distribution # with no dependencies or all dependencies already installed... my $dist-to-install = Zef::Distribution::Local.new($*CWD); my $cur = CompUnit::RepositoryRegistry.repository-for-name("site"); # default install location my $passed = so $installer.install($dist-to-test, :$cur); say $passed ?? "PASS" !! "FAIL"; =end code =head1 Description C class for handling raku C installation (it installs raku modules). You probably never want to use this unless its indirectly through C. =head1 Methods =head2 method probe method probe(--> Bool:D) Returns C if this module believes all run time prerequisites are met. Since the only prerequisite is C<$*EXECUTABLE> this always returns C. =head2 method install-matcher method install-matcher(Distribution $ --> Bool:D) { return True } Returns C if this module knows how to install the given C. Note: This always returns C right now, but may not in the future if zef learns how to install packages from other languages (such as perl via a cpanm wrapper). =head2 method install method install(Distribution $dist, CompUnit::Repository :$cur, Bool :$force --> Bool:D) Install the distribution C<$dist> to the CompUnit::Repository C<$cur>. If C<$force> is C then it will allow reinstalling an already installed distribution. Returns C if the install succeeded. =end pod #| Always return True since this is using the built-in raku installation logic method probe(--> Bool:D) { True } #| Return true as long as we have a Distribution class that raku knows how to install method install-matcher(Distribution $ --> Bool:D) { return True } #| Install the distribution in $candi.dist to the $cur CompUnit::Repository. #| Use :force to install over an existing distribution using the same name/auth/ver/api method install(Distribution $dist, CompUnit::Repository :$cur, Bool :$force --> Bool:D) { $cur.install($dist, :$force); return True; } } zef-0.13.8/lib/Zef/Service/Shell/000077500000000000000000000000001422013762000163255ustar00rootroot00000000000000zef-0.13.8/lib/Zef/Service/Shell/DistributionBuilder.rakumod000066400000000000000000000104311422013762000236760ustar00rootroot00000000000000use Zef; use Zef::Distribution::Local; class Zef::Service::Shell::DistributionBuilder does Builder does Messenger { =begin pod =title class Zef::Service::Shell::DistributionBuilder =subtitle A META6-supplied raku module based implementation of the Builder interface =head1 Synopsis =begin code :lang use Zef; use Zef::Service::Shell::DistributionBuilder; my $builder = Zef::Service::Shell::DistributionBuilder.new; # Add logging if we want to see output $builder.stdout.Supply.tap: { say $_ }; $builder.stderr.Supply.tap: { note $_ }; # Assuming our current directory is a raku distribution with something like # `"builder" : "Distribution::Builder::MakeFromJSON"` in its META6.json # and has no dependencies (or all dependencies already installed)... my $dist-to-build = Zef::Distribution::Local.new($*CWD); my Str @includes = $*CWD.absolute; my $built-ok = so $builder.build($dist-to-build, :@includes); say $built-ok ?? "OK" !! "Something went wrong"; =end code =head1 Description C class for handling local distributions that include a C<"builder" : "..."> in their C. For example C<"builder" : "Distribution::Builder::MakeFromJSON"> will spawn a process where it passes the module C the path of the distribution and the parsed meta data (from which it may use other instructions from non-standard fields in the C). =head1 Methods =head2 method probe method probe(--> Bool:D) Returns C if this module can successfully launch the C command (i.e. always returns C). =head2 method build-matcher method build-matcher(Zef::Distribution::Local $dist --> Bool:D) Returns C if this module knows how to test C<$uri>. This module always returns C right now since it just uses the C command. =head2 method build method build(Zef::Distribution::Local $dist, Str :@includes --> Bool:D) Launches a process to invoke whatever module is in the C field of the C<$dist> META6.json while passing that module the meta data of C<$dist> it is to build (allowing non-spec keys to be used by such modules to allow consumers/authors to supply additional data). See C in the ecosystem for an example of such a C, and see C for an example of a distribution built using such a C. Returns C if the C process spawned to run the build module exits 0. =end pod #| Return true always since it just requires launching another raku process method probe { True } #| Return true if this Builder understands the given meta data (has a 'builder' key) of the provided distribution method build-matcher(Zef::Distribution::Local $dist) { so $dist.builder } #| Run the build step of this distribution. method build(Zef::Distribution::Local $dist, Str :@includes --> Bool:D) { die "path does not exist: {$dist.path}" unless $dist.path.IO.e; # todo: remove this ( and corresponding code in Zef::Distribution.build-depends-specs ) in the near future # Always use the full name 'Distribution::Builder::MakeFromJSON', not 'MakeFromJSON' my $dist-builder-compat = "$dist.builder()" eq 'MakeFromJSON' ?? "Distribution::Builder::MakeFromJSON" !! "$dist.builder()"; my $cmd = "exit((require ::(q|$dist-builder-compat|)).new(" ~ ':meta(EVAL($*IN.slurp(:close)))' ~ ").build(q|$dist.path()|)" ~ '??0!!1)'; my @exec = |($*EXECUTABLE.absolute, |@includes.grep(*.defined).map({ "-I{$_}" }), '-MMONKEY-SEE-NO-EVAL', '-e', "$cmd"); $.stdout.emit("Command: {@exec.join(' ')}"); my $ENV := %*ENV; my $passed; react { my $proc = Zef::zrun-async(@exec, :w); whenever $proc.stdout.lines { $.stdout.emit($_) } whenever $proc.stderr.lines { $.stderr.emit($_) } whenever $proc.start(:$ENV, :cwd($dist.path)) { $passed = $_.so } whenever $proc.print($dist.meta.hash.perl) { $proc.close-stdin } } return $passed; } } zef-0.13.8/lib/Zef/Service/Shell/LegacyBuild.rakumod000066400000000000000000000075341422013762000221060ustar00rootroot00000000000000use Zef; use Zef::Distribution::Local; class Zef::Service::Shell::LegacyBuild does Builder does Messenger { =begin pod =title class Zef::Service::Shell::LegacyBuild =subtitle A raku based implementation of the Builder interface =head1 Synopsis =begin code :lang use Zef; use Zef::Service::Shell::LegacyBuild; my $builder = Zef::Service::Shell::LegacyBuild.new; # Add logging if we want to see output $builder.stdout.Supply.tap: { say $_ }; $builder.stderr.Supply.tap: { note $_ }; # Assuming our current directory is a raku distribution with a # Build.rakumod and has no dependencies (or all dependencies # already installed)... my $dist-to-build = Zef::Distribution::Local.new($*CWD); my Str @includes = $*CWD.absolute; my $built-ok = so $builder.build($dist-to-build, :@includes); say $built-ok ?? "OK" !! "Something went wrong"; =end code =head1 Description C class for handling local distributions that include a .rakumod / .pm6 / .pm alongside their C. Launches an e.g. 'Build.rakumod' file of the provided distribution with the raku executable. Note: These type of build files will be deprecated one day in the (possibly far) future. Prefer build tools like C (which uses C) if possible. =head1 Methods =head2 method probe method probe(--> Bool:D) Returns C if this module can successfully launch the C command (i.e. always returns C). =head2 method build-matcher method build-matcher(Zef::Distribution::Local $dist --> Bool:D) Returns C if this module knows how to test C<$uri>, which it decides based on if the files extracted from C<$dist> contains any of C C or C (must be extracted as these do not get declared in a META6.json file). =head2 method build method build(Zef::Distribution::Local $dist, Str :@includes --> Bool:D) Launch the e.g. C module in the root directory of an extracted C<$dist> using the provided C<@includes> (e.g. C or C) via the C command (essentially doing C<::(Build).new.build($dist-dir)>). Returns C if the C process spawned to run the build module exits 0. =end pod #| Get the path of the Build file that will be executed method !guess-build-file(IO() $prefix --> IO::Path) { return .map({ $prefix.child($_) }).first({ $_.e }) } #| Return true always since it just requires launching another raku process method probe(--> Bool:D) { True } #| Return true if this Builder understands the given uri/path of the provided distribution method build-matcher(Zef::Distribution::Local $dist --> Bool:D) { return so self!guess-build-file($dist.path) } #| Run the Build.rakumod of the given distribution method build(Zef::Distribution::Local $dist, Str :@includes --> Bool:D) { die "path does not exist: {$dist.path}" unless $dist.path.IO.e; my $build-file = self!guess-build-file($dist.path).absolute; my $cmd = "require '$build-file'; ::('Build').new.build('$dist.path.IO.absolute()') ?? exit(0) !! exit(1);"; my @exec = |($*EXECUTABLE.absolute, |@includes.grep(*.defined).map({ "-I{$_}" }), '-e', "$cmd"); $.stdout.emit("Command: {@exec.join(' ')}"); my $ENV := %*ENV; my $passed; react { my $proc = Zef::zrun-async(@exec); whenever $proc.stdout.lines { $.stdout.emit($_) } whenever $proc.stderr.lines { $.stderr.emit($_) } whenever $proc.start(:$ENV, :cwd($dist.path)) { $passed = $_.so } } return $passed; } } zef-0.13.8/lib/Zef/Service/Shell/PowerShell.rakumod000066400000000000000000000033071422013762000220000ustar00rootroot00000000000000use Zef; class Zef::Service::Shell::PowerShell does Probeable { =begin pod =title class Zef::Service::Shell::PowerShell =subtitle A base class for PowerShell invoking adapters =head1 Synopsis =begin code :lang use Zef; use Zef::Service::Shell::PowerShell; class MyPowerShell::HelloWorld is Zef::Service::Shell::PowerShell { method hello-world { say run(|@.ps-invocation, q|Write-Host 'hello world'|, :out).out.slurp } } =end code =head1 Description A base class for Zef::Service::Shell:: classes that handle the powershell invocation portion of commands. Note the invocation itself can be overridden. For strict windows environments you may want to override this in the config (although on the classes using this base class), which can be done be setting C as appropriate: =begin code :lang { "short-name" : "pswebrequest", "module" : "Zef::Service::Shell::PowerShell::download" "options" : { "ps-invocation" : ["powershell","-Command"] } } =end code =head1 Methods =head2 method probe method probe(--> Bool:D) Returns C if this module can successfully launch the C command. =end pod #| The invocation to use when launching PowerShell has @.ps-invocation = 'powershell', '-NoProfile', '-ExecutionPolicy', 'unrestricted', '-Command'; #| Return true if the powershell command is available method probe(--> Bool:D) { state $probe = !$*DISTRO.is-win ?? False !! so try { Zef::zrun('powershell', '-help', :!out, :!err) }; } } zef-0.13.8/lib/Zef/Service/Shell/PowerShell/000077500000000000000000000000001422013762000204115ustar00rootroot00000000000000zef-0.13.8/lib/Zef/Service/Shell/PowerShell/download.rakumod000066400000000000000000000062021422013762000236040ustar00rootroot00000000000000use Zef; use Zef::Service::Shell::PowerShell; class Zef::Service::Shell::PowerShell::download is Zef::Service::Shell::PowerShell does Fetcher does Messenger { =begin pod =title class Zef::Service::Shell::PowerShell::download =subtitle A PowerShell System.Net.WebClient based implementation of the Fetcher interface =head1 Synopsis =begin code :lang use Zef; use Zef::Service::Shell::PowerShell::download; my $downloader = Zef::Service::Shell::PowerShell::download.new; my $source = "https://raw.githubusercontent.com/ugexe/zef/master/META6.json"; my $save-to = $*TMPDIR.child("zef-meta6.json"); my $saved-to = $downloader.fetch($source, $save-to); die "Something went wrong" unless $saved-to; say "Zef META6 from HEAD: "; say $saved-to.slurp; =end code =head1 Description C class for handling http based URIs using a thin PowerShell wrapper around C located in C. You probably never want to use this unless its indirectly through C; handling files and spawning processes will generally be easier using core language functionality. This class exists to provide the means for fetching a file using the C interfaces that the e.g. git/file adapters use. =head1 Methods =head2 method probe method probe(--> Bool:D) Returns C if this module can successfully launch C. =head2 method fetch-matcher method fetch-matcher(Str() $uri --> Bool:D) Returns C if this module knows how to fetch C<$uri>, which it decides based on if C<$uri> starts with C or C. =head2 method fetch method fetch(Str() $uri, IO() $save-as --> IO::Path) Fetches the given C<$uri> to C<$save-to> via a PowerShell script C<%?RESOURCES{"scripts/win32http.ps1"}>. On success it returns the C where the data was actually saved to. On failure it returns C. =end pod #| Delegate to parent class Zef::Service::Shell::PowerShell probe; Returns true if powershell is available method probe(--> Bool:D) { nextsame } #| Return true if this Fetcher understands the given uri/path method fetch-matcher(Str() $uri --> Bool:D) { return so .first({ $uri.lc.starts-with($_) }); } #| Fetch the given url method fetch(Str() $uri, IO() $save-as --> IO::Path) { die "target download directory {$save-as.parent} does not exist and could not be created" unless $save-as.parent.d || mkdir($save-as.parent); my $passed; react { my $cwd := $save-as.IO.parent; my $ENV := %*ENV; my $script := %?RESOURCES.IO.absolute; my $proc = Zef::zrun-async(|@.ps-invocation, $script, $uri, '"' ~ $save-as.absolute ~ '"'); whenever $proc.stdout(:bin) { } whenever $proc.stderr(:bin) { } whenever $proc.start(:$ENV, :$cwd) { $passed = $_.so } } return ($passed && $save-as.IO.e) ?? $save-as !! Nil; } } zef-0.13.8/lib/Zef/Service/Shell/PowerShell/unzip.rakumod000066400000000000000000000111521422013762000231420ustar00rootroot00000000000000use Zef; use Zef::Service::Shell::PowerShell; class Zef::Service::Shell::PowerShell::unzip is Zef::Service::Shell::PowerShell does Extractor does Messenger { =begin pod =title class Zef::Service::Shell::unzip =subtitle A PowerShell unzip based implementation of the Extractor interface =head1 Synopsis =begin code :lang use Zef; use Zef::Service::Shell::PowerShell::unzip; my $unzip = Zef::Service::Shell::PowerShell::unzip.new; # Assuming a zef-master.zip file is in the cwd... my $source = $*HOME.child("zef-master.zip"); my $extract-to = $*TMPDIR.child(time); my $extracted-to = $unzip.extract($source, $extract-to); die "Something went wrong" unless $extracted-to; say "Zef META6 from HEAD: "; say $extracted-to.child("zef-master/META6.json").slurp; =end code =head1 Description C class for handling file based URIs ending in .zip using the C command. C class for handling file based URIs ending in .zip using PowerShell to launch a thin wrapper found in C. You probably never want to use this unless its indirectly through C; handling files and spawning processes will generally be easier using core language functionality. This class exists to provide the means for fetching a file using the C interfaces that the e.g. git/tar adapters use. =head1 Methods =head2 method probe method probe(--> Bool:D) Returns C if this module can successfully launch C. =head2 method extract-matcher method extract-matcher(Str() $uri --> Bool:D) Returns C if this module knows how to extract C<$uri>, which it decides based on if C<$uri> is an existing local file and ends with C<.zip>. =head2 method extract method extract(IO() $archive-file, IO() $extract-to --> IO::Path) Extracts the files in C<$archive-file> to C<$save-to> via a PowerShell script C<%?RESOURCES{"scripts/win32unzip.ps1"}>. On success it returns the C where the data was actually extracted to. On failure it returns C. =head2 method ls-files method ls-files(IO() $archive-file --> Array[Str]) On success it returns an C of relative paths that are available to be extracted from C<$archive-file>. =end pod # Delegate to parent class Zef::Service::Shell::PowerShell probe; Returns true if powershell is available method probe(--> Bool:D) { nextsame } # Return true if this Fetcher understands the given uri/path method extract-matcher(Str() $uri --> Bool:D) { return so $uri.IO.extension.lc eq 'zip'; } # Extract the given $archive-file method extract(IO() $archive-file, IO() $extract-to --> IO::Path) { die "archive file does not exist: {$archive-file.absolute}" unless $archive-file.e && $archive-file.f; die "target extraction directory {$extract-to.absolute} does not exist and could not be created" unless ($extract-to.e && $extract-to.d) || mkdir($extract-to); my $passed; react { my $cwd := $archive-file.IO.parent; my $ENV := %*ENV; my $script := %?RESOURCES.IO.absolute; my $proc = Zef::zrun-async(|@.ps-invocation, $script, $archive-file.basename, '"' ~ $extract-to.absolute ~ '"'); whenever $proc.stdout(:bin) { } whenever $proc.stderr(:bin) { } whenever $proc.start(:$ENV, :$cwd) { $passed = $_.so } } return $passed ?? $extract-to !! Nil; } # Returns an array of strings, where each string is a relative path representing a file that can be extracted from the given $archive-file method ls-files(IO() $archive-file --> Array[Str]) { die "archive file does not exist: {$archive-file.absolute}" unless $archive-file.e && $archive-file.f; my $passed; my $output = Buf.new; react { my $cwd := $archive-file.parent; my $ENV := %*ENV; my $script := %?RESOURCES.IO.absolute; my $proc = Zef::zrun-async(|@.ps-invocation, $script, $archive-file.basename); whenever $proc.stdout(:bin) { $output.append($_) } whenever $proc.stderr(:bin) { } whenever $proc.start(:$ENV, :$cwd) { $passed = $_.so } } my @extracted-paths = $output.decode.lines; my Str @results = $passed ?? @extracted-paths.grep(*.defined) !! (); return @results; } } zef-0.13.8/lib/Zef/Service/Shell/Test.rakumod000066400000000000000000000070231422013762000206320ustar00rootroot00000000000000use Zef; use Zef::Utils::FileSystem; class Zef::Service::Shell::Test does Tester does Messenger { =begin pod =title class Zef::Service::Shell::Test =subtitle A raku executable based implementation of the Tester interface =head1 Synopsis =begin code :lang use Zef; use Zef::Service::Shell::Test; my $test = Zef::Service::Shell::Test.new; # Add logging if we want to see output $test.stdout.Supply.tap: { say $_ }; $test.stderr.Supply.tap: { note $_ }; # Assuming our current directory is a raku distribution # with no dependencies or all dependencies already installed... my $dist-to-test = $*CWD; my Str @includes = $*CWD.absolute; my $passed = so $test.test($dist-to-test, :@includes); say $passed ?? "PASS" !! "FAIL"; =end code =head1 Description C class for handling path based URIs ending in .rakutest / .t6 / .t using the C command. You probably never want to use this unless its indirectly through C; handling files and spawning processes will generally be easier using core language functionality. This class exists to provide the means for fetching a file using the C interfaces that the e.g. Test/TAP adapters use. =head1 Methods =head2 method probe method probe(--> Bool:D) Returns C if this module can successfully launch the C command (i.e. always returns C). =head2 method test-matcher method test-matcher(Str() :uri($) --> Bool:D) Returns C if this module knows how to test C<$uri>. This module always returns C right now since it just launches tests directly with the C command. =head2 method test method test(IO() $path, Str :@includes --> Bool:D) Test the files ending in C<.rakutest> C<.t6> or C<.t> in the C directory of the given C<$path> using the provided C<@includes> (e.g. C or C) via the C command. Returns C if all test files exited with 0. =end pod #| Returns true always since it just uses $*EXECUTABLE method probe(--> Bool:D) { True } #| Return true if this Tester understands the given uri/path method test-matcher(Str() $ --> Bool:D) { return True } #| Test the given paths t/ directory using any provided @includes method test(IO() $path, :@includes --> Bool:D) { die "path does not exist: {$path}" unless $path.IO.e; my $test-path = $path.child('t'); return True unless $test-path.e; my @rel-test-files = sort map *.IO.relative($path), grep *.extension eq any('rakutest', 't', 't6'), list-paths($test-path.absolute, :f, :!d, :r); return True unless +@rel-test-files; my @results = @rel-test-files.map: -> $rel-test-file { my %ENV = %*ENV; my @cur-lib = %ENV.?chars ?? %ENV.split($*DISTRO.cur-sep) !! (); my @new-lib = $path.absolute, |@includes; %ENV = (|@new-lib, |@cur-lib).join($*DISTRO.cur-sep); my $passed; react { my $proc = Zef::zrun-async($*EXECUTABLE.absolute, $rel-test-file); whenever $proc.stdout.lines { $.stdout.emit($_) } whenever $proc.stderr.lines { $.stderr.emit($_) } whenever $proc.start(:%ENV, :cwd($path)) { $passed = $_.so } } $passed; } return so @results.all; } } zef-0.13.8/lib/Zef/Service/Shell/curl.rakumod000066400000000000000000000054641422013762000206670ustar00rootroot00000000000000use Zef; class Zef::Service::Shell::curl does Fetcher does Probeable does Messenger { =begin pod =title class Zef::Service::Shell::curl =subtitle A curl based implementation of the Fetcher interface =head1 Synopsis =begin code :lang use Zef; use Zef::Service::Shell::curl; my $curl = Zef::Service::Shell::curl.new; my $source = "https://raw.githubusercontent.com/ugexe/zef/master/META6.json"; my $save-to = $*TMPDIR.child("zef-meta6.json"); my $saved-to = $curl.fetch($source, $save-to); die "Something went wrong" unless $saved-to; say "Zef META6 from HEAD: "; say $saved-to.slurp; =end code =head1 Description C class for handling http based URIs using the C command. You probably never want to use this unless its indirectly through C; handling files and spawning processes will generally be easier using core language functionality. This class exists to provide the means for fetching a file using the C interfaces that the e.g. git/file adapters use. =head1 Methods =head2 method probe method probe(--> Bool:D) Returns C if this module can successfully launch the C command. =head2 method fetch-matcher method fetch-matcher(Str() $uri --> Bool:D) Returns C if this module knows how to fetch C<$uri>, which it decides based on if C<$uri> starts with C or C. =head2 method fetch method fetch(Str() $uri, IO() $save-as --> IO::Path) Fetches the given C<$uri>, saving it to C<$save-to>. On success it returns the C where the data was actually saved to. On failure it returns C. =end pod #| Return true if the `curl` command is available to use method probe(--> Bool:D) { state $probe = try { Zef::zrun('curl', '--help', :!out, :!err).so }; } #| Return true if this Fetcher understands the given uri/path method fetch-matcher(Str() $uri --> Bool:D) { return so .first({ $uri.lc.starts-with($_) }); } #| Fetch the given url method fetch(Str() $uri, IO() $save-as --> IO::Path) { die "target download directory {$save-as.parent} does not exist and could not be created" unless $save-as.parent.d || mkdir($save-as.parent); my $passed; react { my $cwd := $save-as.parent; my $ENV := %*ENV; my $proc = Zef::zrun-async('curl', '--silent', '-L', '-z', $save-as.absolute, '-o', $save-as.absolute, $uri); whenever $proc.stdout(:bin) { } whenever $proc.stderr(:bin) { } whenever $proc.start(:$ENV, :$cwd) { $passed = $_.so } } return ($passed && $save-as.e) ?? $save-as !! Nil; } } zef-0.13.8/lib/Zef/Service/Shell/git.rakumod000066400000000000000000000247551422013762000205110ustar00rootroot00000000000000use Zef; use Zef::Utils::URI; class Zef::Service::Shell::git does Fetcher does Extractor does Probeable does Messenger { =begin pod =title class Zef::Service::Shell::git =subtitle A git based implementation of the Fetcher and Extractor interfaces =head1 Synopsis =begin code :lang use Zef; use Zef::Service::Shell::git; my $git = Zef::Service::Shell::git.new; # Fetch the git repository my $tag-or-sha = "@v0.9.0"; my $source = "https://github.com/ugexe/zef.git{$tag-or-sha}"; my $save-to = $*CWD.child("backup_dir{$tag-or-sha}"); # must include tag in save path currently :/ my $saved-to = $git.fetch($source, $save-to); say "Zef META6 from HEAD: "; say $saved-to.child("META6.json").slurp; # Extract the repository my $extract-to = $*CWD.child("extracted_backup_dir"); my $extracted-to = $git.extract($saved-to, $extract-to); say "Zef META6 from older $tag-or-sha: "; say $extracted-to.dir.first({ .basename eq "META6.json" }).slurp; =end code =head1 Description C and C class for handling git URIs using the C command. You probably never want to use this unless its indirectly through C or C; handling files and spawning processes will generally be easier using core language functionality. This class exists to provide the means for cloning git repos and checking out revisions using the C and C interfaces that the e.g. http/tar fetching/extracting adapters use. =head1 Methods =head2 method probe method probe(--> Bool:D) Returns C if this module can successfully launch the C command. =head2 method fetch-matcher method fetch-matcher(Str() $uri --> Bool:D) Returns C if this module knows how to fetch C<$uri>, which it decides based on if a parsed C<$uri> ends with C<.git> (including local directories) and starts with any of C C C. =head2 method extract-matcher method extract-matcher(Str() $uri --> Bool:D) Returns C if this module knows how to extract C<$uri>, which it decides based on if a parsed C<$uri> looks like a directory and if C can successfully be run from that directory. =head2 method fetch method fetch(Str() $uri, IO() $save-to --> IO::Path) Fetches the given C<$uri> via C, or via C if C<$save-to> is an existing git repo. On success it returns the C where the data was actually saved to. On failure it returns C. =head2 method extract method extract(IO() $repo-path, IO() $extract-to) Extracts the given C<$repo-path> from the file system to C<$save-to> via C. On success it returns the C where the data was actually extracted to. On failure it returns C. =head2 method ls-files method ls-files(IO() $repo-path --> Array[Str]) On success it returns an C of relative paths that are available to be extracted from C<$repo-path>. =end pod #| This is for overriding the uri scheme used for git, i.e. force https:// over git:// has Str $.scheme; #| Return true if the `git` command is available to use method probe(--> Bool:D) { state $probe = try { run('git', '--help', :!out, :!err).so }; } #| Return true if this Fetcher understands the given uri/path method fetch-matcher(Str() $uri --> Bool:D) { # $uri may contain non-uri-standard, git specific, uri parts (like a trailing @tag) my $clean-uri = self!repo-url($uri).lc; return False unless $clean-uri.ends-with('.git'); return so .first({ $clean-uri.starts-with($_) }); } #| Return true if this Extractor understands the given uri/path method extract-matcher(Str() $uri --> Bool:D) { return False unless $uri.IO.d; # When used to 'extract' we want to ensure the path is a git repository (which may use a non-standard .git dir location) my $proc = Zef::zrun('git', 'status', :!out, :!err, :cwd($uri)); return $proc.so; } #| Fetch the given url. #| First attempts to clone the repository, but if it already exists (or fails) it attempts to pull down new changes method fetch(Str() $uri, IO() $save-as --> IO::Path) { return self!clone(self!repo-url($uri), $save-as) || self!pull($save-as); } #| Extracts the given path. #| For a git repo extraction is equivalent to checking out a specific revision and copying it to separate location method extract(IO() $repo-path, IO() $extract-to) { die "target repo directory {$repo-path.absolute} does not contain a .git/ folder" unless $repo-path.child('.git').d; my $sha1 = self!rev-parse(self!fetch($repo-path)).head; die "target repo directory {$repo-path.absolute} failed to locate checkout revision" unless $sha1; my $checkout-to = $extract-to.child($sha1); die "target repo directory {$extract-to.absolute} does not exist and could not be created" unless ($checkout-to.e && $checkout-to.d) || mkdir($checkout-to); return self!checkout($repo-path, $checkout-to, $sha1); } #| Returns an array of strings, where each string is a relative path representing a file that can be extracted from the given $repo-path method ls-files(IO() $repo-path --> Array[Str]) { die "target repo directory {$repo-path.absolute} does not contain a .git/ folder" unless $repo-path.child('.git').d; my $passed; my $output = Buf.new; react { my $cwd := $repo-path.absolute; my $ENV := %*ENV; my $proc = Zef::zrun-async('git', 'ls-tree', '-r', '--name-only', self!checkout-name($repo-path)); whenever $proc.stdout(:bin) { $output.append($_) } whenever $proc.stderr(:bin) { } whenever $proc.start(:$ENV, :$cwd) { $passed = $_.so } } my @extracted-paths = $output.decode.lines; my Str @results = $passed ?? @extracted-paths.grep(*.defined) !! (); return @results; } #| On success returns an IO::Path to where a `git clone ...` has put files method !clone($url, IO() $save-as --> IO::Path) { die "target download directory {$save-as.absolute} does not exist and could not be created" unless $save-as.d || mkdir($save-as); my $passed; react { my $cwd := $save-as.parent; my $ENV := %*ENV; my $proc = Zef::zrun-async('git', 'clone', $url, $save-as.basename, '--quiet'); whenever $proc.stdout(:bin) { } whenever $proc.stderr(:bin) { } whenever $proc.start(:$ENV, :$cwd) { $passed = $_.so } } return ($passed && $save-as.child('.git').d) ?? $save-as !! Nil; } #| Does a `git pull` on an existing local git repo method !pull(IO() $repo-path --> IO::Path) { die "target download directory {$repo-path.absolute} does not contain a .git/ folder" unless $repo-path.child('.git').d; my $passed; react { my $cwd := $repo-path.absolute; my $ENV := %*ENV; my $proc = Zef::zrun-async('git', 'pull', '--quiet'); whenever $proc.stdout(:bin) { } whenever $proc.stderr(:bin) { } whenever $proc.start(:$ENV, :$cwd) { $passed = $_.so } } return $passed ?? $repo-path !! Nil; } #| Does a `git fetch` on an existing local git repo. Not really related to self.fetch(...) method !fetch(IO() $repo-path --> IO::Path) { die "target download directory {$repo-path.absolute} does not contain a .git/ folder" unless $repo-path.child('.git').d; my $passed; react { my $cwd := $repo-path.absolute; my $ENV := %*ENV; my $proc = Zef::zrun-async('git', 'fetch', '--quiet'); whenever $proc.stdout(:bin) { } whenever $proc.stderr(:bin) { } whenever $proc.start(:$ENV, :$cwd) { $passed = $_.so } } return $passed ?? $repo-path !! Nil; } #| Does a `git checkout ...`, allowing git source urls to have e.g. trailing @tag method !checkout(IO() $repo-path, IO() $extract-to, $target --> IO::Path) { my $passed; react { my $cwd := $repo-path.absolute; my $ENV := %*ENV; my $proc = Zef::zrun-async('git', '--work-tree', $extract-to, 'checkout', $target, '.'); whenever $proc.stdout(:bin) { } whenever $proc.stderr(:bin) { } whenever $proc.start(:$ENV, :$cwd) { $passed = $_.so } } return $passed ?? $extract-to !! Nil; } #| Does a `git rev-parse ...` (used to get a sha1 for saving a copy of a specific checkout) method !rev-parse(IO() $save-as --> Array[Str]) { die "target repo directory {$save-as.absolute} does not contain a .git/ folder" unless $save-as.child('.git').d; my $passed; my $output = Buf.new; react { my $cwd := $save-as.absolute; my $ENV := %*ENV; my $proc = Zef::zrun-async('git', 'rev-parse', self!checkout-name($save-as)); whenever $proc.stdout(:bin) { $output.append($_) } whenever $proc.stderr(:bin) { } whenever $proc.start(:$ENV, :$cwd) { $passed = $_.so } } my @extracted-refs = $output.decode.lines; my Str @results = $passed ?? @extracted-refs.grep(*.defined) !! (); return @results; } #| Git URI parser / transformer #| Handles overriding the uri $.scheme, and removing parts of the URI that e.g. `git clone ...` wouldn't understand #| (like a trailing @tag on the uri) method !repo-url($url --> Str) { my $uri = uri($!scheme ?? $url.subst(/^\w+ '://'/, "{$!scheme}://") !! $url) || return False; #' my $reconstructed-uri = ($uri.scheme // '') ~ '://' ~ ($uri.user-info ?? "{$uri.user-info}@" !! '') ~ ($uri.host // '') ~ ($uri.path // '').subst(/\@.*[\/|\@|\?|\#]?$/, ''); return $reconstructed-uri; } #| Given a $url like http://foo.com/project.git@v1 or ./project.git@v1 will return 'v1' method !checkout-name($url --> Str) { my $uri = uri($url) || return False; my $checkout = ($uri.path // '').match(/\@(.*)[\/|\@|\?|\#]?/)[0]; return $checkout ?? $checkout.Str !! 'HEAD'; } } zef-0.13.8/lib/Zef/Service/Shell/p5tar.rakumod000066400000000000000000000107171422013762000207520ustar00rootroot00000000000000use Zef; class Zef::Service::Shell::p5tar does Extractor does Messenger { =begin pod =title class Zef::Service::Shell::p5tar =subtitle A perl based implementation of the Extractor interface =head1 Synopsis =begin code :lang use Zef; use Zef::Service::Shell::p5tar; my $p5tar = Zef::Service::Shell::p5tar.new; # Assuming a zef-master.tar.gz file is in the cwd... my $source = $*HOME.child("zef-master.tar.gz"); my $extract-to = $*TMPDIR.child(time); my $extracted-to = $p5tar.extract($source, $extract-to); die "Something went wrong" unless $extracted-to; say "Zef META6 from HEAD: "; say $extracted-to.child("zef-master/META6.json").slurp; =end code =head1 Description C class for handling file based URIs ending in .tar.gz / .tgz using the C command and a thing wrapper found in C. You probably never want to use this unless its indirectly through C; handling files and spawning processes will generally be easier using core language functionality. This class exists to provide the means for fetching a file using the C interfaces that the e.g. git/tar adapters use. =head1 Methods =head2 method probe method probe(--> Bool:D) Returns C if this module can successfully launch the C command. =head2 method extract-matcher method extract-matcher(Str() $uri --> Bool:D) Returns C if this module knows how to extract C<$uri>, which it decides based on if C<$uri> is an existing local file and ends with C<.tar.gz> or C<.tgz>. =head2 method extract method extract(IO() $archive-file, IO() $extract-to --> IO::Path) Extracts the files in C<$archive-file> to C<$save-to> via a Perl script C<%?RESOURCES{"scripts/perl5tar.pl"}>. On success it returns the C where the data was actually extracted to. On failure it returns C. =head2 method ls-files method ls-files(IO() $archive-file --> Array[Str]) On success it returns an C of relative paths that are available to be extracted from C<$archive-file>. =end pod #| Returns true if the included Perl script can be executed method probe(--> Bool:D) { state $probe = try { Zef::zrun('perl', %?RESOURCES.IO.absolute, '--help', :!out, :!err).so }; } #| Return true if this Extractor understands the given uri/path method extract-matcher(Str() $uri --> Bool:D) { return so <.tar.gz .tgz>.first({ $uri.lc.ends-with($_) }); } #| Extract the given $archive-file method extract(IO() $archive-file, IO() $extract-to --> IO::Path) { die "archive file does not exist: {$archive-file.absolute}" unless $archive-file.e && $archive-file.f; die "target extraction directory {$extract-to.absolute} does not exist and could not be created" unless ($extract-to.e && $extract-to.d) || mkdir($extract-to); my $passed; react { my $cwd := $extract-to; my $ENV := %*ENV; my $script := %?RESOURCES.IO.absolute; my $proc = Zef::zrun-async('perl', $script, $archive-file.absolute); whenever $proc.stdout(:bin) { } whenever $proc.stderr(:bin) { } whenever $proc.start(:$ENV, :$cwd) { $passed = $_.so } } return $passed ?? $extract-to !! Nil; } #| Returns an array of strings, where each string is a relative path representing a file that can be extracted from the given $archive-file method ls-files(IO() $archive-file --> Array[Str]) { die "archive file does not exist: {$archive-file.absolute}" unless $archive-file.e && $archive-file.f; my $passed; my $output = Buf.new; react { my $cwd := $archive-file.parent; my $ENV := %*ENV; my $script := %?RESOURCES.IO.absolute; my $proc = Zef::zrun-async('perl', $script, '--list', $archive-file.absolute); whenever $proc.stdout(:bin) { $output.append($_) } whenever $proc.stderr(:bin) { } whenever $proc.start(:$ENV, :$cwd) { $passed = $_.so } } my @extracted-paths = $output.decode.lines; my Str @results = $passed ?? @extracted-paths.grep(*.defined) !! (); return @results; } } zef-0.13.8/lib/Zef/Service/Shell/prove.rakumod000066400000000000000000000115421422013762000210470ustar00rootroot00000000000000use Zef; class Zef::Service::Shell::prove does Tester does Messenger { =begin pod =title class Zef::Service::Shell::prove =subtitle A prove based implementation of the Tester interface =head1 Synopsis =begin code :lang use Zef; use Zef::Service::Shell::prove; my $prove = Zef::Service::Shell::prove.new; # Add logging if we want to see output $prove.stdout.Supply.tap: { say $_ }; $prove.stderr.Supply.tap: { note $_ }; # Assuming our current directory is a raku distribution # with no dependencies or all dependencies already installed... my $dist-to-test = $*CWD; my Str @includes = $*CWD.absolute; my $passed = so $prove.test($dist-to-test, :@includes); say $passed ?? "PASS" !! "FAIL"; =end code =head1 Description C class for handling path based URIs ending in .rakutest / .t6 / .t using the C command. You probably never want to use this unless its indirectly through C; handling files and spawning processes will generally be easier using core language functionality. This class exists to provide the means for fetching a file using the C interfaces that the e.g. Test/TAP adapters use. =head1 Methods =head2 method probe method probe(--> Bool:D) Returns C if this module can successfully launch the C command. =head2 method test-matcher method test-matcher(Str() $uri --> Bool:D) Returns C if this module knows how to test C<$uri>, which it decides based on if C<$uri> exists on local file system. =head2 method test method test(IO() $path, Str :@includes --> Bool:D) Test the files ending in C<.rakutest> C<.t6> or C<.t> in the C directory of the given C<$path> using the provided C<@includes> (e.g. C or C) via the C command. Returns C if all tests passed according to C. =end pod #| Return true if the `prove` command is available to use method probe(--> Bool:D) { state $probe; once { if $*EXECUTABLE.absolute.contains(" ") { # prove can't deal with spaces in the executable path. # It assumes everything after the first space to be args to the # executable. So we can't use prove if our executables path # contains a space. Sad. # https://metacpan.org/dist/Test-Harness/view/bin/prove#-exec return False } # `prove --help` has exitcode == 1 unlike most other processes # so it requires a more convoluted probe check try { my $proc = $*DISTRO.is-win ?? Zef::zrun('prove.bat', '--help', :out, :!err) !! Zef::zrun('prove', '--help', :out, :!err); my @out = $proc.out.lines; $proc.out.close; CATCH { when X::Proc::Unsuccessful { $probe = True if $proc.exitcode == 1 && @out.first(*.contains("-exec" | "Mac OS X")); } default { return False } } } } ?$probe; } #| Return true if this Tester understands the given uri/path method test-matcher(Str() $uri --> Bool:D) { return $uri.IO.e } #| Test the given paths t/ directory using any provided @includes method test(IO() $path, Str :@includes --> Bool:D) { die "cannot test path that does not exist: {$path}" unless $path.e; my $test-path = $path.child('t'); return True unless $test-path.e; my Str $test-path-relative = $test-path.relative($path); my Str $test-path-cwd = $path.absolute; my %ENV = %*ENV; my @cur-lib = %ENV.?chars ?? %ENV.split($*DISTRO.cur-sep) !! (); my @new-lib = $path.absolute, |@includes; %ENV = (|@new-lib, |@cur-lib).join($*DISTRO.cur-sep); my @args = '--ext', '.rakutest', '--ext', '.t', '--ext', '.t6', '-r', ('--verbose' if %*ENV), ; my $passed; react { my $proc = $*DISTRO.is-win ?? Proc::Async.new(:win-verbatim-args, 'prove.bat', |@args, '-e', '"' ~ $*EXECUTABLE.absolute ~ '"', '"' ~ $test-path-relative ~ '"') !! Proc::Async.new('prove', |@args, '-e', $*EXECUTABLE.absolute, $test-path-relative); whenever $proc.stdout.lines { $.stdout.emit($_) } whenever $proc.stderr.lines { $.stderr.emit($_) } whenever $proc.start(:%ENV, :cwd($test-path-cwd)) { $passed = $_.so } } return so $passed; } } zef-0.13.8/lib/Zef/Service/Shell/tar.rakumod000066400000000000000000000106201422013762000204760ustar00rootroot00000000000000use Zef; # Note: when passing command line arguments to tar in this module be sure to use # relative paths. ex: set :cwd to $tar-file.parent, and use $tar-file.basename as the target # This is because gnu tar on windows can't handle a windows style volume in path arguments class Zef::Service::Shell::tar does Extractor does Messenger { =begin pod =title class Zef::Service::Shell::tar =subtitle A tar based implementation of the Extractor interface =head1 Synopsis =begin code :lang use Zef; use Zef::Service::Shell::tar; my $tar = Zef::Service::Shell::tar.new; # Assuming a zef-master.tar.gz file is in the cwd... my $source = $*HOME.child("zef-master.tar.gz"); my $extract-to = $*TMPDIR.child(time); my $extracted-to = $tar.extract($source, $extract-to); die "Something went wrong" unless $extracted-to; say "Zef META6 from HEAD: "; say $extracted-to.child("zef-master/META6.json").slurp; =end code =head1 Description C class for handling file based URIs ending in .tar.gz / .tgz using the C command. You probably never want to use this unless its indirectly through C; handling files and spawning processes will generally be easier using core language functionality. This class exists to provide the means for fetching a file using the C interfaces that the e.g. git/unzip adapters use. =head1 Methods =head2 method probe method probe(--> Bool:D) Returns C if this module can successfully launch the C command. =head2 method extract-matcher method extract-matcher(Str() $uri --> Bool:D) Returns C if this module knows how to extract C<$uri>, which it decides based on if C<$uri> is an existing local file and ends with C<.tar.gz> or C<.tgz>. =head2 method extract method extract(IO() $archive-file, IO() $extract-to --> IO::Path) Extracts the files in C<$archive-file> to C<$save-to> via the C command. On success it returns the C where the data was actually extracted to. On failure it returns C. =head2 method ls-files method ls-files(IO() $archive-file --> Array[Str]) On success it returns an C of relative paths that are available to be extracted from C<$archive-file>. =end pod #| Return true if the `tar` command is available to use method probe(--> Bool:D) { state $probe = try { Zef::zrun('tar', '--help', :!out, :!err).so }; } #| Return true if this Extractor understands the given uri/path method extract-matcher(Str() $uri --> Bool:D) { return so <.tar.gz .tgz>.first({ $uri.lc.ends-with($_) }); } #| Extract the given $archive-file method extract(IO() $archive-file, IO() $extract-to --> IO::Path) { die "archive file does not exist: {$archive-file.absolute}" unless $archive-file.e && $archive-file.f; die "target extraction directory {$extract-to.absolute} does not exist and could not be created" unless ($extract-to.e && $extract-to.d) || mkdir($extract-to); my $passed; react { my $cwd := $archive-file.parent; my $ENV := %*ENV; my $proc = Zef::zrun-async('tar', '-zxvf', $archive-file.basename, '-C', $extract-to.relative($cwd)); whenever $proc.stdout(:bin) { } whenever $proc.stderr(:bin) { } whenever $proc.start(:$ENV, :$cwd) { $passed = $_.so } } return $passed ?? $extract-to !! Nil; } #| Returns an array of strings, where each string is a relative path representing a file that can be extracted from the given $archive-file method ls-files(IO() $archive-file) { die "archive file does not exist: {$archive-file.absolute}" unless $archive-file.e && $archive-file.f; my $passed; my $output = Buf.new; react { my $cwd := $archive-file.parent; my $ENV := %*ENV; my $proc = Zef::zrun-async('tar', '--list', '-f', $archive-file.basename); whenever $proc.stdout(:bin) { $output.append($_) } whenever $proc.stderr(:bin) { } whenever $proc.start(:$ENV, :$cwd) { $passed = $_.so } } my @extracted-paths = $output.decode.lines; $passed ?? @extracted-paths.grep(*.defined) !! (); } } zef-0.13.8/lib/Zef/Service/Shell/unzip.rakumod000066400000000000000000000102261422013762000210570ustar00rootroot00000000000000use Zef; class Zef::Service::Shell::unzip does Extractor does Messenger { =begin pod =title class Zef::Service::Shell::unzip =subtitle An unzip based implementation of the Extractor interface =head1 Synopsis =begin code :lang use Zef; use Zef::Service::Shell::unzip; my $unzip = Zef::Service::Shell::unzip.new; # Assuming a zef-master.zip file is in the cwd... my $source = $*HOME.child("zef-master.zip"); my $extract-to = $*TMPDIR.child(time); my $extracted-to = $unzip.extract($source, $extract-to); die "Something went wrong" unless $extracted-to; say "Zef META6 from HEAD: "; say $extracted-to.child("zef-master/META6.json").slurp; =end code =head1 Description C class for handling file based URIs ending in .zip using the C command. You probably never want to use this unless its indirectly through C; handling files and spawning processes will generally be easier using core language functionality. This class exists to provide the means for fetching a file using the C interfaces that the e.g. git/tar adapters use. =head1 Methods =head2 method probe method probe(--> Bool:D) Returns C if this module can successfully launch the C command. =head2 method extract-matcher method extract-matcher(Str() $uri --> Bool:D) Returns C if this module knows how to extract C<$uri>, which it decides based on if C<$uri> is an existing local file and ends with C<.zip>. =head2 method extract method extract(IO() $archive-file, IO() $extract-to --> IO::Path) Extracts the files in C<$archive-file> to C<$save-to> via the C command. On success it returns the C where the data was actually extracted to. On failure it returns C. =head2 method ls-files method ls-files(IO() $archive-file --> Array[Str]) On success it returns an C of relative paths that are available to be extracted from C<$archive-file>. =end pod #| Return true if the `unzip` command is available to use method probe(--> Bool:D) { state $probe = try { Zef::zrun('unzip', '--help', :!out, :!err).so }; } #| Return true if this Fetcher understands the given uri/path method extract-matcher(Str() $uri --> Bool:D) { return so $uri.IO.extension.lc eq 'zip'; } #| Extract the given $archive-file method extract(IO() $archive-file, IO() $extract-to --> IO::Path) { die "archive file does not exist: {$archive-file.absolute}" unless $archive-file.e && $archive-file.f; die "target extraction directory {$extract-to.absolute} does not exist and could not be created" unless ($extract-to.e && $extract-to.d) || mkdir($extract-to); my $passed; react { my $cwd := $archive-file.parent; my $ENV := %*ENV; my $proc = Zef::zrun-async('unzip', '-o', '-qq', $archive-file.basename, '-d', $extract-to.absolute); whenever $proc.stdout(:bin) { } whenever $proc.stderr(:bin) { } whenever $proc.start(:$ENV, :$cwd) { $passed = $_.so } } return $passed ?? $extract-to !! Nil; } #| Returns an array of strings, where each string is a relative path representing a file that can be extracted from the given $archive-file method ls-files(IO() $archive-file) { die "archive file does not exist: {$archive-file.absolute}" unless $archive-file.e && $archive-file.f; my $passed; my $output = Buf.new; react { my $cwd := $archive-file.parent; my $ENV := %*ENV; my $proc = Zef::zrun-async('unzip', '-Z', '-1', $archive-file.basename); whenever $proc.stdout(:bin) { $output.append($_) } whenever $proc.stderr(:bin) { } whenever $proc.start(:$ENV, :$cwd) { $passed = $_.so } } my @extracted-paths = $output.decode.lines; my Str @results = $passed ?? @extracted-paths.grep(*.defined) !! (); return @results; } } zef-0.13.8/lib/Zef/Service/Shell/wget.rakumod000066400000000000000000000054401422013762000206620ustar00rootroot00000000000000use Zef; class Zef::Service::Shell::wget does Fetcher does Probeable does Messenger { =begin pod =title class Zef::Service::Shell::wget =subtitle A wget based implementation of the Fetcher interface =head1 Synopsis =begin code :lang use Zef; use Zef::Service::Shell::wget; my $wget = Zef::Service::Shell::wget.new; my $source = "https://raw.githubusercontent.com/ugexe/zef/master/META6.json"; my $save-to = $*TMPDIR.child("zef-meta6.json"); my $saved-to = $wget.fetch($source, $save-to); die "Something went wrong" unless $saved-to; say "Zef META6 from HEAD: "; say $saved-to.slurp; =end code =head1 Description C class for handling http based URIs using the C command. You probably never want to use this unless its indirectly through C; handling files and spawning processes will generally be easier using core language functionality. This class exists to provide the means for fetching a file using the C interfaces that the e.g. git/file adapters use. =head1 Methods =head2 method probe method probe(--> Bool:D) Returns C if this module can successfully launch the C command. =head2 method fetch-matcher method fetch-matcher(Str() $uri --> Bool:D) Returns C if this module knows how to fetch C<$uri>, which it decides based on if C<$uri> starts with C or C. =head2 method fetch method fetch(Str() $uri, IO() $save-as --> IO::Path) Fetches the given C<$uri>, saving it to C<$save-to>. On success it returns the C where the data was actually saved to. On failure it returns C. =end pod #} Return true if the `wget` command is available to use method probe(--> Bool:D) { state $probe = try { Zef::zrun('wget', '--help', :!out, :!err).so }; } #| Return true if this Fetcher understands the given uri/path method fetch-matcher(Str() $uri --> Bool:D) { return so .first({ $uri.lc.starts-with($_) }); } #| Fetch the given url method fetch(Str() $uri, IO() $save-as --> IO::Path) { die "target download directory {$save-as.parent} does not exist and could not be created" unless $save-as.parent.d || mkdir($save-as.parent); my $passed; react { my $cwd := $save-as.parent; my $ENV := %*ENV; my $proc = Zef::zrun-async('wget', '-P', $cwd, '--quiet', $uri, '-O', $save-as.absolute); whenever $proc.stdout(:bin) { } whenever $proc.stderr(:bin) { } whenever $proc.start(:$ENV, :$cwd) { $passed = $_.so } } return ($passed && $save-as.e) ?? $save-as !! Nil; } } zef-0.13.8/lib/Zef/Service/TAP.rakumod000066400000000000000000000072321422013762000172720ustar00rootroot00000000000000use Zef; use Zef::Utils::FileSystem; class Zef::Service::TAP does Tester does Messenger { =begin pod =title class Zef::Service::TAP =subtitle A TAP module based implementation of the Tester interface =head1 Synopsis =begin code :lang use Zef; use Zef::Service::TAP; my $tap = Zef::Service::TAP.new; # Add logging if we want to see output $tap.stdout.Supply.tap: { say $_ }; $tap.stderr.Supply.tap: { note $_ }; # Assuming our current directory is a raku distribution # with no dependencies or all dependencies already installed... my $dist-to-test = $*CWD; my Str @includes = $*CWD.absolute; my $passed = so $tap.test($dist-to-test, :@includes); say $passed ?? "PASS" !! "FAIL"; =end code =head1 Description C class for handling path based URIs ending in .rakutest / .t6 / .t using the raku C module. You probably never want to use this unless its indirectly through C; handling files and spawning processes will generally be easier using core language functionality. This class exists to provide the means for fetching a file using the C interfaces that the e.g. Test/prove adapters use. =head1 Methods =head2 method probe method probe(--> Bool:D) Returns C if this module can successfully load the C module. =head2 method test-matcher method test-matcher(Str() $uri --> Bool:D) Returns C if this module knows how to test C<$uri>, which it decides based on if C<$uri> exists on local file system. =head2 method test method test(IO() $path, Str :@includes --> Bool:D) Test the files ending in C<.rakutest> C<.t6> or C<.t> in the C directory of the given C<$path> using the provided C<@includes> (e.g. C or C) via the C raku module. Returns C if there were no failed tests and no errors according to C. =end pod #| Return true if the `TAP` raku module is available method probe(--> Bool:D) { state $probe = self!has-correct-tap-version && (try require ::('TAP')) !~~ Nil ?? True !! False; } method !has-correct-tap-version(--> Bool:D) { # 0.3.1 has fixed support for :err and added support for :output return so $*REPO.resolve(CompUnit::DependencySpecification.new( short-name => 'TAP', version-matcher => '0.3.5+', )); } #| Return true if this Tester understands the given uri/path method test-matcher(Str() $uri --> Bool:D) { return $uri.IO.e } #| Test the given paths t/ directory using any provided @includes method test(IO() $path, Str :@includes --> Bool:D) { die "path does not exist: {$path}" unless $path.e; my $test-path = $path.child('t'); return True unless $test-path.e; my @test-files = grep *.extension eq any('rakutest', 't', 't6'), list-paths($test-path.absolute, :f, :!d, :r).sort; return True unless +@test-files; my $result = try { require ::('TAP'); my @incdirs = $path.absolute, |@includes; my @handlers = ::("TAP::Harness::SourceHandler::Raku").new(:@incdirs); my $parser = ::("TAP::Harness").new(:@handlers); my $promise = $parser.run( @test-files.map(*.relative($path)), :cwd($path), :out($.stdout), :err($.stderr), ); $promise.result; } my $passed = $result.failed == 0 && not $result.errors ?? True !! False; return $passed; } } zef-0.13.8/lib/Zef/Test.rakumod000066400000000000000000000111521422013762000161610ustar00rootroot00000000000000use Zef; class Zef::Test does Tester does Pluggable { =begin pod =title class Zef::Test =subtitle A configurable implementation of the Tester interface =head1 Synopsis =begin code :lang use Zef; use Zef::Test; use Zef::Distribution::Local; # Setup with a single tester backend my $tester = Zef::Test.new( backends => [ { module => "Zef::Service::Shell::prove" }, ], ); # Assuming our current directory is a raku distribution... my $dist-to-test = Zef::Distribution::Local.new($*CWD); my $candidate = Candidate.new(dist => $dist-to-test); my $logger = Supplier.new andthen *.Supply.tap: -> $m { say $m. } # ...test the distribution using the first available backend my $passed = so all $tester.test($candidate, :$logger); say $passed ?? "PASS" !! "FAIL"; =end code =head1 Description A C class that uses 1 or more other C instances as backends. It abstracts the logic to do 'test this path with the first backend that supports the given path'. =head1 Methods =head2 method test-matcher method test-matcher($path --> Bool:D) Returns C if any of the probeable C know how to test C<$path>. =head2 method test method test(Candidate $candi, Str :@includes, Supplier :$logger, Int :$timeout --> Array[Bool]) Tests the files for C<$candi> (usually locally extracted files from C<$candi.dist> in the C directory with an extension of C<.rakutest> C<.t6> or C<.t>) using the provided C<@includes> (e.g. C or C. It will use the first matching backend, and will not attempt to use a different backend on failure (like e.g. C) since failing test are not unexpected. An optional C<:$logger> can be supplied to receive events about what is occurring. An optional C<:$timeout> can be passed to denote the number of seconds after which we'll assume failure. Returns an C with some number of C (which depends on the backend used). If there are no C items in the returned C then we assume success. =end pod submethod TWEAK(|) { @ = self.plugins; # preload plugins } #| Returns true if any of the backends 'test-matcher' understand the given uri/path method test-matcher($path --> Bool:D) { return so self!test-matcher($path) } #| Returns the backends that understand the given uri based on their test-matcher result method !test-matcher($path --> Array[Tester]) { my @matching-backends = self.plugins.grep(*.test-matcher($path)); my Tester @results = @matching-backends; return @results; } #| Test the given path using any provided @includes, #| Will return results from the first Tester backend that supports the given path (via $candi.dist.path) method test(Candidate $candi, Str :@includes, Supplier :$logger, Int :$timeout --> Array[Bool]) { my $path := $candi.dist.path; die "Can't test non-existent path: {$path}" unless $path.IO.e; my $testers := self!test-matcher($path).cache; unless +$testers { my @report_enabled = self.plugins.map(*.short-name); my @report_disabled = self.backends.map(*.).grep({ $_ ~~ none(@report_enabled) }); die "Enabled testing backends [{@report_enabled}] don't understand $path\n" ~ "You may need to configure one of the following backends, or install its underlying software - [{@report_disabled}]"; } my $tester = $testers.head; if ?$logger { $logger.emit({ level => DEBUG, stage => TEST, phase => START, candi => $candi, message => "Testing with plugin: {$tester.^name}" }); $tester.stdout.Supply.grep(*.defined).act: -> $out is copy { $logger.emit({ level => VERBOSE, stage => TEST, phase => LIVE, candi => $candi, message => $out }) } $tester.stderr.Supply.grep(*.defined).act: -> $err is copy { $logger.emit({ level => ERROR, stage => TEST, phase => LIVE, candi => $candi, message => $err }) } } my $todo = start { try $tester.test($path, :@includes) }; my $time-up = ($timeout ?? Promise.in($timeout) !! Promise.new); await Promise.anyof: $todo, $time-up; $logger.emit({ level => DEBUG, stage => TEST, phase => LIVE, message => "Testing $path timed out" }) if ?$logger && $time-up.so && $todo.not; my Bool @results = $todo.so ?? $todo.result !! False; return @results; } } zef-0.13.8/lib/Zef/Utils/000077500000000000000000000000001422013762000147565ustar00rootroot00000000000000zef-0.13.8/lib/Zef/Utils/FileSystem.rakumod000066400000000000000000000160511422013762000204310ustar00rootroot00000000000000module Zef::Utils::FileSystem { =begin pod =title module Zef::Utils::FileSystem =subtitle Utility subroutines for interacting with the file system =head1 Synopsis =begin code :lang use Zef::Utils::FileSystem; # Recursively list, copy, move, or delete paths my @files_in_lib = list-paths("lib/"); my @copied_to_lib2 = copy-paths("lib/", "lib2/"); my @moved_to_lib3 = move-paths("lib2/", "lib3/"); my @deleted_from_lib3 = delete-paths("lib3/"); # Locate a bin script from $PATH my $zef_in_path = Zef::Utils::FileSystem::which("zef"); say "zef bin location: {$zef_in_path // 'n/a'}"; # A Lock.protect like interface that is backed by a file lock my $lock-file = $*TMP.add("myapp.lock"); lock-file-protect($lock-file, { # do some work here that may want to use cross-process locking }); =end code =head1 Description Provides additional facilities for interacting with the file system. =head1 Subroutines =head2 sub list-paths sub list-paths(IO() $path, Bool :$d, Bool :$f = True, Bool :$r = True, Bool :$dot --> Array[IO::Path]) Returns an C of C of all paths under C<$path>. If C<:$d> is C directories will be returned. If C<$f> is set to C then files will not be returned. If C<$r> is set to C it will not recurse into directories. If C<$dot> is C then the current directory may be included in the return results. =head2 sub copy-paths sub copy-paths(IO() $from-path, IO() $to-path, Bool :$d, Bool :$f = True, Bool :$r = True, Bool :$dot --> Array[IO::Path]) Copy all paths under C<$from-path> to a directory C<$to-path> and returns an C of C of the successfully copied files (their new locations). If C<:$d> is C directories without files may be created. If C<$f> is set to C then files will not be copied. If C<$r> is C it will not recurse into directories. If C<$dot> is C then the current directory may be copied. =head2 sub move-paths sub move-paths(IO() $from-path!, IO() $to-path, Bool :$d, Bool :$f = True, Bool :$r = True, Bool :$dot --> Array[IO::Path]) Move all paths under C<$from-path> to a directory C<$to-path> and returns an C of C of the successfully moved files (their new locations). If C<:$d> is C directories without files won't be created. If C<$f> is set to C then files will not be moved. If C<$r> is C it will not recurse into directories. If C<$dot> is C then the current directory may be moved. =head2 sub delete-paths sub delete-paths(IO() $path!, Bool :$d, Bool :$f = True, Bool :$r = True, Bool :$dot --> Array[IO::Path]) Delete all paths under C<$path> and returns an C of C of what was deleted. If C<:$d> is C directories without files won't be deleted. If C<$f> is set to C then files will not be deleted. If C<$r> is C it will not recurse into directories. If C<$dot> is C then the current directory may be deleted. =head2 sub file-lock-protect sub lock-file-protect(IO() $path, &code, Bool :$shared = False) Provides an interface similar to C that is backed by a file lock on C<$path> instead of a semaphore. Its purpose is to help keep multiple instances of zef from trying to edit the e.g. p6c/cpan ecosystem index at the same time (although how well it serves that purpose in practice is unknown). =head2 sub which our sub which(Str $name --> Array[IO::Path]) Search the current env C, returning an C of C with paths that contain a matching file C<$name>. This is used for determining if a dependency such as C> is installed. =end pod sub list-paths(IO() $path, Bool :$d, Bool :$f = True, Bool :$r = True, Bool :$dot --> Array[IO::Path]) is export { die "{$path} does not exists" unless $path.e; my &wanted-paths := -> @_ { grep { .basename.starts-with('.') && !$dot ?? 0 !! 1 }, @_ } my IO::Path @results = gather { my @stack = $path.f ?? $path !! dir($path); while @stack.splice -> @paths { for wanted-paths(@paths) -> IO() $current { take $current if ($current.f && ?$f) || ($current.d && ?$d); @stack.append(dir($current)) if ?$r && $current.d; } } } return @results; } sub copy-paths(IO() $from-path, IO() $to-path, Bool :$d, Bool :$f = True, Bool :$r = True, Bool :$dot --> Array[IO::Path]) is export { die "{$from-path} does not exists" unless $from-path.IO.e; mkdir($to-path) unless $to-path.e; my IO::Path @results = eager gather for list-paths($from-path, :$d, :$f, :$r, :$dot).sort -> $from-file { my $from-relpath = $from-file.relative($from-path); my $to-file = IO::Path.new($from-relpath, :CWD($to-path)); mkdir($to-file.parent) unless $to-file.e; next if $from-file eq $to-file; # copy can deadlock on an older rakudo take $to-file if copy($from-file, $to-file); } return @results; } sub move-paths(IO() $from-path, IO() $to-path, Bool :$d = True, Bool :$f = True, Bool :$r = True, Bool :$dot --> Array[IO::Path]) is export { my IO::Path @copied = copy-paths($from-path, $to-path, :$d, :$f, :$r, :$dot); @ = delete-paths($from-path, :$d, :$f, :$r, :$dot); return @copied; } sub delete-paths(IO() $path, Bool :$d = True, Bool :$f = True, Bool :$r = True, Bool :$dot = True --> Array[IO::Path]) is export { my @paths = list-paths($path, :$d, :$f, :$r, :$dot).unique(:as(*.absolute)); my @files = @paths.grep(*.f); my @dirs = @paths.grep(*.d); $path.f ?? @files.push($path.IO) !! @dirs.push($path.IO); my IO::Path @results = eager gather { for @files.sort(*.chars).reverse { take $_ if try unlink($_) } for @dirs\.sort(*.chars).reverse { take $_ if try rmdir($_) } } return @results; } sub lock-file-protect(IO() $path, &code, Bool :$shared = False) is export { do given ($shared ?? $path.IO.open(:r) !! $path.IO.open(:w)) { LEAVE {.close} LEAVE {try .path.unlink} .lock(:$shared); code(); } } our sub which(Str $name --> Array[IO::Path]) { my $source-paths := $*SPEC.path.grep(*.?chars).map(*.IO).grep(*.d); my $path-guesses := $source-paths.map({ $_.child($name) }); my $possibilities := $path-guesses.map: -> $path { ((BEGIN $*DISTRO.is-win) ?? ($path.absolute, %*ENV.split(';').map({ $path.absolute ~ $_ }).Slip) !! $path.absolute).Slip } my IO::Path @results = $possibilities.grep(*.defined).grep(*.IO.f).map(*.IO); return @results; } } zef-0.13.8/lib/Zef/Utils/SystemQuery.rakumod000066400000000000000000000120471422013762000206600ustar00rootroot00000000000000module Zef::Utils::SystemQuery { =begin pod =title module Zef::Utils::SystemQuery =subtitle Utility subroutines for resolving declarative logic based dependencies =head1 Synopsis =begin code :lang use Zef::Utils::SystemQuery; my @depends = ( { "name" => { "by-distro.name" => { "mswin32" => "Windows::Dependency", "" => "NonWindows::Dependency" }, }, }, ); my @resolved-depends := system-collapse(@depends); say @resolved-depends.raku; # [{:name("Windows::Dependency")},] # on windows systems # [{:name("NonWindows::Dependency")},] # on non-windows systems =end code =head1 Description Provides facilities for resolving dependencies that use declarative logic. =head1 Subroutines =head2 sub system-collapse our sub system-collapse($data) Traverses an C or C C<$data>, collapsing the blocks of declarative logic and returns the otherwise same data structure. Declarative logic current supports three main query forms: # by-env-exists.$FOO - selects "yes" key %*ENV{$FOO} exists, else the "no" key "by-env-exists.FOO" : { "yes" : "Env::Exists", "no" : "Env::DoesNotExists" } # by-env.$FOO - selects the value of %*ENV{$FOO} as the key, else the "" key if there is no matching key "by-env.FOO" : { "SomeValue" : "Env::FOO::SomeValue", "" : "Env::FOO::DefaultValue" } # by-[distro|kernel|raku|vm].$FOO - selects the value of e.g. $*DISTRO.name as the key, else the "" key if there is no matching key # where $FOO is e.g. $*DISTRO.^methods (or $*KERNEL.^methods, $*RAKU.^methods, $*VM.^methods) "by-distro.name" : { "macosx" : "OSX::Dependency", "" : "NonOSX::Dependency" } Note that e.g. C<$*DISTRO.name> (and thus the C form) depends on potentially C backend specific stuff -- for instance libuv based backends would have similar e.g. C<$*DISTRO> values, but on the JVM C<$*DISTRO.name> might return "linux" when MoarVM returns "debian". When using this query form you will want to test on multiple systems. =end pod our sub system-collapse($data) is export { return $data unless $data ~~ Hash|Array; my sub walk(@path, $idx, $query-source) { die "Attempting to find \$*{@path[0].uc}.{@path[1..*].join('.')}" if !$query-source.^can("{@path[$idx]}") && $idx < @path.elems; return $query-source."{@path[$idx]}"() if $idx+1 == @path.elems; return walk(@path, $idx+1, $query-source."{@path[$idx]}"()); } my $return = $data.WHAT.new; for $data.keys -> $idx { given $idx { when /^'by-env-exists'/ { my $key = $idx.split('.')[1]; my $value = %*ENV{$key}:exists ?? 'yes' !! 'no'; die "Unable to resolve path: {$idx} in \%*ENV\nhad: {$value}" unless $data{$idx}{$value}:exists; return system-collapse($data{$idx}{$value}); } when /^'by-env'/ { my $key = $idx.split('.')[1]; my $value = %*ENV{$key}; die "Unable to resolve path: {$idx} in \%*ENV\nhad: {$value // ''}" unless defined($value) && ($data{$idx}{$value}:exists); return system-collapse($data{$idx}{$value}); } when /^'by-' (distro|kernel|perl|vm)/ { my $query-source = do given $/[0] { when 'distro' { $*DISTRO } when 'kernel' { $*KERNEL } when 'perl' { $*RAKU } when 'raku' { $*RAKU } when 'vm' { $*VM } } my $path = $idx.split('.'); my $value = walk($path, 1, $query-source).Str; # to stringify e.g. True my $fkey = ($data{$idx}{$value}:exists) ?? $value !! ($data{$idx}{''}:exists) ?? '' !! Any; die "Unable to resolve path: {$path.cache[*-1].join('.')} in \$*DISTRO\nhad: {$value} ~~ {$value.WHAT.^name}" if Any ~~ $fkey; return system-collapse($data{$idx}{$fkey}); } default { my $val = system-collapse($data ~~ Array ?? $data[$idx] !! $data{$idx}); $return{$idx} = $val if $return ~~ Hash; $return.push($val) if $return ~~ Array; } }; } return $return; } } zef-0.13.8/lib/Zef/Utils/URI.rakumod000066400000000000000000000177771422013762000170240ustar00rootroot00000000000000class Zef::Utils::URI { has $.is-relative; has $.match; has $.scheme; has $.host; has $.port; has $.user-info; has $.path; has $.query; has $.fragment; my grammar URI { token URI-reference { || } token URI { ':' ['?' ]? ['#' ]? } token relative-ref { ['?' ]? ['#' ]? } token heir-part { || '//' || || || } token relative-part { || '//' || || || } token scheme { <.alpha> [ || <.alpha> || <.digit> || '+' || '-' || '.' ]* } token authority { [ '@']? [':' ]? } token userinfo { [<.unreserved> || <.pct-encoded> || <.sub-delims> || ':']* } token host { <.IP-literal> || <.IPv4address> || <.reg-name> } token IP-literal { '[' [<.IPv6address> || <.IPv6addrz> || <.IPvFuture>] ']' } token IPv6addz { <.IPv6address> '%25' <.ZoneID> } token ZoneID { [<.unreserved> || <.pct-encoded>]+ } token IPvFuture { 'v' <.xdigit>+ '.' [<.unreserved> || <.sub-delims> || ':']+ } token IPv6address { || [<.h16> ':'] ** 6 <.ls32> || '::' [<.h16> ':'] ** 5 <.ls32> || [ <.h16> ]? '::' [<.h16> ':'] ** 4 <.ls32> || [[<.h16> ':'] ** 0..1 <.h16> ]? '::' [<.h16> ':'] ** 3 <.ls32> || [[<.h16> ':'] ** 0..2 <.h16> ]? '::' [<.h16> ':'] ** 2 <.ls32> || [[<.h16> ':'] ** 0..3 <.h16> ]? '::' <.h16> ':' <.ls32> || [[<.h16> ':'] ** 0..4 <.h16> ]? '::' <.ls32> || [[<.h16> ':'] ** 0..5 <.h16> ]? '::' <.h16> || [[<.h16> ':'] ** 0..6 <.h16> ]? '::' } token h16 { <.xdigit> ** 1..4 } token ls32 { [<.h16> ':' <.h16>] || <.IPv4address> } token IPv4address { <.dec-octet> '.' <.dec-octet> '.' <.dec-octet> '.' <.decoctet> } token dec-octet { || <.digit> || [\x[31]..\x[39]] <.digit> || '1' <.digit> ** 2 || '2' [\x[30]..\x[34]] <.digit> || '25' [\x[30]..\x[35]] } token reg-name { [<.unreserved> || <.pct-encoded> || <.sub-delims>]* } token port { <.digit>* } token path { || <.path-abempty> || <.path-absolute> || <.path-noscheme> || <.path-rootless> || <.path-empty> } token path-abempty { ['/' <.segment>]* } token path-absolute { '/' [<.segment-nz> ['/' <.segment>]*]? } token path-noscheme { <.segment-nz-nc> ['/' <.segment>]* } token path-rootless { <.segment-nz> ['/' <.segment>]* } token path-empty { <.pchar> ** 0 } token segment { <.pchar>* } token segment-nz { <.pchar>+ } token segment-nz-nc { [<.unreserved> || <.pct-encoded> || <.sub-delims>]+ } token pchar { <.unreserved> || <.pct-encoded> || <.sub-delims> || ':' || '@' } token query { [<.pchar> || '/' || '?']* } token fragment { [<.pchar> || '/' || '?']* } token pct-encoded { '%' <.xdigit> <.xdigit> } token unreserved { <.alpha> || <.digit> || < - . _ ~ > } token reserved { <.gen-delims> || <.sub-delims> } token gen-delims { < : / ? # [ ] @ > } token sub-delims { < ! $ & ' ( ) * + , ; = > } # ' <- fixes syntax highlighting } my grammar URI::File is URI { token TOP { } token file-URI { ":" [ "?" ]? } token scheme { "file" } token heir-part { "//"? || } token auth-path { [ ]? || || } token auth { [ "@" ]? } token local-path { || } token unc-path { "//" "/"? } token windows-path { } token drive-letter { [ ]? } token drive-marker { ":" || "|" } # XXX: this is a bit of a hack -- see: # https://github.com/ugexe/zef/issues/204#issuecomment-366957374 token pchar { <.unreserved> || <.pct-encoded> || <.sub-delims> || ':' || '@' || ' ' } } method new($id is copy) { # prefix windows paths with `file://` so they get parsed as a 'uri' type identity. my $possible-file-uri = "{$id.starts-with('file://')??''!!'file://'}{$*DISTRO.is-win??$id.subst('\\','/',:g)!!$id}"; if URI::File.parse($possible-file-uri, :rule) -> $m { my $ap = $m.; my $volume = ~($ap.. // ''); # what IO::SPEC::Win32 understands my $path = ~($ap.. // $ap. // die "Could not parse path from: $id"); my $host = ~($ap. // ''); my $scheme = ~$m.; my $is-relative = $path.IO.is-relative || not $ap...defined; # because `|` is sometimes used as a windows volume separator in a file-URI my $normalized-path = $is-relative ?? $path !! $*SPEC.join($volume, $path, ''); self.bless( :match($m), :$is-relative, :$scheme, :$host, :path($normalized-path) ); } elsif URI.parse($id, :rule) -> $m { my $heir = $m.; my $auth = $heir.; self.bless( match => $m, is-relative => False, scheme => ~($m. // '').lc, host => ~($auth. // ''), port => ($auth. // Int).Int, user-info => ~($auth. // ''), path => ~($heir. // '/'), query => ~($m. // ''), fragment => ~($m. // ''), ); } elsif URI.parse($id, :rule) -> $m { self.bless( match => $m, is-relative => True, scheme => ~($m. // '').lc, path => ~($m. || '/'), query => ~($m. // ''), fragment => ~($m. // ''), ); } elsif $id ~~ /^(.+?) '@' (.+?) ':' (.*)/ and URI.parse("ssh\:\/\/$0\@$1\/$2", :rule) -> $m { my $heir = $m.; my $auth = $heir.; self.bless( match => $m, is-relative => False, scheme => ~($m. // '').lc, host => ~($auth. // ''), port => ($auth. // Int).Int, user-info => ~($auth. // ''), path => ~($heir. // '/'), query => ~($m. // ''), fragment => ~($m. // ''), ); } else { die "Cannot parse $id as an URI"; } } } sub uri(Str() $uri) is export { try Zef::Utils::URI.new($uri) } zef-0.13.8/logotype/000077500000000000000000000000001422013762000142265ustar00rootroot00000000000000zef-0.13.8/logotype/logo_32x32.png000066400000000000000000000024751422013762000165450ustar00rootroot00000000000000PNG  IHDR szzsBIT|d pHYsXtEXtSoftwarewww.inkscape.org<IDATX͗KlUgfܹ^Z胖>-E۶"S#&$jxp,Xb,Q_ %@ &ւTXJ[/mz_vAdf9ߜ9uuk\iV { ̑4j!yzmERd{ Q+y quz1j%ie|o\M`.豣4kɽKJ%4~a<(:$ߴ̷Ӡ'zkd%6_ƞ`-GKS2{m;m\Zuy;~]UM a_,ḩp:1j=PFJ.fFɝqPX+ȷ^ .rҝ5j!gX X< eYtX#<,gQ1VislWo E ?#cBڬoD --list \n"; print "Extract files: \n"; exit 0; } elsif( $ARGV[0] eq '--list' ) { my $extractor = Archive::Tar->new(); $extractor->read($ARGV[1]); print "$_\n" for( $extractor->list_files() ); exit 0; } else { my $extractor = Archive::Tar->new(); $extractor->read($ARGV[0]); $extractor->extract(); exit 0; } zef-0.13.8/resources/scripts/win32http.ps1000066400000000000000000000005701422013762000203560ustar00rootroot00000000000000Param ( [Parameter(Mandatory=$True)] [System.Uri]$uri, [Parameter(Mandatory=$True)] [string]$FilePath, $UserAgent = "rakudo perl6/zef powershell downloader" ) [Net.ServicePointManager]::SecurityProtocol = "tls12, tls11, tls"; $client = New-Object System.Net.WebClient; $client.Headers['User-Agent'] = $UserAgent; $client.DownloadFile($uri.ToString(), $FilePath) zef-0.13.8/resources/scripts/win32unzip.ps1000066400000000000000000000013361422013762000205450ustar00rootroot00000000000000Param ( [Parameter(Mandatory=$True)] [string]$FilePath, $out = "" ) $shell = New-Object -com shell.application $FilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($FilePath) $items = $shell.NameSpace($FilePath).items() function List-ZipFiles { $ns = $shell.NameSpace($args[0]) foreach( $item in $ns.Items() ) { if( $item.IsFolder ) { List-ZipFiles($item) } else { $path = $item | Select -ExpandProperty Path Write-Host $path } } } if( $out -ne '' ) { $to = $shell.NameSpace($out) $to.CopyHere($items, 0x14) } else { $path = $items | Select -ExpandProperty Path Write-Host $path List-ZipFiles $path } zef-0.13.8/t/000077500000000000000000000000001422013762000126275ustar00rootroot00000000000000zef-0.13.8/t/00-load.t000066400000000000000000000027411422013762000141540ustar00rootroot00000000000000use v6; use Test; plan 2; subtest 'Core' => { use-ok("Zef"); # Just `use Zef::CLI` will make it output usage # use-ok("Zef::CLI"); use-ok("Zef::Build"); use-ok("Zef::Config"); use-ok("Zef::Extract"); use-ok("Zef::Identity"); use-ok("Zef::Test"); use-ok("Zef::Install"); use-ok("Zef::Fetch"); use-ok("Zef::Client"); use-ok("Zef::Repository"); use-ok("Zef::Repository::LocalCache"); use-ok("Zef::Repository::Ecosystems"); use-ok("Zef::Distribution"); use-ok("Zef::Distribution::DependencySpecification"); use-ok("Zef::Distribution::Local"); use-ok("Zef::Utils::FileSystem"); use-ok("Zef::Utils::SystemQuery"); use-ok("Zef::Utils::URI"); } subtest 'Plugins' => { use-ok("Zef::Service::FetchPath"); use-ok("Zef::Service::TAP"); use-ok("Zef::Service::InstallRakuDistribution"); use-ok("Zef::Service::FileReporter"); use-ok("Zef::Service::Shell::DistributionBuilder"); use-ok("Zef::Service::Shell::LegacyBuild"); use-ok("Zef::Service::Shell::Test"); use-ok("Zef::Service::Shell::prove"); use-ok("Zef::Service::Shell::unzip"); use-ok("Zef::Service::Shell::tar"); use-ok("Zef::Service::Shell::p5tar"); use-ok("Zef::Service::Shell::curl"); use-ok("Zef::Service::Shell::git"); use-ok("Zef::Service::Shell::wget"); use-ok("Zef::Service::Shell::PowerShell"); use-ok("Zef::Service::Shell::PowerShell::download"); use-ok("Zef::Service::Shell::PowerShell::unzip"); } zef-0.13.8/t/build.t000066400000000000000000000061651422013762000141230ustar00rootroot00000000000000use v6; use Test; plan 1; use Zef; use Zef::Build; use Zef::Distribution; subtest 'Zef::Build.build' => { subtest 'Two builders, first does not match/handle uri' => { my class Mock::Builder::One does Builder { method build-matcher(|--> False) { } method build($) { die 'should not get called' } } my class Mock::Builder::Two does Builder { method build-matcher(|--> True) { } method build($ --> True) { } } my $save-to = $*TMPDIR.child(100000.rand).mkdir; my $builder = Zef::Build.new but role :: { method plugins(|--> List) { Mock::Builder::One.new, Mock::Builder::Two.new } }; my $dist = Zef::Distribution.new(:name) but role :: { method path { $save-to } }; ok $builder.build(Candidate.new(:$dist)); try $save-to.rmdir; } subtest 'Two builders, first not capable of handling given uri' => { my class Mock::Builder::One does Builder { method build-matcher(|--> False) { } method build($) { die 'should not get called' } } my class Mock::Builder::Two does Builder { method build-matcher(|--> True) { } method build($ --> True) { } } my $save-to = $*TMPDIR.child(100000.rand).mkdir; my $builder = Zef::Build.new but role :: { method plugins(|--> List) { Mock::Builder::One.new, Mock::Builder::Two.new } }; my $dist = Zef::Distribution.new(:name) but role :: { method path { $save-to } }; ok $builder.build(Candidate.new(:$dist)); try $save-to.rmdir; } subtest 'Two builders, first fails' => { my class Mock::Builder::One does Builder { method build-matcher(|--> True) { } method build($ --> Nil) { } } my class Mock::Builder::Two does Builder { method build-matcher(|--> True) { } method build($ --> True) { } } my $save-to = $*TMPDIR.child(100000.rand).mkdir; my $builder = Zef::Build.new but role :: { method plugins(|--> List) { Mock::Builder::One.new, Mock::Builder::Two.new } }; my $dist = Zef::Distribution.new(:name) but role :: { method path { $save-to } }; ok $builder.build(Candidate.new(:$dist)); try $save-to.rmdir; } subtest 'Two builders, first times out' => { my constant timeout = 1; my class Mock::Builder::One does Builder { method build-matcher(|--> True) { } method build($) { sleep(timeout * 5); timeout; } } my class Mock::Builder::Two does Builder { method build-matcher(|--> True) { } method build($ --> True) { } } my $save-to = $*TMPDIR.child(100000.rand).mkdir; my $builder = Zef::Build.new but role :: { method plugins(|--> List) { Mock::Builder::One.new, Mock::Builder::Two.new } }; my $dist = Zef::Distribution.new(:name) but role :: { method path { $save-to } }; ok $builder.build(Candidate.new(:$dist), :timeout(timeout)); try $save-to.rmdir; } } done-testing;zef-0.13.8/t/distribution-depends-parsing.t000066400000000000000000000243141422013762000206200ustar00rootroot00000000000000use v6; use Test; plan 35; use Zef; use Zef::Config; use Zef::Client; use Zef::Distribution; my $json = q:to/META6/; { "perl":"6", "name":"Test::Complex::Depends", "version":"0", "auth":"github:stranger", "description":"Test hash-based depends and native depends parsing", "license":"none", "depends": [ "Zef::Client", { "from": "native", "name": { "by-distro.name": { "macosx": "mac", "win32" : "win", "linux" : "linux", "" : "unknown" } } } ], "build-depends": [ "Zef::Build" ], "test-depends": [ "Zef::Test" ], "provides": { } } META6 my $dist = Zef::Distribution.new(|Rakudo::Internals::JSON.from-json($json)); ok any($dist.depends-specs.map(*.name)) ~~ 'Zef::Client'; ok any($dist.depends-specs.map(*.from-matcher)) ~~ 'Perl6'; ok any($dist.depends-specs.map(*.name)) ~~ any('mac', 'win', 'linux', 'unknown'); ok any($dist.depends-specs.map(*.from-matcher)) ~~ 'native'; with Zef::Distribution.new(|Rakudo::Internals::JSON.from-json(q:to/META6/)) -> $dist { { "perl":"6", "name":"Test::Complex::Depends", "version":"0", "auth":"github:stranger", "description":"Test hash-based depends and native depends parsing", "license":"none", "depends": { "runtime" : { "requires" : [ { "by-distro.name": { "win32": ["Foo::Win32_1", "Foo::Win32_2"], "linux": ["Foo::Linux_1", "Foo::Linux_2"], "" : ["Foo::Unknown_1", "Foo::Unknown_2"] } } ] } }, "provides": { } } META6 is $dist.depends-specs.elems, 2; ok any($dist.depends-specs.map(*.name)) ~~ any("Foo::Win32_1", "Foo::Linux_1", "Foo::Unknown_1"); ok any($dist.depends-specs.map(*.name)) ~~ any("Foo::Win32_2", "Foo::Linux_2", "Foo::Unknown_2"); ok any($dist.depends-specs.map(*.from-matcher)) ~~ 'Perl6'; } # mixed depends types with Zef::Distribution.new(|Rakudo::Internals::JSON.from-json(q:to/META6/)) -> $dist { { "perl":"6", "name":"Test::Complex::Depends", "version":"0", "auth":"github:stranger", "description":"Test hash-based depends and native depends parsing", "license":"none", "build-depends" : [ "Foo::Build" ], "test-depends" : [ "Foo::Test" ], "depends": { "runtime" : { "requires" : [ { "by-distro.name": { "win32": "Win32::Runtime", "linux": "Linux::Runtime", "" : "Unknown::Runtime" } } ] }, "build" : { "requires" : [ "Foo::BuildX" ] }, "test" : { "requires" : [ "Foo::TestX" ] } }, "provides": { } } META6 is $dist.depends-specs.elems, 1; is $dist.build-depends-specs.elems, 2; is $dist.test-depends-specs.elems, 2; ok any($dist.depends-specs.map(*.name)) ~~ any("Win32::Runtime", "Linux::Runtime", "Unknown::Runtime"); ok any($dist.build-depends-specs.map(*.name)) ~~ 'Foo::Build'; ok any($dist.build-depends-specs.map(*.name)) ~~ 'Foo::BuildX'; ok any($dist.test-depends-specs.map(*.name)) ~~ 'Foo::Test'; ok any($dist.test-depends-specs.map(*.name)) ~~ 'Foo::TestX'; } with Zef::Distribution.new(|Rakudo::Internals::JSON.from-json(q:to/META6/)) -> $dist { { "perl":"6", "name":"Test::Complex::Depends", "version":"0", "auth":"github:stranger", "description":"Test hash-based depends and native depends parsing", "license":"none", "depends": [ "Foo", {"any": ["Bar", "Baz"]} ], "provides": { } } META6 is $dist.depends-specs()[1].specs[1].name, "Baz" or note $dist.depends-specs; } with Zef::Distribution.new(|Rakudo::Internals::JSON.from-json(q:to/META6/)) -> $dist { { "perl":"6", "name":"Test::Complex::Depends", "version":"0", "auth":"github:stranger", "description":"Test hash-based depends and native depends parsing", "license":"none", "depends": [ "Foo", {"any": ["Bar", "Baz"]} ], "provides": { } } META6 my class Zef::Client::Fake is Zef::Client { method list-installed(*@) { [] } } my $client = Zef::Client::Fake.CREATE; $client.depends = True; is $client.list-dependencies( Candidate.new(:$dist), )[1].specs[0].name, "Bar"; } my $guess-path = $?FILE.IO.parent.parent.child('resources/config.json'); my $config-file = $guess-path.e ?? ~$guess-path !! Zef::Config::guess-path(); my $config = Zef::Config::parse-file($config-file); my $recommendation-manager = ( Zef::Repository but role :: { method plugins(*@) { [ [ class :: does PackageRepository { method search(*@identities --> Seq) { gather for @identities -> $as { take Candidate.new(:dist(Zef::Distribution.new(:name($as))), :$as, :from) if $as ∈ ; take Candidate.new(:dist(Zef::Distribution.new(:name($as), :depends['Unsatisfiable'])), :$as, :from) if $as eq 'HasUnsatisfiableDep'; take Candidate.new(:dist(Zef::Distribution.new(:name($as), :depends['HasUnsatisfiableDep'])), :$as, :from) if $as eq 'HasTransitivelyUnsatisfiableDep'; take Candidate.new(:dist(Zef::Distribution.new(:name($as), :depends[{:any["AvailableToo", "Available"]},])), :$as, :from) if $as eq 'DependsOnAvailableTooOrAvailable'; } } }, ], ] } } ).new(:backends[]); for ( ["Available", {:any["Unavailable", "Installed"]}] => -> $prereq-candidates { is $prereq-candidates.elems, 1; is $prereq-candidates[0].dist.name, "Available"; }, [{:any["Unavailable", "Available"]},] => -> $prereq-candidates { is $prereq-candidates.elems, 1; is $prereq-candidates[0].dist.name, "Available"; }, ["Available", {:any["Unavailable", "AvailableToo"]}] => -> $prereq-candidates { is $prereq-candidates.elems, 2; is $prereq-candidates.map(*.dist.name).sort, ; }, [{:any["AvailableToo", "Available"]}, "Available"] => -> $prereq-candidates { is $prereq-candidates.elems, 1; is $prereq-candidates.map(*.dist.name).sort, ; }, ["DependsOnAvailableTooOrAvailable", "Available"] => -> $prereq-candidates { is $prereq-candidates.elems, 2; is $prereq-candidates.map(*.dist.name).sort, ; }, ["DependsOnAvailableTooOrAvailable"] => -> $prereq-candidates { is $prereq-candidates.elems, 2; is $prereq-candidates.map(*.dist.name).sort, ; }, ["Available", {:any["HasUnsatisfiableDep", "AvailableToo"]}] => -> $prereq-candidates { is $prereq-candidates.elems, 2; is $prereq-candidates.map(*.dist.name).sort, ; }, ["Available", {:any["HasTransitivelyUnsatisfiableDep", "AvailableToo"]}] => -> $prereq-candidates { is $prereq-candidates.elems, 2; is $prereq-candidates.map(*.dist.name).sort, ; }, ) -> $test { with Zef::Distribution.new( :perl(6), :name, :version<0>, :auth('github:stranger'), :description('Test hash-based depends and native depends parsing'), :license, :depends($test.key), :provides{ }, ) -> $dist { my class Zef::Client::Fake is Zef::Client { method list-installed(*@) { [Candidate.new(:dist(Zef::Distribution.new(:name)))] } method logger() { class :: { method emit($) { } } } } my $client = Zef::Client::Fake.new(:$config, :$recommendation-manager); $client.depends = True; my $prereq-candidates = $client.find-prereq-candidates( Candidate.new(:$dist), ); $test.value.($prereq-candidates); } } subtest 'X::Zef::UnsatisfiableDependency' => { for ( ["Unavailable"], ["Available", "Unavailable"], ["Available", {:any["Unavailable", "Unavailable2"]}], ["Available", {:any[{:name, :from}, {:name, :from}]}], ) -> $test { with Zef::Distribution.new( :perl(6), :name, :version<0>, :auth('github:stranger'), :description('Test hash-based depends and native depends parsing'), :license, :depends($test), :provides{ }, ) -> $dist { my class Zef::Client::Fake is Zef::Client { method list-installed(*@) { [Candidate.new(:dist(Zef::Distribution.new(:name)))] } method logger() { class :: { method emit($) { } } } } my $client = Zef::Client::Fake.new(:$config, :$recommendation-manager); $client.depends = True; throws-like { $client.find-prereq-candidates(Candidate.new(:$dist)) }, X::Zef::UnsatisfiableDependency; } } } zef-0.13.8/t/extract.t000066400000000000000000000064001422013762000144660ustar00rootroot00000000000000use v6; use Test; plan 1; use Zef; use Zef::Extract; subtest 'Zef::Extract.extract' => { subtest 'Two extracters, first does not match/handle uri' => { my class Mock::Extracter::One does Extractor { method extract-matcher(|--> False) { } method extract($, $) { die 'should not get called' } method ls-files { } } my class Mock::Extracter::Two does Extractor { method extract-matcher(|--> True) { } method extract($, $to) { $to } method ls-files { } } my $save-to = $*TMPDIR.child(100000.rand).mkdir; my $extracter = Zef::Extract.new but role :: { method plugins(|--> List) { Mock::Extracter::One.new, Mock::Extracter::Two.new } }; is $extracter.extract(Candidate.new(:uri($*CWD)), $save-to.absolute), $save-to.absolute; try $save-to.rmdir; } subtest 'Two extracters, first not capable of handling given uri' => { my class Mock::Extracter::One does Extractor { method extract-matcher(|--> False) { } method extract($, $) { die 'should not get called' } method ls-files { } } my class Mock::Extracter::Two does Extractor { method extract-matcher(|--> True) { } method extract($, $to) { $to } method ls-files { } } my $save-to = $*TMPDIR.child(100000.rand).mkdir; my $extracter = Zef::Extract.new but role :: { method plugins(|--> List) { Mock::Extracter::One.new, Mock::Extracter::Two.new } }; is $extracter.extract(Candidate.new(:uri($*CWD)), $save-to.absolute), $save-to.absolute; try $save-to.rmdir; } subtest 'Two extracters, first fails' => { my class Mock::Extracter::One does Extractor { method extract-matcher(|--> True) { } method extract($, $ --> Nil) { } method ls-files { } } my class Mock::Extracter::Two does Extractor { method extract-matcher(|--> True) { } method extract($, $to) { $to } method ls-files { } } my $save-to = $*TMPDIR.child(100000.rand).mkdir; my $extracter = Zef::Extract.new but role :: { method plugins(|--> List) { Mock::Extracter::One.new, Mock::Extracter::Two.new } }; is $extracter.extract(Candidate.new(:uri($*CWD)), $save-to.absolute), $save-to.absolute; try $save-to.rmdir; } subtest 'Two extracters, first times out' => { my constant timeout = 1; my class Mock::Extracter::One does Extractor { method extract-matcher(|--> True) { } method extract($, $) { sleep(timeout * 5); timeout; } method ls-files { } } my class Mock::Extracter::Two does Extractor { method extract-matcher(|--> True) { } method extract($, $to) { $to } method ls-files { } } my $save-to = $*TMPDIR.child(100000.rand).mkdir; my $extracter = Zef::Extract.new but role :: { method plugins(|--> List) { Mock::Extracter::One.new, Mock::Extracter::Two.new } }; is $extracter.extract(Candidate.new(:uri($*CWD)), $save-to.absolute, :timeout(timeout)), $save-to.absolute; try $save-to.rmdir; } } done-testing;zef-0.13.8/t/fetch.t000066400000000000000000000055721422013762000141160ustar00rootroot00000000000000use v6; use Test; plan 1; use Zef; use Zef::Fetch; subtest 'Zef::Fetch.fetch' => { subtest 'Two fetchers, first does not match/handle uri' => { my class Mock::Fetcher::One does Fetcher { method fetch-matcher(|--> False) { } method fetch($, $) { die 'should not get called' } } my class Mock::Fetcher::Two does Fetcher { method fetch-matcher(|--> True) { } method fetch($, $to) { $to } } my $save-to = $*TMPDIR.child(100000.rand).mkdir; my $fetcher = Zef::Fetch.new but role :: { method plugins(|--> List) { Mock::Fetcher::One.new, Mock::Fetcher::Two.new } }; is $fetcher.fetch(Candidate.new(:uri($*CWD)), $save-to.absolute), $save-to.absolute; try $save-to.rmdir; } subtest 'Two fetchers, first not capable of handling given uri' => { my class Mock::Fetcher::One does Fetcher { method fetch-matcher(|--> False) { } method fetch($, $) { die 'should not get called' } } my class Mock::Fetcher::Two does Fetcher { method fetch-matcher(|--> True) { } method fetch($, $to) { $to } } my $save-to = $*TMPDIR.child(100000.rand).mkdir; my $fetcher = Zef::Fetch.new but role :: { method plugins(|--> List) { Mock::Fetcher::One.new, Mock::Fetcher::Two.new } }; is $fetcher.fetch(Candidate.new(:uri($*CWD)), $save-to.absolute), $save-to.absolute; try $save-to.rmdir; } subtest 'Two fetchers, first fails' => { my class Mock::Fetcher::One does Fetcher { method fetch-matcher(|--> True) { } method fetch($, $ --> Nil) { } } my class Mock::Fetcher::Two does Fetcher { method fetch-matcher(|--> True) { } method fetch($, $to) { $to } } my $save-to = $*TMPDIR.child(100000.rand).mkdir; my $fetcher = Zef::Fetch.new but role :: { method plugins(|--> List) { Mock::Fetcher::One.new, Mock::Fetcher::Two.new } }; is $fetcher.fetch(Candidate.new(:uri($*CWD)), $save-to.absolute), $save-to.absolute; try $save-to.rmdir; } subtest 'Two fetchers, first times out' => { my constant timeout = 1; my class Mock::Fetcher::One does Fetcher { method fetch-matcher(|--> True) { } method fetch($, $) { sleep(timeout * 5); timeout; } } my class Mock::Fetcher::Two does Fetcher { method fetch-matcher(|--> True) { } method fetch($, $to) { $to } } my $save-to = $*TMPDIR.child(100000.rand).mkdir; my $fetcher = Zef::Fetch.new but role :: { method plugins(|--> List) { Mock::Fetcher::One.new, Mock::Fetcher::Two.new } }; is $fetcher.fetch(Candidate.new(:uri($*CWD)), $save-to.absolute, :timeout(timeout)), $save-to.absolute; try $save-to.rmdir; } } done-testing;zef-0.13.8/t/identity.t000066400000000000000000000045021422013762000146460ustar00rootroot00000000000000use v6; use Test; plan 6; use Zef::Identity; subtest 'Require spec - exact' => { my @variations = ( 'Net::HTTP:ver<1.0>:auth>', 'Net::HTTP:auth>:ver<1.0>:api<>', ); for @variations -> $identity { my $ident = Zef::Identity.new($identity); is $ident.auth, 'Foo Bar '; is $ident.name, 'Net::HTTP'; is $ident.version, '1.0'; } } subtest 'Require spec - range *' => { my @variations = ( "Net::HTTP:ver<*>:auth", ); for @variations -> $identity { my $ident = Zef::Identity.new($identity); is $ident.auth, 'github:ugexe'; is $ident.name, 'Net::HTTP'; is $ident.version, '*'; } } subtest 'Require spec - range +' => { my @variations = ( "Net::HTTP:ver<1.0+>:auth", "Net::HTTP:auth:ver<1.0+>:api<>", ); for @variations -> $identity { my $ident = Zef::Identity.new($identity); is $ident.auth, 'github:ugexe'; is $ident.name, 'Net::HTTP'; is $ident.version, '1.0+'; } } subtest 'str2identity' => { ok ?str2identity("***not valid***"); subtest 'exact' => { my $expected = "Net::HTTP:ver<1.0+>:auth"; my $require = "Net::HTTP:ver<1.0+>:auth:api<>"; my $i-require = str2identity($require); is $i-require, $expected; } subtest 'not exact' => { my $require = "Net::HTTP"; my $i-require = str2identity($require); is $i-require, 'Net::HTTP'; } subtest 'root namespace' => { my $require = "HTTP"; my $i-require = str2identity($require); is $i-require, 'HTTP'; } } subtest 'identity2hash' => { my $require = "Net::HTTP:ver<1.0+>:auth"; ok ?identity2hash("***not valid***"); my %i-require = identity2hash($require); is %i-require, 'Net::HTTP'; is %i-require, '1.0+'; is %i-require, 'github:ugexe'; } subtest 'hash2identity' => { my %hash = %( :name, :ver<1.0+>, :auth ); ok ?hash2identity("***not valid***"); my $i-require = hash2identity(%hash); is $i-require, "Net::HTTP:ver<1.0+>:auth"; } zef-0.13.8/t/install.t000066400000000000000000000067651422013762000145000ustar00rootroot00000000000000use v6; use Test; plan 1; use Zef; use Zef::Install; use Zef::Distribution; my $cur = (class :: does CompUnit::Repository { method need { }; method loaded { }; method id { } }).new; subtest 'Zef::Install.install' => { subtest 'Two installers, first does not match/handle uri' => { my class Mock::Installer::One does Installer { method install-matcher(|--> False) { } method install($) { die 'should not get called' } } my class Mock::Installer::Two does Installer { method install-matcher(|--> True) { } method install($ --> True) { } } my $save-to = $*TMPDIR.child(100000.rand).mkdir; my $installer = Zef::Install.new but role :: { method plugins(|--> List) { Mock::Installer::One.new, Mock::Installer::Two.new } }; my $dist = Zef::Distribution.new(:name) but role :: { method path { $save-to } }; ok $installer.install(Candidate.new(:$dist), :$cur); try $save-to.rmdir; } subtest 'Two installers, first not capable of handling given uri' => { my class Mock::Installer::One does Installer { method install-matcher(|--> False) { } method install($) { die 'should not get called' } } my class Mock::Installer::Two does Installer { method install-matcher(|--> True) { } method install($ --> True) { } } my $save-to = $*TMPDIR.child(100000.rand).mkdir; my $installer = Zef::Install.new but role :: { method plugins(|--> List) { Mock::Installer::One.new, Mock::Installer::Two.new } }; my $dist = Zef::Distribution.new(:name) but role :: { method path { $save-to } }; ok $installer.install(Candidate.new(:$dist), :$cur); try $save-to.rmdir; } subtest 'Two installers, first fails and second is not tried' => { my class Mock::Installer::One does Installer { method install-matcher(|--> True) { } method install($ --> False) { } } my class Mock::Installer::Two does Installer { method install-matcher(|--> True) { } method install($ --> True) { die 'should not get called' } } my $save-to = $*TMPDIR.child(100000.rand).mkdir; my $installer = Zef::Install.new but role :: { method plugins(|--> List) { Mock::Installer::One.new, Mock::Installer::Two.new } }; my $dist = Zef::Distribution.new(:name) but role :: { method path { $save-to } }; nok $installer.install(Candidate.new(:$dist), :$cur); try $save-to.rmdir; } subtest 'Two installers, first times out and second is not tried' => { my constant timeout = 1; my class Mock::Installer::One does Installer { method install-matcher(|--> True) { } method install($) { sleep(timeout * 5); timeout; } } my class Mock::Installer::Two does Installer { method install-matcher(|--> True) { } method install($ --> True) { die 'should not get called' } } my $save-to = $*TMPDIR.child(100000.rand).mkdir; my $installer = Zef::Install.new but role :: { method plugins(|--> List) { Mock::Installer::One.new, Mock::Installer::Two.new } }; my $dist = Zef::Distribution.new(:name) but role :: { method path { $save-to } }; nok $installer.install(Candidate.new(:$dist), :$cur, :timeout(timeout)); try $save-to.rmdir; } } done-testing;zef-0.13.8/t/repository.t000066400000000000000000000123441422013762000152370ustar00rootroot00000000000000use v6; use Test; plan 1; use Zef; use Zef::Distribution; use Zef::Repository; subtest 'Zef::Repository.candidates' => { my class Mock::Repository::One does PackageRepository { method fetch-matcher(|--> True ) { } method search(*@_) { return Empty unless @_.any ~~ 'Foo::Quick'; my Candidate @candidates = Candidate.new( dist => Zef::Distribution.new(:name('Foo::Quick'), :ver<0>, :api<1>,), as => 'Foo::Quick', ), Candidate.new( dist => Zef::Distribution.new(:name('Foo::Quick'), :ver<0.1>, :api<2>), as => 'Foo::Quick', ), Candidate.new( dist => Zef::Distribution.new(:name('Foo::Quick'), :ver<0.2>, :api<1>), as => 'Foo::Quick', ); return @candidates; } } my class Mock::Repository::Two does PackageRepository { method fetch-matcher(|--> True) { } method search(*@_) { return Empty unless @_.any ~~ 'Foo::Quick'; my Candidate @candidates = Candidate.new( dist => Zef::Distribution.new(:name('Foo::Quick'), :ver<0.3>,), as => 'Foo::Quick', ), Candidate.new( dist => Zef::Distribution.new(:name('Foo::Quick'), :ver<0>,), as => 'Foo::Quick', ), Candidate.new( dist => Zef::Distribution.new(:name('Foo::Quick'), :ver<0>, :auth), as => 'Foo::Quick', ); return @candidates; } } my class Mock::Repository::Three does PackageRepository { method fetch-matcher(|--> True) { } method search(*@_) { return Empty unless @_.any ~~ 'Bar'; my Candidate @candidates = Candidate.new( dist => Zef::Distribution.new(:name('Bar'), :ver<0.1>,), as => 'Bar', ); return @candidates; } } subtest 'api + version sorting' => { { my $zef-repository = Zef::Repository.new but role :: { method plugins(|--> List) { [[Mock::Repository::One.new, Mock::Repository::Two.new],] } }; my @candidates = $zef-repository.candidates('Foo::Quick'); is @candidates.elems, 1, 'Results are grouped by Candidate.as'; is @candidates.head.dist.ver, v0.1, 'Results return sorted from highest api/ver to lowest'; } # Like the previous test, but switching the order of the plugins { my $zef-repository = Zef::Repository.new but role :: { method plugins(|--> List) { [[Mock::Repository::Two.new, Mock::Repository::One.new],] } }; my @candidates = $zef-repository.candidates('Foo::Quick'); is @candidates.elems, 1, 'Results are grouped by Candidate.as'; is @candidates.head.dist.ver, v0.1, 'Results return sorted from highest api/ver to lowest'; } } subtest 'tiered ecosystems with api + version sorting' => { { my $zef-repository = Zef::Repository.new but role :: { method plugins(|--> List) { [ [Mock::Repository::One.new,], [Mock::Repository::Two.new, Mock::Repository::Three.new], ] } } my @candidates = $zef-repository.candidates('Foo::Quick','Bar'); is @candidates.elems, 2, 'Results are grouped by Candidate.as'; my $foo-dist = @candidates.first({ .dist.name eq 'Foo::Quick' }).dist; ok $foo-dist, 'Found correct dist'; is $foo-dist.ver, v0.1, 'Found correct dist'; my $bar-dist = @candidates.first({ .dist.name eq 'Bar' }).dist; ok $bar-dist, 'Results return sorted from highest api/ver to lowest from first tier with any matches'; is $bar-dist.ver, v0.1, 'Results return sorted from highest api/ver to lowest from first tier with any matches'; } # Like the previous test, but switching the order of the plugins { my $zef-repository = Zef::Repository.new but role :: { method plugins(|--> List) { [ [Mock::Repository::Two.new, Mock::Repository::Three.new], [Mock::Repository::One.new,], ] } }; my @candidates = $zef-repository.candidates('Foo::Quick','Bar'); is @candidates.elems, 2, 'Results are grouped by Candidate.as'; my $foo-dist = @candidates.first({ .dist.name eq 'Foo::Quick' }).dist; ok $foo-dist, 'Found correct dist'; is $foo-dist.ver, v0.3, 'Found correct dist'; my $bar-dist = @candidates.first({ .dist.name eq 'Bar' }).dist; ok $bar-dist, 'Results return sorted from highest api/ver to lowest from first tier with any matches'; is $bar-dist.ver, v0.1, 'Results return sorted from highest api/ver to lowest from first tier with any matches'; } } } done-testing;zef-0.13.8/t/test.t000066400000000000000000000063121422013762000137750ustar00rootroot00000000000000use v6; use Test; plan 1; use Zef; use Zef::Test; use Zef::Distribution; subtest 'Zef::Test.test' => { subtest 'Two testers, first does not match/handle uri' => { my class Mock::Tester::One does Tester { method test-matcher(|--> False) { } method test($) { die 'should not get called' } } my class Mock::Tester::Two does Tester { method test-matcher(|--> True) { } method test($ --> True) { } } my $save-to = $*TMPDIR.child(100000.rand).mkdir; my $tester = Zef::Test.new but role :: { method plugins(|--> List) { Mock::Tester::One.new, Mock::Tester::Two.new } }; my $dist = Zef::Distribution.new(:name) but role :: { method path { $save-to } }; ok $tester.test(Candidate.new(:$dist)); try $save-to.rmdir; } subtest 'Two testers, first not capable of handling given uri' => { my class Mock::Tester::One does Tester { method test-matcher(|--> False) { } method test($) { die 'should not get called' } } my class Mock::Tester::Two does Tester { method test-matcher(|--> True) { } method test($ --> True) { } } my $save-to = $*TMPDIR.child(100000.rand).mkdir; my $tester = Zef::Test.new but role :: { method plugins(|--> List) { Mock::Tester::One.new, Mock::Tester::Two.new } }; my $dist = Zef::Distribution.new(:name) but role :: { method path { $save-to } }; ok $tester.test(Candidate.new(:$dist)); try $save-to.rmdir; } subtest 'Two testers, first fails and second is not tried' => { my class Mock::Tester::One does Tester { method test-matcher(|--> True) { } method test($ --> Nil) { } } my class Mock::Tester::Two does Tester { method test-matcher(|--> True) { } method test($ --> True) { die 'should not get called' } } my $save-to = $*TMPDIR.child(100000.rand).mkdir; my $tester = Zef::Test.new but role :: { method plugins(|--> List) { Mock::Tester::One.new, Mock::Tester::Two.new } }; my $dist = Zef::Distribution.new(:name) but role :: { method path { $save-to } }; is $tester.test(Candidate.new(:$dist)).grep(*.so).elems, 0; try $save-to.rmdir; } subtest 'Two testers, first times out and second is not tried' => { my constant timeout = 1; my class Mock::Tester::One does Tester { method test-matcher(|--> True) { } method test($) { sleep(timeout * 5); timeout; } } my class Mock::Tester::Two does Tester { method test-matcher(|--> True) { } method test($ --> True) { die 'should not get called' } } my $save-to = $*TMPDIR.child(100000.rand).mkdir; my $tester = Zef::Test.new but role :: { method plugins(|--> List) { Mock::Tester::One.new, Mock::Tester::Two.new } }; my $dist = Zef::Distribution.new(:name) but role :: { method path { $save-to } }; is $tester.test(Candidate.new(:$dist), :timeout(timeout)).grep(*.so).elems, 0; try $save-to.rmdir; } } done-testing;zef-0.13.8/t/utils-filesystem.t000066400000000000000000000134161422013762000163430ustar00rootroot00000000000000use v6; use Zef::Utils::FileSystem; use Test; plan 4; my $save-to = $*TMPDIR.child(time); my $dir-id = 0; # :d :f :r subtest "list-paths and delete-paths :d :f :r (rm -rf)" => { temp $save-to = $save-to.child(++$dir-id); my @delete-us; # 1. Folder: /{temp folder} # 2. File: /{temp folder}/base-delete.me # 3. Folder: /{temp folder}/deleteme-subfolder # 4. File: /{temp folder}/deleteme-subfolder/base-delete.me # All 4 items should get deleted mkdir($_) and @delete-us.append($_) with ~$save-to; my $sub-folder = $save-to.child('deleteme-subfolder'); mkdir($_) and @delete-us.append($_) with ~$sub-folder; # create 2 test files, one in each directory we created above my $save-to-file = $save-to.child('base-delete.me'); my $sub-folder-file = $sub-folder.child('sub-delete.me'); $save-to-file.spurt(time); $sub-folder-file.spurt(time); @delete-us.append($save-to-file.path); @delete-us.append($sub-folder-file.path); ok $save-to.d, "Folder available to delete"; my @paths = list-paths($save-to, :f, :d, :r); my @deleted = delete-paths($save-to, :f, :d, :r); is +@deleted, +@paths + 1; my $to-be-deleted = any($save-to, $sub-folder, $save-to-file, $sub-folder-file); for @delete-us -> $path-to-delete { is $path-to-delete, any(|@paths,$save-to), 'file was found in list-paths'; is $path-to-delete, $to-be-deleted, "Deleted: {$path-to-delete}"; } } # :d :f subtest "list-paths and delete-paths :d :f (no recursion)" => { temp $save-to = $save-to.child(++$dir-id); my @delete-us; # 1. Folder: /{temp folder} # 2. File: /{temp folder}/base-delete.me # 3. Folder: /{temp folder}/deleteme-subfolder # 4. File: /{temp folder}/deleteme-subfolder/base-delete.me # Only item 2 should get deleted my $sub-folder = $save-to.child('deleteme-subfolder'); mkdir($sub-folder); # create 2 test files, one in each directory we created above my $save-to-file = $save-to.child('base-delete.me'); my $sub-folder-file = $sub-folder.child('sub-delete.me'); $save-to-file.spurt(time); $sub-folder-file.spurt(time); @delete-us.append($save-to-file); ok $save-to.d, "Folder available to delete"; my @paths = list-paths($save-to, :d, :f); my @deleted = delete-paths($save-to, :d, :f); is +@deleted, +@paths + 1; my $to-be-deleted = any($save-to-file); my $not-deleted = any($save-to, $sub-folder, $sub-folder-file); for @delete-us -> $path-to-delete { is $path-to-delete, any(@paths), "File was found in list-paths"; is $path-to-delete, $to-be-deleted, "Deleted: {$path-to-delete.path}"; isnt $path-to-delete, $not-deleted, 'Did not delete sub-file or delete non-empty directory'; } } # :d :r subtest "list-paths and delete-paths :d :r" => { temp $save-to = $save-to.child(++$dir-id); my @delete-us; # 1. Folder: /{temp folder} # 2. File: /{temp folder}/base-delete.me # 3. Folder: /{temp folder}/deleteme-subfolder # 4. File: /{temp folder}/deleteme-subfolder/base-delete.me # 5. Folder /{temp folder}/empty-subfolder # Only item 5 will be deleted my $sub-folder = $save-to.child('deleteme-subfolder'); mkdir($sub-folder); my $sub-folder-empty = $save-to.child('empty-subfolder'); @delete-us.append($sub-folder-empty); mkdir($sub-folder-empty); # create 2 test files, one in each directory we created above my $save-to-file = $save-to.child('base-delete.me'); my $sub-folder-file = $sub-folder.child('sub-delete.me'); $save-to-file.spurt(time); $sub-folder-file.spurt(time); ok $save-to.d, "Folder available to delete"; my @paths = list-paths($save-to, :d, :r); my @deleted = delete-paths($save-to, :d, :r); is +@deleted, +@paths + 1; my $to-be-deleted = any($sub-folder-empty); my $not-deleted = any($save-to, $save-to-file, $sub-folder, $sub-folder-file); for @delete-us -> $path-to-delete { is $path-to-delete, any(@paths), "File was found in list-paths"; is $path-to-delete, $to-be-deleted, "Deleted: {$path-to-delete.path}"; isnt $path-to-delete, $not-deleted, 'Did not delete sub-file or delete non-empty directory'; } } # :f :r subtest "list-paths and delete-paths :f :r" => { temp $save-to = $save-to.child(++$dir-id); my @delete-us; # 1. Folder: /{temp folder} # 2. File: /{temp folder}/base-delete.me # 3. Folder: /{temp folder}/deleteme-subfolder # 4. File: /{temp folder}/deleteme-subfolder/base-delete.me # 5. Folder /{temp folder}/empty-subfolder # Delete items 2 and 4 my $sub-folder = $save-to.child('deleteme-subfolder'); mkdir($sub-folder); my $sub-folder-empty = $save-to.child('empty-subfolder'); mkdir($sub-folder-empty); # create 2 test files, one in each directory we created above my $save-to-file = $save-to.child('base-delete.me'); my $sub-folder-file = $sub-folder.child('sub-delete.me'); $save-to-file.spurt(time); $sub-folder-file.spurt(time); @delete-us.append($save-to-file); @delete-us.append($sub-folder-file); ok $save-to.d, "Folder available to delete"; my @paths = list-paths($save-to, :f, :r); my @deleted = delete-paths($save-to, :f, :r); is +@deleted, +@paths + 3; my $to-be-deleted = any($save-to-file, $sub-folder-file); my $not-deleted = any($save-to, $sub-folder, $sub-folder-empty); for @delete-us -> $path-to-delete { is $path-to-delete, any(@paths), "File was found in list-paths"; is $path-to-delete, $to-be-deleted, "Deleted: {$path-to-delete.path}"; isnt $path-to-delete, $not-deleted, 'Did not delete sub-file or delete non-empty directory'; } } try rmdir($save-to); done-testing; zef-0.13.8/xt/000077500000000000000000000000001422013762000130175ustar00rootroot00000000000000zef-0.13.8/xt/install.t000066400000000000000000000074501422013762000146600ustar00rootroot00000000000000use v6; use Test; plan 3; use Zef; use Zef::Client; use Zef::Utils::FileSystem; use Zef::Identity; use Zef::Config; my $path = $*TMPDIR.child("zef").child("{time}.{$*PID}"); my $dist-dir = $path.child('dist'); my $sources-dir = $path.child('sources'); my CompUnit::Repository @cur = CompUnit::RepositoryRegistry\ .repository-for-spec("inst#{$path.absolute}", :next-repo($*REPO)); END { try delete-paths($path, :r, :d, :f, :dot) } my $guess-path = $?FILE.IO.parent.parent.child('resources/config.json'); my $config-file = $guess-path.e ?? ~$guess-path !! Zef::Config::guess-path(); my $config = Zef::Config::parse-file($config-file); $config = "$path/.cache"; $config = "$path/.cache/store"; $config = "$path/.cache/tmp"; my @installed; # keep track of what gets installed for the optional uninstall test at the end my $client = Zef::Client.new(:$config); # Keeps every $client.install from printing to stdout sub test-install($path = $?FILE.IO.parent.parent) { # Need to remove all stdout/stderr output from Zef::Client, or at least complete # the message passing mechanism so it can be turned off at will. Until then just # turn off stdout for this test as it will output details to stdout even when !$verbose) temp $*OUT = class :: { method print(|) {}; method flush(|) {}; }; # No test distribution to install yet, so test install zef itself my $candidate = Candidate.new( dist => Zef::Distribution::Local.new($path), uri => $path.IO.absolute, as => ~$path, from => ~$?FILE, ); my @got = |$client.make-install( :to(@cur), :!test, :!fetch, $candidate ); @installed = unique(|@installed, |@got, :as(*.dist.identity)); } ######################################################################################### subtest 'install' => { my @installed = test-install(); is +@installed, 1, 'Installed a single module'; is +$dist-dir.dir.grep(*.f), 1, 'A single distribution file should exist'; # $dist-info is the content of a file that holds meta information, such as # the new names of the files. If ~$filename from $sources-dir is found in # ~$dist-info then just assume everything worked correctly my $filename = $sources-dir.dir.first(*.f).basename; my $dist-info = $dist-dir.dir.first(*.f).slurp; ok $dist-info.contains($filename), 'Verify install succeeded'; } subtest 'reinstall' => { subtest 'Without force' => { test-install(); # XXX: Need to find a way to test when this fails is +@installed, 1, 'Installed nothing new'; is +$dist-dir.dir.grep(*.f), 1, 'Only a single distribution file should still exist'; my $filename = $sources-dir.dir.first(*.f).basename; my $dist-info = $dist-dir.dir.first(*.f).slurp; ok $dist-info.contains($filename), 'Verify previous install appears valid'; } subtest 'With force-install' => { temp $client.force-install = True; my @installed = test-install(); is +@installed, 1, 'Install count remains 1'; is +$dist-dir.dir.grep(*.f), 1, 'Only a single distribution file should still exist'; my $filename = ~$sources-dir.dir.first(*.f).basename; my $dist-info = ~$dist-dir.dir.first(*.f).slurp; ok $dist-info.contains($filename), 'Verify reinstall appears valid'; } } subtest 'uninstall' => { +@cur.grep(*.can('uninstall')) == 0 ?? skip("Need a newer rakudo for uninstall") !! do { my @uninstalled = Zef::Client.new(:$config).uninstall( :from(@cur), |@installed>>.dist>>.identity ); is +@uninstalled, 1, 'Uninstalled a single module'; is +$sources-dir.dir, 0, 'No source files should remain'; is +$dist-dir.dir, 0, 'No distribution files should remain'; } } done-testing;zef-0.13.8/xt/repository.t000066400000000000000000000107451422013762000154320ustar00rootroot00000000000000use v6; use Test; plan 5; use Zef; use Zef::Repository; use Zef::Repository::Ecosystems; use Zef::Fetch; subtest 'Repository' => { class Mock::Repository does PackageRepository { method search(*@identities) { my Candidate @candidates = Candidate.new(:as("{@identities[0]}::X")), Candidate.new(:as("{@identities[0]}::XX")); return @candidates; } } subtest 'Mock::Repository' => { my $mock-repository = Mock::Repository.new; my @candidates = $mock-repository.search("Mock::Repository"); is +@candidates, 2; is @candidates[0].as, "Mock::Repository::X"; is @candidates[1].as, "Mock::Repository::XX"; } subtest 'Zef::Repository service aggregation' => { my $mock-repository1 = Mock::Repository.new; my $mock-repository2 = Mock::Repository.new; my $repository = Zef::Repository.new but role :: { method plugins { [[$mock-repository1, $mock-repository2],] } } my @candidates = $repository.search("Mock::Repository"); is +@candidates, 4; is @candidates[0].as, "Mock::Repository::X"; is @candidates[1].as, "Mock::Repository::XX"; is @candidates[2].as, "Mock::Repository::X"; is @candidates[3].as, "Mock::Repository::XX"; } } subtest 'Ecosystems => p6c' => { my $wanted = 'zef'; my @mirrors = 'https://github.com/ugexe/Perl6-ecosystems.git'; my @backends = [ { module => "Zef::Service::Shell::git" }, { module => "Zef::Service::Shell::wget" }, { module => "Zef::Service::Shell::curl" }, { module => "Zef::Service::Shell::PowerShell::download" }, ]; my $fetcher = Zef::Fetch.new(:@backends); my $cache = $*HOME.child('.zef/store') andthen { mkdir $_ unless $_.IO.e }; my $p6c = Zef::Repository::Ecosystems.new(name => 'p6c', :$fetcher, :$cache, :auto-update, :@mirrors); ok $p6c.available > 0; subtest 'search' => { my @candidates = $p6c.search($wanted, :strict); ok +@candidates > 0; is @candidates.grep({ .dist.name ne $wanted }).elems, 0; } } subtest 'Ecosystems => cpan' => { my $wanted = 'zef'; my @mirrors = 'https://raw.githubusercontent.com/ugexe/Perl6-ecosystems/11efd9077b398df3766eaa7cf8e6a9519f63c272/cpan.json'; my @backends = [ { module => "Zef::Service::Shell::wget" }, { module => "Zef::Service::Shell::curl" }, { module => "Zef::Service::Shell::PowerShell::download" }, ]; my $fetcher = Zef::Fetch.new(:@backends); my $cache = $*HOME.child('.zef/store') andthen { mkdir $_ unless $_.IO.e }; my $cpan = Zef::Repository::Ecosystems.new(name => 'cpan', :$fetcher, :$cache, :auto-update, :@mirrors); ok $cpan.available > 0; subtest 'search' => { my @candidates = $cpan.search($wanted, :strict); ok +@candidates > 0; is @candidates.grep({ .dist.name ne $wanted }).elems, 0; } } subtest 'Ecosystems => fez' => { my $wanted = 'fez'; my @mirrors = 'http://360.zef.pm/'; my @backends = [ { module => "Zef::Service::Shell::wget" }, { module => "Zef::Service::Shell::curl" }, { module => "Zef::Service::Shell::PowerShell::download" }, ]; my $fetcher = Zef::Fetch.new(:@backends); my $cache = $*HOME.child('.zef/store') andthen { mkdir $_ unless $_.IO.e }; my $fez = Zef::Repository::Ecosystems.new(name => 'fez', :$fetcher, :$cache, :auto-update, :@mirrors); ok $fez.available > 0; subtest 'search' => { my @candidates = $fez.search($wanted, :strict); ok +@candidates > 0; is @candidates.grep({ .dist.name ne $wanted }).elems, 0; } } subtest 'Ecosystems => rea' => { my $wanted = 'zef'; my @mirrors = 'https://raw.githubusercontent.com/Raku/REA/main/META.json'; my @backends = [ { module => "Zef::Service::Shell::wget" }, { module => "Zef::Service::Shell::curl" }, { module => "Zef::Service::Shell::PowerShell::download" }, ]; my $fetcher = Zef::Fetch.new(:@backends); my $cache = $*HOME.child('.zef/store') andthen { mkdir $_ unless $_.IO.e }; my $rea = Zef::Repository::Ecosystems.new(name => 'rea', :$fetcher, :$cache, :auto-update, :@mirrors); ok $rea.available > 0; subtest 'search' => { my @candidates = $rea.search($wanted, :strict); ok +@candidates > 0; is @candidates.grep({ .dist.name ne $wanted }).elems, 0; } } done-testing;