From e31f9059a9f3918f12c40d4d66f2db885d6f914a Mon Sep 17 00:00:00 2001 From: Lorry Tar Creator Date: Fri, 15 May 2015 19:21:51 +0000 Subject: Carton-v1.0.21 --- Changes | 248 +++++++++++++ LICENSE | 379 ++++++++++++++++++++ MANIFEST | 66 ++++ META.json | 96 +++++ META.yml | 62 ++++ Makefile.PL | 73 ++++ README | 165 +++++++++ cpanfile | 32 ++ dist.ini | 3 + lib/Carton.pm | 179 ++++++++++ lib/Carton/Builder.pm | 114 ++++++ lib/Carton/CLI.pm | 396 +++++++++++++++++++++ lib/Carton/CPANfile.pm | 44 +++ lib/Carton/Dependency.pm | 21 ++ lib/Carton/Dist.pm | 37 ++ lib/Carton/Dist/Core.pm | 23 ++ lib/Carton/Doc/Bundle.pod | 20 ++ lib/Carton/Doc/Check.pod | 24 ++ lib/Carton/Doc/Exec.pod | 19 + lib/Carton/Doc/FAQ.pod | 112 ++++++ lib/Carton/Doc/Fatpack.pod | 15 + lib/Carton/Doc/Install.pod | 95 +++++ lib/Carton/Doc/List.pod | 23 ++ lib/Carton/Doc/Show.pod | 12 + lib/Carton/Doc/Tree.pod | 13 + lib/Carton/Doc/Update.pod | 40 +++ lib/Carton/Doc/Upgrading.pod | 48 +++ lib/Carton/Doc/Version.pod | 11 + lib/Carton/Environment.pm | 100 ++++++ lib/Carton/Error.pm | 42 +++ lib/Carton/Index.pm | 68 ++++ lib/Carton/Mirror.pm | 23 ++ lib/Carton/Package.pm | 12 + lib/Carton/Packer.pm | 97 +++++ lib/Carton/Snapshot.pm | 191 ++++++++++ lib/Carton/Snapshot/Emitter.pm | 30 ++ lib/Carton/Snapshot/Parser.pm | 126 +++++++ lib/Carton/Tree.pm | 69 ++++ lib/Carton/Util.pm | 31 ++ script/carton | 27 ++ t/release-pod-syntax.t | 14 + xt/CLI.pm | 61 ++++ xt/cli/bundle.t | 19 + xt/cli/check.t | 96 +++++ xt/cli/cpanfile.t | 44 +++ xt/cli/deployment.t | 26 ++ xt/cli/deps_phase.t | 29 ++ xt/cli/exec.t | 84 +++++ xt/cli/freeze.t | 24 ++ xt/cli/help.t | 25 ++ xt/cli/install.t | 56 +++ xt/cli/json_pp.t | 23 ++ xt/cli/mirror.t | 38 ++ xt/cli/mismatch.t | 24 ++ xt/cli/no_cpanfile.t | 13 + xt/cli/perl.t | 23 ++ xt/cli/snapshot.t | 65 ++++ xt/cli/subdir.t | 25 ++ xt/cli/tree.t | 23 ++ xt/cli/update.t | 78 ++++ xt/cli/version.t | 12 + xt/cli/without.t | 67 ++++ .../id/M/MI/MIYAGAWA/Hash-MultiValue-0.08.tar.gz | Bin 0 -> 47374 bytes xt/mirror/modules/02packages.details.txt | 25 ++ xt/mirror/modules/02packages.details.txt.gz | Bin 0 -> 520 bytes 65 files changed, 4080 insertions(+) create mode 100644 Changes create mode 100644 LICENSE create mode 100644 MANIFEST create mode 100644 META.json create mode 100644 META.yml create mode 100644 Makefile.PL create mode 100644 README create mode 100644 cpanfile create mode 100644 dist.ini create mode 100644 lib/Carton.pm create mode 100644 lib/Carton/Builder.pm create mode 100644 lib/Carton/CLI.pm create mode 100644 lib/Carton/CPANfile.pm create mode 100644 lib/Carton/Dependency.pm create mode 100644 lib/Carton/Dist.pm create mode 100644 lib/Carton/Dist/Core.pm create mode 100644 lib/Carton/Doc/Bundle.pod create mode 100644 lib/Carton/Doc/Check.pod create mode 100644 lib/Carton/Doc/Exec.pod create mode 100644 lib/Carton/Doc/FAQ.pod create mode 100644 lib/Carton/Doc/Fatpack.pod create mode 100644 lib/Carton/Doc/Install.pod create mode 100644 lib/Carton/Doc/List.pod create mode 100644 lib/Carton/Doc/Show.pod create mode 100644 lib/Carton/Doc/Tree.pod create mode 100644 lib/Carton/Doc/Update.pod create mode 100644 lib/Carton/Doc/Upgrading.pod create mode 100644 lib/Carton/Doc/Version.pod create mode 100644 lib/Carton/Environment.pm create mode 100644 lib/Carton/Error.pm create mode 100644 lib/Carton/Index.pm create mode 100644 lib/Carton/Mirror.pm create mode 100644 lib/Carton/Package.pm create mode 100644 lib/Carton/Packer.pm create mode 100644 lib/Carton/Snapshot.pm create mode 100644 lib/Carton/Snapshot/Emitter.pm create mode 100644 lib/Carton/Snapshot/Parser.pm create mode 100644 lib/Carton/Tree.pm create mode 100644 lib/Carton/Util.pm create mode 100755 script/carton create mode 100644 t/release-pod-syntax.t create mode 100644 xt/CLI.pm create mode 100644 xt/cli/bundle.t create mode 100644 xt/cli/check.t create mode 100644 xt/cli/cpanfile.t create mode 100644 xt/cli/deployment.t create mode 100644 xt/cli/deps_phase.t create mode 100644 xt/cli/exec.t create mode 100644 xt/cli/freeze.t create mode 100644 xt/cli/help.t create mode 100644 xt/cli/install.t create mode 100644 xt/cli/json_pp.t create mode 100644 xt/cli/mirror.t create mode 100644 xt/cli/mismatch.t create mode 100644 xt/cli/no_cpanfile.t create mode 100644 xt/cli/perl.t create mode 100644 xt/cli/snapshot.t create mode 100644 xt/cli/subdir.t create mode 100644 xt/cli/tree.t create mode 100644 xt/cli/update.t create mode 100644 xt/cli/version.t create mode 100644 xt/cli/without.t create mode 100644 xt/mirror/authors/id/M/MI/MIYAGAWA/Hash-MultiValue-0.08.tar.gz create mode 100644 xt/mirror/modules/02packages.details.txt create mode 100644 xt/mirror/modules/02packages.details.txt.gz diff --git a/Changes b/Changes new file mode 100644 index 0000000..addbcce --- /dev/null +++ b/Changes @@ -0,0 +1,248 @@ +Revision history for carton + +v1.0.21 2015-05-15 12:21:47 PDT + - Changed Module::Reader dependency to requires for now, since it will be required + on the runtime by the fatpacked carton. + +v1.0.20 2015-05-08 16:20:37 PDT + - INCOMPATIBLE: Disable fatpack generation in carton bundle by default. + Use the new standalone command carton fatpack, to generate vendor/bin/carton + +v1.0.19 2015-05-01 17:48:06 PDT + - Remove Module::Build and MakeMaker from prerequisite since cpanm will + install them as required + +v1.0.18 2015-04-29 13:46:21 PDT + - Sort 02packages case insensitive, like PAUSE + +v1.0.17 2015-04-27 16:18:04 PDT + - Add back warnings to Carton::CLI + - Properly fail when a command is not found in carton exec (hachi) #193 + +v1.0.16 2015-04-25 06:37:17 PDT + - update 02packages.details.txt whitespace padding to follow PAUSE + +v1.0.15 2015-04-20 11:13:32 CEST + - downgrade some dependencies for fatpack-related tools to recommends + (probably ship it as a separate distribution in the future) + +v1.0.14 2015-04-20 00:07:26 CEST + - same as v1.0.14 + +v1.0.13 2015-04-19 19:38:42 CEST + - require cpanm 1.7030 for better cpanfile support + - switch to MakeMaker + - remove Exception::Class and Moo in favor of Class::Tiny + - add an ability to set generator in Carton::Index for Carmel use + +v1.0.12 2013-09-24 20:03:47 JST + - up Path::Tiny + - Add --no-fatpack to carton bundle #140 + +v1.0.11 2013-09-18 18:51:14 JST + - Disable fatal warnings that comes with Moo. This will make Path::Tiny not fail + on NFS without flock. #135 + +v1.0.10 2013-09-02 17:52:42 PDT + - Documentation fixes + - Support CRLF in snapshot (kan) #133 + +v1.0.9 2013-08-17 11:24:46 PDT + - Workaround carton help shows wrong doc on case insensitive filesystems + +v1.0.8 2013-08-16 18:38:14 PDT + - Include POD documentation for carton(1) + +v1.0.7 2013-08-10 21:55:29 PDT + - Worked around fatpack issue with perl < 5.16 with missing File::Spec + - Included missing requirements for fatpack executable + +v1.0.6 2013-08-10 17:59:51 PDT + - Added upgrading documentation (carton help upgrading) + - Experimental support for fatpacked carton executable in vendor/bin in bundle + +v1.0.5 2013-08-08 12:50:39 PDT + - Bump cpanm for version extraction #126 + - Fix doc about --cached (shibayu36) + - Fix Usage errors #123 + +v1.0.4 2013-08-05 19:20:11 PDT + - Bump cpanm to deal with failing to extract versions with version.pm #120 (lestrrat) + +v1.0.3 2013-08-05 10:17:59 PDT + - Update obsolete docs + - Added missing docs to some commands + +v1.0.2 2013-08-04 21:49:14 PDT + - Bump cpanm dependency to deal with old dists with ancient META.yml (tokuhirom) + +v1.0.1 2013-08-04 17:10:10 PDT + - Update docs + - Fixed bug where version range requirements are not properly preserved in the snapshot #117 (lestrrat) + +v1.0.0 2013-08-04 12:29:31 PDT + - This makes 1.0 release + - Documentation update + - Bump cpanm dependency + +v0.9.68 2013-07-26 17:49:28 PDT + - Change the distribution name case, to match with the module name + - Add Module::CoreList as a dependency for perl 5.8 + +v0.9.67 2013-07-24 14:53:53 PDT + - Use cpanm's fatscript interface rather than share dir + +v0.9.66 2013-07-24 08:46:27 PDT + - Correctly raises an exception when badly formatted snapshot file is read + - Fixed a bug in tree scanner where seen hash is not preserved + - tree scanner should be much faster for giant set of dependencies + +v0.9.65 2013-07-23 18:51:59 PDT + - BIG CHANGE: Use cpanfile.snapshot instead of carton.lock for 1.0 onwards + There is no way to migrate carton.lock to cpanfile.snapshot just yet. + - New cpanfile.snapshot is text based and is more VCS friendly + - Reworked internal of prereqs/requirements loader (again!) to eliminate + lots of duplicate code + +v0.9.64 2013-07-23 14:39:54 PDT + - Locate cpanm within a dist share dir so that cpanm in user's $PATH is not executed. + This will solve issues with cpanm in /usr/bin, perlbrew or with bad shebang #92 + +v0.9.63 2013-07-23 02:26:04 PDT + - Bump cpanminus requirement + - Support --cpanfile for carton install + - Support PERL_CARTON_CPANFILE (for commands other than install) + +v0.9.62 2013-07-22 14:33:11 PDT + - Now all carton commands can be run from a subdirectory #69 + - Refactored the way cpanfile/carton.lock files are detected + +v0.9.61 2013-07-22 10:24:35 PDT + - Implemented experimental --without option for carton install + +v0.9.60 2013-06-26 12:22:21 PDT + - Bump MakeMaker and Module::Build dependencies to support test requires + +v0.9.59 2013-06-17 17:13:21 PDT + - carton exec -Ilib gives a warning, while carton exec perl -Ilib won't #97 + +v0.9.58 2013-06-10 03:17:23 PDT + - Fix tests + - carton exec without an arg should raise an error + - typo fixes + +v0.9.57 2013-06-05 19:21:17 JST + - Changed the output of carton tree command to include module and dist versions + - Bunch of refactorings around requirements + - carton install now saves tarballs in local/cache, then carton bundle copies from there + - Implement carton check which checks if cpanfile requirements is satisfied locally with lock + - Implement carton update! + - Fix the installation collector logic to ignore dists that don't satisfy cpanfile + +v0.9.56 2013-06-04 00:21:53 JST + - Fixed carton tree output to avoid duplicates + +v0.9.55 2013-06-03 23:43:52 JST + - Added back carton tree command + - Added --distfile option to list command + +v0.9.54 2013-06-02 12:38:20 JST + - Install develop phase dependencies by default with carton install + - carton exec now doesn't set PERL5OPT with lib::core::only, so as not to mess with + the subprocess and site_perl modules (#60, #70, #82) + +v0.9.53 2013-06-01 23:54:53 JST + - use Moo + - refactored installer/downloader as Carton::Builder + +v0.9.52 2013-06-01 17:01:51 JST + - carton exec doesn't need '--' before perl anymore #77 + - remove even more unused code + - backed out color output support + - stopped collecting dependencies from cpanfile, since cpanm installdeps can read it directly + - Temporarily disabled check command for now + - Upped cpanm dependency + - Use vendor/cache for bundling since local is most likely gitignored #88 + - carton exec now requires carton install beforehand + +v0.9.51 2013-05-31 09:02:58 JST + - Documentation fixes + - Fixes test dependencies and build system + +v0.9.50 2013-05-31 02:18:07 JST + - Documentation fixes + - remove bunch of code that is unused + - removed tree command for now + - Overhauled the way bundle command works + - refactored lock and index generation code + - Enabled Travis CI tests + +v0.9.15 2013-03-31 18:11:28 PDT + - Add minimum perl dependency + +v0.9.14 2013-03-30 18:25:39 PDT + - Unset $VERSION on PAUSE (Thanks andk) + +v0.9.13 2013-03-30 15:14:49 PDT + - repackage for better META files with Milla v0.9.3 + +v0.9.12 2013-03-30 15:01:55 PDT + - repackage to set $VERSION + +v0.9.11 2013-03-30 14:54:21 PDT + - Ignore 'perl' requirements so as it won't fail, for now. #71 + - Install 'test' dependencies by default. #66 + - Convert to Milla, do not install carton-* man pages + +v0.9.10 Tue Feb 26 13:32:34 PST 2013 + - Same as v0.9_9. Still considered pre-1.0! + +v0.9_9 Wed Feb 6 11:02:46 PST 2013 + - Fixed bundle command where it updated modules, not the versions specified in carton.lock. + bundle now builds mirror files like install --deployment, and downloads tarballs for the + specified versions. (vti) + +v0.9_8 Tue Feb 5 12:17:54 PST 2013 + - Do not use carton.lock to build extra dependencies. Everything has to be + pulled out of cpanfile, even with the deployment mode. This makes the deployment + much more reliable, and could possibly work with differing os/perl versions + across development and deployments. + +v0.9_7 Sat May 12 06:15:44 EEST 2012 + - Experimental multiple mirror support (nihen) + - Fixed cpanm dependency to avoid cascading bug + +v0.9_6 Thu May 10 21:05:35 CEST 2012 + - use cpanfile + Module::Install for dogfooding + - `carton` without args now does `carton install` (inspired by bundler) + - Update bundle command to use install.json (masaki) + - code cleanups and doc overhauls + - removed `uninstall` command for now + - Fixed CPAN::Meta::Requirements dependency + +v0.9_5 Thu Apr 12 19:39:19 JST 2012 + - Added experimental cpanfile support + - Fixed POD (yanick) + +v0.9.4 Sat Mar 31 13:49:41 CEST 2012 + - use Capture::Tiny to capture output (wchristian) + - Improve synopsis for exec (dagolden) + - Implemented bundle command (masaki) + - Fix Getopt::Long dependency (pfig) + +v0.9.3 Wed Oct 19 14:30:50 JST 2011 + - Fixed META.yml by patching Module::Install and repackaging + +v0.9.2 Tue Oct 18 12:53:57 JST 2011 + - Fixed packaging *again* by declaring version as a simple string + via http://www.dagolden.com/index.php/369/version-numbers-should-be-boring/ + +v0.9.1 Mon Oct 17 19:05:12 JST 2011 + - Fixed packaging + - Fixed UTF8 encoding warnings for JSON + +v0.9.0 Fri Oct 14 01:27:02 JST 2011 + - Initial non-dev release. Still considered beta before it hits 1.0.0! + +v0.1_0 Sun Jun 26 11:03:50 PDT 2011 + - original version diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7b5e320 --- /dev/null +++ b/LICENSE @@ -0,0 +1,379 @@ +This software is copyright (c) 2011- by Tatsuhiko Miyagawa. + +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. + +Terms of the Perl programming language system itself + +a) the GNU General Public License as published by the Free + Software Foundation; either version 1, or (at your option) any + later version, or +b) the "Artistic License" + +--- The GNU General Public License, Version 1, February 1989 --- + +This software is Copyright (c) 2011- by Tatsuhiko Miyagawa. + +This is free software, licensed under: + + The GNU General Public License, Version 1, February 1989 + + GNU GENERAL PUBLIC LICENSE + Version 1, February 1989 + + Copyright (C) 1989 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The license agreements of most software companies try to keep users +at the mercy of those companies. By contrast, our General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. The +General Public License applies to the Free Software Foundation's +software and to any other program whose authors commit to using it. +You can use it for your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Specifically, the General Public License is designed to make +sure that you have the freedom to give away or sell copies of free +software, that you receive source code or can get it if you want it, +that you can change the software or use pieces of it in new free +programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of a such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must tell them their rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any program or other work which +contains a notice placed by the copyright holder saying it may be +distributed under the terms of this General Public License. The +"Program", below, refers to any such program or work, and a "work based +on the Program" means either the Program or any work containing the +Program or a portion of it, either verbatim or with modifications. Each +licensee is addressed as "you". + + 1. You may copy and distribute verbatim copies of the Program's source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and +disclaimer of warranty; keep intact all the notices that refer to this +General Public License and to the absence of any warranty; and give any +other recipients of the Program a copy of this General Public License +along with the Program. You may charge a fee for the physical act of +transferring a copy. + + 2. You may modify your copy or copies of the Program or any portion of +it, and copy and distribute such modifications under the terms of Paragraph +1 above, provided that you also do the following: + + a) cause the modified files to carry prominent notices stating that + you changed the files and the date of any change; and + + b) cause the whole of any work that you distribute or publish, that + in whole or in part contains the Program or any part thereof, either + with or without modifications, to be licensed at no charge to all + third parties under the terms of this General Public License (except + that you may choose to grant warranty protection to some or all + third parties, at your option). + + c) If the modified program normally reads commands interactively when + run, you must cause it, when started running for such interactive use + in the simplest and most usual way, to print or display an + announcement including an appropriate copyright notice and a notice + that there is no warranty (or else, saying that you provide a + warranty) and that users may redistribute the program under these + conditions, and telling the user how to view a copy of this General + Public License. + + d) You may charge a fee for the physical act of transferring a + copy, and you may at your option offer warranty protection in + exchange for a fee. + +Mere aggregation of another independent work with the Program (or its +derivative) on a volume of a storage or distribution medium does not bring +the other work under the scope of these terms. + + 3. You may copy and distribute the Program (or a portion or derivative of +it, under Paragraph 2) in object code or executable form under the terms of +Paragraphs 1 and 2 above provided that you also do one of the following: + + a) accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of + Paragraphs 1 and 2 above; or, + + b) accompany it with a written offer, valid for at least three + years, to give any third party free (except for a nominal charge + for the cost of distribution) a complete machine-readable copy of the + corresponding source code, to be distributed under the terms of + Paragraphs 1 and 2 above; or, + + c) accompany it with the information you received as to where the + corresponding source code may be obtained. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form alone.) + +Source code for a work means the preferred form of the work for making +modifications to it. For an executable file, complete source code means +all the source code for all modules it contains; but, as a special +exception, it need not include source code for modules which are standard +libraries that accompany the operating system on which the executable +file runs, or for standard header files or definitions files that +accompany that operating system. + + 4. You may not copy, modify, sublicense, distribute or transfer the +Program except as expressly provided under this General Public License. +Any attempt otherwise to copy, modify, sublicense, distribute or transfer +the Program is void, and will automatically terminate your rights to use +the Program under this License. However, parties who have received +copies, or rights to use copies, from you under this General Public +License will not have their licenses terminated so long as such parties +remain in full compliance. + + 5. By copying, distributing or modifying the Program (or any work based +on the Program) you indicate your acceptance of this license to do so, +and all its terms and conditions. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the original +licensor to copy, distribute or modify the Program subject to these +terms and conditions. You may not impose any further restrictions on the +recipients' exercise of the rights granted herein. + + 7. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of the license which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +the license, you may choose any version ever published by the Free Software +Foundation. + + 8. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to humanity, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + + To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively convey +the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 1, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19xx name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the +appropriate parts of the General Public License. Of course, the +commands you use may be called something other than `show w' and `show +c'; they could even be mouse-clicks or menu items--whatever suits your +program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + program `Gnomovision' (a program to direct compilers to make passes + at assemblers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +That's all there is to it! + + +--- The Artistic License 1.0 --- + +This software is Copyright (c) 2011- by Tatsuhiko Miyagawa. + +This is free software, licensed under: + + The Artistic License 1.0 + +The Artistic License + +Preamble + +The intent of this document is to state the conditions under which a Package +may be copied, such that the Copyright Holder maintains some semblance of +artistic control over the development of the package, while giving the users of +the package the right to use and distribute the Package in a more-or-less +customary fashion, plus the right to make reasonable modifications. + +Definitions: + + - "Package" refers to the collection of files distributed by the Copyright + Holder, and derivatives of that collection of files created through + textual modification. + - "Standard Version" refers to such a Package if it has not been modified, + or has been modified in accordance with the wishes of the Copyright + Holder. + - "Copyright Holder" is whoever is named in the copyright or copyrights for + the package. + - "You" is you, if you're thinking about copying or distributing this Package. + - "Reasonable copying fee" is whatever you can justify on the basis of media + cost, duplication charges, time of people involved, and so on. (You will + not be required to justify it to the Copyright Holder, but only to the + computing community at large as a market that must bear the fee.) + - "Freely Available" means that no fee is charged for the item itself, though + there may be fees involved in handling the item. It also means that + recipients of the item may redistribute it under the same conditions they + received it. + +1. You may make and give away verbatim copies of the source form of the +Standard Version of this Package without restriction, provided that you +duplicate all of the original copyright notices and associated disclaimers. + +2. You may apply bug fixes, portability fixes and other modifications derived +from the Public Domain or from the Copyright Holder. A Package modified in such +a way shall still be considered the Standard Version. + +3. You may otherwise modify your copy of this Package in any way, provided that +you insert a prominent notice in each changed file stating how and when you +changed that file, and provided that you do at least ONE of the following: + + a) place your modifications in the Public Domain or otherwise make them + Freely Available, such as by posting said modifications to Usenet or an + equivalent medium, or placing the modifications on a major archive site + such as ftp.uu.net, or by allowing the Copyright Holder to include your + modifications in the Standard Version of the Package. + + b) use the modified Package only within your corporation or organization. + + c) rename any non-standard executables so the names do not conflict with + standard executables, which must also be provided, and provide a separate + manual page for each non-standard executable that clearly documents how it + differs from the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + +4. You may distribute the programs of this Package in object code or executable +form, provided that you do at least ONE of the following: + + a) distribute a Standard Version of the executables and library files, + together with instructions (in the manual page or equivalent) on where to + get the Standard Version. + + b) accompany the distribution with the machine-readable source of the Package + with your modifications. + + c) accompany any non-standard executables with their corresponding Standard + Version executables, giving the non-standard executables non-standard + names, and clearly documenting the differences in manual pages (or + equivalent), together with instructions on where to get the Standard + Version. + + d) make other distribution arrangements with the Copyright Holder. + +5. You may charge a reasonable copying fee for any distribution of this +Package. You may charge any fee you choose for support of this Package. You +may not charge a fee for this Package itself. However, you may distribute this +Package in aggregate with other (possibly commercial) programs as part of a +larger (possibly commercial) software distribution provided that you do not +advertise this Package as a product of your own. + +6. The scripts and library files supplied as input to or produced as output +from the programs of this Package do not automatically fall under the copyright +of this Package, but belong to whomever generated them, and may be sold +commercially, and may be aggregated with this Package. + +7. C or perl subroutines supplied by you and linked into this Package shall not +be considered part of this Package. + +8. The name of the Copyright Holder may not be used to endorse or promote +products derived from this software without specific prior written permission. + +9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +The End + diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..6791b35 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,66 @@ +# This file was automatically generated by Dist::Zilla::Plugin::Manifest v5.036. +Changes +LICENSE +MANIFEST +META.json +META.yml +Makefile.PL +README +cpanfile +dist.ini +lib/Carton.pm +lib/Carton/Builder.pm +lib/Carton/CLI.pm +lib/Carton/CPANfile.pm +lib/Carton/Dependency.pm +lib/Carton/Dist.pm +lib/Carton/Dist/Core.pm +lib/Carton/Doc/Bundle.pod +lib/Carton/Doc/Check.pod +lib/Carton/Doc/Exec.pod +lib/Carton/Doc/FAQ.pod +lib/Carton/Doc/Fatpack.pod +lib/Carton/Doc/Install.pod +lib/Carton/Doc/List.pod +lib/Carton/Doc/Show.pod +lib/Carton/Doc/Tree.pod +lib/Carton/Doc/Update.pod +lib/Carton/Doc/Upgrading.pod +lib/Carton/Doc/Version.pod +lib/Carton/Environment.pm +lib/Carton/Error.pm +lib/Carton/Index.pm +lib/Carton/Mirror.pm +lib/Carton/Package.pm +lib/Carton/Packer.pm +lib/Carton/Snapshot.pm +lib/Carton/Snapshot/Emitter.pm +lib/Carton/Snapshot/Parser.pm +lib/Carton/Tree.pm +lib/Carton/Util.pm +script/carton +t/release-pod-syntax.t +xt/CLI.pm +xt/cli/bundle.t +xt/cli/check.t +xt/cli/cpanfile.t +xt/cli/deployment.t +xt/cli/deps_phase.t +xt/cli/exec.t +xt/cli/freeze.t +xt/cli/help.t +xt/cli/install.t +xt/cli/json_pp.t +xt/cli/mirror.t +xt/cli/mismatch.t +xt/cli/no_cpanfile.t +xt/cli/perl.t +xt/cli/snapshot.t +xt/cli/subdir.t +xt/cli/tree.t +xt/cli/update.t +xt/cli/version.t +xt/cli/without.t +xt/mirror/authors/id/M/MI/MIYAGAWA/Hash-MultiValue-0.08.tar.gz +xt/mirror/modules/02packages.details.txt +xt/mirror/modules/02packages.details.txt.gz diff --git a/META.json b/META.json new file mode 100644 index 0000000..614fef4 --- /dev/null +++ b/META.json @@ -0,0 +1,96 @@ +{ + "abstract" : "Perl module dependency manager (aka Bundler for Perl)", + "author" : [ + "Tatsuhiko Miyagawa" + ], + "dynamic_config" : 0, + "generated_by" : "Dist::Zilla version 5.036, Dist::Milla version v1.0.15, CPAN::Meta::Converter version 2.150001", + "license" : [ + "perl_5" + ], + "meta-spec" : { + "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", + "version" : 2 + }, + "name" : "Carton", + "no_index" : { + "directory" : [ + "t", + "xt", + "inc", + "share", + "eg", + "examples" + ] + }, + "prereqs" : { + "configure" : { + "requires" : { + "ExtUtils::MakeMaker" : "0", + "version" : "0.77" + } + }, + "develop" : { + "requires" : { + "Capture::Tiny" : "0", + "Dist::Milla" : "v1.0.15", + "Test::More" : "0.9", + "Test::Pod" : "1.41", + "Test::Requires" : "0" + } + }, + "runtime" : { + "recommends" : { + "App::FatPacker" : "0.009018", + "File::pushd" : "0" + }, + "requires" : { + "App::cpanminus" : "1.703", + "CPAN::Meta" : "2.120921", + "CPAN::Meta::Requirements" : "2.121", + "Class::Tiny" : "1.001", + "Getopt::Long" : "2.39", + "JSON" : "2.53", + "Module::CPANfile" : "0.9031", + "Module::CoreList" : "0", + "Module::Metadata" : "1.000003", + "Module::Reader" : "0.002", + "Path::Tiny" : "0.033", + "Try::Tiny" : "0.09", + "parent" : "0.223", + "perl" : "v5.8.5" + } + } + }, + "release_status" : "stable", + "resources" : { + "bugtracker" : { + "web" : "https://github.com/perl-carton/carton/issues" + }, + "homepage" : "https://github.com/perl-carton/carton", + "repository" : { + "type" : "git", + "url" : "https://github.com/perl-carton/carton.git", + "web" : "https://github.com/perl-carton/carton" + } + }, + "version" : "v1.0.21", + "x_contributors" : [ + "Christian Walde ", + "David Golden ", + "David Steinbrunner ", + "ikasam_a ", + "Jonathan Steinert ", + "Kan Fushihara ", + "Masahiro Chiba ", + "NAKAGAWA Masaki ", + "Olaf Alders ", + "Pedro Figueiredo ", + "Peter Oliver ", + "shiba_yu36 ", + "Tatsuhiko Miyagawa ", + "Tatsuhiko Miyagawa ", + "Yanick Champoux " + ] +} + diff --git a/META.yml b/META.yml new file mode 100644 index 0000000..9e01fae --- /dev/null +++ b/META.yml @@ -0,0 +1,62 @@ +--- +abstract: 'Perl module dependency manager (aka Bundler for Perl)' +author: + - 'Tatsuhiko Miyagawa' +build_requires: {} +configure_requires: + ExtUtils::MakeMaker: '0' + version: '0.77' +dynamic_config: 0 +generated_by: 'Dist::Zilla version 5.036, Dist::Milla version v1.0.15, CPAN::Meta::Converter version 2.150001' +license: perl +meta-spec: + url: http://module-build.sourceforge.net/META-spec-v1.4.html + version: '1.4' +name: Carton +no_index: + directory: + - t + - xt + - inc + - share + - eg + - examples +recommends: + App::FatPacker: '0.009018' + File::pushd: '0' +requires: + App::cpanminus: '1.703' + CPAN::Meta: '2.120921' + CPAN::Meta::Requirements: '2.121' + Class::Tiny: '1.001' + Getopt::Long: '2.39' + JSON: '2.53' + Module::CPANfile: '0.9031' + Module::CoreList: '0' + Module::Metadata: '1.000003' + Module::Reader: '0.002' + Path::Tiny: '0.033' + Try::Tiny: '0.09' + parent: '0.223' + perl: v5.8.5 +resources: + bugtracker: https://github.com/perl-carton/carton/issues + homepage: https://github.com/perl-carton/carton + repository: https://github.com/perl-carton/carton.git +version: v1.0.21 +x_contributors: + - 'Christian Walde ' + - 'David Golden ' + - 'David Steinbrunner ' + - 'ikasam_a ' + - 'Jonathan Steinert ' + - 'Kan Fushihara ' + - 'Masahiro Chiba ' + - 'NAKAGAWA Masaki ' + - 'Olaf Alders ' + - 'Pedro Figueiredo ' + - 'Peter Oliver ' + - 'shiba_yu36 ' + - 'Tatsuhiko Miyagawa ' + - 'Tatsuhiko Miyagawa ' + - 'Yanick Champoux ' diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..fda5390 --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,73 @@ +# This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v5.036. +use strict; +use warnings; + +use 5.008005; + +use ExtUtils::MakeMaker; + +my %WriteMakefileArgs = ( + "ABSTRACT" => "Perl module dependency manager (aka Bundler for Perl)", + "AUTHOR" => "Tatsuhiko Miyagawa", + "CONFIGURE_REQUIRES" => { + "ExtUtils::MakeMaker" => 0, + "version" => "0.77" + }, + "DISTNAME" => "Carton", + "EXE_FILES" => [ + "script/carton" + ], + "LICENSE" => "perl", + "MIN_PERL_VERSION" => "5.008005", + "NAME" => "Carton", + "PREREQ_PM" => { + "App::cpanminus" => "1.703", + "CPAN::Meta" => "2.120921", + "CPAN::Meta::Requirements" => "2.121", + "Class::Tiny" => "1.001", + "Getopt::Long" => "2.39", + "JSON" => "2.53", + "Module::CPANfile" => "0.9031", + "Module::CoreList" => 0, + "Module::Metadata" => "1.000003", + "Module::Reader" => "0.002", + "Path::Tiny" => "0.033", + "Try::Tiny" => "0.09", + "parent" => "0.223" + }, + "VERSION" => "v1.0.21", + "test" => { + "TESTS" => "t/*.t" + } +); + + +my %FallbackPrereqs = ( + "App::cpanminus" => "1.703", + "CPAN::Meta" => "2.120921", + "CPAN::Meta::Requirements" => "2.121", + "Class::Tiny" => "1.001", + "ExtUtils::MakeMaker" => 0, + "Getopt::Long" => "2.39", + "JSON" => "2.53", + "Module::CPANfile" => "0.9031", + "Module::CoreList" => 0, + "Module::Metadata" => "1.000003", + "Module::Reader" => "0.002", + "Path::Tiny" => "0.033", + "Try::Tiny" => "0.09", + "parent" => "0.223", + "version" => "0.77" +); + + +unless ( eval { ExtUtils::MakeMaker->VERSION(6.63_03) } ) { + delete $WriteMakefileArgs{TEST_REQUIRES}; + delete $WriteMakefileArgs{BUILD_REQUIRES}; + $WriteMakefileArgs{PREREQ_PM} = \%FallbackPrereqs; +} + +delete $WriteMakefileArgs{CONFIGURE_REQUIRES} + unless eval { ExtUtils::MakeMaker->VERSION(6.52) }; + +WriteMakefile(%WriteMakefileArgs); diff --git a/README b/README new file mode 100644 index 0000000..32be945 --- /dev/null +++ b/README @@ -0,0 +1,165 @@ +NAME + + Carton - Perl module dependency manager (aka Bundler for Perl) + +SYNOPSIS + + # On your development environment + > cat cpanfile + requires 'Plack', '0.9980'; + requires 'Starman', '0.2000'; + + > carton install + > git add cpanfile cpanfile.snapshot + > git commit -m "add Plack and Starman" + + # Other developer's machine, or on a deployment box + > carton install + > carton exec starman -p 8080 myapp.psgi + +AVAILABILITY + + Carton only works with perl installation with the complete set of core + modules. If you use perl installed by a vendor package with modules + stripped from core, Carton is not expected to work correctly. + + Also, Carton requires you to run your command/application with carton + exec command, which means it's difficult or impossible to run in an + embedded perl use case such as mod_perl. + +DESCRIPTION + + carton is a command line tool to track the Perl module dependencies for + your Perl application. Dependencies are declared using cpanfile format, + and the managed dependencies are tracked in a cpanfile.snapshot file, + which is meant to be version controlled, and the snapshot file allows + other developers of your application will have the exact same versions + of the modules. + + For cpanfile syntax, see cpanfile documentation. + +TUTORIAL + + Initializing the environment + + carton will use the local directory to install modules into. You're + recommended to exclude these directories from the version control + system. + + > echo local/ >> .gitignore + > git add cpanfile cpanfile.snapshot + > git commit -m "Start using carton" + + Tracking the dependencies + + You can manage the dependencies of your application via cpanfile. + + # cpanfile + requires 'Plack', '0.9980'; + requires 'Starman', '0.2000'; + + And then you can install these dependencies via: + + > carton install + + The modules are installed into your local directory, and the + dependencies tree and version information are analyzed and saved into + cpanfile.snapshot in your directory. + + Make sure you add cpanfile and cpanfile.snapshot to your version + controlled repository and commit changes as you update dependencies. + This will ensure that other developers on your app, as well as your + deployment environment, use exactly the same versions of the modules + you just installed. + + > git add cpanfile cpanfile.snapshot + > git commit -m "Added Plack and Starman" + + Deploying your application + + Once you've done installing all the dependencies, you can push your + application directory to a remote machine (excluding local and .carton) + and run the following command: + + > carton install --deployment + + This will look at the cpanfile.snapshot and install the exact same + versions of the dependencies into local, and now your application is + ready to run. + + The --deployment flag makes sure that carton will only install modules + and versions available in your snapshot, and won't fallback to query + for CPAN Meta DB for missing modules. + + Bundling modules + + carton can bundle all the tarballs for your dependencies into a + directory so that you can even install dependencies that are not + available on CPAN, such as internal distribution aka DarkPAN. + + > carton bundle + + will bundle these tarballs into vendor/cache directory, and + + > carton install --cached + + will install modules using this local cache. Combined with --deployment + option, you can avoid querying for a database like CPAN Meta DB or + downloading files from CPAN mirrors upon deployment time. + +PERL VERSIONS + + When you take a snapshot in one perl version and deploy on another + (different) version, you might have troubles with core modules. + + The simplest solution, which might not work for everybody, is to use + the same version of perl in the development and deployment. + + To enforce that, you're recommended to use plenv and .perl-version to + lock perl versions in development. + + You can also specify the minimum perl required in cpanfile: + + requires 'perl', '5.16.3'; + + and carton (and cpanm) will give you errors when deployed on hosts with + perl lower than the specified version. + +COMMUNITY + + https://github.com/miyagawa/carton + + Code repository, Wiki and Issue Tracker + + irc://irc.perl.org/#carton + + IRC chat room + +AUTHOR + + Tatsuhiko Miyagawa + +COPYRIGHT + + Tatsuhiko Miyagawa 2011- + +LICENSE + + This software is licensed under the same terms as Perl itself. + +SEE ALSO + + cpanm + + cpanfile + + Bundler + + pip + + npm + + perlrocks + + only + diff --git a/cpanfile b/cpanfile new file mode 100644 index 0000000..bdaa6cd --- /dev/null +++ b/cpanfile @@ -0,0 +1,32 @@ +on configure => sub { + requires 'version', 0.77; +}; + +requires 'perl', '5.8.5'; + +requires 'JSON', 2.53; +requires 'Module::Metadata', 1.000003; +requires 'Module::CPANfile', 0.9031; + +requires 'Try::Tiny', 0.09; +requires 'parent', 0.223; +requires 'Getopt::Long', 2.39; +requires 'Class::Tiny', 1.001; +requires 'Path::Tiny', 0.033; + +requires 'App::cpanminus', 1.7030; + +requires 'CPAN::Meta', 2.120921; +requires 'CPAN::Meta::Requirements', 2.121; +requires 'Module::CoreList'; + +# for fatpack +requires 'Module::Reader', 0.002; +recommends 'File::pushd'; +recommends 'App::FatPacker', 0.009018; + +on develop => sub { + requires 'Test::More', 0.90; + requires 'Test::Requires'; + requires 'Capture::Tiny'; +}; diff --git a/dist.ini b/dist.ini new file mode 100644 index 0000000..5abd671 --- /dev/null +++ b/dist.ini @@ -0,0 +1,3 @@ +name = Carton +[@Milla] +installer = MakeMaker diff --git a/lib/Carton.pm b/lib/Carton.pm new file mode 100644 index 0000000..7546c77 --- /dev/null +++ b/lib/Carton.pm @@ -0,0 +1,179 @@ +package Carton; +use strict; +use 5.008_005; +use version; our $VERSION = version->declare("v1.0.21"); + +1; +__END__ + +=head1 NAME + +Carton - Perl module dependency manager (aka Bundler for Perl) + +=head1 SYNOPSIS + + # On your development environment + > cat cpanfile + requires 'Plack', '0.9980'; + requires 'Starman', '0.2000'; + + > carton install + > git add cpanfile cpanfile.snapshot + > git commit -m "add Plack and Starman" + + # Other developer's machine, or on a deployment box + > carton install + > carton exec starman -p 8080 myapp.psgi + +=head1 AVAILABILITY + +Carton only works with perl installation with the complete set of core +modules. If you use perl installed by a vendor package with modules +stripped from core, Carton is not expected to work correctly. + +Also, Carton requires you to run your command/application with +C command, which means it's difficult or impossible to +run in an embedded perl use case such as mod_perl. + +=head1 DESCRIPTION + +carton is a command line tool to track the Perl module dependencies +for your Perl application. Dependencies are declared using L +format, and the managed dependencies are tracked in a +I file, which is meant to be version controlled, +and the snapshot file allows other developers of your application will +have the exact same versions of the modules. + +For C syntax, see L documentation. + +=head1 TUTORIAL + +=head2 Initializing the environment + +carton will use the I directory to install modules into. You're +recommended to exclude these directories from the version control +system. + + > echo local/ >> .gitignore + > git add cpanfile cpanfile.snapshot + > git commit -m "Start using carton" + +=head2 Tracking the dependencies + +You can manage the dependencies of your application via C. + + # cpanfile + requires 'Plack', '0.9980'; + requires 'Starman', '0.2000'; + +And then you can install these dependencies via: + + > carton install + +The modules are installed into your I directory, and the +dependencies tree and version information are analyzed and saved into +I in your directory. + +Make sure you add I and I to your version +controlled repository and commit changes as you update +dependencies. This will ensure that other developers on your app, as +well as your deployment environment, use exactly the same versions of +the modules you just installed. + + > git add cpanfile cpanfile.snapshot + > git commit -m "Added Plack and Starman" + +=head2 Deploying your application + +Once you've done installing all the dependencies, you can push your +application directory to a remote machine (excluding I and +I<.carton>) and run the following command: + + > carton install --deployment + +This will look at the I and install the exact same +versions of the dependencies into I, and now your application +is ready to run. + +The C<--deployment> flag makes sure that carton will only install +modules and versions available in your snapshot, and won't fallback to +query for CPAN Meta DB for missing modules. + +=head2 Bundling modules + +carton can bundle all the tarballs for your dependencies into a +directory so that you can even install dependencies that are not +available on CPAN, such as internal distribution aka DarkPAN. + + > carton bundle + +will bundle these tarballs into I directory, and + + > carton install --cached + +will install modules using this local cache. Combined with +C<--deployment> option, you can avoid querying for a database like +CPAN Meta DB or downloading files from CPAN mirrors upon deployment +time. + +=head1 PERL VERSIONS + +When you take a snapshot in one perl version and deploy on another +(different) version, you might have troubles with core modules. + +The simplest solution, which might not work for everybody, is to use +the same version of perl in the development and deployment. + +To enforce that, you're recommended to use L and +C<.perl-version> to lock perl versions in development. + +You can also specify the minimum perl required in C: + + requires 'perl', '5.16.3'; + +and carton (and cpanm) will give you errors when deployed on hosts +with perl lower than the specified version. + +=head1 COMMUNITY + +=over 4 + +=item L + +Code repository, Wiki and Issue Tracker + +=item L + +IRC chat room + +=back + +=head1 AUTHOR + +Tatsuhiko Miyagawa + +=head1 COPYRIGHT + +Tatsuhiko Miyagawa 2011- + +=head1 LICENSE + +This software is licensed under the same terms as Perl itself. + +=head1 SEE ALSO + +L + +L + +L + +L + +L + +L + +L + +=cut diff --git a/lib/Carton/Builder.pm b/lib/Carton/Builder.pm new file mode 100644 index 0000000..de456d7 --- /dev/null +++ b/lib/Carton/Builder.pm @@ -0,0 +1,114 @@ +package Carton::Builder; +use strict; +use Class::Tiny { + mirror => undef, + index => undef, + cascade => sub { 1 }, + without => sub { [] }, + cpanfile => undef, + fatscript => sub { $_[0]->_build_fatscript }, +}; + +sub effective_mirrors { + my $self = shift; + + # push default CPAN mirror always, as a fallback + # TODO don't pass fallback if --cached is set? + + my @mirrors = ($self->mirror); + push @mirrors, Carton::Mirror->default if $self->custom_mirror; + push @mirrors, Carton::Mirror->new('http://backpan.perl.org/'); + + @mirrors; +} + +sub custom_mirror { + my $self = shift; + ! $self->mirror->is_default; +} + +sub bundle { + my($self, $path, $cache_path, $snapshot) = @_; + + for my $dist ($snapshot->distributions) { + my $source = $path->child("cache/authors/id/" . $dist->pathname); + my $target = $cache_path->child("authors/id/" . $dist->pathname); + + if ($source->exists) { + warn "Copying ", $dist->pathname, "\n"; + $target->parent->mkpath; + $source->copy($target) or warn "$target: $!"; + } else { + warn "Couldn't find @{[ $dist->pathname ]}\n"; + } + } +} + +sub install { + my($self, $path) = @_; + + $self->run_cpanm( + "-L", $path, + (map { ("--mirror", $_->url) } $self->effective_mirrors), + ( $self->index ? ("--mirror-index", $self->index) : () ), + ( $self->cascade ? "--cascade-search" : () ), + ( $self->custom_mirror ? "--mirror-only" : () ), + "--save-dists", "$path/cache", + $self->groups, + "--cpanfile", $self->cpanfile, + "--installdeps", $self->cpanfile->dirname, + ) or die "Installing modules failed\n"; +} + +sub groups { + my $self = shift; + + # TODO support --without test (don't need test on deployment) + my @options = ('--with-all-features', '--with-develop'); + + for my $group (@{$self->without}) { + push @options, '--without-develop' if $group eq 'develop'; + push @options, "--without-feature=$group"; + } + + return @options; +} + +sub update { + my($self, $path, @modules) = @_; + + $self->run_cpanm( + "-L", $path, + (map { ("--mirror", $_->url) } $self->effective_mirrors), + ( $self->custom_mirror ? "--mirror-only" : () ), + "--save-dists", "$path/cache", + @modules + ) or die "Updating modules failed\n"; +} + +sub _build_fatscript { + my $self = shift; + + my $fatscript; + if ($Carton::Fatpacked) { + require Module::Reader; + my $content = Module::Reader::module_content('App::cpanminus::fatscript') + or die "Can't locate App::cpanminus::fatscript"; + $fatscript = Path::Tiny->tempfile; + $fatscript->spew($content); + } else { + require Module::Metadata; + $fatscript = Module::Metadata->find_module_by_name("App::cpanminus::fatscript") + or die "Can't locate App::cpanminus::fatscript"; + } + + return $fatscript; +} + +sub run_cpanm { + my($self, @args) = @_; + local $ENV{PERL_CPANM_OPT}; + !system $^X, $self->fatscript, "--quiet", "--notest", @args; +} + +1; diff --git a/lib/Carton/CLI.pm b/lib/Carton/CLI.pm new file mode 100644 index 0000000..b990ad2 --- /dev/null +++ b/lib/Carton/CLI.pm @@ -0,0 +1,396 @@ +package Carton::CLI; +use strict; +use warnings; +use Config; +use Getopt::Long; +use Path::Tiny; +use Try::Tiny; +use Module::CoreList; +use Scalar::Util qw(blessed); + +use Carton; +use Carton::Builder; +use Carton::Mirror; +use Carton::Snapshot; +use Carton::Util; +use Carton::Environment; +use Carton::Error; + +use constant { SUCCESS => 0, INFO => 1, WARN => 2, ERROR => 3 }; + +our $UseSystem = 0; # 1 for unit testing + +use Class::Tiny { + verbose => undef, + carton => sub { $_[0]->_build_carton }, + mirror => sub { $_[0]->_build_mirror }, +}; + +sub _build_mirror { + my $self = shift; + Carton::Mirror->new($ENV{PERL_CARTON_MIRROR} || $Carton::Mirror::DefaultMirror); +} + +sub run { + my($self, @args) = @_; + + my @commands; + my $p = Getopt::Long::Parser->new( + config => [ "no_ignore_case", "pass_through" ], + ); + $p->getoptionsfromarray( + \@args, + "h|help" => sub { unshift @commands, 'help' }, + "v|version" => sub { unshift @commands, 'version' }, + "verbose!" => sub { $self->verbose($_[1]) }, + ); + + push @commands, @args; + + my $cmd = shift @commands || 'install'; + + my $code = try { + my $call = $self->can("cmd_$cmd") + or Carton::Error::CommandNotFound->throw(error => "Could not find command '$cmd'"); + $self->$call(@commands); + return 0; + } catch { + die $_ unless blessed $_ && $_->can('rethrow'); + + if ($_->isa('Carton::Error::CommandExit')) { + return $_->code || 255; + } elsif ($_->isa('Carton::Error::CommandNotFound')) { + warn $_->error, "\n\n"; + $self->cmd_usage; + return 255; + } elsif ($_->isa('Carton::Error')) { + warn $_->error, "\n"; + return 255; + } + }; + + return $code; +} + +sub commands { + my $self = shift; + + no strict 'refs'; + map { s/^cmd_//; $_ } + grep { /^cmd_.*/ && $self->can($_) } sort keys %{__PACKAGE__."::"}; +} + +sub cmd_usage { + my $self = shift; + $self->print(< + +where is one of: + @{[ join ", ", $self->commands ]} + +Run carton -h for help. +HELP +} + +sub parse_options { + my($self, $args, @spec) = @_; + my $p = Getopt::Long::Parser->new( + config => [ "no_auto_abbrev", "no_ignore_case" ], + ); + $p->getoptionsfromarray($args, @spec); +} + +sub parse_options_pass_through { + my($self, $args, @spec) = @_; + + my $p = Getopt::Long::Parser->new( + config => [ "no_auto_abbrev", "no_ignore_case", "pass_through" ], + ); + $p->getoptionsfromarray($args, @spec); + + # with pass_through keeps -- in args + shift @$args if $args->[0] && $args->[0] eq '--'; +} + +sub printf { + my $self = shift; + my $type = pop; + my($temp, @args) = @_; + $self->print(sprintf($temp, @args), $type); +} + +sub print { + my($self, $msg, $type) = @_; + my $fh = $type && $type >= WARN ? *STDERR : *STDOUT; + print {$fh} $msg; +} + +sub error { + my($self, $msg) = @_; + $self->print($msg, ERROR); + Carton::Error::CommandExit->throw; +} + +sub cmd_help { + my $self = shift; + my $module = $_[0] ? ("Carton::Doc::" . ucfirst $_[0]) : "Carton.pm"; + system "perldoc", $module; +} + +sub cmd_version { + my $self = shift; + $self->print("carton $Carton::VERSION\n"); +} + +sub cmd_bundle { + my($self, @args) = @_; + + my $env = Carton::Environment->build; + $env->snapshot->load; + + $self->print("Bundling modules using @{[$env->cpanfile]}\n"); + + my $builder = Carton::Builder->new( + mirror => $self->mirror, + cpanfile => $env->cpanfile, + ); + $builder->bundle($env->install_path, $env->vendor_cache, $env->snapshot); + + $self->printf("Complete! Modules were bundled into %s\n", $env->vendor_cache, SUCCESS); +} + +sub cmd_fatpack { + my($self, @args) = @_; + + my $env = Carton::Environment->build; + require Carton::Packer; + Carton::Packer->new->fatpack_carton($env->vendor_bin); +} + +sub cmd_install { + my($self, @args) = @_; + + my($install_path, $cpanfile_path, @without); + + $self->parse_options( + \@args, + "p|path=s" => \$install_path, + "cpanfile=s" => \$cpanfile_path, + "without=s" => sub { push @without, split /,/, $_[1] }, + "deployment!" => \my $deployment, + "cached!" => \my $cached, + ); + + my $env = Carton::Environment->build($cpanfile_path, $install_path); + $env->snapshot->load_if_exists; + + if ($deployment && !$env->snapshot->loaded) { + $self->error("--deployment requires cpanfile.snapshot: Run `carton install` and make sure cpanfile.snapshot is checked into your version control.\n"); + } + + my $builder = Carton::Builder->new( + cascade => 1, + mirror => $self->mirror, + without => \@without, + cpanfile => $env->cpanfile, + ); + + # TODO: --without with no .lock won't fetch the groups, resulting in insufficient requirements + + if ($deployment) { + $self->print("Installing modules using @{[$env->cpanfile]} (deployment mode)\n"); + $builder->cascade(0); + } else { + $self->print("Installing modules using @{[$env->cpanfile]}\n"); + } + + # TODO merge CPANfile git to mirror even if lock doesn't exist + if ($env->snapshot->loaded) { + my $index_file = $env->install_path->child("cache/modules/02packages.details.txt"); + $index_file->parent->mkpath; + + $env->snapshot->write_index($index_file); + $builder->index($index_file); + } + + if ($cached) { + $builder->mirror(Carton::Mirror->new($env->vendor_cache)); + } + + $builder->install($env->install_path); + + unless ($deployment) { + $env->cpanfile->load; + $env->snapshot->find_installs($env->install_path, $env->cpanfile->requirements); + $env->snapshot->save; + } + + $self->print("Complete! Modules were installed into @{[$env->install_path]}\n", SUCCESS); +} + +sub cmd_show { + my($self, @args) = @_; + + my $env = Carton::Environment->build; + $env->snapshot->load; + + for my $module (@args) { + my $dist = $env->snapshot->find($module) + or $self->error("Couldn't locate $module in cpanfile.snapshot\n"); + $self->print( $dist->name . "\n" ); + } +} + +sub cmd_list { + my($self, @args) = @_; + + my $format = 'name'; + + $self->parse_options( + \@args, + "distfile" => sub { $format = 'distfile' }, + ); + + my $env = Carton::Environment->build; + $env->snapshot->load; + + for my $dist ($env->snapshot->distributions) { + $self->print($dist->$format . "\n"); + } +} + +sub cmd_tree { + my($self, @args) = @_; + + my $env = Carton::Environment->build; + $env->snapshot->load; + $env->cpanfile->load; + + my %seen; + my $dumper = sub { + my($dependency, $reqs, $level) = @_; + return if $level == 0; + return Carton::Tree::STOP if $dependency->dist->is_core; + return Carton::Tree::STOP if $seen{$dependency->distname}++; + $self->printf( "%s%s (%s)\n", " " x ($level - 1), $dependency->module, $dependency->distname, INFO ); + }; + + $env->tree->walk_down($dumper); +} + +sub cmd_check { + my($self, @args) = @_; + + my $cpanfile_path; + $self->parse_options( + \@args, + "cpanfile=s" => \$cpanfile_path, + ); + + my $env = Carton::Environment->build($cpanfile_path); + $env->snapshot->load; + $env->cpanfile->load; + + # TODO remove snapshot + # TODO pass git spec to Requirements? + my $merged_reqs = $env->tree->merged_requirements; + + my @missing; + for my $module ($merged_reqs->required_modules) { + my $install = $env->snapshot->find_or_core($module); + if ($install) { + unless ($merged_reqs->accepts_module($module => $install->version_for($module))) { + push @missing, [ $module, 1, $install->version_for($module) ]; + } + } else { + push @missing, [ $module, 0 ]; + } + } + + if (@missing) { + $self->print("Following dependencies are not satisfied.\n", INFO); + for my $missing (@missing) { + my($module, $unsatisfied, $version) = @$missing; + if ($unsatisfied) { + $self->printf(" %s has version %s. Needs %s\n", + $module, $version, $merged_reqs->requirements_for_module($module), INFO); + } else { + $self->printf(" %s is not installed. Needs %s\n", + $module, $merged_reqs->requirements_for_module($module), INFO); + } + } + $self->printf("Run `carton install` to install them.\n", INFO); + Carton::Error::CommandExit->throw; + } else { + $self->print("cpanfile's dependencies are satisfied.\n", INFO); + } +} + +sub cmd_update { + my($self, @args) = @_; + + my $env = Carton::Environment->build; + $env->cpanfile->load; + + + my $cpanfile = Module::CPANfile->load($env->cpanfile); + @args = grep { $_ ne 'perl' } $env->cpanfile->required_modules unless @args; + + $env->snapshot->load; + + my @modules; + for my $module (@args) { + my $dist = $env->snapshot->find_or_core($module) + or $self->error("Could not find module $module.\n"); + next if $dist->is_core; + push @modules, "$module~" . $env->cpanfile->requirements_for_module($module); + } + + my $builder = Carton::Builder->new( + mirror => $self->mirror, + cpanfile => $env->cpanfile, + ); + $builder->update($env->install_path, @modules); + + $env->snapshot->find_installs($env->install_path, $env->cpanfile->requirements); + $env->snapshot->save; +} + +sub cmd_exec { + my($self, @args) = @_; + + my $env = Carton::Environment->build; + $env->snapshot->load; + + # allows -Ilib + @args = map { /^(-[I])(.+)/ ? ($1,$2) : $_ } @args; + + while (@args) { + if ($args[0] eq '-I') { + warn "exec -Ilib is deprecated. You might want to run: carton exec perl -Ilib ...\n"; + splice(@args, 0, 2); + } else { + last; + } + } + + $self->parse_options_pass_through(\@args); # to handle -- + + unless (@args) { + $self->error("carton exec needs a command to run.\n"); + } + + # PERL5LIB takes care of arch + my $path = $env->install_path; + local $ENV{PERL5LIB} = "$path/lib/perl5"; + local $ENV{PATH} = "$path/bin:$ENV{PATH}"; + + if ($UseSystem) { + system @args; + } else { + exec @args; + exit 127; # command not found + } +} + +1; diff --git a/lib/Carton/CPANfile.pm b/lib/Carton/CPANfile.pm new file mode 100644 index 0000000..a60d690 --- /dev/null +++ b/lib/Carton/CPANfile.pm @@ -0,0 +1,44 @@ +package Carton::CPANfile; +use Path::Tiny (); +use Module::CPANfile; + +use overload q{""} => sub { $_[0]->stringify }, fallback => 1; + +use subs 'path'; + +use Class::Tiny { + path => undef, + _cpanfile => undef, + requirements => sub { $_[0]->_build_requirements }, +}; + +sub stringify { shift->path->stringify(@_) } +sub dirname { shift->path->dirname(@_) } +sub prereqs { shift->_cpanfile->prereqs(@_) } +sub required_modules { shift->requirements->required_modules(@_) } +sub requirements_for_module { shift->requirements->requirements_for_module(@_) } + +sub path { + my $self = shift; + if (@_) { + $self->{path} = Path::Tiny->new($_[0]); + } else { + $self->{path}; + } +} + +sub load { + my $self = shift; + $self->_cpanfile( Module::CPANfile->load($self->path) ); +} + +sub _build_requirements { + my $self = shift; + my $reqs = CPAN::Meta::Requirements->new; + $reqs->add_requirements($self->prereqs->requirements_for($_, 'requires')) + for qw( configure build runtime test develop ); + $reqs->clear_requirement('perl'); + $reqs; +} + +1; diff --git a/lib/Carton/Dependency.pm b/lib/Carton/Dependency.pm new file mode 100644 index 0000000..d3e11ac --- /dev/null +++ b/lib/Carton/Dependency.pm @@ -0,0 +1,21 @@ +package Carton::Dependency; +use strict; +use Class::Tiny { + module => undef, + requirement => undef, + dist => undef, +}; + +sub requirements { shift->dist->requirements(@_) } + +sub distname { + my $self = shift; + $self->dist->name; +} + +sub version { + my $self = shift; + $self->dist->version_for($self->module); +} + +1; diff --git a/lib/Carton/Dist.pm b/lib/Carton/Dist.pm new file mode 100644 index 0000000..9310e70 --- /dev/null +++ b/lib/Carton/Dist.pm @@ -0,0 +1,37 @@ +package Carton::Dist; +use strict; +use Class::Tiny { + name => undef, + pathname => undef, + provides => sub { +{} }, + requirements => sub { $_[0]->_build_requirements }, +}; + +use CPAN::Meta; + +sub add_string_requirement { shift->requirements->add_string_requirement(@_) } +sub required_modules { shift->requirements->required_modules(@_) } +sub requirements_for_module { shift->requirements->requirements_for_module(@_) } + +sub is_core { 0 } + +sub distfile { + my $self = shift; + $self->pathname; +} + +sub _build_requirements { + CPAN::Meta::Requirements->new; +} + +sub provides_module { + my($self, $module) = @_; + exists $self->provides->{$module}; +} + +sub version_for { + my($self, $module) = @_; + $self->provides->{$module}{version}; +} + +1; diff --git a/lib/Carton/Dist/Core.pm b/lib/Carton/Dist/Core.pm new file mode 100644 index 0000000..760ce66 --- /dev/null +++ b/lib/Carton/Dist/Core.pm @@ -0,0 +1,23 @@ +package Carton::Dist::Core; +use strict; +use parent 'Carton::Dist'; + +use Class::Tiny qw( module_version ); + +sub BUILDARGS { + my($class, %args) = @_; + + # TODO represent dual-life + $args{name} =~ s/::/-/g; + + \%args; +} + +sub is_core { 1 } + +sub version_for { + my($self, $module) = @_; + $self->module_version; +} + +1; diff --git a/lib/Carton/Doc/Bundle.pod b/lib/Carton/Doc/Bundle.pod new file mode 100644 index 0000000..68ce8cc --- /dev/null +++ b/lib/Carton/Doc/Bundle.pod @@ -0,0 +1,20 @@ +=head1 NAME + +Carton::Doc::Bundle - Bundle cached tarballs in vendor/cache + +=head1 SYNOPSIS + + carton bundle + +=head1 DESCRIPTION + +This command bundles cached tarballs into C +directory. These tarballs have been cached in C while +resolving dependencies in the snapshot file.snapshot. + +Bundled modules can be committed to a version control system, or +transferred to another host with scp/rsync etc. to use with C. + +See also C that generates C executable in +C. diff --git a/lib/Carton/Doc/Check.pod b/lib/Carton/Doc/Check.pod new file mode 100644 index 0000000..5283a7a --- /dev/null +++ b/lib/Carton/Doc/Check.pod @@ -0,0 +1,24 @@ +=head1 NAME + +Carton::Doc::Check - Check if your cpanfile and local environment are in sync + +=head1 SYNOPSIS + + carton check + +=head1 DESCRIPTION + +This command checks the consistency between your C, +C and the local environment. + +=head2 MISSING MODULES + +If one or more of the modules specified in your I are not +found in your snapshot, C will warn you about this: + + $ carton check + Following dependencies are not satisfied. + JSON has version 2.51. Needs 2.52 + Run `carton install` to install them. + +You can run C again to reinstall these missing dependencies. diff --git a/lib/Carton/Doc/Exec.pod b/lib/Carton/Doc/Exec.pod new file mode 100644 index 0000000..9eeca09 --- /dev/null +++ b/lib/Carton/Doc/Exec.pod @@ -0,0 +1,19 @@ +=head1 NAME + +Carton::Doc::Exec - execute your script in a carton local environment + +=head1 SYNOPSIS + + carton exec perl myscript.pl + +=head1 DESCRIPTION + +This command allows you to run your script in an isolated carton local +environment, which means the perl 5 library path C<@INC> are the only +ones from perl's core library path, carton's library path +(i.e. C) and the current directory. + +This is useful to make sure your scripts and application use the exact +same versions of the modules in your library path, and are not using +any of the modules you accidentally installed into your system perl or +perlbrew's site library path. diff --git a/lib/Carton/Doc/FAQ.pod b/lib/Carton/Doc/FAQ.pod new file mode 100644 index 0000000..ec9a575 --- /dev/null +++ b/lib/Carton/Doc/FAQ.pod @@ -0,0 +1,112 @@ +=head1 NAME + +Carton::Doc::FAQ - Frequently Asked Questions + +=head1 QUESTIONS + +=head2 It looks useful, but what is the use case of this tool? + +The particular problem that carton is trying to address is this: + +You develop a Perl-based application, possibly but not limited to +webapps, with dozens of CPAN module dependencies. You install these +modules on your development machine, and describe these dependencies +in your I. + +Now you get a production environment, either on PaaS provider or some +VPS, you install the dependencies using C and +it will pull all the latest releases from CPAN as of today and +everything just works. + +A few weeks later, your application becomes more popular, and you +think you need another machine to serve more requests. You set up +another machine with vanilla perl installation and install the +dependencies the same way. That will pull the I releases from +CPAN I, rather than the same as what you have today. + +And that is the problem. It's not likely that everything just breaks +one day, but there's always a chance that one of the dependencies +breaks an API compatibility, or just uploaded a buggy version to CPAN +on that particular day. + +Carton allows you to I these dependencies into a version +controlled system, so that every time you deploy from a checkout, it +is guaranteed that all the same versions are installed into the local +environment. + +=head2 How is this different from Pinto or CPAN::Mini::Inject? + +carton definitely shares the goal with these private CPAN repository +management tool. But the main difference is that rather than creating +an actual CPAN-like repository that works with any CPAN clients, +Carton provides a way to install specific versions of distributions +from CPAN, or any CPAN-like mirrors (as well as git repositories in +the future version of Carton). + +Existing tools are designed to work I CPAN clients such as +L or L, and have accomplished that by working around +the CPAN mirror structure. + +carton I does the same thing, but its user interface is +centered around the installer, by implementing a wrapper for +L, so you can use the same commands in the +development mode and deployment mode. + +Carton automatically maintains the L file, which is meant +to be version controlled, inside your application directory. You don't +need a separate database, a directory or a web server to maintain +tarballs outside your application. The I file can always +be generated with C command, and C on +another machine can use the version in the snapshot. + +=head2 I already use Pinto to create DarkPAN mirror. Can I use Carton with this? + +Yes, by specifying Pinto mirror as your Carton mirror, you can take a +snapshot of your dependencies including your private modules on Pinto, +or whatever DarkPAN mirror. + +=head2 I'm already using perlbrew and local::lib. Can I use carton with this? + +If you're using L already with L perl, possibly +with the new C command, that's great! There are multiple +benefits over using L and L for development and +use L for deployment. + +The best practice and workflow to get your perl environment as clean +as possible with lots of modules installed for quick development would +be this: + +=over + +=item * + +Install fresh perl using perlbrew. The version must be the same +against the version you'll run on the production environment. + +=item * + +Once the installation is done, use C command to create a +new local lib environment (let's call it I) and always use the +library as a default environment. Install as many modules as you would +like into the I library path. + +This ensures to have a vanilla C library path as clean as +possible. + +=item * + +When you build a new project that you want to manage dependencies via +Carton, turn off the I local::lib and create a new one, like +I. Install L and all of its dependencies to the +I local::lib path. Then run C like you +normally do. + +Becuase I and I are isolated, the modules you installed +into I doesn't affect the process when carton builds the +dependency tree for your new project at all. This could often be +critical when you have a conditional dependency in your tree, like +L. + +=back + + diff --git a/lib/Carton/Doc/Fatpack.pod b/lib/Carton/Doc/Fatpack.pod new file mode 100644 index 0000000..15282e0 --- /dev/null +++ b/lib/Carton/Doc/Fatpack.pod @@ -0,0 +1,15 @@ +=head1 NAME + +Carton::Doc::Fatpack - Fatpack carton executable into vendor/bin + +=head1 SYNOPSIS + + carton fatpack + +=head1 DESCRIPTION + +This command creates a fatpack executable of C in +C directory, so that it can be used to bootstrap +deployment process, combined with C and C. + diff --git a/lib/Carton/Doc/Install.pod b/lib/Carton/Doc/Install.pod new file mode 100644 index 0000000..df3b58f --- /dev/null +++ b/lib/Carton/Doc/Install.pod @@ -0,0 +1,95 @@ +=head1 NAME + +Carton::Doc::Install - Install the dependencies + +=head1 SYNOPSIS + + carton install [--deployment] [--cached] [--path=PATH] [--without develop] + +=head1 DESCRIPTION + +Install the dependencies for your application. This command has two +modes and the behavior is slightly different. + +=head2 DEVELOPMENT MODE + +=over 4 + +=item carton install + +If you run C without any arguments and if I +exists, carton will scan dependencies from I and install +the modules. + +=back + +If you run C for the first time +(i.e. I does not exist), carton will fetch all the +modules specified, resolve dependencies and install all required +modules from CPAN. + +If I file does exist, carton will still try to install +modules specified or updated in I, but uses I +for the dependency resolution, and then cascades to CPAN. + +carton will analyze all the dependencies and their version +information, and it is saved into I file. It is important +to add I file into a version controlled repository and +commit the changes as you update your dependencies. + +=head2 DEPLOYMENT MODE + +If you specify the C<--deployment> command line option or the +I exists, carton will only use the dependencies +specified in the I instead of resolving +dependencies. + +=head1 OPTIONS + +=over 4 + +=item --deployment + +Force the deployment mode. See L above. + +=item --cached + +Locate distribution tarballs in C rather than fetching +them from CPAN mirrors. This requires you to run C +prior to the deployment and commit or sync the content of C +directory to the other host you run C on. + +=item --cpanfile + +Specify the alternate path for cpanfile. By default, C +will look for the file C in the current directory, then +upwards till the root directory, in case the command runs from a sub +directory. + +Carton assumes the directory where your cpanfile (or altenate path) +exists as a project root directory, and will look for the snapshot file as +well as install directory (C) and C relative to it. + +=item --path + +Specify the path to install modules to. Defaults to I in the +directory relative to where C is. + +B: this option, as of version 1.0, is not preserved across +multiple runs of C or other commands such as C or C. You can choose to set the path in +C environment variable to persist it across +commands. + +=item --without + +By default, C will install all the phases for +dependencies, including C. You can specify phases or features +to exclude, in the comma separated list. + + carton install --deployment --without develop + +B: C<--without> for the initial installation (without +cpanfile.snapshot) is not supported at this moment. + +=back diff --git a/lib/Carton/Doc/List.pod b/lib/Carton/Doc/List.pod new file mode 100644 index 0000000..40c54e0 --- /dev/null +++ b/lib/Carton/Doc/List.pod @@ -0,0 +1,23 @@ +=head1 NAME + +Carton::Doc::List - List dependencies tracked in the cpanfile.snapshot file + +=head1 SYNOPSIS + + carton list + +=head1 DESCRIPTION + +List the dependencies and version information tracked in the +I file. This command by default displays the name of the +distribution (e.g. I) in a flat list. + +=head1 OPTIONS + +=over 4 + +=item --distfile + +Displays the list of distributions in a distfile format (i.e. C) + +=back diff --git a/lib/Carton/Doc/Show.pod b/lib/Carton/Doc/Show.pod new file mode 100644 index 0000000..20618b4 --- /dev/null +++ b/lib/Carton/Doc/Show.pod @@ -0,0 +1,12 @@ +=head1 NAME + +Carton::Doc::Show - Show the module information + +=head1 SYNOPSIS + + carton show Module + +=head1 DESCRIPTION + +Displays the information about modules, distribution and its versions. + diff --git a/lib/Carton/Doc/Tree.pod b/lib/Carton/Doc/Tree.pod new file mode 100644 index 0000000..0080eef --- /dev/null +++ b/lib/Carton/Doc/Tree.pod @@ -0,0 +1,13 @@ +=head1 NAME + +Carton::Doc::Tree - Show the tree of dependency graph + +=head1 SYNOPSIS + + carton tree + +=head1 DESCRIPTION + +Displays the tree representation of dependency graph for your application. + + diff --git a/lib/Carton/Doc/Update.pod b/lib/Carton/Doc/Update.pod new file mode 100644 index 0000000..146f03e --- /dev/null +++ b/lib/Carton/Doc/Update.pod @@ -0,0 +1,40 @@ +=head1 NAME + +Carton::Doc::Update - Update the dependencies + +=head1 SYNOPSIS + + carton update [module] + +=head1 DESCRIPTION + +Update the dependencies version for your application. + +Carton is designed to update your dependency in a conservative way, +meaning that it doesn't update modules that aren't explicitly required +to. + +C is a command to explicitly update one or all of +modules in your cpanfile to the latest available that satisfies the +requirements in cpanfile. + +=head1 EXAMPLE + +Suppose you have a cpanfile with: + + requires 'DBI', '1.600'; + requires 'Plack', '== 1.0011'; + +and then run C to get DBI 1.610 (the latest at that +time) and Plack 1.0011 (as specified in the requirement). + +A few weeks later, DBI and Plack have been updated a couple of +times. Running C I update the versions, because +the installed versions satisfy the requirements in C. + +Running C will update DBI to the latest version, say +1.611, because the version still satisfies the requirement. However, +it won't update Plack's version, since whatever latest version on CPAN +will not satisfy the Plack's requirement C<== 1.0011> because it wants +an exact version. + diff --git a/lib/Carton/Doc/Upgrading.pod b/lib/Carton/Doc/Upgrading.pod new file mode 100644 index 0000000..d7ad57e --- /dev/null +++ b/lib/Carton/Doc/Upgrading.pod @@ -0,0 +1,48 @@ +=head1 NAME + +Carton::Doc::Upgrading - Upgrading document + +=head1 UPGRADING + +Carton adds, changes and deprecates some features between major +releases in backward incompatible ways. Here's the list of major +changes between versions. See C file for more details. + +=head2 v0.9 to v1.0 + +=over 4 + +=item * + +C is deprecated. You must pass the optional include +path to perl interpreter in the normal way, like: + + carton exec perl -Ilib myscript + +Or make your script to take its own C<-I> option, like many command line +launcher does (i.e. plackup, prove) + + carton exec plackup -Ilib myapp.psgi + +=item * + +C is now C. Its name and file format +has been changed. There's no automatic migration, but you can do: + + # run with Carton v0.9.64 + > carton install + + # upgrade to Carton v1.0 + > cpanm Carton + > carton install + > git add cpanfile.snapshot + > git rm carton.lock + +This process will most likely preserve modules in your local library. + +=item * + +cpanfile is now a requirement, and extracting requirements from build +files (C, C) is not supported. + +=back diff --git a/lib/Carton/Doc/Version.pod b/lib/Carton/Doc/Version.pod new file mode 100644 index 0000000..8ff36ad --- /dev/null +++ b/lib/Carton/Doc/Version.pod @@ -0,0 +1,11 @@ +=head1 NAME + +Carton::Doc::Version - Display version + +=head1 SYNOPSIS + + carton version + +=head1 DESCRIPTION + +This command displays the current version number of carton. diff --git a/lib/Carton/Environment.pm b/lib/Carton/Environment.pm new file mode 100644 index 0000000..6a58944 --- /dev/null +++ b/lib/Carton/Environment.pm @@ -0,0 +1,100 @@ +package Carton::Environment; +use strict; +use Carton::CPANfile; +use Carton::Snapshot; +use Carton::Error; +use Carton::Tree; +use Path::Tiny; + +use Class::Tiny { + cpanfile => undef, + snapshot => sub { $_[0]->_build_snapshot }, + install_path => sub { $_[0]->_build_install_path }, + vendor_cache => sub { $_[0]->_build_vendor_cache }, + tree => sub { $_[0]->_build_tree }, +}; + +sub _build_snapshot { + my $self = shift; + Carton::Snapshot->new(path => $self->cpanfile . ".snapshot"); +} + +sub _build_install_path { + my $self = shift; + if ($ENV{PERL_CARTON_PATH}) { + return Path::Tiny->new($ENV{PERL_CARTON_PATH}); + } else { + return $self->cpanfile->path->parent->child("local"); + } +} + +sub _build_vendor_cache { + my $self = shift; + Path::Tiny->new($self->install_path->dirname . "/vendor/cache"); +} + +sub _build_tree { + my $self = shift; + Carton::Tree->new(cpanfile => $self->cpanfile, snapshot => $self->snapshot); +} + +sub vendor_bin { + my $self = shift; + $self->vendor_cache->parent->child('bin'); +} + +sub build_with { + my($class, $cpanfile) = @_; + + $cpanfile = Path::Tiny->new($cpanfile)->absolute; + if ($cpanfile->is_file) { + return $class->new(cpanfile => Carton::CPANfile->new(path => $cpanfile)); + } else { + Carton::Error::CPANfileNotFound->throw(error => "Can't locate cpanfile: $cpanfile"); + } +} + +sub build { + my($class, $cpanfile_path, $install_path) = @_; + + my $self = $class->new; + + $cpanfile_path &&= Path::Tiny->new($cpanfile_path)->absolute; + + my $cpanfile = $self->locate_cpanfile($cpanfile_path || $ENV{PERL_CARTON_CPANFILE}); + if ($cpanfile && $cpanfile->is_file) { + $self->cpanfile( Carton::CPANfile->new(path => $cpanfile) ); + } else { + Carton::Error::CPANfileNotFound->throw(error => "Can't locate cpanfile: (@{[ $cpanfile_path || 'cpanfile' ]})"); + } + + $self->install_path( Path::Tiny->new($install_path)->absolute ) if $install_path; + + $self; +} + +sub locate_cpanfile { + my($self, $path) = @_; + + if ($path) { + return Path::Tiny->new($path)->absolute; + } + + my $current = Path::Tiny->cwd; + my $previous = ''; + + until ($current eq '/' or $current eq $previous) { + # TODO support PERL_CARTON_CPANFILE + my $try = $current->child('cpanfile'); + if ($try->is_file) { + return $try->absolute; + } + + ($previous, $current) = ($current, $current->parent); + } + + return; +} + +1; + diff --git a/lib/Carton/Error.pm b/lib/Carton/Error.pm new file mode 100644 index 0000000..b469eac --- /dev/null +++ b/lib/Carton/Error.pm @@ -0,0 +1,42 @@ +package Carton::Error; +use strict; +use overload '""' => sub { $_[0]->error }; +use Carp; + +sub throw { + my($class, @args) = @_; + die $class->new(@args); +} + +sub rethrow { + die $_[0]; +} + +sub new { + my($class, %args) = @_; + bless \%args, $class; +} + +sub error { + $_[0]->{error} || ref $_[0]; +} + +package Carton::Error::CommandNotFound; +use parent 'Carton::Error'; + +package Carton::Error::CommandExit; +use parent 'Carton::Error'; +sub code { $_[0]->{code} } + +package Carton::Error::CPANfileNotFound; +use parent 'Carton::Error'; + +package Carton::Error::SnapshotParseError; +use parent 'Carton::Error'; +sub path { $_[0]->{path} } + +package Carton::Error::SnapshotNotFound; +use parent 'Carton::Error'; +sub path { $_[0]->{path} } + +1; diff --git a/lib/Carton/Index.pm b/lib/Carton/Index.pm new file mode 100644 index 0000000..3ce215c --- /dev/null +++ b/lib/Carton/Index.pm @@ -0,0 +1,68 @@ +package Carton::Index; +use strict; +use Class::Tiny { + _packages => sub { +{} }, + generator => sub { require Carton; "Carton $Carton::VERSION" }, +}; + +sub add_package { + my($self, $package) = @_; + $self->_packages->{$package->name} = $package; # XXX ||= +} + +sub count { + my $self = shift; + scalar keys %{$self->_packages}; +} + +sub packages { + my $self = shift; + sort { lc $a->name cmp lc $b->name } values %{$self->_packages}; +} + +sub write { + my($self, $fh) = @_; + + print $fh <generator ]} +Line-Count: @{[ $self->count ]} +Last-Updated: @{[ scalar localtime ]} + +EOF + for my $p ($self->packages) { + print $fh $self->_format_line($p->name, $p->version || 'undef', $p->pathname); + } +} + +sub _format_line { + my($self, @row) = @_; + + # from PAUSE::mldistwatch::rewrite02 + my $one = 30; + my $two = 8; + + if (length $row[0] > $one) { + $one += 8 - length $row[1]; + $two = length $row[1]; + } + + sprintf "%-${one}s %${two}s %s\n", @row; +} + +sub pad { + my($str, $len, $left) = @_; + + my $howmany = $len - length($str); + return $str if $howmany <= 0; + + my $pad = " " x $howmany; + return $left ? "$pad$str" : "$str$pad"; +} + + +1; diff --git a/lib/Carton/Mirror.pm b/lib/Carton/Mirror.pm new file mode 100644 index 0000000..60bc937 --- /dev/null +++ b/lib/Carton/Mirror.pm @@ -0,0 +1,23 @@ +package Carton::Mirror; +use strict; +use Class::Tiny qw( url ); + +our $DefaultMirror = 'http://cpan.metacpan.org/'; + +sub BUILDARGS { + my($class, $url) = @_; + return { url => $url }; +} + +sub default { + my $class = shift; + $class->new($DefaultMirror); +} + +sub is_default { + my $self = shift; + $self->url eq $DefaultMirror; +} + +1; + diff --git a/lib/Carton/Package.pm b/lib/Carton/Package.pm new file mode 100644 index 0000000..6b1f381 --- /dev/null +++ b/lib/Carton/Package.pm @@ -0,0 +1,12 @@ +package Carton::Package; +use strict; +use Class::Tiny qw( name version pathname ); + +sub BUILDARGS { + my($class, @args) = @_; + return { name => $args[0], version => $args[1], pathname => $args[2] }; +} + +1; + + diff --git a/lib/Carton/Packer.pm b/lib/Carton/Packer.pm new file mode 100644 index 0000000..dc1a2cf --- /dev/null +++ b/lib/Carton/Packer.pm @@ -0,0 +1,97 @@ +package Carton::Packer; +use Class::Tiny; +use warnings NONFATAL => 'all'; +use App::FatPacker; +use File::pushd (); +use Path::Tiny (); +use CPAN::Meta (); +use File::Find (); + +sub fatpack_carton { + my($self, $dir) = @_; + + my $temp = Path::Tiny->tempdir; + my $pushd = File::pushd::pushd $temp; + + my $file = $temp->child('carton.pre.pl'); + + $file->spew(<<'EOF'); +#!/usr/bin/env perl +use strict; +use 5.008001; +use Carton::CLI; +$Carton::Fatpacked = 1; +exit Carton::CLI->new->run(@ARGV); +EOF + + my $fatpacked = $self->do_fatpack($file); + + my $executable = $dir->child('carton'); + warn "Bundling $executable\n"; + + $dir->mkpath; + $executable->spew($fatpacked); + chmod 0755, $executable; +} + +sub do_fatpack { + my($self, $file) = @_; + + my $packer = App::FatPacker->new; + + my @modules = split /\r?\n/, $packer->trace(args => [$file], use => $self->required_modules); + my @packlists = $packer->packlists_containing(\@modules); + $packer->packlists_to_tree(Path::Tiny->new('fatlib')->absolute, \@packlists); + + my $fatpacked = do { + local $SIG{__WARN__} = sub {}; + $packer->fatpack_file($file); + }; + + # HACK: File::Spec bundled into arch in < 5.16, but is loadable as pure-perl + use Config; + $fatpacked =~ s/\$fatpacked{"$Config{archname}\/(Cwd|File)/\$fatpacked{"$1/g; + + $fatpacked; +} + +sub required_modules { + my($self, $packer) = @_; + + my $meta = $self->installed_meta('Carton') + or die "Couldn't find install metadata for Carton"; + + my %excludes = ( + perl => 1, + 'ExtUtils::MakeMaker' => 1, + 'Module::Build' => 1, + ); + + my @requirements = grep !$excludes{$_}, + $meta->effective_prereqs->requirements_for('runtime', 'requires')->required_modules; + + return \@requirements; +} + +sub installed_meta { + my($self, $dist) = @_; + + my @meta; + my $finder = sub { + if (m!\b$dist-.*[\\/]MYMETA.json!) { + my $meta = CPAN::Meta->load_file($_); + push @meta, $meta if $meta->name eq $dist; + } + }; + + my @meta_dirs = grep -d, map "$_/.meta", @INC; + File::Find::find({ wanted => $finder, no_chdir => 1 }, @meta_dirs) + if @meta_dirs; + + # return the latest version + @meta = sort { version->new($b->version) cmp version->new($a->version) } @meta; + + return $meta[0]; +} + +1; diff --git a/lib/Carton/Snapshot.pm b/lib/Carton/Snapshot.pm new file mode 100644 index 0000000..9e57f18 --- /dev/null +++ b/lib/Carton/Snapshot.pm @@ -0,0 +1,191 @@ +package Carton::Snapshot; +use strict; +use Config; +use Carton::Dist; +use Carton::Dist::Core; +use Carton::Error; +use Carton::Package; +use Carton::Index; +use Carton::Util; +use Carton::Snapshot::Emitter; +use Carton::Snapshot::Parser; +use CPAN::Meta; +use CPAN::Meta::Requirements; +use File::Find (); +use Try::Tiny; +use Path::Tiny (); +use Module::CoreList; + +use constant CARTON_SNAPSHOT_VERSION => '1.0'; + +use subs 'path'; +use Class::Tiny { + path => undef, + version => sub { CARTON_SNAPSHOT_VERSION }, + loaded => undef, + _distributions => sub { +[] }, +}; + +sub BUILD { + my $self = shift; + $self->path( $self->{path} ); +} + +sub path { + my $self = shift; + if (@_) { + $self->{path} = Path::Tiny->new($_[0]); + } else { + $self->{path}; + } +} + +sub load_if_exists { + my $self = shift; + $self->load if $self->path->is_file; +} + +sub load { + my $self = shift; + + return 1 if $self->loaded; + + if ($self->path->is_file) { + my $parser = Carton::Snapshot::Parser->new; + $parser->parse($self->path->slurp_utf8, $self); + $self->loaded(1); + + return 1; + } else { + Carton::Error::SnapshotNotFound->throw( + error => "Can't find cpanfile.snapshot: Run `carton install` to build the snapshot file.", + path => $self->path, + ); + } +} + +sub save { + my $self = shift; + $self->path->spew_utf8( Carton::Snapshot::Emitter->new->emit($self) ); +} + +sub find { + my($self, $module) = @_; + (grep $_->provides_module($module), $self->distributions)[0]; +} + +sub find_or_core { + my($self, $module) = @_; + $self->find($module) || $self->find_in_core($module); +} + +sub find_in_core { + my($self, $module) = @_; + + if (exists $Module::CoreList::version{$]}{$module}) { + my $version = $Module::CoreList::version{$]}{$module}; # maybe undef + return Carton::Dist::Core->new(name => $module, module_version => $version); + } + + return; +} + +sub index { + my $self = shift; + + my $index = Carton::Index->new; + for my $package ($self->packages) { + $index->add_package($package); + } + + return $index; +} + +sub distributions { + @{$_[0]->_distributions}; +} + +sub add_distribution { + my($self, $dist) = @_; + push @{$self->_distributions}, $dist; +} + +sub packages { + my $self = shift; + + my @packages; + for my $dist ($self->distributions) { + while (my($package, $provides) = each %{$dist->provides}) { + # TODO what if duplicates? + push @packages, Carton::Package->new($package, $provides->{version}, $dist->pathname); + } + } + + return @packages; +} + +sub write_index { + my($self, $file) = @_; + + open my $fh, ">", $file or die $!; + $self->index->write($fh); +} + +sub find_installs { + my($self, $path, $reqs) = @_; + + my $libdir = "$path/lib/perl5/$Config{archname}/.meta"; + return {} unless -e $libdir; + + my @installs; + my $wanted = sub { + if ($_ eq 'install.json') { + push @installs, [ $File::Find::name, "$File::Find::dir/MYMETA.json" ]; + } + }; + File::Find::find($wanted, $libdir); + + my %installs; + + my $accepts = sub { + my $module = shift; + + return 0 unless $reqs->accepts_module($module->{name}, $module->{provides}{$module->{name}}{version}); + + if (my $exist = $installs{$module->{name}}) { + my $old_ver = version::->new($exist->{provides}{$module->{name}}{version}); + my $new_ver = version::->new($module->{provides}{$module->{name}}{version}); + return $new_ver >= $old_ver; + } else { + return 1; + } + }; + + for my $file (@installs) { + my $module = Carton::Util::load_json($file->[0]); + my $prereqs = -f $file->[1] ? CPAN::Meta->load_file($file->[1])->effective_prereqs : CPAN::Meta::Prereqs->new; + + my $reqs = CPAN::Meta::Requirements->new; + $reqs->add_requirements($prereqs->requirements_for($_, 'requires')) + for qw( configure build runtime ); + + if ($accepts->($module)) { + $installs{$module->{name}} = Carton::Dist->new( + name => $module->{dist}, + pathname => $module->{pathname}, + provides => $module->{provides}, + version => $module->{version}, + requirements => $reqs, + ); + } + } + + my @new_dists; + for my $module (sort keys %installs) { + push @new_dists, $installs{$module}; + } + + $self->_distributions(\@new_dists); +} + +1; diff --git a/lib/Carton/Snapshot/Emitter.pm b/lib/Carton/Snapshot/Emitter.pm new file mode 100644 index 0000000..9486ba7 --- /dev/null +++ b/lib/Carton/Snapshot/Emitter.pm @@ -0,0 +1,30 @@ +package Carton::Snapshot::Emitter; +use Class::Tiny; +use warnings NONFATAL => 'all'; + +sub emit { + my($self, $snapshot) = @_; + + my $data = ''; + $data .= "# carton snapshot format: version @{[$snapshot->version]}\n"; + $data .= "DISTRIBUTIONS\n"; + + for my $dist (sort { $a->name cmp $b->name } $snapshot->distributions) { + $data .= " @{[$dist->name]}\n"; + $data .= " pathname: @{[$dist->pathname]}\n"; + + $data .= " provides:\n"; + for my $package (sort keys %{$dist->provides}) { + $data .= " $package @{[$dist->provides->{$package}{version} || 'undef' ]}\n"; + } + + $data .= " requirements:\n"; + for my $module (sort $dist->required_modules) { + $data .= " $module @{[ $dist->requirements_for_module($module) || '0' ]}\n"; + } + } + + $data; +} + +1; diff --git a/lib/Carton/Snapshot/Parser.pm b/lib/Carton/Snapshot/Parser.pm new file mode 100644 index 0000000..21aa0c1 --- /dev/null +++ b/lib/Carton/Snapshot/Parser.pm @@ -0,0 +1,126 @@ +package Carton::Snapshot::Parser; +use Class::Tiny; +use warnings NONFATAL => 'all'; +use Carton::Dist; +use Carton::Error; + +my $machine = { + init => [ + { + re => qr/^\# carton snapshot format: version (1\.0)/, + code => sub { + my($stash, $snapshot, $ver) = @_; + $snapshot->version($ver); + }, + goto => 'section', + }, + # TODO support pasing error and version mismatch etc. + ], + section => [ + { + re => qr/^DISTRIBUTIONS$/, + goto => 'dists', + }, + { + re => qr/^__EOF__$/, + done => 1, + }, + ], + dists => [ + { + re => qr/^ (\S+)$/, + code => sub { $_[0]->{dist} = Carton::Dist->new(name => $1) }, + goto => 'distmeta', + }, + { + re => qr/^\S/, + goto => 'section', + redo => 1, + }, + ], + distmeta => [ + { + re => qr/^ pathname: (.*)$/, + code => sub { $_[0]->{dist}->pathname($1) }, + }, + { + re => qr/^\s{4}provides:$/, + code => sub { $_[0]->{property} = 'provides' }, + goto => 'properties', + }, + { + re => qr/^\s{4}requirements:$/, + code => sub { + $_[0]->{property} = 'requirements'; + }, + goto => 'properties', + }, + { + re => qr/^\s{0,2}\S/, + code => sub { + my($stash, $snapshot) = @_; + $snapshot->add_distribution($stash->{dist}); + %$stash = (); # clear + }, + goto => 'dists', + redo => 1, + }, + ], + properties => [ + { + re => qr/^\s{6}([0-9A-Za-z_:]+) ([v0-9\._,=\!<>\s]+|undef)/, + code => sub { + my($stash, $snapshot, $module, $version) = @_; + + if ($stash->{property} eq 'provides') { + $stash->{dist}->provides->{$module} = { version => $version }; + } else { + $stash->{dist}->add_string_requirement($module, $version); + } + }, + }, + { + re => qr/^\s{0,4}\S/, + goto => 'distmeta', + redo => 1, + }, + ], +}; + +sub parse { + my($self, $data, $snapshot) = @_; + + my @lines = split /\r?\n/, $data; + + my $state = $machine->{init}; + my $stash = {}; + + LINE: + for my $line (@lines, '__EOF__') { + last LINE unless @$state; + + STATE: { + for my $trans (@{$state}) { + if (my @match = $line =~ $trans->{re}) { + if (my $code = $trans->{code}) { + $code->($stash, $snapshot, @match); + } + if (my $goto = $trans->{goto}) { + $state = $machine->{$goto}; + if ($trans->{redo}) { + redo STATE; + } else { + next LINE; + } + } + + last STATE; + } + } + + Carton::Error::SnapshotParseError->throw(error => "Could not parse snapshot file."); + } + } +} + +1; diff --git a/lib/Carton/Tree.pm b/lib/Carton/Tree.pm new file mode 100644 index 0000000..6ce22a1 --- /dev/null +++ b/lib/Carton/Tree.pm @@ -0,0 +1,69 @@ +package Carton::Tree; +use strict; +use Carton::Dependency; + +use Class::Tiny qw( cpanfile snapshot ); + +use constant STOP => -1; + +sub walk_down { + my($self, $cb) = @_; + + my $dumper; $dumper = sub { + my($dependency, $reqs, $level, $parent) = @_; + + my $ret = $cb->($dependency, $reqs, $level); + return if $ret && $ret == STOP; + + local $parent->{$dependency->distname} = 1 if $dependency; + + for my $module (sort $reqs->required_modules) { + my $dependency = $self->dependency_for($module, $reqs); + if ($dependency->dist) { + next if $parent->{$dependency->distname}; + $dumper->($dependency, $dependency->requirements, $level + 1, $parent); + } else { + # no dist found in lock + } + } + }; + + $dumper->(undef, $self->cpanfile->requirements, 0, {}); + undef $dumper; +} + +sub dependency_for { + my($self, $module, $reqs) = @_; + + my $requirement = $reqs->requirements_for_module($module); + + my $dep = Carton::Dependency->new; + $dep->module($module); + $dep->requirement($requirement); + + if (my $dist = $self->snapshot->find_or_core($module)) { + $dep->dist($dist); + } + + return $dep; +} + +sub merged_requirements { + my $self = shift; + + my $merged_reqs = CPAN::Meta::Requirements->new; + + my %seen; + $self->walk_down(sub { + my($dependency, $reqs, $level) = @_; + return Carton::Tree::STOP if $dependency && $seen{$dependency->distname}++; + $merged_reqs->add_requirements($reqs); + }); + + $merged_reqs->clear_requirement('perl'); + $merged_reqs->finalize; + + $merged_reqs; +} + +1; diff --git a/lib/Carton/Util.pm b/lib/Carton/Util.pm new file mode 100644 index 0000000..cc9c775 --- /dev/null +++ b/lib/Carton/Util.pm @@ -0,0 +1,31 @@ +package Carton::Util; +use strict; +use warnings; + +sub load_json { + my $file = shift; + + open my $fh, "<", $file or die "$file: $!"; + from_json(join '', <$fh>); +} + +sub dump_json { + my($data, $file) = @_; + + open my $fh, ">", $file or die "$file: $!"; + binmode $fh; + print $fh to_json($data); +} + +sub from_json { + require JSON; + JSON::decode_json(@_); +} + +sub to_json { + my($data) = @_; + require JSON; + JSON->new->utf8->pretty->canonical->encode($data); +} + +1; diff --git a/script/carton b/script/carton new file mode 100755 index 0000000..86b9ad3 --- /dev/null +++ b/script/carton @@ -0,0 +1,27 @@ +#!perl +use strict; +use 5.008001; +use Carton::CLI; + +exit Carton::CLI->new->run(@ARGV); + +__END__ + +=head1 NAME + +carton - Perl module dependency manager + +=head1 SYNOPSIS + + > carton install + > carton exec ./myscript + +=head1 DESCRIPTION + +For more documentation, refer to L by running C or C. + +=head1 SEE ALSO + +L + +=cut diff --git a/t/release-pod-syntax.t b/t/release-pod-syntax.t new file mode 100644 index 0000000..cdd6a6c --- /dev/null +++ b/t/release-pod-syntax.t @@ -0,0 +1,14 @@ +#!perl + +BEGIN { + unless ($ENV{RELEASE_TESTING}) { + require Test::More; + Test::More::plan(skip_all => 'these tests are for release candidate testing'); + } +} + +# This file was automatically generated by Dist::Zilla::Plugin::PodSyntaxTests. +use Test::More; +use Test::Pod 1.41; + +all_pod_files_ok(); diff --git a/xt/CLI.pm b/xt/CLI.pm new file mode 100644 index 0000000..93d50c8 --- /dev/null +++ b/xt/CLI.pm @@ -0,0 +1,61 @@ +package xt::CLI; +use strict; +use base qw(Exporter); +our @EXPORT = qw(run cli); + +use Test::Requires qw( Capture::Tiny File::pushd ); + +sub cli { + my $cli = Carton::CLI::Tested->new; + $cli->dir( Path::Tiny->tempdir(CLEANUP => !$ENV{NO_CLEANUP}) ); + warn "Temp directory: ", $cli->dir, "\n" if $ENV{NO_CLEANUP}; + $cli; +} + +package Carton::CLI::Tested; +use Carton::CLI; +use Capture::Tiny qw(capture); +use File::pushd (); +use Path::Tiny; + +$Carton::CLI::UseSystem = 1; + +use Class::Tiny qw( dir stdout stderr exit_code ); + +sub write_file { + my($self, $file, @args) = @_; + $self->dir->child($file)->spew(@args); +} + +sub write_cpanfile { + my($self, @args) = @_; + $self->write_file(cpanfile => @args); +} + +sub run_in_dir { + my($self, $dir, @args) = @_; + local $self->{dir} = $self->dir->child($dir); + $self->run(@args); +} + +sub run { + my($self, @args) = @_; + + my $pushd = File::pushd::pushd $self->dir; + + my @capture = capture { + my $code = eval { Carton::CLI->new->run(@args) }; + $self->exit_code($@ ? 255 : $code); + }; + + $self->stdout($capture[0]); + $self->stderr($capture[1]); +} + +sub clean_local { + my $self = shift; + $self->dir->child("local")->remove_tree({ safe => 0 }); +} + +1; + diff --git a/xt/cli/bundle.t b/xt/cli/bundle.t new file mode 100644 index 0000000..aa84f24 --- /dev/null +++ b/xt/cli/bundle.t @@ -0,0 +1,19 @@ +use strict; +use Test::More; +use xt::CLI; + +{ + my $app = cli(); + + $app->write_cpanfile(<run("install"); + $app->run("bundle"); + + ok -f ($app->dir . "/vendor/cache/authors/id/D/DO/DOY/Try-Tiny-0.12.tar.gz"); +} + +done_testing; + diff --git a/xt/cli/check.t b/xt/cli/check.t new file mode 100644 index 0000000..dd00ed9 --- /dev/null +++ b/xt/cli/check.t @@ -0,0 +1,96 @@ +use strict; +use Test::More; +use xt::CLI; + +subtest 'carton check fails when there is no lock' => sub { + my $app = cli(); + $app->write_cpanfile(<run("check"); + like $app->stderr, qr/find cpanfile\.snapshot/; +}; + +subtest 'carton install and check' => sub { + my $app = cli(); + $app->write_cpanfile(<run("install"); + + $app->run("check"); + like $app->stdout, qr/are satisfied/; + + $app->run("list"); + like $app->stdout, qr/Try-Tiny-0\.11/; + + $app->write_cpanfile(<run("check"); + like $app->stdout, qr/not satisfied/; + + TODO: { + local $TODO = 'exec does not verify lock'; + $app->run("exec", "perl", "use Try::Tiny"); + like $app->stderr, qr/\.snapshot/; + } + + $app->run("install"); + + $app->run("check"); + like $app->stdout, qr/are satisfied/; + + $app->run("list"); + like $app->stdout, qr/Try-Tiny-0\.\d\d/; + + $app->write_cpanfile(<run("check"); + like $app->stdout, qr/not satisfied/; + + $app->run("install"); + like $app->stderr, qr/failed/; + + $app->run("check"); + like $app->stdout, qr/not satisfied/; +}; + +subtest 'detect unused modules' => sub { + my $app = cli; + $app->write_cpanfile("requires 'Try::Tiny';"); + + $app->run("install"); + $app->write_cpanfile(""); + + + TODO: { + local $TODO = "Can't detect superflous modules"; + $app->run("install"); + $app->run("list"); + is $app->stdout, ""; + + $app->run("check"); + like $app->stdout, qr/unused/; + } +}; + +subtest 'detect downgrade' => sub { + my $app = cli; + $app->write_cpanfile("requires 'URI';"); + $app->run("install"); + + $app->write_cpanfile("requires 'URI', '== 1.59';"); + $app->run("check"); + + like $app->stdout, qr/not satisfied/; + like $app->stdout, qr/URI has version .* Needs == 1\.59/; +}; + +done_testing; + diff --git a/xt/cli/cpanfile.t b/xt/cli/cpanfile.t new file mode 100644 index 0000000..b663b37 --- /dev/null +++ b/xt/cli/cpanfile.t @@ -0,0 +1,44 @@ +use strict; +use Test::More; +use xt::CLI; + +subtest 'carton install --cpanfile' => sub { + my $app = cli(); + $app->write_file('cpanfile.foo', <run("install", "--cpanfile", "cpanfile.foo"); + $app->run("check", "--cpanfile", "cpanfile.foo"); + + ok !$app->dir->child('cpanfile.snapshot')->exists; + ok $app->dir->child('cpanfile.foo.snapshot')->exists; + + like $app->stdout, qr/are satisfied/; + + local $ENV{PERL_CARTON_CPANFILE} = $app->dir->child('cpanfile.foo')->absolute; + + $app->run("list"); + like $app->stdout, qr/Try-Tiny-0\.11/; + + $app->run("exec", "perl", "-e", "use Try::Tiny\ 1"); + like $app->stderr, qr/Try::Tiny .* 0\.11/; +}; + +subtest 'PERL_CARTON_CPANFILE' => sub { + my $app = cli(); + + local $ENV{PERL_CARTON_CPANFILE} = $app->dir->child('cpanfile.foo')->absolute; + + $app->write_file('cpanfile.foo', <run("install"); + $app->run("list"); + + like $app->stdout, qr/Try-Tiny-0\.11/; + ok $app->dir->child('cpanfile.foo.snapshot')->exists; +}; + +done_testing; + diff --git a/xt/cli/deployment.t b/xt/cli/deployment.t new file mode 100644 index 0000000..175f87a --- /dev/null +++ b/xt/cli/deployment.t @@ -0,0 +1,26 @@ +use strict; +use Test::More; +use xt::CLI; + +{ + my $app = cli(); + $app->write_cpanfile(<run("install", "--deployment"); + like $app->stderr, qr/deployment requires cpanfile\.snapshot/; + + $app->run("install"); + $app->clean_local; + + $app->run("install", "--deployment"); + $app->run("list"); + like $app->stdout, qr/Try-Tiny-0\.11/; + + $app->run("exec", "perl", "-e", "use Try::Tiny 2;"); + like $app->stderr, qr/Try::Tiny.* version 0\.11/; +} + +done_testing; + diff --git a/xt/cli/deps_phase.t b/xt/cli/deps_phase.t new file mode 100644 index 0000000..862dc76 --- /dev/null +++ b/xt/cli/deps_phase.t @@ -0,0 +1,29 @@ +use strict; +use Test::More; +use xt::CLI; + +{ + my $app = cli(); + + $app->write_cpanfile(< sub { + requires 'Test::NoWarnings'; + recommends 'Test::Pretty'; +}; +on develop => sub { + requires 'Path::Tiny'; +}; +EOF + + $app->run("install"); + + $app->run("list"); + like $app->stdout, qr/Test-NoWarnings/; + like $app->stdout, qr/Path-Tiny/; + unlike $app->stdout, qr/Test-Pretty/; +} + +done_testing; + + + diff --git a/xt/cli/exec.t b/xt/cli/exec.t new file mode 100644 index 0000000..233f790 --- /dev/null +++ b/xt/cli/exec.t @@ -0,0 +1,84 @@ +use strict; +use Test::More; +use xt::CLI; + +subtest 'carton exec without a command', sub { + my $app = cli(); + $app->write_cpanfile(''); + $app->run("install"); + $app->run("exec"); + like $app->stderr, qr/carton exec needs a command/; + is $app->exit_code, 255; +}; + +subtest 'exec without cpanfile', sub { + my $app = cli(); + $app->run("exec", "perl", "-e", 1); + like $app->stderr, qr/Can't locate cpanfile/; + is $app->exit_code, 255; +}; + +subtest 'exec without a snapshot', sub { + my $app = cli(); + $app->write_cpanfile(); + $app->run("exec", "perl", "-e", 1); + like $app->stderr, qr/cpanfile\.snapshot/; + is $app->exit_code, 255; +}; + +subtest 'carton exec', sub { + my $app = cli(); + $app->write_cpanfile(''); + $app->run("install"); + + TODO: { + local $TODO = "exec now does not strip site_perl"; + $app->run("exec", "perl", "-e", "use Try::Tiny"); + like $app->stderr, qr/Can't locate Try\/Tiny.pm/; + } + + $app->write_cpanfile(<run("install"); + + $app->run("exec", "--", "perl", "-e", 'use Try::Tiny; print $Try::Tiny::VERSION, "\n"'); + like $app->stdout, qr/0\.11/; + + $app->run("exec", "perl", "-e", 'use Try::Tiny; print $Try::Tiny::VERSION, "\n"'); + like $app->stdout, qr/0\.11/, "No need for -- as well"; + + $app->run("exec", "perl", "-MTry::Tiny", "-e", 'print $Try::Tiny::VERSION, "\n"'); + like $app->stdout, qr/0\.11/; + + $app->write_cpanfile(<run("install"); + $app->run("exec", "--", "ack", "--version"); + + like $app->stdout, qr/ack 2\.02/; +}; + +subtest 'carton exec perl -Ilib', sub { + my $app = cli(); + $app->write_cpanfile(''); + $app->run("install"); + + $app->dir->child("lib")->mkpath; + $app->dir->child("lib/FooBarBaz.pm")->spew("package FooBarBaz; 1"); + + $app->run("exec", "perl", "-Ilib", "-e", 'use FooBarBaz; print "foo"'); + like $app->stdout, qr/foo/; + unlike $app->stderr, qr/exec -Ilib is deprecated/; + + $app->run("exec", "-Ilib", "perl", "-e", 'print "foo"'); + like $app->stdout, qr/foo/; + like $app->stderr, qr/exec -Ilib is deprecated/; +}; + +done_testing; + diff --git a/xt/cli/freeze.t b/xt/cli/freeze.t new file mode 100644 index 0000000..f02a2c3 --- /dev/null +++ b/xt/cli/freeze.t @@ -0,0 +1,24 @@ +use strict; +use Test::More; +use xt::CLI; + +{ + my $app = cli(); + + $app->write_cpanfile(<run("install"); + $app->run("list"); + like $app->stdout, qr/Try-Tiny-0\.11/; + + $app->clean_local; + + $app->run("install"); + $app->run("list"); + like $app->stdout, qr/Try-Tiny-0\.11/; +} + +done_testing; + diff --git a/xt/cli/help.t b/xt/cli/help.t new file mode 100644 index 0000000..0ad0896 --- /dev/null +++ b/xt/cli/help.t @@ -0,0 +1,25 @@ +use strict; +use Test::More; +use xt::CLI; + +{ + my $app = cli(); + $app->run("help"); + like $app->stdout, qr/Carton - Perl module/; + + $app->run("-h"); + like $app->stdout, qr/Carton - Perl module/; + + $app->run("help", "install"); + like $app->stdout, qr/Install the dependencies/; + + $app->run("install", "-h"); + like $app->stdout, qr/Install the dependencies/; + + $app->run("help", "foobarbaz"); + is $app->stdout, ''; + like $app->stderr, qr/No documentation found/; +} + +done_testing; + diff --git a/xt/cli/install.t b/xt/cli/install.t new file mode 100644 index 0000000..926f3de --- /dev/null +++ b/xt/cli/install.t @@ -0,0 +1,56 @@ +use strict; +use Test::More; +use xt::CLI; + +subtest 'carton install with version range' => sub { + my $app = cli(); + $app->write_cpanfile(<run("install"); + $app->run("tree"); + like $app->stdout, qr/Try::Tiny/; + unlike $app->stderr, qr/Could not parse snapshot file/; +}; + +subtest 'meta info for ancient modules' => sub { + my $app = cli(); + $app->write_cpanfile(<run("install"); + $app->run("list"); + + like $app->stdout, qr/Algorithm-Diff/; +}; + +subtest 'meta info for modules with version->declare' => sub { + my $app = cli(); + $app->write_cpanfile(<run("install"); + $app->run("check"); + + like $app->stdout, qr/are satisfied/; + unlike $app->stderr, qr/is not installed/; +}; + +subtest 'meta info for modules with qv()' => sub { + my $app = cli(); + $app->write_cpanfile(<run("install"); + $app->run("check"); + + like $app->stdout, qr/are satisfied/; + unlike $app->stderr, qr/is not installed/; +}; + +done_testing; + diff --git a/xt/cli/json_pp.t b/xt/cli/json_pp.t new file mode 100644 index 0000000..0c64395 --- /dev/null +++ b/xt/cli/json_pp.t @@ -0,0 +1,23 @@ +use strict; +use Test::More; +use xt::CLI; + +plan skip_all => "perl <= 5.14" if $] >= 5.015; + +{ + my $app = cli(); + + $app->write_cpanfile(<run("install"); + $app->clean_local; + + $app->run("install", "--deployment"); + unlike $app->stderr, qr/JSON::PP is not in range/; +} + +done_testing; + diff --git a/xt/cli/mirror.t b/xt/cli/mirror.t new file mode 100644 index 0000000..5ab9a29 --- /dev/null +++ b/xt/cli/mirror.t @@ -0,0 +1,38 @@ +use strict; +use Test::More; +use xt::CLI; + +my $cwd = Path::Tiny->cwd; + +{ + my $app = cli(); + + $app->write_cpanfile(<run("install"); + + $app->run("list"); + like $app->stdout, qr/^Hash-MultiValue-0.08/m; +} + +{ + # fallback to CPAN + my $app = cli(); + $app->write_cpanfile(<run("install"); + + $app->run("list"); + like $app->stdout, qr/^PSGI-/m; +} + +done_testing; + + + diff --git a/xt/cli/mismatch.t b/xt/cli/mismatch.t new file mode 100644 index 0000000..11d5af4 --- /dev/null +++ b/xt/cli/mismatch.t @@ -0,0 +1,24 @@ +use strict; +use Test::More; +use xt::CLI; + +{ + my $app = cli(); + + $app->write_cpanfile(< '== 2.139'; +requires 'Test::Differences' => '== 0.61'; +EOF + + $app->run("install"); + $app->run("list"); + + like $app->stdout, qr/Data-Dumper-2\.139/; + like $app->stdout, qr/Test-Differences-0\.61/; + + $app->run("check"); + like $app->stdout, qr/are satisfied/; +} + +done_testing; + diff --git a/xt/cli/no_cpanfile.t b/xt/cli/no_cpanfile.t new file mode 100644 index 0000000..e12d2c3 --- /dev/null +++ b/xt/cli/no_cpanfile.t @@ -0,0 +1,13 @@ +use strict; +use Test::More; +use xt::CLI; + +{ + my $app = cli(); + $app->run("install"); + like $app->stderr, qr/Can't locate cpanfile/; + is $app->exit_code, 255; +} + +done_testing; + diff --git a/xt/cli/perl.t b/xt/cli/perl.t new file mode 100644 index 0000000..a7e0143 --- /dev/null +++ b/xt/cli/perl.t @@ -0,0 +1,23 @@ +use strict; +use Test::More; +use xt::CLI; + +{ + my $app = cli(); + + $app->write_cpanfile(<run("install"); + like $app->stdout, qr/Complete/; + + $app->run("list"); + like $app->stdout, qr/Hash-MultiValue-/; +} + +done_testing; + + + diff --git a/xt/cli/snapshot.t b/xt/cli/snapshot.t new file mode 100644 index 0000000..b036af7 --- /dev/null +++ b/xt/cli/snapshot.t @@ -0,0 +1,65 @@ +use strict; +use Test::More; +use xt::CLI; + +subtest 'snapshot file has canonical representation' => sub { + my $app = cli(); + $app->write_cpanfile(<run("install"); + + my $content = $app->dir->child('cpanfile.snapshot')->slurp; + for (1..3) { + $app->dir->child('cpanfile.snapshot')->remove; + $app->run("install"); + is $content, $app->dir->child('cpanfile.snapshot')->slurp; + } +}; + +subtest 'Bad snapshot version' => sub { + my $app = cli(); + $app->write_cpanfile(''); + $app->write_file('cpanfile.snapshot', <run("install"); + like $app->stderr, qr/Could not parse/; +}; + +subtest 'Bad snapshot file' => sub { + my $app = cli(); + $app->write_cpanfile(''); + $app->write_file('cpanfile.snapshot', <run("install"); + like $app->stderr, qr/Could not parse/; +}; + +subtest 'snapshot file support separate CRLF' => sub { + my $app = cli(); + $app->write_cpanfile(<run("install"); + + my $content = $app->dir->child('cpanfile.snapshot')->slurp; + $content =~ s/\n/\r\n/g; + $app->write_file('cpanfile.snapshot', $content); + + $app->run("install"); + ok !$app->stderr; +}; + +done_testing; + diff --git a/xt/cli/subdir.t b/xt/cli/subdir.t new file mode 100644 index 0000000..84d244f --- /dev/null +++ b/xt/cli/subdir.t @@ -0,0 +1,25 @@ +use strict; +use Test::More; +use xt::CLI; + +subtest 'carton exec in subdir', sub { + my $app = cli(); + $app->write_cpanfile(<run('install'); + + $app->dir->child('x')->mkpath; + + $app->run_in_dir('x' => 'list'); + like $app->stdout, qr/Try-Tiny/; + + $app->run_in_dir('x' => 'check'); + like $app->stdout, qr/are satisfied/; + + $app->run_in_dir('x' => 'install'); + like $app->stdout, qr/Complete/; + unlike $app->stderr, qr/failed/; +}; + +done_testing; diff --git a/xt/cli/tree.t b/xt/cli/tree.t new file mode 100644 index 0000000..bc7ce8c --- /dev/null +++ b/xt/cli/tree.t @@ -0,0 +1,23 @@ +use strict; +use Test::More; +use xt::CLI; + +{ + my $app = cli(); + + $app->write_cpanfile(<run("install"); + $app->run("tree"); + + is $app->exit_code, 0; + like $app->stdout, qr/^HTML::Parser \(HTML-Parser-/m; + like $app->stdout, qr/^ HTML::Tagset \(HTML-Tagset-/m; +} + +done_testing; + + + diff --git a/xt/cli/update.t b/xt/cli/update.t new file mode 100644 index 0000000..22745bd --- /dev/null +++ b/xt/cli/update.t @@ -0,0 +1,78 @@ +use strict; +use Test::More; +use xt::CLI; + +subtest 'carton update NonExistentModule' => sub { + my $app = cli(); + + $app->write_cpanfile(<run("install"); + $app->run("update", "XYZ"); + like $app->stderr, qr/Could not find module XYZ/; +}; + +subtest 'carton update upgrades a dist' => sub { + my $app = cli(); + + $app->write_cpanfile(<run("install"); + $app->run("list"); + like $app->stdout, qr/Try-Tiny-0\.09/; + + $app->write_cpanfile(<= 0.09, <= 0.12'; +EOF + + $app->run("install"); + $app->run("check"); + like $app->stdout, qr/are satisfied/; + + $app->run("list"); + like $app->stdout, qr/Try-Tiny-0\.09/; + + $app->run("update", "Try::Tiny"); + like $app->stdout, qr/installed Try-Tiny-0\.12.*upgraded from 0\.09/; + + $app->run("check"); + like $app->stdout, qr/are satisfied/; + + $app->run("list"); + like $app->stdout, qr/Try-Tiny-0\.12/; +}; + +subtest 'downgrade a distribution' => sub { + my $app = cli(); + + $app->write_cpanfile(<run("install"); + $app->run("list"); + like $app->stdout, qr/Try-Tiny-0\.\d\d/; + + $app->write_cpanfile(<run("update"); + $app->run("list"); + like $app->stdout, qr/Try-Tiny-0\.09/; + + TODO: { + local $TODO = 'collecting wrong install info'; + $app->write_cpanfile(<run("install"); + $app->run("list"); + like $app->stdout, qr/Try-Tiny-0\.09/; + } +}; + +done_testing; + diff --git a/xt/cli/version.t b/xt/cli/version.t new file mode 100644 index 0000000..d99b9d6 --- /dev/null +++ b/xt/cli/version.t @@ -0,0 +1,12 @@ +use strict; +use Test::More; + +use xt::CLI; + +my $app = cli(); +$app->run("version"); + +like $app->stdout, qr/carton $Carton::VERSION/; + +done_testing; + diff --git a/xt/cli/without.t b/xt/cli/without.t new file mode 100644 index 0000000..d9c51f7 --- /dev/null +++ b/xt/cli/without.t @@ -0,0 +1,67 @@ +use strict; +use Test::More; +use xt::CLI; + +subtest 'carton install --without develop' => sub { + my $app = cli(); + $app->write_cpanfile(< sub { + requires 'Hash::MultiValue', '== 0.14'; +}; +EOF + + $app->run("install"); + $app->run("list"); + like $app->stdout, qr/Try-Tiny-/; + like $app->stdout, qr/Hash-MultiValue-0\.14/; + + $app->run("exec", "perl", "-e", "use Hash::MultiValue\ 1"); + like $app->stderr, qr/Hash::MultiValue .* version 0.14/; + + $app->clean_local; + + $app->run("install", "--without", "develop"); + $app->run("list"); + like $app->stdout, qr/Try-Tiny-/; + + TODO: { + local $TODO = "--without is not remembered for list"; + unlike $app->stdout, qr/Hash-MultiValue-/; + } + + $app->run("exec", "perl", "-e", "use Hash::MultiValue\ 1"); + unlike $app->stderr, qr/Hash::MultiValue .* version 0.14/; +}; + +subtest 'without features' => sub { + my $app = cli(); + $app->write_cpanfile(< sub { + requires 'Stream::Buffered', '== 0.01'; +}; +EOF + + $app->run("install"); + $app->run("list"); + like $app->stdout, qr/Stream-Buffered-0\.01/; + + $app->clean_local; + + $app->run("install", "--deployment"); + $app->run("exec", "perl", "-e", "use Stream::Buffered 1"); + like $app->stderr, qr/Stream::Buffered .* version 0\.01/; + + $app->clean_local; + + $app->run("install", "--without", "stream"); + + $app->run("exec", "perl", "-e", "use Stream::Buffered 1"); + unlike $app->stderr, qr/Stream::Buffered .* version 0\.01/; +}; + +done_testing; + diff --git a/xt/mirror/authors/id/M/MI/MIYAGAWA/Hash-MultiValue-0.08.tar.gz b/xt/mirror/authors/id/M/MI/MIYAGAWA/Hash-MultiValue-0.08.tar.gz new file mode 100644 index 0000000..bb431bc Binary files /dev/null and b/xt/mirror/authors/id/M/MI/MIYAGAWA/Hash-MultiValue-0.08.tar.gz differ diff --git a/xt/mirror/modules/02packages.details.txt b/xt/mirror/modules/02packages.details.txt new file mode 100644 index 0000000..40ed304 --- /dev/null +++ b/xt/mirror/modules/02packages.details.txt @@ -0,0 +1,25 @@ +File: 02packages.details.txt +URL: http://www.perl.com/CPAN/modules/02packages.details.txt +Description: Package names found in carton.lock +Columns: package name, version, path +Intended-For: Automated fetch routines, namespace documentation. +Written-By: Carton v0.9.0 +Line-Count: 16 +Last-Updated: Wed Jun 29 22:54:55 2011 + +CGI 3.55 M/MA/MARKSTOS/CGI.pm-3.55.tar.gz +CGI::Apache 1.01 M/MA/MARKSTOS/CGI.pm-3.55.tar.gz +CGI::Carp 3.51 M/MA/MARKSTOS/CGI.pm-3.55.tar.gz +CGI::Cookie 1.30 M/MA/MARKSTOS/CGI.pm-3.55.tar.gz +CGI::Fast 1.08 M/MA/MARKSTOS/CGI.pm-3.55.tar.gz +CGI::Pretty 3.46 M/MA/MARKSTOS/CGI.pm-3.55.tar.gz +CGI::Push 1.05 M/MA/MARKSTOS/CGI.pm-3.55.tar.gz +CGI::Switch 1.01 M/MA/MARKSTOS/CGI.pm-3.55.tar.gz +CGI::Util 3.53 M/MA/MARKSTOS/CGI.pm-3.55.tar.gz +CGITempFile undef M/MA/MARKSTOS/CGI.pm-3.55.tar.gz +FCGI 0.73 F/FL/FLORA/FCGI-0.73.tar.gz +FCGI::Stream undef F/FL/FLORA/FCGI-0.73.tar.gz +Fh undef M/MA/MARKSTOS/CGI.pm-3.55.tar.gz +Hash::MultiValue 0.08 M/MI/MIYAGAWA/Hash-MultiValue-0.08.tar.gz +MultipartBuffer undef M/MA/MARKSTOS/CGI.pm-3.55.tar.gz +Try::Tiny 0.09 D/DO/DOY/Try-Tiny-0.09.tar.gz diff --git a/xt/mirror/modules/02packages.details.txt.gz b/xt/mirror/modules/02packages.details.txt.gz new file mode 100644 index 0000000..1535767 Binary files /dev/null and b/xt/mirror/modules/02packages.details.txt.gz differ -- cgit v1.2.1