├── .editorconfig ├── minil.toml ├── script └── minil ├── t ├── 00_compile.t ├── migrate │ ├── dzil │ │ └── Acme-Dzil │ │ │ ├── dist.ini │ │ │ └── lib │ │ │ └── Acme │ │ │ └── Dzil.pm │ ├── dzil.t │ ├── no-changes.t │ ├── no-pod.t │ ├── eumm.t │ ├── tmpfiles.t │ └── changes.t ├── gitignore.t ├── 01_load_all.t ├── cli │ ├── clean.t │ ├── build.t │ ├── release.t │ ├── release_notest.t │ ├── release_no_pause_file.t │ ├── release_no_bump_versions.t │ ├── release_with_hooks.t │ ├── regenerate_BuildPL.t │ └── release_check_branch.t ├── project │ ├── detect_source_path.t │ ├── script_files.t │ ├── meta_no_index.t │ ├── license.t │ ├── unstable.t │ ├── from.t │ ├── dist_name.t │ ├── format_tag.t │ ├── in_submodule.t │ ├── unsupported.t │ ├── contributors.t │ ├── xsutil.t │ └── static_install.t ├── 05_metadata.t ├── profile │ └── module-build.t ├── new │ └── dist-name.t ├── module_maker │ ├── unsupported.t │ ├── PL_files.t │ ├── tiny │ │ ├── unsupported.t │ │ ├── run_tests.t │ │ └── requires_external_bin.t │ ├── tap_harness_args.t │ ├── c_source.t │ ├── eumm │ │ ├── unsupported.t │ │ └── run_tests.t │ ├── allow_pureperl.t │ ├── tiny.t │ ├── eumm.t │ ├── requires_external_bin.t │ └── xsutil.t ├── 03_step.t ├── profile-xs.t ├── work_dir │ ├── copy.t │ ├── _rewrite_pod.t │ ├── release_test.t │ └── dist.t ├── release │ └── bump_version.t ├── dist.t ├── data │ └── bsd.dat ├── release_test │ └── config.t ├── lib │ └── Util.pm ├── filegatherer.t ├── bumpversion.t └── filegatherer │ └── submodules-recursive.t ├── lib ├── Minilla │ ├── Profile │ │ ├── Default.pm │ │ ├── ModuleBuild.pm │ │ ├── ModuleBuildTiny.pm │ │ ├── ExtUtilsMakeMaker.pm │ │ ├── XS.pm │ │ └── Base.pm │ ├── Release │ │ ├── RegenerateFiles.pm │ │ ├── MakeDist.pm │ │ ├── CheckOrigin.pm │ │ ├── DistTest.pm │ │ ├── CheckUntrackedFiles.pm │ │ ├── RewriteChanges.pm │ │ ├── Tag.pm │ │ ├── RunHooks.pm │ │ ├── CheckReleaseBranch.pm │ │ ├── Commit.pm │ │ ├── CheckChanges.pm │ │ ├── UploadToCPAN.pm │ │ └── BumpVersion.pm │ ├── Unsupported.pm │ ├── License │ │ └── Unknown.pm │ ├── Errors.pm │ ├── CLI │ │ ├── Help.pm │ │ ├── Run.pm │ │ ├── Install.pm │ │ ├── Dist.pm │ │ ├── Build.pm │ │ ├── Clean.pm │ │ ├── Migrate.pm │ │ ├── Test.pm │ │ ├── Release.pm │ │ └── New.pm │ ├── Logger.pm │ ├── Gitignore.pm │ ├── FileGatherer.pm │ ├── CLI.pm │ ├── Git.pm │ ├── ModuleMaker │ │ ├── ModuleBuildTiny.pm │ │ ├── ExtUtilsMakeMaker.pm │ │ └── ModuleBuild.pm │ ├── ReleaseTest.pm │ ├── Util.pm │ ├── Migrate.pm │ ├── WorkDir.pm │ └── Tutorial.pod └── Module │ └── BumpVersion.pm ├── .mailmap ├── Build.PL ├── .gitignore ├── eg └── dist-bumpversion ├── .github └── workflows │ └── test.yml ├── xt └── 02_perlcritic.t ├── cpanfile ├── contrib ├── bash-completion.sh └── zsh-completion.sh └── META.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = LF 7 | -------------------------------------------------------------------------------- /minil.toml: -------------------------------------------------------------------------------- 1 | name="Minilla" 2 | module_maker = "ModuleBuildTiny" 3 | badges = ['github-actions/test.yml', 'metacpan'] 4 | -------------------------------------------------------------------------------- /script/minil: -------------------------------------------------------------------------------- 1 | #!perl 2 | use strict; 3 | use warnings; 4 | use Minilla::CLI; 5 | 6 | Minilla::CLI->new()->run(@ARGV); 7 | 8 | -------------------------------------------------------------------------------- /t/00_compile.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::More; 3 | 4 | use_ok $_ for qw( 5 | Minilla 6 | Module::BumpVersion 7 | ); 8 | 9 | done_testing; 10 | -------------------------------------------------------------------------------- /lib/Minilla/Profile/Default.pm: -------------------------------------------------------------------------------- 1 | package Minilla::Profile::Default; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use parent qw(Minilla::Profile::ModuleBuildTiny); 6 | 7 | # The default profile is Module::Build. 8 | 9 | 1; 10 | __DATA__ 11 | -------------------------------------------------------------------------------- /lib/Minilla/Release/RegenerateFiles.pm: -------------------------------------------------------------------------------- 1 | package Minilla::Release::RegenerateFiles; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use Minilla::Project; 6 | 7 | sub run { 8 | my ($self, $project, $opts) = @_; 9 | 10 | $project->regenerate_files(); 11 | } 12 | 13 | 1; 14 | 15 | -------------------------------------------------------------------------------- /lib/Minilla/Unsupported.pm: -------------------------------------------------------------------------------- 1 | package Minilla::Unsupported; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use Moo; 7 | 8 | has os => ( 9 | is => 'ro', 10 | isa => sub { ref $_[0] eq 'ARRAY' }, 11 | default => sub { [] }, 12 | ); 13 | 14 | no Moo; 15 | 16 | 1; 17 | -------------------------------------------------------------------------------- /lib/Minilla/Release/MakeDist.pm: -------------------------------------------------------------------------------- 1 | package Minilla::Release::MakeDist; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | sub run { 7 | my ($self, $project, $opts) = @_; 8 | 9 | my $work_dir = $project->work_dir(); 10 | $work_dir->dist; 11 | } 12 | 13 | 1; 14 | 15 | 16 | 1; 17 | 18 | -------------------------------------------------------------------------------- /t/migrate/dzil/Acme-Dzil/dist.ini: -------------------------------------------------------------------------------- 1 | name = Acme-Dzil 2 | author = Tokuhiro Matsuno 3 | license = Perl_5 4 | copyright_holder = Tokuhiro Matsuno 5 | copyright_year = 2013 6 | 7 | version = 0.001 8 | 9 | [@Basic] 10 | [Prereqs] 11 | Sub::Exporter = 0.979 ; to get INIT arg 12 | -------------------------------------------------------------------------------- /t/gitignore.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | 8 | use Minilla::Gitignore; 9 | 10 | my $gi = Minilla::Gitignore->new(); 11 | $gi->add('foo'); 12 | $gi->add('bar'); 13 | $gi->remove('foo'); 14 | is($gi->as_string(), "bar\n"); 15 | 16 | done_testing; 17 | 18 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | tokuhirom tokuhiråm 2 | tokuhirom Tokuhiro Matsuno 3 | tokuhirom tokuhirom 4 | tokuhirom tokuhirom 5 | Yuji Shimada xaicron 6 | -------------------------------------------------------------------------------- /Build.PL: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # THIS FILE IS AUTOMATICALLY GENERATED BY MINILLA. 3 | # DO NOT EDIT DIRECTLY. 4 | # ========================================================================= 5 | 6 | use 5.008_001; 7 | use strict; 8 | 9 | use Module::Build::Tiny 0.035; 10 | 11 | Build_PL(); 12 | 13 | -------------------------------------------------------------------------------- /lib/Minilla/Release/CheckOrigin.pm: -------------------------------------------------------------------------------- 1 | package Minilla::Release::CheckOrigin; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use Minilla::Logger; 6 | 7 | sub run { 8 | my ($self, $project, $opts) = @_; 9 | 10 | my $remotes = `git remote`; 11 | if ($remotes !~ /^origin$/m) { 12 | errorf("No git remote named origin.\n"); 13 | } 14 | } 15 | 16 | 1; 17 | 18 | -------------------------------------------------------------------------------- /t/01_load_all.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use File::Find; 6 | 7 | find { 8 | wanted => sub { 9 | return unless /\.pm$/; 10 | my $class = substr $_, length($File::Find::topdir) + 1; $class =~ s/\.pm$//; 11 | $class =~ s!/!::!g; 12 | use_ok $class; 13 | }, 14 | no_chdir => 1, 15 | }, 'lib'; 16 | 17 | done_testing; 18 | -------------------------------------------------------------------------------- /lib/Minilla/Release/DistTest.pm: -------------------------------------------------------------------------------- 1 | package Minilla::Release::DistTest; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | sub run { 7 | my ($self, $project, $opts) = @_; 8 | 9 | $opts->{test} or return; 10 | 11 | local $ENV{RELEASE_TESTING} = 1; 12 | my $work_dir = $project->work_dir(); 13 | if ($work_dir->dist_test) { 14 | # Failed. 15 | exit 1; 16 | } 17 | } 18 | 19 | 1; 20 | 21 | -------------------------------------------------------------------------------- /lib/Minilla/Release/CheckUntrackedFiles.pm: -------------------------------------------------------------------------------- 1 | package Minilla::Release::CheckUntrackedFiles; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use Minilla::Logger; 7 | 8 | sub run { 9 | my ($self, $project, $opts) = @_; 10 | 11 | if ( my $unk = `git ls-files -z --others --exclude-standard` ) { 12 | $unk =~ s/\0/\n/g; 13 | errorf("Unknown local files:\n$unk\n\nUpdate .gitignore, or git add them\n"); 14 | } 15 | } 16 | 17 | 1; 18 | 19 | -------------------------------------------------------------------------------- /lib/Minilla/License/Unknown.pm: -------------------------------------------------------------------------------- 1 | package Minilla::License::Unknown; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use Moo; 7 | 8 | has holder => ( 9 | is => 'rw', 10 | required => 1, 11 | ); 12 | 13 | no Moo; 14 | 15 | sub name { 'Unknown license' } 16 | sub url { 'http://example.com' } 17 | sub meta_name { 'unknown' } 18 | sub meta2_name { 'unknown' } 19 | 20 | sub fulltext { 21 | my ($self) = @_; 22 | return "Minilla cannot detect license terms."; 23 | } 24 | 25 | 1; 26 | 27 | -------------------------------------------------------------------------------- /t/migrate/dzil/Acme-Dzil/lib/Acme/Dzil.pm: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | package Acme::Dzil; 4 | # ABSTRACT: turns baubles into trinkets 5 | 1; 6 | __END__ 7 | 8 | =head1 NAME 9 | 10 | Acme::Dzil 11 | 12 | =head1 DESCRIPTION 13 | 14 | Acme::Foo is ... 15 | 16 | =head1 LICENSE 17 | 18 | Copyright (C) tokuhirom 19 | 20 | This library is free software; you can redistribute it and/or modify 21 | it under the same terms as Perl itself. 22 | 23 | =head1 AUTHOR 24 | 25 | tokuhirom Etokuhirom@gmail.comE 26 | 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Makefile 2 | inc/ 3 | .c 4 | ppport.h 5 | *.sw[po] 6 | *.bak 7 | *.old 8 | *~ 9 | Build 10 | _build/ 11 | xshelper.h 12 | tags 13 | pm_to_blib 14 | blib/ 15 | META.yml 16 | MYMETA.* 17 | META.yml 18 | README 19 | /.build/ 20 | carton.lock 21 | /local/ 22 | /.carton/ 23 | /Acme-Mattn/ 24 | /Minilla-v*.tar.gz 25 | /Acme-Foo/ 26 | /nytprof.out 27 | /nytprof/ 28 | /_build_params 29 | .build 30 | _build_params 31 | /Build 32 | !Build/ 33 | !META.json 34 | Minilla-*.tar.gz 35 | Minilla-* 36 | /.build 37 | /Minilla-* 38 | minil.toml 39 | -------------------------------------------------------------------------------- /lib/Minilla/Errors.pm: -------------------------------------------------------------------------------- 1 | package Minilla::Errors; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use Carp (); 7 | 8 | package Minilla::Error::CommandExit; 9 | 10 | use overload '""' => 'message', fallback => 1; 11 | 12 | sub throw { 13 | my ($class, $body) = @_; 14 | my $self = bless { body => $body, message => Carp::longmess($class) }, $class; 15 | die $self; 16 | } 17 | 18 | sub body { shift->{body} } 19 | 20 | sub message { 21 | my($self) = @_; 22 | return $self->{message}; 23 | } 24 | 25 | 1; 26 | 27 | -------------------------------------------------------------------------------- /t/cli/clean.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | use Minilla::CLI::Clean; 8 | 9 | my $guard = pushd(tempdir(CLEANUP => 1)); 10 | 11 | { 12 | write_minil_toml({ 13 | name => 'Acme-Foo', 14 | }); 15 | git_init_add_commit(); 16 | 17 | mkdir 'Acme-Foo-0.01'; 18 | mkdir 'Acme-Foo-1.00'; 19 | 20 | Minilla::CLI::Clean->run('-y'); 21 | 22 | ok(!-d 'Acme-Foo-0.01/' && !-d 'Acme-Foo-1.00', 'Cleaned built directories'); 23 | } 24 | 25 | done_testing; 26 | -------------------------------------------------------------------------------- /lib/Minilla/CLI/Help.pm: -------------------------------------------------------------------------------- 1 | package Minilla::CLI::Help; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | sub run { 7 | my ($self, @args) = @_; 8 | 9 | my $module = $args[0] ? ( "Minilla::CLI::" . ucfirst $args[0] ) : "Minilla"; 10 | system "perldoc", $module; 11 | } 12 | 13 | 1; 14 | __END__ 15 | 16 | =head1 NAME 17 | 18 | Minilla::CLI::Help - Help me! 19 | 20 | =head1 SYNOPSIS 21 | 22 | # show help for minil itself 23 | minil help 24 | 25 | # show help page for `install` sub-command 26 | minil help install 27 | 28 | -------------------------------------------------------------------------------- /lib/Minilla/Release/RewriteChanges.pm: -------------------------------------------------------------------------------- 1 | package Minilla::Release::RewriteChanges; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use Minilla::Util qw(slurp_raw spew_raw); 6 | 7 | sub run { 8 | my ($self, $project, $opts) = @_; 9 | return if $opts->{dry_run}; 10 | 11 | my $content = slurp_raw('Changes'); 12 | $content =~ s!\{\{\$NEXT\}\}! 13 | "{{\$NEXT}}\n\n" . $project->version . " " . $project->work_dir->changes_time->strftime('%Y-%m-%dT%H:%M:%SZ') 14 | !e; 15 | spew_raw('Changes' => $content); 16 | } 17 | 18 | 19 | 1; 20 | 21 | -------------------------------------------------------------------------------- /lib/Minilla/Release/Tag.pm: -------------------------------------------------------------------------------- 1 | package Minilla::Release::Tag; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use Minilla::Util qw(cmd); 7 | use Minilla::Logger; 8 | 9 | sub run { 10 | my ($self, $project, $opts) = @_; 11 | 12 | my $ver = $project->version; 13 | if ( $opts->{dry_run} ) { 14 | infof("DRY-RUN. Would have tagged version $ver.\n"); 15 | return; 16 | } 17 | 18 | my $tag = $project->format_tag($ver); 19 | cmd('git', 'tag', $tag); 20 | cmd('git', "push", 'origin', tag => $tag); 21 | } 22 | 23 | 1; 24 | 25 | -------------------------------------------------------------------------------- /t/project/detect_source_path.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | 8 | use Minilla; 9 | use Minilla::Project; 10 | 11 | my $guard = pushd(tempdir(CLEANUP => 1)); 12 | 13 | spew('minil.toml', <<'...'); 14 | name = "foo-bar" 15 | ... 16 | 17 | mkpath('lib/App'); 18 | spew('lib/App/foobar.pm', <<'...'); 19 | package App::foobar; 20 | 1; 21 | ... 22 | 23 | git_init(); 24 | git_add('.'); 25 | git_commit('-m', 'foo'); 26 | 27 | my $project = Minilla::Project->new(); 28 | is($project->main_module_path, 'lib/App/foobar.pm'); 29 | 30 | done_testing; 31 | 32 | -------------------------------------------------------------------------------- /t/project/script_files.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | 8 | use Minilla; 9 | use Minilla::Project; 10 | 11 | my $guard = pushd(tempdir(CLEANUP => 1)); 12 | 13 | spew('minil.toml', <<'...'); 14 | name = "foo-bar" 15 | script_files = ['bin/foo', 'script/*'] 16 | ... 17 | 18 | mkpath('lib/App'); 19 | spew('lib/App/foobar.pm', <<'...'); 20 | package App::foobar; 21 | 1; 22 | ... 23 | 24 | git_init_add_commit(); 25 | my $project = Minilla::Project->new(); 26 | 27 | is $project->script_files, "glob('bin/foo'), glob('script/*')"; 28 | 29 | done_testing; 30 | -------------------------------------------------------------------------------- /lib/Minilla/CLI/Run.pm: -------------------------------------------------------------------------------- 1 | package Minilla::CLI::Run; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use Minilla::WorkDir; 7 | use Minilla::Project; 8 | 9 | sub run { 10 | my ($self, @args) = @_; 11 | 12 | my $project = Minilla::Project->new(); 13 | my $work_dir = $project->work_dir; 14 | my $code = $work_dir->run(@args); 15 | exit $code; 16 | } 17 | 18 | 1; 19 | __END__ 20 | 21 | =head1 NAME 22 | 23 | Minilla::CLI::Run - Run Arbitrary Commands 24 | 25 | =head1 SYNOPSIS 26 | 27 | % minil run ... 28 | 29 | =head1 DESCRIPTION 30 | 31 | This sub-command allows you to run arbitrary commands on your build directory 32 | 33 | =cut 34 | -------------------------------------------------------------------------------- /lib/Minilla/Release/RunHooks.pm: -------------------------------------------------------------------------------- 1 | package Minilla::Release::RunHooks; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | sub run { 7 | my ($self, $project, $opts) = @_; 8 | 9 | my $return_value = 0; 10 | my $commands = $project->config->{release}->{hooks}; 11 | 12 | if ($commands) { 13 | if (ref $commands ne 'ARRAY') { 14 | warn "Release hooks must be array"; 15 | exit 1; 16 | } 17 | $return_value = system(join ' && ', @$commands); 18 | } 19 | 20 | if ($return_value != 0) { 21 | # Failure executing command of hooks 22 | exit 1; 23 | } 24 | } 25 | 26 | 1; 27 | 28 | -------------------------------------------------------------------------------- /t/project/meta_no_index.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | 8 | use Minilla; 9 | use Minilla::Project; 10 | 11 | my $guard = pushd(tempdir(CLEANUP => 1)); 12 | 13 | spew('minil.toml', <<'...'); 14 | name = "foo-bar" 15 | [no_index] 16 | directory = ["samplesample"] 17 | ... 18 | 19 | mkpath('lib/App'); 20 | spew('lib/App/foobar.pm', <<'...'); 21 | package App::foobar; 22 | 1; 23 | ... 24 | 25 | git_init_add_commit(); 26 | my $project = Minilla::Project->new(); 27 | 28 | is_deeply( 29 | $project->no_index, 30 | { 31 | directory => ["samplesample"], 32 | } 33 | ); 34 | 35 | done_testing; 36 | -------------------------------------------------------------------------------- /t/05_metadata.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | # use Test::Requires 'Software::License'; 8 | use Minilla::Metadata; 9 | 10 | isa_ok(Minilla::Metadata->new(source => 'lib/Minilla.pm')->license, 'Minilla::License::Perl_5'); 11 | if (eval "require Software::License; 1;") { 12 | isa_ok(Minilla::Metadata->new(source => 't/data/bsd.dat')->license, 'Software::License::BSD'); 13 | eval { 14 | Minilla::Metadata->new( 15 | source => 't/data/bsd.dat', 16 | _license_name => 'FreeBSD', 17 | )->license; 18 | }; 19 | ok !$@; 20 | } else { 21 | diag "Software::License is not installed"; 22 | } 23 | 24 | done_testing; 25 | 26 | -------------------------------------------------------------------------------- /lib/Minilla/Release/CheckReleaseBranch.pm: -------------------------------------------------------------------------------- 1 | package Minilla::Release::CheckReleaseBranch; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use Minilla::Logger; 6 | 7 | sub run { 8 | my ($self, $project, $opts) = @_; 9 | 10 | # The checking only performs when the config explicitly sets the release branch. 11 | # That's why we don't use "$project->release_branch". 12 | my $release_branch = $project->config->{release}->{branch}; 13 | return unless $release_branch; 14 | 15 | my $current_branch = `git rev-parse --abbrev-ref HEAD`; 16 | chomp $current_branch; 17 | unless ($current_branch eq $release_branch) { 18 | errorf("Release branch must be $release_branch.\n"); 19 | } 20 | } 21 | 22 | 1; 23 | 24 | -------------------------------------------------------------------------------- /t/cli/build.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | use Minilla::Profile::ModuleBuild; 8 | use Minilla::CLI::Build; 9 | 10 | my $guard = pushd(tempdir(CLEANUP => 1)); 11 | 12 | { 13 | Minilla::Profile::ModuleBuild->new( 14 | author => 'hoge', 15 | dist => 'Acme-Foo', 16 | module => 'Acme::Foo', 17 | path => 'Acme/Foo.pm', 18 | version => '0.01', 19 | )->generate(); 20 | 21 | spew('MANIFEST', <<'...'); 22 | Build.PL 23 | lib/Acme/Foo.pm 24 | ... 25 | write_minil_toml({ 26 | name => 'Acme-Foo', 27 | }); 28 | git_init_add_commit(); 29 | 30 | Minilla::CLI::Build->run(); 31 | 32 | ok(-d 'Acme-Foo-0.01/', 'Created build directory'); 33 | } 34 | 35 | done_testing; 36 | 37 | -------------------------------------------------------------------------------- /eg/dist-bumpversion: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use strict; 3 | use warnings; 4 | use Pod::Usage; 5 | use Getopt::Long; 6 | use Module::BumpVersion; 7 | use Version::Next; 8 | 9 | my $show; 10 | my $p = Getopt::Long::Parser->new( 11 | config => [qw(posix_default no_ignorecase auto_help)], 12 | ); 13 | $p->getoptions( 14 | 'show' => \$show, 15 | ); 16 | my $target = shift or pod2usage; 17 | 18 | my $bump = Module::BumpVersion->load($target); 19 | my $version = $bump->find_version 20 | or die $bump->errstr; 21 | print "Current version is: $version\n"; 22 | if ($show) { 23 | exit 0; 24 | } 25 | 26 | { 27 | my $next = Version::Next::next_version($version); 28 | $bump->set_version($next); 29 | print "Bumped to $next\n"; 30 | } 31 | 32 | __END__ 33 | 34 | =head1 SYNOPSIS 35 | 36 | % dist-bumpversion --show /path/to/Library.pm 37 | 38 | -------------------------------------------------------------------------------- /t/profile/module-build.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | 8 | use Minilla::Profile::ModuleBuild; 9 | use Minilla::Project; 10 | 11 | my $guard = pushd(tempdir(CLEANUP => 1)); 12 | 13 | Minilla::Profile::ModuleBuild->new( 14 | author => 'hoge', 15 | dist => 'Acme-Foo', 16 | module => 'Acme::Foo', 17 | path => 'Acme/Foo.pm', 18 | version => '0.01', 19 | )->generate(); 20 | 21 | spew('MANIFEST', <<'...'); 22 | Build.PL 23 | lib/Acme/Foo.pm 24 | ... 25 | write_minil_toml('Acme::Foo'); 26 | git_init_add_commit(); 27 | Minilla::Project->new()->regenerate_files(); 28 | git_init_add_commit(); 29 | 30 | cmd($^X, 'Build.PL'); 31 | 32 | like(slurp('MYMETA.json'), qr(CPAN::Meta), 'CPAN::Meta is required'); 33 | like(slurp('MYMETA.yml'), qr(CPAN::Meta), 'CPAN::Meta is required'); 34 | 35 | like(slurp('.gitignore'), qr{!LICENSE}); 36 | 37 | done_testing; 38 | 39 | -------------------------------------------------------------------------------- /t/cli/release.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use Test::Requires 'Version::Next', 'CPAN::Uploader'; 6 | use lib "t/lib"; 7 | use Util; 8 | use Minilla::Profile::ModuleBuild; 9 | use Minilla::CLI::Release; 10 | 11 | my $repo = tempdir(CLEANUP => 1); 12 | { 13 | my $guard = pushd($repo); 14 | cmd('git', 'init', '--bare'); 15 | } 16 | 17 | my $guard = pushd(tempdir(CLEANUP => 1)); 18 | 19 | Minilla::Profile::ModuleBuild->new( 20 | author => 'hoge', 21 | dist => 'Acme-Foo', 22 | module => 'Acme::Foo', 23 | path => 'Acme/Foo.pm', 24 | version => '0.01', 25 | )->generate(); 26 | write_minil_toml('Acme-Foo'); 27 | git_init_add_commit(); 28 | git_remote('add', 'origin', "file://$repo"); 29 | 30 | { 31 | local $ENV{PERL_MM_USE_DEFAULT} = 1; 32 | local $ENV{PERL_MINILLA_SKIP_CHECK_CHANGE_LOG} = 1; 33 | local $ENV{FAKE_RELEASE} = 1; 34 | Minilla::CLI::Release->run(); 35 | pass 'released.'; 36 | } 37 | 38 | done_testing; 39 | 40 | -------------------------------------------------------------------------------- /t/new/dist-name.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | use File::Temp qw(tempdir); 8 | use File::pushd; 9 | 10 | use Minilla::CLI::New; 11 | use Minilla::CLI; 12 | 13 | subtest 'Acme::Speciality' => sub { 14 | my $guard = pushd(tempdir(CLEANUP => 1)); 15 | Minilla::CLI::New->run('Acme::Speciality', '--username' => 'foo', '--email' => 'bar'); 16 | ok -e 'Acme-Speciality/lib/Acme/Speciality.pm'; 17 | 18 | # generate 'minil.toml' as default 19 | ok -e 'Acme-Speciality/minil.toml'; 20 | open my $fh, "Acme-Speciality/minil.toml"; 21 | chomp (my $got = <$fh>); 22 | is $got, q{name = "Acme-Speciality"}; 23 | }; 24 | 25 | # `minil new` should allow Dist-Name style. 26 | subtest 'Acme-Speciality' => sub { 27 | my $guard = pushd(tempdir(CLEANUP => 1)); 28 | Minilla::CLI::New->run('Acme::Speciality', '--username' => 'foo', '--email' => 'bar'); 29 | ok -e 'Acme-Speciality/lib/Acme/Speciality.pm'; 30 | }; 31 | 32 | done_testing; 33 | 34 | -------------------------------------------------------------------------------- /lib/Minilla/CLI/Install.pm: -------------------------------------------------------------------------------- 1 | package Minilla::CLI::Install; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use Minilla::WorkDir; 7 | use Minilla::Util qw(cmd parse_options); 8 | 9 | sub run { 10 | my ($self, @args) = @_; 11 | 12 | my $test = 1; 13 | parse_options( 14 | \@args, 15 | 'test!' => \$test, 16 | ); 17 | 18 | my $project = Minilla::Project->new(); 19 | my $work_dir = $project->work_dir(); 20 | 21 | if ($test) { 22 | local $ENV{RELEASE_TESTING} = 1; 23 | $work_dir->dist_test(); 24 | } 25 | my $tar = $work_dir->dist(); 26 | 27 | cmd( 28 | 'cpanm', 29 | '--notest', 30 | $tar 31 | ); 32 | unlink($tar) unless Minilla->debug; 33 | } 34 | 35 | 1; 36 | __END__ 37 | 38 | =head1 NAME 39 | 40 | Minilla::CLI::Install - Install the dist to system 41 | 42 | =head1 SYNOPSIS 43 | 44 | % minil install 45 | 46 | --no-test Do not run test 47 | 48 | =head1 DESCRIPTION 49 | 50 | This sub-command install the dist for your system. 51 | 52 | -------------------------------------------------------------------------------- /t/project/license.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use Test::Requires 'Software::License'; 6 | use lib "t/lib"; 7 | use Util; 8 | 9 | use CPAN::Meta; 10 | 11 | use Minilla::Profile::Default; 12 | use Minilla::Project; 13 | use Minilla::Git; 14 | 15 | subtest 'develop deps' => sub { 16 | my $guard = pushd(tempdir(CLEANUP => 1)); 17 | 18 | my $profile = Minilla::Profile::Default->new( 19 | author => 'Tokuhiro Matsuno', 20 | dist => 'Acme-Foo', 21 | path => 'Acme/Foo.pm', 22 | suffix => 'Foo', 23 | module => 'Acme::Foo', 24 | version => '0.01', 25 | email => 'tokuhirom@example.com', 26 | ); 27 | $profile->generate(); 28 | write_minil_toml({ 29 | name => 'Acme-Foo', 30 | license => 'artistic_2', 31 | }); 32 | git_init_add_commit(); 33 | 34 | my $project = Minilla::Project->new(); 35 | isa_ok( 36 | $project->license, 37 | 'Software::License::Artistic_2_0', 38 | ); 39 | }; 40 | 41 | done_testing; 42 | 43 | -------------------------------------------------------------------------------- /t/module_maker/unsupported.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | 8 | use Minilla::Profile::ModuleBuild; 9 | use Minilla::Project; 10 | 11 | test(sub { 12 | like(slurp('Build.PL'), qr{^die "OS unsupported\\n" if \$\^O eq "MSWin32";$}m); 13 | }); 14 | 15 | done_testing; 16 | 17 | sub test { 18 | my $code = shift; 19 | 20 | my $guard = pushd(tempdir(CLEANUP => 1)); 21 | 22 | Minilla::Profile::ModuleBuild->new( 23 | author => 'hoge', 24 | dist => 'Acme-Foo', 25 | module => 'Acme::Foo', 26 | path => 'Acme/Foo.pm', 27 | version => '0.01', 28 | )->generate(); 29 | 30 | spew('MANIFEST', <<'...'); 31 | Build.PL 32 | lib/Acme/Foo.pm 33 | ... 34 | write_minil_toml({ 35 | name => 'Acme-Foo', 36 | module_maker => "ModuleBuild", 37 | unsupported => { os => [qw/MSWin32/] }, 38 | }); 39 | git_init_add_commit(); 40 | Minilla::Project->new()->regenerate_files(); 41 | git_init_add_commit(); 42 | $code->(); 43 | } 44 | -------------------------------------------------------------------------------- /t/module_maker/PL_files.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | 8 | use Minilla::Profile::ModuleBuild; 9 | use Minilla::Project; 10 | 11 | test(sub { 12 | like(slurp('Build.PL'), qr!PL_files\s+=>\s*\{\s*"foo"\s+=>\s+"bar"\s*\}!); 13 | }); 14 | 15 | done_testing; 16 | 17 | sub test { 18 | my $code = shift; 19 | 20 | my $guard = pushd(tempdir(CLEANUP => 1)); 21 | 22 | Minilla::Profile::ModuleBuild->new( 23 | author => 'hoge', 24 | dist => 'Acme-Foo', 25 | module => 'Acme::Foo', 26 | path => 'Acme/Foo.pm', 27 | version => '0.01', 28 | )->generate(); 29 | 30 | spew('MANIFEST', <<'...'); 31 | Build.PL 32 | lib/Acme/Foo.pm 33 | ... 34 | write_minil_toml({ 35 | name => 'Acme-Foo', 36 | module_maker => "ModuleBuild", 37 | PL_files => { 38 | foo => 'bar', 39 | }, 40 | }); 41 | git_init_add_commit(); 42 | Minilla::Project->new()->regenerate_files(); 43 | git_init_add_commit(); 44 | $code->(); 45 | } 46 | -------------------------------------------------------------------------------- /t/module_maker/tiny/unsupported.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | 8 | use Minilla::Profile::ModuleBuild; 9 | use Minilla::Project; 10 | 11 | test(sub { 12 | like(slurp('Build.PL'), qr{^die "OS unsupported\\n" if \$\^O eq "MSWin32";$}m); 13 | }); 14 | 15 | done_testing; 16 | 17 | sub test { 18 | my $code = shift; 19 | 20 | my $guard = pushd(tempdir(CLEANUP => 1)); 21 | 22 | Minilla::Profile::ModuleBuild->new( 23 | author => 'hoge', 24 | dist => 'Acme-Foo', 25 | module => 'Acme::Foo', 26 | path => 'Acme/Foo.pm', 27 | version => '0.01', 28 | )->generate(); 29 | 30 | spew('MANIFEST', <<'...'); 31 | Build.PL 32 | lib/Acme/Foo.pm 33 | ... 34 | write_minil_toml({ 35 | name => 'Acme-Foo', 36 | module_maker => "ModuleBuildTiny", 37 | unsupported => { os => [qw/MSWin32/] }, 38 | }); 39 | git_init_add_commit(); 40 | Minilla::Project->new()->regenerate_files(); 41 | git_init_add_commit(); 42 | $code->(); 43 | } 44 | -------------------------------------------------------------------------------- /t/module_maker/tap_harness_args.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | 8 | use Minilla::Profile::ModuleBuild; 9 | use Minilla::Project; 10 | 11 | test(sub { 12 | like(slurp('Build.PL'), qr{tap_harness_args\s*=>\s*{\s*"jobs"\s*=>\s*9\s*}}); 13 | }); 14 | 15 | done_testing; 16 | 17 | sub test { 18 | my $code = shift; 19 | 20 | my $guard = pushd(tempdir(CLEANUP => 1)); 21 | 22 | Minilla::Profile::ModuleBuild->new( 23 | author => 'hoge', 24 | dist => 'Acme-Foo', 25 | module => 'Acme::Foo', 26 | path => 'Acme/Foo.pm', 27 | version => '0.01', 28 | )->generate(); 29 | 30 | spew('MANIFEST', <<'...'); 31 | Build.PL 32 | lib/Acme/Foo.pm 33 | ... 34 | 35 | write_minil_toml({ 36 | name => 'Acme-Foo', 37 | module_maker => "ModuleBuild", 38 | tap_harness_args => { 39 | jobs => 9, 40 | }, 41 | }); 42 | git_init_add_commit(); 43 | Minilla::Project->new()->regenerate_files(); 44 | git_init_add_commit(); 45 | $code->(); 46 | } 47 | -------------------------------------------------------------------------------- /t/cli/release_notest.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use Test::Requires 'Version::Next', 'CPAN::Uploader'; 6 | use lib "t/lib"; 7 | use Util; 8 | use Minilla::Profile::ModuleBuild; 9 | use Minilla::CLI::Release; 10 | use Test::Output; 11 | 12 | my $repo = tempdir(CLEANUP => 1); 13 | { 14 | my $guard = pushd($repo); 15 | cmd('git', 'init', '--bare'); 16 | } 17 | 18 | my $guard = pushd(tempdir(CLEANUP => 1)); 19 | 20 | Minilla::Profile::ModuleBuild->new( 21 | author => 'hoge', 22 | dist => 'Acme-Foo', 23 | module => 'Acme::Foo', 24 | path => 'Acme/Foo.pm', 25 | version => '0.01', 26 | )->generate(); 27 | write_minil_toml('Acme-Foo'); 28 | git_init_add_commit(); 29 | git_remote('add', 'origin', "file://$repo"); 30 | 31 | { 32 | local $ENV{PERL_MM_USE_DEFAULT} = 1; 33 | local $ENV{PERL_MINILLA_SKIP_CHECK_CHANGE_LOG} = 1; 34 | local $ENV{FAKE_RELEASE} = 1; 35 | stdout_unlike { 36 | Minilla::CLI::Release->run(qw/--notest/); 37 | } qr/All tests successful/, 'notest option'; 38 | pass 'released.'; 39 | } 40 | 41 | done_testing; 42 | 43 | -------------------------------------------------------------------------------- /lib/Minilla/Profile/ModuleBuild.pm: -------------------------------------------------------------------------------- 1 | package Minilla::Profile::ModuleBuild; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use parent qw(Minilla::Profile::Base); 7 | 8 | use File::Spec::Functions qw(catfile); 9 | use File::Path qw(mkpath); 10 | use File::Basename qw(dirname); 11 | use CPAN::Meta; 12 | use Data::Section::Simple qw(get_data_section); 13 | use File::pushd; 14 | 15 | use Minilla::License::Perl_5; 16 | 17 | sub generate { 18 | my $self = shift; 19 | 20 | $self->render('Module.pm', catfile('lib', $self->path)); 21 | 22 | $self->render('Changes'); 23 | $self->render('t/00_compile.t'); 24 | $self->render('github_actions_test.yml', catfile('.github', 'workflows', 'test.yml')); 25 | 26 | $self->render('.gitignore'); 27 | $self->write_file('LICENSE', Minilla::License::Perl_5->new( 28 | holder => sprintf('%s <%s>', $self->author, $self->email) 29 | )->fulltext); 30 | 31 | $self->render('cpanfile'); 32 | } 33 | 34 | 1; 35 | __DATA__ 36 | 37 | @@ cpanfile 38 | requires 'perl', '5.008001'; 39 | 40 | on 'test' => sub { 41 | requires 'Test::More', '0.98'; 42 | }; 43 | 44 | -------------------------------------------------------------------------------- /lib/Minilla/Profile/ModuleBuildTiny.pm: -------------------------------------------------------------------------------- 1 | package Minilla::Profile::ModuleBuildTiny; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use parent qw(Minilla::Profile::Base); 7 | 8 | use File::Spec::Functions qw(catfile); 9 | use File::Path qw(mkpath); 10 | use File::Basename qw(dirname); 11 | use CPAN::Meta; 12 | use Data::Section::Simple qw(get_data_section); 13 | use File::pushd; 14 | 15 | use Minilla::License::Perl_5; 16 | 17 | sub generate { 18 | my $self = shift; 19 | 20 | $self->render('Module.pm', catfile('lib', $self->path)); 21 | 22 | $self->render('Changes'); 23 | $self->render('t/00_compile.t'); 24 | $self->render('github_actions_test.yml', catfile('.github', 'workflows', 'test.yml')); 25 | 26 | $self->render('.gitignore'); 27 | $self->write_file('LICENSE', Minilla::License::Perl_5->new( 28 | holder => sprintf('%s <%s>', $self->author, $self->email) 29 | )->fulltext); 30 | 31 | $self->render('cpanfile'); 32 | } 33 | 34 | 1; 35 | __DATA__ 36 | 37 | @@ cpanfile 38 | requires 'perl', '5.008001'; 39 | 40 | on 'test' => sub { 41 | requires 'Test::More', '0.98'; 42 | }; 43 | 44 | -------------------------------------------------------------------------------- /t/module_maker/c_source.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | 8 | use Minilla::Profile::ModuleBuild; 9 | use Minilla::Project; 10 | 11 | test(["src"], sub { 12 | like(slurp('Build.PL'), qr{c_source\s+=>\s+\[qw\(src\)\]}); 13 | }); 14 | test([], sub { 15 | unlike(slurp('Build.PL'), qr{c_source}); 16 | }); 17 | 18 | done_testing; 19 | 20 | sub test { 21 | my ($c_source, $code) = @_; 22 | 23 | my $guard = pushd(tempdir(CLEANUP => 1)); 24 | 25 | Minilla::Profile::ModuleBuild->new( 26 | author => 'hoge', 27 | dist => 'Acme-Foo', 28 | module => 'Acme::Foo', 29 | path => 'Acme/Foo.pm', 30 | version => '0.01', 31 | )->generate(); 32 | 33 | spew('MANIFEST', <<'...'); 34 | Build.PL 35 | lib/Acme/Foo.pm 36 | ... 37 | write_minil_toml({ 38 | name => 'Acme-Foo', 39 | module_maker => "ModuleBuild", 40 | c_source => $c_source, 41 | }); 42 | git_init_add_commit(); 43 | Minilla::Project->new()->regenerate_files(); 44 | git_init_add_commit(); 45 | $code->(); 46 | } 47 | -------------------------------------------------------------------------------- /lib/Minilla/Profile/ExtUtilsMakeMaker.pm: -------------------------------------------------------------------------------- 1 | package Minilla::Profile::ExtUtilsMakeMaker; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use parent qw(Minilla::Profile::Base); 7 | 8 | use File::Spec::Functions qw(catfile); 9 | use File::Path qw(mkpath); 10 | use File::Basename qw(dirname); 11 | use CPAN::Meta; 12 | use Data::Section::Simple qw(get_data_section); 13 | use File::pushd; 14 | 15 | use Minilla::License::Perl_5; 16 | 17 | sub generate { 18 | my $self = shift; 19 | 20 | $self->render('Module.pm', catfile('lib', $self->path)); 21 | 22 | $self->render('Changes'); 23 | $self->render('t/00_compile.t'); 24 | $self->render('github_actions_test.yml', catfile('.github', 'workflows', 'test.yml')); 25 | 26 | $self->render('.gitignore'); 27 | $self->write_file('LICENSE', Minilla::License::Perl_5->new( 28 | holder => sprintf('%s <%s>', $self->author, $self->email) 29 | )->fulltext); 30 | 31 | $self->render('cpanfile'); 32 | } 33 | 34 | 1; 35 | __DATA__ 36 | 37 | @@ cpanfile 38 | requires 'perl', '5.008001'; 39 | 40 | on 'test' => sub { 41 | requires 'Test::More', '0.98'; 42 | }; 43 | 44 | -------------------------------------------------------------------------------- /lib/Minilla/CLI/Dist.pm: -------------------------------------------------------------------------------- 1 | package Minilla::CLI::Dist; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use File::Spec::Functions qw(catfile); 7 | use File::Basename qw(basename); 8 | use File::Copy qw(copy); 9 | 10 | use Minilla::Project; 11 | use Minilla::Util qw(check_git parse_options); 12 | 13 | sub run { 14 | my ($self, @args) = @_; 15 | 16 | my $test = 1; 17 | 18 | check_git; 19 | 20 | parse_options( 21 | \@args, 22 | 'test!' => \$test, 23 | ); 24 | 25 | my $project = Minilla::Project->new(); 26 | unless ($project->validate()) { 27 | return; 28 | } 29 | my $work_dir = $project->work_dir; 30 | if ($test) { 31 | local $ENV{RELEASE_TESTING} = 1; 32 | $work_dir->dist_test(); 33 | } 34 | my $tar = $work_dir->dist(); 35 | my $dst = catfile($project->dir, basename($tar)); 36 | copy($tar, $dst); 37 | } 38 | 39 | 1; 40 | __END__ 41 | 42 | =head1 NAME 43 | 44 | Minilla::CLI::Dist - Make tar ball distribution 45 | 46 | =head1 SYNOPSIS 47 | 48 | % minil dist 49 | 50 | =head1 DESCRIPTION 51 | 52 | This sub-command makes distribution tar ball. 53 | 54 | -------------------------------------------------------------------------------- /t/project/unstable.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | 8 | use CPAN::Meta; 9 | 10 | use Minilla::Profile::Default; 11 | use Minilla::Project; 12 | use Minilla::Git; 13 | use CPAN::Meta; 14 | use File::Spec; 15 | 16 | subtest 'unstable' => sub { 17 | my $guard = pushd(tempdir(CLEANUP => 1)); 18 | 19 | my $profile = Minilla::Profile::Default->new( 20 | author => 'Tokuhiro Matsuno', 21 | dist => 'Acme-Foo', 22 | path => 'Acme/Foo.pm', 23 | suffix => 'Foo', 24 | module => 'Acme::Foo', 25 | version => '0.01_01', 26 | email => 'tokuhirom@example.com', 27 | ); 28 | $profile->generate(); 29 | write_minil_toml('Acme-Foo'); 30 | 31 | git_init_add_commit(); 32 | 33 | my $project = Minilla::Project->new(); 34 | my $work_dir = $project->work_dir(); 35 | $work_dir->build; 36 | 37 | my $metapath = File::Spec->catfile($work_dir->dir, 'META.json'); 38 | ok -f $metapath; 39 | my $meta = CPAN::Meta->load_file($metapath); 40 | is($meta->{release_status}, 'unstable'); 41 | }; 42 | 43 | done_testing; 44 | 45 | -------------------------------------------------------------------------------- /t/module_maker/eumm/unsupported.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | 8 | use Minilla::Profile::ModuleBuild; 9 | use Minilla::Project; 10 | 11 | test(sub { 12 | my $mf = slurp('Makefile.PL'); 13 | like($mf, qr{^use ExtUtils::MakeMaker 7\.26;$}m); 14 | like($mf, qr{^os_unsupported if \$\^O eq "MSWin32";$}m); 15 | }); 16 | 17 | done_testing; 18 | 19 | sub test { 20 | my $code = shift; 21 | 22 | my $guard = pushd(tempdir(CLEANUP => 1)); 23 | 24 | Minilla::Profile::ModuleBuild->new( 25 | author => 'hoge', 26 | dist => 'Acme-Foo', 27 | module => 'Acme::Foo', 28 | path => 'Acme/Foo.pm', 29 | version => '0.01', 30 | )->generate(); 31 | 32 | spew('MANIFEST', <<'...'); 33 | Build.PL 34 | lib/Acme/Foo.pm 35 | ... 36 | write_minil_toml({ 37 | name => 'Acme-Foo', 38 | module_maker => "ExtUtilsMakeMaker", 39 | unsupported => { os => [qw/MSWin32/] }, 40 | }); 41 | git_init_add_commit(); 42 | Minilla::Project->new()->regenerate_files(); 43 | git_init_add_commit(); 44 | $code->(); 45 | } 46 | -------------------------------------------------------------------------------- /lib/Minilla/CLI/Build.pm: -------------------------------------------------------------------------------- 1 | package Minilla::CLI::Build; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use File::Path qw(rmtree mkpath); 7 | use File::Spec; 8 | 9 | use Minilla::Project; 10 | use Minilla::WorkDir; 11 | use Minilla::Logger; 12 | use Minilla::Util qw(parse_options); 13 | 14 | sub run { 15 | my ($class, @args) = @_; 16 | 17 | my $test = 1; 18 | parse_options( 19 | \@args, 20 | 'test!' => \$test, 21 | ); 22 | 23 | my $project = Minilla::Project->new(); 24 | $project->regenerate_files(); 25 | unless ($project->validate()) { 26 | return; 27 | } 28 | 29 | my $dst = File::Spec->rel2abs(sprintf("%s-%s", $project->dist_name, $project->version)); 30 | 31 | # generate project directory 32 | infof("Create %s\n", $dst); 33 | rmtree($dst); 34 | mkpath($dst); 35 | my $work_dir = Minilla::WorkDir->new(project => $project, dir => $dst, cleanup => 0); 36 | $work_dir->build(); 37 | } 38 | 39 | 1; 40 | __END__ 41 | 42 | =head1 NAME 43 | 44 | Minilla::CLI::Build - Build dist directory 45 | 46 | =head1 SYNOPSIS 47 | 48 | % minil build 49 | 50 | =head1 DESCRIPTION 51 | 52 | TBD 53 | -------------------------------------------------------------------------------- /t/03_step.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use Test::Requires 'Version::Next'; 6 | use lib "t/lib"; 7 | use Util; 8 | use File::Spec; 9 | use File::Path; 10 | use File::pushd; 11 | 12 | my $lib = File::Spec->rel2abs('lib'); 13 | my $bin = File::Spec->rel2abs('script/minil'); 14 | 15 | rmtree('Acme-Foo'); 16 | 17 | is(minil('new', '--username=anonymous', '--email=foo@example.com','Acme::Foo'), 0); 18 | ok(-f 'Acme-Foo/Build.PL'); 19 | ok(-f 'Acme-Foo/.github/workflows/test.yml'); 20 | ok(-f 'Acme-Foo/t/00_compile.t'); 21 | { 22 | local $ENV{PERL_MM_USE_DEFAULT} = 1; 23 | local $ENV{PERL_MINILLA_SKIP_CHECK_CHANGE_LOG} = 1; 24 | local $ENV{FAKE_RELEASE} = 1; 25 | my $guard = pushd('Acme-Foo'); 26 | is(minil('migrate'), 0); 27 | is(minil('build'), 0); 28 | is(minil('test'), 0); 29 | is(minil('dist'), 0); 30 | if (eval "require CPAN::Uploader; 1") { 31 | is(minil('release', '--dry-run'), 0); 32 | } else { 33 | diag "CPAN::Upoader is not installed?, skip releng tests"; 34 | } 35 | } 36 | 37 | rmtree('Acme-Foo'); 38 | 39 | done_testing; 40 | 41 | sub minil { 42 | system($^X, "-I$lib", $bin, @_); 43 | } 44 | -------------------------------------------------------------------------------- /t/profile-xs.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use Test::Requires 'Devel::PPPort'; 6 | use lib "t/lib"; 7 | use Util; 8 | 9 | use File::Spec; 10 | use File::Path; 11 | use File::pushd; 12 | use Minilla::Util qw(slurp); 13 | use Minilla::Git; 14 | use Minilla::Profile::XS; 15 | use Minilla::Project; 16 | 17 | my $guard = pushd(tempdir(CLEANUP => 1)); 18 | 19 | Minilla::Profile::XS->new( 20 | dist => 'Acme-Foo', 21 | module => 'Acme::Foo', 22 | path => 'Acme/Foo.pm', 23 | )->generate(); 24 | write_minil_toml('Acme::Foo'); 25 | git_init_add_commit(); 26 | Minilla::Project->new()->regenerate_files(); 27 | git_init_add_commit(); 28 | 29 | ok(-f 'Build.PL'); 30 | cmp_ok((-s 'Build.PL'), '>', 0); 31 | ok(-f 'lib/Acme/Foo.pm'); 32 | like(slurp('lib/Acme/Foo.pm'), qr{XSLoader}); 33 | ok(-f '.github/workflows/test.yml'); 34 | ok(-f 't/00_compile.t'); 35 | note(join(" ", git_ls_files())); 36 | note slurp('.gitignore'); 37 | ok(0+(grep /ppport\.h/, git_ls_files())); 38 | 39 | { 40 | my $project = Minilla::Project->new(); 41 | my $work_dir = $project->work_dir; 42 | $work_dir->build; 43 | $work_dir->dist_test(); 44 | } 45 | 46 | done_testing; 47 | 48 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push, pull_request] 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | strategy: 8 | matrix: 9 | perl: 10 | [ 11 | "5.42", 12 | "5.40", 13 | "5.38", 14 | "5.36", 15 | "5.34", 16 | "5.32", 17 | "5.30", 18 | "5.28", 19 | "5.26", 20 | "5.24", 21 | "5.22", 22 | "5.20", 23 | "5.18", 24 | "5.16", 25 | "5.14", 26 | "5.12", 27 | "5.10" 28 | ] 29 | name: Perl ${{ matrix.perl }} 30 | steps: 31 | - uses: actions/checkout@v4 32 | - name: Setup perl 33 | uses: shogo82148/actions-setup-perl@v1 34 | with: 35 | perl-version: ${{ matrix.perl }} 36 | - name: Git config 37 | run: | 38 | git config --global user.email "you@example.com" 39 | git config --global user.name "Your Name" 40 | - name: Install dependencies 41 | run: cpanm -nq --installdeps --with-develop --with-recommends . 42 | - name: Run test 43 | run: prove -lr t 44 | -------------------------------------------------------------------------------- /t/work_dir/copy.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | 6 | use lib "t/lib"; 7 | use Util; 8 | use File::Spec::Functions qw(catfile); 9 | use Archive::Tar; 10 | 11 | use Minilla::Profile::Default; 12 | use Minilla::Project; 13 | use Minilla::Git; 14 | 15 | subtest 'copy' => sub { 16 | my $guard = pushd(tempdir(CLEANUP => 1)); 17 | 18 | my $profile = Minilla::Profile::Default->new( 19 | author => 'tokuhirom', 20 | dist => 'Acme-Foo', 21 | path => 'Acme/Foo.pm', 22 | suffix => 'Foo', 23 | module => 'Acme::Foo', 24 | version => '0.01', 25 | email => 'tokuhirom@example.com', 26 | ); 27 | $profile->generate(); 28 | mkdir 'bin'; 29 | spew('bin/foo', ''); 30 | chmod(0777, 'bin/foo') or die "chmod: $!"; 31 | write_minil_toml('Acme-Foo'); 32 | 33 | git_init_add_commit(); 34 | 35 | my $work_dir = Minilla::Project->new()->work_dir; 36 | ok($work_dir); 37 | ok -f catfile($work_dir->dir, 'bin/foo'); 38 | SKIP: { 39 | skip "-x test is not portable", 1 if $^O eq 'MSWin32'; 40 | ok -x catfile($work_dir->dir, 'bin/foo'); 41 | } 42 | }; 43 | 44 | done_testing; 45 | 46 | -------------------------------------------------------------------------------- /lib/Minilla/Release/Commit.pm: -------------------------------------------------------------------------------- 1 | package Minilla::Release::Commit; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use Minilla::Util qw(find_file cmd); 7 | use Minilla::Logger; 8 | 9 | sub run { 10 | my ($self, $project, $opts) = @_; 11 | 12 | my @modified_files = split /\0/, `git ls-files --deleted --modified -z`; 13 | return if @modified_files == 0; 14 | 15 | $project->clear_metadata(); 16 | my $ver = $project->metadata->version; 17 | 18 | my $msg = "Checking in changes prior to tagging of version $ver.\n\nChangelog diff is:\n\n"; 19 | $msg .= `git diff Changes`; 20 | 21 | if ($opts->{dry_run}) { 22 | infof("DRY-RUN. Would have committed message of:\n----------------\n$msg\n-----------\n"); 23 | return; 24 | } 25 | 26 | cmd('git', 'commit', '-a', '-m', $msg, '--cleanup=verbatim'); 27 | 28 | $self->_push_to_origin(); 29 | } 30 | 31 | sub _push_to_origin { 32 | my ($self) = @_; 33 | 34 | # git v1.7.10 is required? 35 | my $branch = `git symbolic-ref --short HEAD` 36 | or return; 37 | $branch =~ s/\n//g; 38 | infof("Pushing to origin\n"); 39 | cmd('git', 'push', 'origin', $branch); 40 | } 41 | 42 | 1; 43 | 44 | -------------------------------------------------------------------------------- /t/cli/release_no_pause_file.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use Test::Requires 'Version::Next', 'CPAN::Uploader'; 6 | use lib "t/lib"; 7 | use Util; 8 | use Minilla::Profile::ModuleBuild; 9 | use Minilla::CLI::Release; 10 | use Carp; 11 | 12 | my $repo = tempdir(CLEANUP => 1); 13 | { 14 | my $guard = pushd($repo); 15 | cmd('git', 'init', '--bare'); 16 | } 17 | 18 | my $guard = pushd(tempdir(CLEANUP => 1)); 19 | 20 | Minilla::Profile::ModuleBuild->new( 21 | author => 'hoge', 22 | dist => 'Acme-Foo', 23 | module => 'Acme::Foo', 24 | path => 'Acme/Foo.pm', 25 | version => '0.01', 26 | )->generate(); 27 | write_minil_toml('Acme-Foo'); 28 | git_init_add_commit(); 29 | git_remote('add', 'origin', "file://$repo"); 30 | 31 | { 32 | local $ENV{PERL_MM_USE_DEFAULT} = 1; 33 | local $ENV{PERL_MINILLA_SKIP_CHECK_CHANGE_LOG} = 1; 34 | local $ENV{FAKE_RELEASE} = 0; 35 | no warnings 'redefine'; 36 | local *CPAN::Uploader::read_config_file = sub { 37 | return {}; 38 | }; 39 | eval { 40 | Minilla::CLI::Release->run(); 41 | }; 42 | my $e = $@; 43 | like $e, qr!Missing ~/.pause!; 44 | } 45 | 46 | done_testing; 47 | 48 | -------------------------------------------------------------------------------- /lib/Minilla/CLI/Clean.pm: -------------------------------------------------------------------------------- 1 | package Minilla::CLI::Clean; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use ExtUtils::MakeMaker qw(prompt); 6 | use File::Path qw(rmtree); 7 | 8 | use Minilla::Project; 9 | use Minilla::Util qw(parse_options); 10 | 11 | sub run { 12 | my ($self, @args) = @_; 13 | 14 | my $yes_opt = 0; 15 | parse_options( 16 | \@args, 17 | 'y!' => \$yes_opt, 18 | ); 19 | 20 | my $project = Minilla::Project->new(); 21 | my @targets = grep { -e $_ } ( 22 | glob(sprintf("%s-*", $project->dist_name)), 23 | 'blib', 24 | 'Build', 25 | 'MYMETA.json', 26 | 'MYMETA.yml', 27 | '_build_params', 28 | '_build', # M::B 29 | 'Makefile', 30 | 'pm_to_blib', 31 | ); 32 | print("Would remove $_\n") for (@targets); 33 | if ($yes_opt || prompt('Remove it?', 'y') =~ /y/i) { 34 | rmtree($_) for @targets; 35 | } 36 | } 37 | 38 | 1; 39 | __END__ 40 | 41 | =head1 NAME 42 | 43 | Minilla::CLI::Clean - Clean up directory 44 | 45 | =head1 SYNOPSIS 46 | 47 | % minil clean 48 | 49 | -y delete files without asking 50 | 51 | =head1 DESCRIPTION 52 | 53 | Remove some temporary files. 54 | 55 | -------------------------------------------------------------------------------- /t/cli/release_no_bump_versions.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use Test::Requires 'Version::Next', 'CPAN::Uploader'; 6 | use lib "t/lib"; 7 | use Util; 8 | use Minilla::Profile::ModuleBuild; 9 | use Minilla::CLI::Release; 10 | 11 | my $repo = tempdir(CLEANUP => 1); 12 | { 13 | my $guard = pushd($repo); 14 | cmd('git', 'init', '--bare'); 15 | } 16 | 17 | my $guard = pushd(tempdir(CLEANUP => 1)); 18 | 19 | Minilla::Profile::ModuleBuild->new( 20 | author => 'hoge', 21 | dist => 'Acme-Foo', 22 | module => 'Acme::Foo', 23 | path => 'Acme/Foo.pm', 24 | version => '1.00', 25 | )->generate(); 26 | 27 | spew("t/TestModule.pm", <<'___'); 28 | package TestModule; 29 | our $VERSION = '0.01'; 30 | 1; 31 | ___ 32 | write_minil_toml('Acme-Foo'); 33 | git_init_add_commit(); 34 | git_remote('add', 'origin', "file://$repo"); 35 | 36 | { 37 | local $ENV{PERL_MM_USE_DEFAULT} = 1; 38 | local $ENV{PERL_MINILLA_SKIP_CHECK_CHANGE_LOG} = 1; 39 | local $ENV{FAKE_RELEASE} = 1; 40 | Minilla::CLI::Release->run(); 41 | 42 | my $content = slurp "t/TestModule.pm"; 43 | my $expect = q|our $VERSION = '0.01';|; 44 | like $content, qr/\Q$expect/; 45 | } 46 | 47 | done_testing; 48 | 49 | -------------------------------------------------------------------------------- /t/migrate/dzil.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | use Test::Requires { 8 | 'Dist::Zilla' => 4.300039 9 | }; 10 | use File::Which; 11 | use Cwd 'getcwd'; 12 | 13 | plan skip_all => "No dzil command" unless which 'dzil'; 14 | plan skip_all => "No git configuration" unless `git config user.email` =~ /\@/; 15 | 16 | use Minilla::CLI; 17 | use File::Temp qw(tempdir); 18 | use File::Copy::Recursive qw(rcopy); 19 | use Minilla::Util qw(slurp); 20 | use Module::CPANfile; 21 | use Minilla::Git; 22 | 23 | @INC = map { File::Spec->rel2abs($_) } @INC; 24 | 25 | my $tmp = tempdir(CLEANUP => 1); 26 | rcopy('t/migrate/dzil/' => $tmp); 27 | my $dst = File::Spec->catdir($tmp, 'Acme-Dzil'); 28 | my $cwd = getcwd; 29 | chdir $dst; 30 | git_init(); 31 | git_add('.'); 32 | git_commit('-m', 'initial import'); 33 | 34 | Minilla::CLI->new()->run('migrate'); 35 | 36 | { 37 | my $cpanfile = Module::CPANfile->load('cpanfile'); 38 | ok(not exists $cpanfile->prereq_specs->{configure}->{requires}->{'ExtUtils::MakeMaker'}); 39 | } 40 | 41 | ok(-f 'Build.PL'); 42 | for (qw(Build.PL cpanfile minil.toml)) { 43 | note "--------- $_\n"; 44 | note slurp($_); 45 | } 46 | 47 | chdir $cwd; # this is need for File::Temp 48 | done_testing; 49 | 50 | -------------------------------------------------------------------------------- /t/cli/release_with_hooks.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use Test::Requires 'Version::Next', 'CPAN::Uploader'; 6 | use lib "t/lib"; 7 | use Util; 8 | use Minilla::Profile::ModuleBuild; 9 | use Minilla::CLI::Release; 10 | 11 | if (system('which touch') != 0) { 12 | plan skip_all => "touch: command not found"; 13 | } 14 | 15 | my $repo = tempdir(CLEANUP => 1); 16 | { 17 | my $guard = pushd($repo); 18 | cmd('git', 'init', '--bare'); 19 | } 20 | 21 | my $guard = pushd(tempdir(CLEANUP => 1)); 22 | 23 | Minilla::Profile::ModuleBuild->new( 24 | author => 'hoge', 25 | dist => 'Acme-Foo', 26 | module => 'Acme::Foo', 27 | path => 'Acme/Foo.pm', 28 | version => '0.01', 29 | )->generate(); 30 | write_minil_toml({ 31 | name => 'Acme-Foo', 32 | release => { 33 | hooks => [ 34 | 'touch foo', 35 | 'touch bar', 36 | ], 37 | }, 38 | }); 39 | git_init_add_commit(); 40 | git_remote('add', 'origin', "file://$repo"); 41 | 42 | { 43 | local $ENV{PERL_MM_USE_DEFAULT} = 1; 44 | local $ENV{PERL_MINILLA_SKIP_CHECK_CHANGE_LOG} = 1; 45 | local $ENV{FAKE_RELEASE} = 1; 46 | Minilla::CLI::Release->run(); 47 | pass 'released.'; 48 | } 49 | 50 | ok(-f 'foo'); 51 | ok(-f 'bar'); 52 | 53 | done_testing; 54 | 55 | -------------------------------------------------------------------------------- /lib/Minilla/CLI/Migrate.pm: -------------------------------------------------------------------------------- 1 | package Minilla::CLI::Migrate; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use Minilla::Util qw(check_git slurp spew); 7 | use Minilla::Migrate; 8 | 9 | sub run { 10 | my ($self, @args) = @_; 11 | 12 | check_git; 13 | 14 | Minilla::Migrate->new()->run; 15 | } 16 | 17 | 1; 18 | __END__ 19 | 20 | =head1 NAME 21 | 22 | Minilla::CLI::Migrate - Migrate existed distribution repo 23 | 24 | =head1 SYNOPSIS 25 | 26 | % minil migrate 27 | 28 | =head1 DESCRIPTION 29 | 30 | This sub-command migrate existed distribution repository to minil ready repository. 31 | 32 | =head1 HOW IT WORKS 33 | 34 | This module runs script like following shell script. 35 | 36 | # Generate META.json from Module::Build or EU::MM 37 | perl Build.PL 38 | 39 | # Create cpanfile from META.json 40 | mymeta-cpanfile > cpanfile 41 | 42 | # MANIFEST, MANIFEST.SKIP is no longer needed. 43 | git rm MANIFEST MANIFEST.SKIP 44 | 45 | # generate META.json 46 | minil build 47 | git add -f META.json 48 | 49 | # remove META.json from ignored file list 50 | perl -i -pe 's!^META.json\n$!!' .gitignore 51 | echo '.build/' >> .gitignore 52 | 53 | # remove .shipit if it's exists. 54 | if [ -f '.shipit' ]; then git rm .shipit; fi 55 | 56 | # add things 57 | git add . 58 | 59 | -------------------------------------------------------------------------------- /t/release/bump_version.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | 6 | use Minilla::Release::BumpVersion; 7 | 8 | subtest 'version_format' => sub { 9 | my @tests = ( 10 | ['0.11' => 'decimal', 'decimal v0'], 11 | ['0.11_33' => 'decimal', 'decimal alpha'], 12 | ['1.234567' => 'decimal', 'decimal'], 13 | ['v0.2.3' => 'dotted', 'dotted v0'], 14 | ['v1.2.3' => 'dotted', 'dotted'], 15 | ['v1.2.3_1' => 'dotted', 'dotted alpha'], 16 | ['v1.2' => 'dotted', 'dotted without patch'], 17 | ['v1.2_1' => 'dotted', 'dotted without patch with alpha'], 18 | ['0.2.3' => 'lax dotted', 'lzx dotted v0'], 19 | ['1.2.3' => 'lax dotted', 'lax dotted'], 20 | ['1.2.3_2' => 'lax dotted', 'lax dotted with alpha'], 21 | 22 | ['unknown' => 'unknown', 'unknown'], 23 | ['01.3333' => 'unknown', 'invalid prefixed zero'], 24 | ['v01.33.22' => 'unknown', 'invalid prefixed zero with dotted'], 25 | ['v1.2.3_dev' => 'unknown', 'invalid alpha'], 26 | ); 27 | 28 | for my $t (@tests) { 29 | my ($ver, $expect, $desc) = @$t; 30 | is(Minilla::Release::BumpVersion::version_format($ver), $expect, $desc); 31 | } 32 | }; 33 | 34 | done_testing; 35 | -------------------------------------------------------------------------------- /t/module_maker/allow_pureperl.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | 8 | use Minilla::Profile::ModuleBuild; 9 | use Minilla::Project; 10 | 11 | test(1, sub { 12 | my $build_pl = slurp('Build.PL'); 13 | like($build_pl, qr{allow_pureperl\s+=>\s+1}); 14 | like($build_pl, qr{'Module::Build'\s+=>\s+'0\.4005'}); 15 | }); 16 | test(0, sub { 17 | my $build_pl = slurp('Build.PL'); 18 | like($build_pl, qr{allow_pureperl\s+=>\s+0}); 19 | like($build_pl, qr{'Module::Build'\s+=>\s+'0\.4005'}); 20 | }); 21 | 22 | done_testing; 23 | 24 | sub test { 25 | my $allow = shift; 26 | my $code = shift; 27 | 28 | my $guard = pushd(tempdir(CLEANUP => 1)); 29 | 30 | Minilla::Profile::ModuleBuild->new( 31 | author => 'hoge', 32 | dist => 'Acme-Foo', 33 | module => 'Acme::Foo', 34 | path => 'Acme/Foo.pm', 35 | version => '0.01', 36 | )->generate(); 37 | 38 | spew('MANIFEST', <<'...'); 39 | Build.PL 40 | lib/Acme/Foo.pm 41 | ... 42 | write_minil_toml({ 43 | name => 'Acme-Foo', 44 | module_maker => "ModuleBuild", 45 | allow_pureperl => $allow, 46 | }); 47 | git_init_add_commit(); 48 | Minilla::Project->new()->regenerate_files(); 49 | git_init_add_commit(); 50 | $code->(); 51 | } 52 | -------------------------------------------------------------------------------- /lib/Minilla/Logger.pm: -------------------------------------------------------------------------------- 1 | package Minilla::Logger; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use parent qw(Exporter); 6 | 7 | use Term::ANSIColor qw(colored); 8 | require Win32::Console::ANSI if $^O eq 'MSWin32'; 9 | 10 | use Minilla::Errors; 11 | 12 | our @EXPORT = qw(debugf infof warnf errorf); 13 | 14 | our $COLOR; 15 | 16 | use constant { DEBUG => 1, INFO => 2, WARN => 3, ERROR => 4 }; 17 | 18 | our $Colors = { 19 | DEBUG, => 'green', 20 | WARN, => 'yellow', 21 | INFO, => 'cyan', 22 | ERROR, => 'red', 23 | }; 24 | 25 | sub _printf { 26 | my $type = pop; 27 | my($temp, @args) = @_; 28 | _print(sprintf($temp, map { defined($_) ? $_ : '-' } @args), $type); 29 | } 30 | 31 | sub _print { 32 | my($msg, $type) = @_; 33 | return if $type == DEBUG && !Minilla->debug; 34 | $msg = colored $msg, $Colors->{$type} if defined $type && $COLOR; 35 | my $fh = $type && $type >= WARN ? *STDERR : *STDOUT; 36 | print {$fh} $msg; 37 | } 38 | 39 | sub infof { 40 | _printf(@_, INFO); 41 | } 42 | 43 | sub warnf { 44 | _printf(@_, WARN); 45 | } 46 | 47 | sub debugf { 48 | _printf(@_, DEBUG); 49 | } 50 | 51 | sub errorf { 52 | my(@msg) = @_; 53 | _printf(@msg, ERROR); 54 | 55 | my $fmt = shift @msg; 56 | Minilla::Error::CommandExit->throw(sprintf($fmt, @msg)); 57 | } 58 | 59 | 1; 60 | 61 | -------------------------------------------------------------------------------- /t/project/from.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | 8 | use CPAN::Meta; 9 | 10 | use Minilla::Profile::Default; 11 | use Minilla::Project; 12 | use Minilla::Git; 13 | 14 | subtest 'abstract_from' => sub { 15 | my $guard = pushd(tempdir(CLEANUP => 1)); 16 | 17 | my $profile = Minilla::Profile::Default->new( 18 | author => 'Tokuhiro Matsuno', 19 | dist => 'Acme-Foo', 20 | path => 'Acme/Foo.pm', 21 | suffix => 'Foo', 22 | module => 'Acme::Foo', 23 | version => '0.01', 24 | email => 'tokuhirom@example.com', 25 | ); 26 | $profile->generate(); 27 | mkpath('lib/Acme/'); 28 | spew('lib/Acme/Foo.pod' => <<'...'); 29 | __END__ 30 | 31 | =encoding utf-8 32 | 33 | =pod 34 | 35 | =head1 NAME 36 | 37 | Acme::Foo - Yeah!! 38 | 39 | =head1 SYNOPSIS 40 | 41 | Gah 42 | 43 | =head1 AUTHORS 44 | 45 | author foo 46 | 47 | ... 48 | write_minil_toml({ 49 | name => 'Acme-Foo', 50 | abstract_from => 'lib/Acme/Foo.pod', 51 | authors_from => 'lib/Acme/Foo.pod', 52 | }); 53 | 54 | git_init_add_commit(); 55 | 56 | my $project = Minilla::Project->new(); 57 | is($project->abstract(), 'Yeah!!'); 58 | is_deeply($project->authors(), ['author foo']); 59 | }; 60 | 61 | done_testing; 62 | 63 | -------------------------------------------------------------------------------- /t/migrate/no-changes.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | 8 | package Minilla::Profile::NoChanges; 9 | use parent qw(Minilla::Profile::Default); 10 | 11 | use Test::More; 12 | 13 | plan skip_all => "No git configuration" unless `git config user.email` =~ /\@/; 14 | 15 | use File::Temp qw(tempdir); 16 | use File::pushd; 17 | use Data::Section::Simple qw(get_data_section); 18 | use File::Basename qw(dirname); 19 | use File::Path qw(mkpath); 20 | 21 | use Minilla::Util qw(spew cmd slurp); 22 | use Minilla::Migrate; 23 | use Minilla::Git; 24 | 25 | subtest 'No Changes' => sub { 26 | my $guard = pushd(tempdir(CLEANUP => 1)); 27 | 28 | my $profile = __PACKAGE__->new( 29 | author => 'foo', 30 | dist => 'Acme-Foo', 31 | path => 'Acme/Foo.pm', 32 | suffix => 'Foo', 33 | module => 'Acme::Foo', 34 | version => '0.01', 35 | email => 'foo@example.com', 36 | ); 37 | $profile->generate(); 38 | $profile->render('minil.toml'); 39 | unlink 'Changes'; 40 | 41 | git_init(); 42 | git_add(); 43 | git_commit('-m', 'initial import'); 44 | 45 | Minilla::Migrate->new->run(); 46 | 47 | like(slurp('Changes'), qr!\{\{\$NEXT\}\}!); 48 | }; 49 | 50 | done_testing; 51 | 52 | __DATA__ 53 | 54 | @@ minil.toml 55 | name = "Acme-Foo" 56 | 57 | 58 | -------------------------------------------------------------------------------- /lib/Minilla/Gitignore.pm: -------------------------------------------------------------------------------- 1 | package Minilla::Gitignore; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use Moo; 7 | 8 | has lines => ( 9 | is => 'rw', 10 | default => sub { +[ ] }, 11 | ); 12 | 13 | no Moo; 14 | 15 | sub load { 16 | my ($class, $filename) = @_; 17 | 18 | open my $fh, '<', $filename 19 | or die "Cannot open $filename: $!"; 20 | my @lines; 21 | while (defined($_ = <$fh>)) { 22 | chomp; 23 | push @lines, $_; 24 | } 25 | 26 | return $class->new( 27 | lines => [@lines], 28 | ); 29 | } 30 | 31 | sub remove { 32 | my ($self, $pattern) = @_; 33 | if (ref $pattern) { 34 | $self->lines([grep { $_ !~ $pattern } @{$self->lines}]); 35 | } else { 36 | $self->lines([grep { $_ ne $pattern } @{$self->lines}]); 37 | } 38 | } 39 | 40 | sub add { 41 | my ($self, $pattern) = @_; 42 | 43 | unless (grep { $pattern eq $_ } @{$self->lines}) { 44 | push @{$self->lines}, $pattern; 45 | } 46 | } 47 | 48 | sub as_string { 49 | my $self = shift; 50 | return join('', map { "$_\n" } @{$self->lines}); 51 | } 52 | 53 | sub save { 54 | my ($self, $filename) = @_; 55 | open my $fh, '>', $filename 56 | or die "Cannot open $filename: $!"; 57 | for (@{$self->lines}) { 58 | print {$fh} $_, "\n"; 59 | } 60 | close $fh; 61 | } 62 | 63 | 1; 64 | 65 | -------------------------------------------------------------------------------- /lib/Minilla/Release/CheckChanges.pm: -------------------------------------------------------------------------------- 1 | package Minilla::Release::CheckChanges; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use ExtUtils::MakeMaker qw(prompt); 6 | 7 | use Minilla::Util qw(edit_file slurp); 8 | use Minilla::Logger; 9 | 10 | sub run { 11 | my ($self, $project, $opts) = @_; 12 | 13 | my $version = $project->version; 14 | 15 | if ($ENV{PERL_MINILLA_SKIP_CHECK_CHANGE_LOG}) { 16 | infof("Okay, you are debugging now.\n"); 17 | return; 18 | } 19 | 20 | while (1) { 21 | my $changes = slurp('Changes'); 22 | last if $changes =~ /^\{\{\$NEXT\}\}\h*\R+\h+\S/m; 23 | 24 | # Tell the user what the problem is 25 | if ($changes !~ /\{\{\$NEXT\}\}/m) { 26 | infof("No mention of {{\$NEXT}} in changelog file 'Changes'\n"); 27 | } elsif ($changes !~ /^\{\{\$NEXT\}\}/m) { 28 | infof("{{\$NEXT}} must be at the beginning of a line in changelog file 'Changes'\n"); 29 | } elsif ($changes !~ /^\{\{\$NEXT\}\}\h*\R/m) { 30 | infof("{{\$NEXT}} in changelog file 'Changes' must be the only non-whitespace on its line\n"); 31 | } else { 32 | infof("{{\$NEXT}} in changelog file 'Changes' must be followed by at least one indented line describing a change\n"); 33 | } 34 | 35 | if (prompt("Edit file?", 'y') =~ /y/i) { 36 | edit_file('Changes'); 37 | } else { 38 | errorf("Giving up!\n"); 39 | } 40 | } 41 | } 42 | 43 | 1; 44 | -------------------------------------------------------------------------------- /xt/02_perlcritic.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | 5 | eval { 6 | require Perl::Critic; 7 | Perl::Critic->VERSION(1.105); 8 | 9 | require Test::Perl::Critic; 10 | Test::Perl::Critic->VERSION(1.02); 11 | Test::Perl::Critic->import( 12 | -profile => \(join q{}, ) 13 | ); 14 | }; 15 | note $@ if $@; 16 | plan skip_all => "Perl::Critic 1.105+ or Test::Perl::Critic 1.02+ is not installed." if $@; 17 | 18 | all_critic_ok('lib', 'script', 'bin'); 19 | 20 | __END__ 21 | 22 | only=1 23 | 24 | # ------------------------------------------------------------------------- 25 | # Not important. 26 | 27 | [BuiltinFunctions::ProhibitSleepViaSelect] 28 | [BuiltinFunctions::RequireGlobFunction] 29 | [ClassHierarchies::ProhibitOneArgBless] 30 | 31 | # ------------------------------------------------------------------------- 32 | # Bug detection 33 | [InputOutput::ProhibitBarewordFileHandles] 34 | [Modules::RequireFilenameMatchesPackage] 35 | [Subroutines::ProhibitNestedSubs] 36 | [Subroutines::ProhibitReturnSort] 37 | [TestingAndDebugging::RequireUseStrict] 38 | [Variables::ProhibitConditionalDeclarations] 39 | [Variables::RequireLexicalLoopIterators] 40 | 41 | [TestingAndDebugging::ProhibitNoStrict] 42 | allow=refs 43 | 44 | # ------------------------------------------------------------------------- 45 | # Security issue detection 46 | [InputOutput::RequireEncodingWithUTF8Layer] 47 | [Modules::ProhibitEvilModules] 48 | [InputOutput::ProhibitTwoArgOpen] 49 | 50 | -------------------------------------------------------------------------------- /t/migrate/no-pod.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | 8 | package Minilla::Profile::NoPod; 9 | use parent qw(Minilla::Profile::Default); 10 | 11 | use Test::More; 12 | 13 | plan skip_all => "No git configuration" unless `git config user.email` =~ /\@/; 14 | 15 | use File::Temp qw(tempdir); 16 | use File::pushd; 17 | use Data::Section::Simple qw(get_data_section); 18 | use File::Basename qw(dirname); 19 | use File::Path qw(mkpath); 20 | 21 | use Minilla::Util qw(spew cmd slurp); 22 | use Minilla::Migrate; 23 | use Minilla::Git; 24 | 25 | subtest 'No Changes' => sub { 26 | my $guard = pushd(tempdir(CLEANUP => 1)); 27 | 28 | my $profile = __PACKAGE__->new( 29 | author => 'foo', 30 | dist => 'Acme-Foo', 31 | path => 'Acme/Foo.pm', 32 | suffix => 'Foo', 33 | module => 'Acme::Foo', 34 | version => '0.01', 35 | email => 'foo@example.com', 36 | ); 37 | $profile->generate(); 38 | $profile->render('minil.toml'); 39 | $profile->render('lib/Acme/Foo.pm'); 40 | 41 | git_init(); 42 | git_add(); 43 | git_commit('-m', 'initial import'); 44 | 45 | eval { 46 | Minilla::Migrate->new->run(); 47 | }; 48 | my $e = $@; 49 | isa_ok($e, 'Minilla::Error::CommandExit'); 50 | like($e->body, qr/Cannot retrieve 'abstract'/); 51 | }; 52 | 53 | done_testing; 54 | 55 | __DATA__ 56 | 57 | @@ minil.toml 58 | name = "Acme-Foo" 59 | 60 | @@ lib/Acme/Foo.pm 61 | package Acme::Foo; 62 | our $VERSION='0.01'; 63 | 1; 64 | -------------------------------------------------------------------------------- /t/dist.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | 8 | use File::Spec; 9 | use File::Spec::Functions qw(catdir); 10 | use File::Path; 11 | use File::Basename; 12 | 13 | use Minilla::Git; 14 | 15 | my $minil = File::Spec->rel2abs('script/minil'); 16 | 17 | for my $datfile (map { File::Spec->rel2abs($_) } 't/dist/Acme-FooXS.dat') { 18 | basename($datfile) =~ m{^(.*)\.dat$} or die; 19 | my $distname = $1; 20 | subtest $datfile => sub { 21 | note $distname; 22 | my $tempdir = tempdir(CLEANUP => 1); 23 | my $distdir = catdir($tempdir, $distname); 24 | mkpath($distdir); 25 | my $guard = pushd($distdir); 26 | extract_archive($datfile); 27 | git_init_add_commit(); 28 | 29 | cmd_perl($minil, 'test'); 30 | 31 | pass $distname; 32 | }; 33 | } 34 | 35 | sub extract_archive { 36 | unpack_archive(parse_archive(shift)); 37 | } 38 | 39 | sub parse_archive { 40 | my $archive = shift; 41 | my $fname; 42 | my %result; 43 | open my $fh, '<', $archive or die; 44 | while (<$fh>) { 45 | if (/^==> (.+) <==$/) { 46 | $fname = $1; 47 | } else { 48 | $result{$fname} .= $_; 49 | } 50 | } 51 | return %result; 52 | } 53 | 54 | sub unpack_archive { 55 | my %filemap = @_; 56 | for my $filename (keys %filemap) { 57 | mkpath(dirname($filename)); 58 | note "Writing $filename"; 59 | spew($filename, $filemap{$filename}); 60 | } 61 | } 62 | 63 | done_testing; 64 | 65 | -------------------------------------------------------------------------------- /t/module_maker/tiny.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use Test::Requires {'Module::Build::Tiny', 0.035}; 6 | use lib "t/lib"; 7 | use Util; 8 | use FindBin; 9 | use lib "$FindBin::Bin/../../lib"; 10 | use File::Temp qw(tempdir); 11 | use File::pushd; 12 | use File::Which qw(which); 13 | 14 | use Minilla::CLI::New; 15 | use Minilla::CLI; 16 | use Minilla::Util; 17 | 18 | subtest 'Acme::Speciality' => sub { 19 | my $guard = pushd(tempdir(CLEANUP => 1)); 20 | Minilla::CLI::New->run('Acme::Speciality', '--username' => 'foo', '--email' => 'bar', '--profile', 'ModuleBuildTiny'); 21 | ok -e 'Acme-Speciality/lib/Acme/Speciality.pm'; 22 | 23 | subtest 'minil.toml was generated', sub { 24 | ok -e 'Acme-Speciality/minil.toml'; 25 | }; 26 | 27 | subtest '"module_maker" was specified', sub { 28 | my $got = slurp("Acme-Speciality/minil.toml"); 29 | like $got, qr{module_maker="ModuleBuildTiny"}; 30 | }; 31 | 32 | subtest 'Build.PL uses M::B::Tiny', sub { 33 | my $got = slurp("Acme-Speciality/Build.PL"); 34 | like $got, qr{use Module::Build::Tiny}; 35 | }; 36 | 37 | { 38 | my $guard2 = pushd("Acme-Speciality"); 39 | 40 | my $project = Minilla::Project->new(dir => "."); 41 | isa_ok $project->module_maker(), 'Minilla::ModuleMaker::ModuleBuildTiny'; 42 | $project->regenerate_files; 43 | 44 | ok -f 'Build.PL'; 45 | is(system($^X, 'Build.PL'), 0); 46 | is(system($^X, './Build'), 0); 47 | system 'tree' if which('tree'); 48 | } 49 | }; 50 | 51 | done_testing; 52 | 53 | -------------------------------------------------------------------------------- /t/project/dist_name.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use File::Basename qw(basename); 6 | use lib "t/lib"; 7 | use Util; 8 | 9 | use Minilla; 10 | use Minilla::Project; 11 | 12 | subtest 'Single hyphen delimiter' => sub { 13 | my $guard = pushd( tempdir( 'App-foobar-XXXX', CLEANUP => 1 ) ); 14 | my $delimiter = '-'; 15 | test_dist_name( $guard, $delimiter ); 16 | }; 17 | 18 | subtest 'Double hyphen delimiter' => sub { 19 | my $guard = pushd( tempdir( 'App--foobar--XXXX', CLEANUP => 1 ) ); 20 | my $delimiter = '--'; 21 | test_dist_name( $guard, $delimiter ); 22 | }; 23 | 24 | subtest 'Single hyphen delimiter with "p5-" prefix' => sub { 25 | my $guard = pushd( tempdir( 'p5-App-foobar-XXXX', CLEANUP => 1 ) ); 26 | my $delimiter = '-'; 27 | test_dist_name( $guard, $delimiter ); 28 | }; 29 | 30 | subtest 'Double hyphen delimiter with "p5-" prefix' => sub { 31 | my $guard = pushd( tempdir( 'p5-App--foobar--XXXX', CLEANUP => 1 ) ); 32 | my $delimiter = '--'; 33 | test_dist_name( $guard, $delimiter ); 34 | }; 35 | 36 | done_testing; 37 | 38 | sub test_dist_name { 39 | my ($guard, $delimiter) = @_; 40 | 41 | my $base_name = basename( $guard->{_pushd} ); 42 | my ($module_name) = $base_name =~ /$delimiter([^-]*)$/; 43 | 44 | mkpath('lib/App/foobar'); 45 | spew("lib/App/foobar/$module_name.pm", <<"..."); 46 | package App::foobar::$module_name; 47 | 1; 48 | ... 49 | 50 | git_init(); 51 | git_add('.'); 52 | git_commit('-m', 'foo'); 53 | 54 | my $project = Minilla::Project->new(); 55 | is($project->dist_name, "App-foobar-$module_name"); 56 | } 57 | 58 | -------------------------------------------------------------------------------- /lib/Minilla/CLI/Test.pm: -------------------------------------------------------------------------------- 1 | package Minilla::CLI::Test; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use File::pushd; 6 | 7 | use Minilla::WorkDir; 8 | use Minilla::Project; 9 | use Minilla::Util qw(check_git parse_options); 10 | 11 | sub run { 12 | my ($self, @args) = @_; 13 | 14 | my $release = 0; 15 | my $author = 1; 16 | my $automated = 0; 17 | my $all = 0; 18 | 19 | check_git; 20 | 21 | parse_options( 22 | \@args, 23 | 'release!' => \$release, 24 | 'author!' => \$author, 25 | 'automated!' => \$automated, 26 | 'all!' => \$all, 27 | ); 28 | 29 | if ($all) { 30 | $release = $author = $automated = 1; 31 | } 32 | 33 | my $project = Minilla::Project->new(); 34 | $project->verify_prereqs( ); 35 | 36 | $ENV{RELEASE_TESTING} =1 if $release; 37 | $ENV{AUTHOR_TESTING} =1 if $author; 38 | $ENV{AUTOMATED_TESTING} =1 if $automated; 39 | 40 | my $work_dir = $project->work_dir; 41 | my $code = $work_dir->dist_test(); 42 | exit $code; 43 | } 44 | 45 | 1; 46 | __END__ 47 | 48 | =head1 NAME 49 | 50 | Minilla::CLI::Test - Run test cases 51 | 52 | =head1 SYNOPSIS 53 | 54 | % minil test 55 | 56 | --release enables the RELEASE_TESTING env variable 57 | --automated enables the AUTOMATED_TESTING env variable 58 | --author enables the AUTHOR_TESTING env variable (default 59 | behavior) 60 | --all enables the RELEASE_TESTING, AUTOMATED_TESTING and 61 | AUTHOR_TESTING env variables 62 | 63 | =head1 DESCRIPTION 64 | 65 | This sub-command run test cases. 66 | 67 | -------------------------------------------------------------------------------- /t/project/format_tag.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | 8 | use Test::More; 9 | 10 | use File::Temp qw(tempdir); 11 | use File::pushd; 12 | use File::Basename qw(dirname); 13 | use File::Path qw(mkpath); 14 | use Minilla::Profile::Default; 15 | use Minilla::Project; 16 | use CPAN::Meta::Validator; 17 | 18 | subtest 'basic' => sub { 19 | my $guard = pushd(tempdir(CLEANUP => 1)); 20 | 21 | my $profile = Minilla::Profile::Default->new( 22 | author => 'foo', 23 | dist => 'Acme-Foo', 24 | path => 'Acme/Foo.pm', 25 | suffix => 'Foo', 26 | module => 'Acme::Foo', 27 | version => '0.01', 28 | email => 'foo@example.com', 29 | ); 30 | $profile->generate(); 31 | spew('cpanfile', 'requires "Moose";'); 32 | write_minil_toml('Acme-Foo'); 33 | 34 | git_init_add_commit(); 35 | 36 | is(Minilla::Project->new()->format_tag('1.0'), '1.0'); 37 | }; 38 | 39 | subtest 'customized' => sub { 40 | my $guard = pushd(tempdir(CLEANUP => 1)); 41 | 42 | my $profile = Minilla::Profile::Default->new( 43 | author => 'foo', 44 | dist => 'Acme-Foo', 45 | path => 'Acme/Foo.pm', 46 | suffix => 'Foo', 47 | module => 'Acme::Foo', 48 | version => '0.01', 49 | email => 'foo@example.com', 50 | ); 51 | $profile->generate(); 52 | spew('cpanfile', 'requires "Moose";'); 53 | write_minil_toml({ 54 | name => 'Acme-Foo', 55 | 'tag_format' => 'moris/%v', 56 | }); 57 | 58 | git_init_add_commit(); 59 | 60 | is(Minilla::Project->new()->format_tag('1.0'), 'moris/1.0'); 61 | }; 62 | 63 | done_testing; 64 | 65 | -------------------------------------------------------------------------------- /t/project/in_submodule.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | 8 | use CPAN::Meta; 9 | 10 | use Minilla::Profile::Default; 11 | use Minilla::Project; 12 | use Minilla::Git; 13 | 14 | subtest 'project_in_submodule' => sub { 15 | my $guard = pushd(tempdir(CLEANUP => 1)); 16 | mkdir('main'); 17 | mkdir('sub'); 18 | chdir('sub'); 19 | 20 | my $profile = Minilla::Profile::Default->new( 21 | author => 'Tokuhiro Matsuno', 22 | dist => 'Acme-Foo', 23 | path => 'Acme/Foo.pm', 24 | suffix => 'Foo', 25 | module => 'Acme::Foo', 26 | version => '0.01', 27 | email => 'tokuhirom@example.com', 28 | ); 29 | $profile->generate(); 30 | mkpath('lib/Acme/'); 31 | spew('lib/Acme/Foo.pod' => <<'...'); 32 | __END__ 33 | 34 | =encoding utf-8 35 | 36 | =pod 37 | 38 | =head1 NAME 39 | 40 | Acme::Foo - Yeah!! 41 | 42 | =head1 SYNOPSIS 43 | 44 | Gah 45 | 46 | =head1 LICENSE 47 | 48 | Copyright 2013- by author foo. All rights reserved. 49 | 50 | =head1 AUTHORS 51 | 52 | author foo 53 | 54 | ... 55 | write_minil_toml({ 56 | name => 'Acme-Foo', 57 | abstract_from => 'lib/Acme/Foo.pod', 58 | authors_from => 'lib/Acme/Foo.pod', 59 | }); 60 | 61 | git_init_add_commit(); 62 | 63 | chdir('../main'); 64 | git_init(); 65 | git_submodule_add('../sub'); 66 | git_add('.'); 67 | git_commit('-m', 'initial import'); 68 | 69 | chdir('sub'); 70 | 71 | my $project = Minilla::Project->new(); 72 | is($project->abstract(), 'Yeah!!'); 73 | is_deeply($project->authors(), ['author foo']); 74 | }; 75 | 76 | done_testing; 77 | 78 | -------------------------------------------------------------------------------- /t/data/bsd.dat: -------------------------------------------------------------------------------- 1 | package t::data::bsd; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | 7 | 1; 8 | __END__ 9 | 10 | =head1 LICENSE 11 | 12 | The BSD License 13 | 14 | Redistribution and use in source and binary forms, with or without 15 | modification, are permitted provided that the following conditions are 16 | met: 17 | 18 | * Redistributions of source code must retain the above copyright 19 | notice, this list of conditions and the following disclaimer. 20 | 21 | * Redistributions in binary form must reproduce the above copyright 22 | notice, this list of conditions and the following disclaimer in the 23 | documentation and/or other materials provided with the distribution. 24 | 25 | * Neither the name of {{$self->holder}} nor the names of its 26 | contributors may be used to endorse or promote products derived from 27 | this software without specific prior written permission. 28 | 29 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 30 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 31 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 32 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR 33 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 34 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 35 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 36 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 37 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 38 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 39 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 40 | 41 | =head1 AUTHOR 42 | 43 | Tokuhiro Matsuno 44 | -------------------------------------------------------------------------------- /t/project/unsupported.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | 8 | use CPAN::Meta; 9 | 10 | use Minilla::Profile::Default; 11 | use Minilla::Project; 12 | use Minilla::Git; 13 | use CPAN::Meta; 14 | use File::Spec; 15 | 16 | subtest 'unsupported' => sub { 17 | my $guard = pushd(tempdir(CLEANUP => 1)); 18 | 19 | my $profile = Minilla::Profile::Default->new( 20 | author => 'Tokuhiro Matsuno', 21 | dist => 'Acme-Foo', 22 | path => 'Acme/Foo.pm', 23 | suffix => 'Foo', 24 | module => 'Acme::Foo', 25 | version => '0.01', 26 | email => 'tokuhirom@example.com', 27 | ); 28 | $profile->generate(); 29 | write_minil_toml({ 30 | name => 'Acme-Foo', 31 | unsupported => { 32 | os => [qw/MSWin32/], 33 | }, 34 | }); 35 | 36 | git_init_add_commit(); 37 | 38 | my $project = Minilla::Project->new(); 39 | is @{ $project->unsupported->os }, 1; 40 | is $project->unsupported->os->[0], 'MSWin32'; 41 | }; 42 | 43 | subtest 'empty unsupported' => sub { 44 | my $guard = pushd(tempdir(CLEANUP => 1)); 45 | 46 | my $profile = Minilla::Profile::Default->new( 47 | author => 'Tokuhiro Matsuno', 48 | dist => 'Acme-Foo', 49 | path => 'Acme/Foo.pm', 50 | suffix => 'Foo', 51 | module => 'Acme::Foo', 52 | version => '0.01', 53 | email => 'tokuhirom@example.com', 54 | ); 55 | $profile->generate(); 56 | write_minil_toml('Acme-Foo'); 57 | 58 | git_init_add_commit(); 59 | 60 | my $project = Minilla::Project->new(); 61 | is @{ $project->unsupported->os }, 0; 62 | }; 63 | 64 | done_testing; 65 | 66 | -------------------------------------------------------------------------------- /t/module_maker/eumm.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | use FindBin; 8 | use lib "$FindBin::Bin/../../lib"; 9 | use File::Temp qw(tempdir); 10 | use File::pushd; 11 | use File::Which qw(which); 12 | 13 | use Minilla::CLI::New; 14 | use Minilla::CLI; 15 | use Minilla::Util; 16 | use Config; 17 | 18 | subtest 'Acme::Speciality' => sub { 19 | my $guard = pushd(tempdir(CLEANUP => 1)); 20 | Minilla::CLI::New->run( 21 | 'Acme::Speciality', 22 | '--username' => 'foo', 23 | '--email' => 'bar', 24 | '--profile', 'ExtUtilsMakeMaker' 25 | ); 26 | ok -e 'Acme-Speciality/lib/Acme/Speciality.pm'; 27 | 28 | subtest 'minil.toml was generated', sub { 29 | ok -e 'Acme-Speciality/minil.toml'; 30 | }; 31 | 32 | subtest '"module_maker" was specified', sub { 33 | my $got = slurp("Acme-Speciality/minil.toml"); 34 | like $got, qr{module_maker="ExtUtilsMakeMaker"}; 35 | }; 36 | 37 | subtest 'Makefile.PL uses EUMM', sub { 38 | ok !-f 'Acme-Speciality/Build.PL'; 39 | ok -f 'Acme-Speciality/Makefile.PL'; 40 | my $got = slurp("Acme-Speciality/Makefile.PL"); 41 | like $got, qr{use ExtUtils::MakeMaker}; 42 | note $got; 43 | }; 44 | 45 | { 46 | my $guard2 = pushd("Acme-Speciality"); 47 | 48 | my $project = Minilla::Project->new(dir => "."); 49 | isa_ok $project->module_maker(), 'Minilla::ModuleMaker::ExtUtilsMakeMaker'; 50 | $project->regenerate_files; 51 | 52 | ok -f 'Makefile.PL'; 53 | is(system($^X, 'Makefile.PL'), 0); 54 | is(system($Config{'make'}), 0); 55 | note `tree` if which('tree'); 56 | } 57 | }; 58 | 59 | done_testing; 60 | 61 | -------------------------------------------------------------------------------- /t/work_dir/_rewrite_pod.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | use File::Spec::Functions qw(catfile); 8 | 9 | use Minilla::Profile::Default; 10 | use Minilla::Project; 11 | use Minilla::Git; 12 | 13 | plan skip_all => 'Pod rewriting is temporary disabled.'; 14 | 15 | subtest 'rewrite pod' => sub { 16 | my $guard = pushd(tempdir(CLEANUP => 1)); 17 | 18 | my $profile = Minilla::Profile::Default->new( 19 | author => 'tokuhirom', 20 | dist => 'Acme-Foo', 21 | path => 'Acme/Foo.pm', 22 | suffix => 'Foo', 23 | module => 'Acme::Foo', 24 | version => '0.01', 25 | email => 'tokuhirom@example.com', 26 | ); 27 | $profile->generate(); 28 | write_minil_toml('Acme-Foo'); 29 | 30 | git_init(); 31 | git_add('.'); 32 | git_config(qw(user.name tokuhirom)); 33 | git_config(qw(user.email tokuhirom@example.com)); 34 | Minilla::Project->new()->regenerate_files(); 35 | git_commit('-m', 'initial import'); 36 | ok -f 'Build.PL'; 37 | 38 | git_config(qw(user.name Foo)); 39 | git_config(qw(user.email foo@example.com)); 40 | git_commit('--allow-empty', '-m', 'foo'); 41 | git_config(qw(user.name Bar)); 42 | git_config(qw(user.email bar@example.com)); 43 | git_commit('--allow-empty', '-m', 'bar'); 44 | git_commit('--allow-empty', '-m', 'bar2'); 45 | 46 | my $work_dir = Minilla::Project->new()->work_dir(); 47 | $work_dir->build; 48 | ok -f catfile($work_dir->dir, 'Build.PL'); 49 | my $pod = slurp(catfile($work_dir->dir, $work_dir->project->main_module_path)); 50 | # note $pod; 51 | ok $pod =~ /=head1 CONTRIBUTORS/; 52 | ok $pod =~ /Bar Ebar\@example\.comE/; 53 | }; 54 | 55 | done_testing; 56 | 57 | -------------------------------------------------------------------------------- /t/release_test/config.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | use File::Temp qw(tempdir); 8 | use File::pushd; 9 | use File::Spec::Functions qw(catdir); 10 | use Minilla::Profile::Default; 11 | use Minilla::Project; 12 | 13 | subtest 'ReleaseTest.MinimumVersion' => sub { 14 | subtest 'no minimumversion' => sub { 15 | my $guard = pushd(tempdir(CLEANUP => 1)); 16 | 17 | my $project = create_project(); 18 | 19 | spew('minil.toml', <<'...'); 20 | name = "Acme-Foo" 21 | [ReleaseTest] 22 | MinimumVersion = false 23 | ... 24 | my $workdir = $project->work_dir(); 25 | $workdir->build; 26 | 27 | { 28 | my $guard = pushd($workdir->dir); 29 | ok -f 'xt/minilla/pod.t', 'Exists xt/minilla/minimum_version.t'; 30 | ok !-f 'xt/minilla/minimum_version.t'; 31 | } 32 | }; 33 | 34 | subtest 'normal case' => sub { 35 | my $guard = pushd(tempdir(CLEANUP => 1)); 36 | 37 | my $project = create_project(); 38 | 39 | write_minil_toml('Acme-Foo'); 40 | 41 | my $workdir = $project->work_dir(); 42 | $workdir->build; 43 | 44 | { 45 | my $guard = pushd($workdir->dir); 46 | ok -f 'xt/minilla/pod.t'; 47 | ok -f 'xt/minilla/minimum_version.t'; 48 | } 49 | }; 50 | }; 51 | 52 | done_testing; 53 | 54 | sub create_project { 55 | my $profile = Minilla::Profile::Default->new( 56 | author => 'foo', 57 | dist => 'Acme-Foo', 58 | path => 'Acme/Foo.pm', 59 | suffix => 'Foo', 60 | module => 'Acme::Foo', 61 | version => '0.01', 62 | email => 'foo@example.com', 63 | ); 64 | $profile->generate(); 65 | git_init_add_commit(); 66 | my $project = Minilla::Project->new(); 67 | return $project; 68 | } 69 | -------------------------------------------------------------------------------- /lib/Minilla/FileGatherer.pm: -------------------------------------------------------------------------------- 1 | package Minilla::FileGatherer; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use File::pushd; 6 | use File::Spec; 7 | use ExtUtils::Manifest 1.54 qw(maniskip); 8 | 9 | use Minilla::Git; 10 | 11 | use Moo; 12 | 13 | has exclude_match => ( 14 | is => 'ro', 15 | default => sub { +[ ] }, 16 | ); 17 | 18 | has include_dotfiles => ( 19 | is => 'ro', 20 | default => sub { undef }, 21 | ); 22 | 23 | no Moo; 24 | 25 | sub gather_files { 26 | my ($self, $root) = @_; 27 | my $guard = pushd($root); 28 | my @files = grep { _topdir($_) ne 'extlib' } 29 | grep { not -l $_ } 30 | map { File::Spec->abs2rel($_, $root) } 31 | git_ls_files(), git_submodule_files(); 32 | if ($self->exclude_match) { 33 | for my $pattern (@{$self->exclude_match || []}) { 34 | @files = grep { _normalize($_) !~ $pattern } @files; 35 | } 36 | } 37 | 38 | if (-f 'MANIFEST.SKIP') { 39 | my $skip = maniskip('MANIFEST.SKIP') ; 40 | @files = grep { !$skip->($_) } @files; 41 | } 42 | unless ($self->include_dotfiles) { 43 | @files = grep { 44 | !(grep { $_ =~ qr/^\./ } split m!/!, _normalize($_)) 45 | } @files; 46 | } 47 | if ($^O eq 'MSWin32') { 48 | @files = map { 49 | my $x = $_; 50 | $x =~ s!\\!/!g; 51 | $x; 52 | } @files; 53 | } 54 | 55 | my @submodules = git_submodules; 56 | if (@submodules) { 57 | for my $filename (@submodules) { 58 | @files = grep { $_ ne $filename } @files; 59 | } 60 | } 61 | 62 | return @files; 63 | } 64 | 65 | sub _topdir { 66 | my ($path) = @_; 67 | [File::Spec->splitdir($path)]->[0] || ''; 68 | } 69 | 70 | # for Windows 71 | sub _normalize { 72 | local $_ = shift; 73 | s!\\!/!g; 74 | $_; 75 | } 76 | 77 | 1; 78 | -------------------------------------------------------------------------------- /lib/Minilla/CLI.pm: -------------------------------------------------------------------------------- 1 | package Minilla::CLI; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use Getopt::Long; 6 | use Try::Tiny; 7 | 8 | use Minilla; 9 | use Minilla::Errors; 10 | use Minilla::Project; 11 | use Minilla::Util qw(find_dir); 12 | use Minilla::Logger; 13 | 14 | use Minilla::CLI::New; 15 | use Minilla::CLI::Help; 16 | use Minilla::CLI::Dist; 17 | use Minilla::CLI::Test; 18 | use Minilla::CLI::Release; 19 | use Minilla::CLI::Install; 20 | 21 | use Moo; 22 | 23 | no Moo; 24 | 25 | sub run { 26 | my ($self, @args) = @_; 27 | 28 | local $Minilla::AUTO_INSTALL = 1; 29 | local $Minilla::Logger::COLOR = -t STDOUT ? 1 : 0; 30 | local @ARGV = @args; 31 | my @commands; 32 | my $version; 33 | my $p = Getopt::Long::Parser->new( 34 | config => [ "no_ignore_case", "pass_through" ], 35 | ); 36 | $p->getoptions( 37 | "h|help" => sub { unshift @commands, 'help' }, 38 | "color!" => \$Minilla::Logger::COLOR, 39 | "debug!" => \$Minilla::DEBUG, 40 | "auto-install!" => \$Minilla::AUTO_INSTALL, 41 | 'version!' => \$version, 42 | ); 43 | 44 | if ($version) { 45 | print "Minilla: $Minilla::VERSION\n"; 46 | exit 0; 47 | } 48 | 49 | push @commands, @ARGV; 50 | 51 | my $cmd = shift @commands || 'help'; 52 | my $klass = sprintf("Minilla::CLI::%s", ucfirst($cmd)); 53 | 54 | ## no critic 55 | if (eval sprintf("require %s; 1;", $klass)) { 56 | try { 57 | $klass->run(@commands); 58 | } catch { 59 | /Minilla::Error::CommandExit/ and return; 60 | errorf("%s\n", $_); 61 | exit 1; 62 | } 63 | } else { 64 | warnf("Could not find command '%s'\n", $cmd); 65 | if ($@ !~ /^Can't locate Minilla/) { 66 | errorf("$@\n"); 67 | } 68 | exit 2; 69 | } 70 | } 71 | 72 | 1; 73 | 74 | -------------------------------------------------------------------------------- /t/module_maker/eumm/run_tests.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | use FindBin; 8 | use lib "$FindBin::Bin/../../../lib"; 9 | use File::Temp qw(tempdir); 10 | use File::pushd; 11 | 12 | use Minilla::CLI; 13 | use Minilla::CLI::New; 14 | use Minilla::ReleaseTest; 15 | 16 | my $original_write_release_tests = *Minilla::ReleaseTest::write_release_tests{CODE}; 17 | undef *Minilla::ReleaseTest::write_release_tests; 18 | *Minilla::ReleaseTest::write_release_tests = sub {}; # Do nothing 19 | 20 | my $minil = File::Spec->rel2abs('script/minil'); 21 | 22 | subtest 'dist test' => sub { 23 | my $guard = pushd(tempdir(CLEANUP => 1)); 24 | 25 | Minilla::CLI::New->run('Acme::Speciality', '--username' => 'foo', '--email' => 'bar', '--profile', 'ExtUtilsMakeMaker'); 26 | 27 | chdir 'Acme-Speciality'; 28 | 29 | { 30 | mkdir 'xt'; 31 | open my $fh, '>', 'xt/fail.t'; 32 | print $fh <<'...'; 33 | use strict; 34 | use warnings; 35 | use Test::More; 36 | ok 0; # Failure 37 | done_testing; 38 | ... 39 | system 'git add .'; 40 | } 41 | 42 | subtest 'run only t/*.t and pass all' => sub { 43 | is test_by(''), 0; 44 | is test_by('--automated'), 0; 45 | is test_by('--author'), 0; 46 | }; 47 | 48 | subtest 'run t/*.t and xt/*.t and fail' => sub { 49 | isnt test_by('--release'), 0; 50 | isnt test_by('--all'), 0; 51 | }; 52 | }; 53 | 54 | sub test_by { 55 | my $run_opt = shift; 56 | 57 | $ENV{RELEASE_TESTING} = 0; 58 | $ENV{AUTHOR_TESTING} = 0; 59 | $ENV{AUTOMATED_TESTING} = 0; 60 | 61 | my $pid = fork; 62 | fail("Fork failed") unless defined $pid; 63 | if ($pid) { 64 | waitpid($pid, 0); 65 | return $?; 66 | } 67 | else { 68 | Minilla::CLI->new->run('test', $run_opt); 69 | } 70 | } 71 | 72 | done_testing; 73 | 74 | -------------------------------------------------------------------------------- /t/lib/Util.pm: -------------------------------------------------------------------------------- 1 | package Util; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use parent qw(Exporter); 6 | 7 | use File::pushd; 8 | use File::Temp qw(tempdir); 9 | use Test::More; 10 | use File::Path; 11 | use File::Which qw(which); 12 | use File::Spec::Functions qw(catfile); 13 | use TOML 0.92 qw(to_toml); 14 | 15 | use Minilla; 16 | use Minilla::Git; 17 | use Minilla::Util qw/:all/; 18 | use TOML; 19 | 20 | $Minilla::DEBUG=1 if $ENV{MINILLA_DEBUG}; 21 | 22 | plan skip_all => "No git command" unless which('git'); 23 | plan skip_all => "No cpanm command" unless which('cpanm'); 24 | plan skip_all => "No git configuration" unless `git config user.email` =~ /\@/; 25 | $ENV{PERL_CPANM_HOME} = tempdir(CLEANUP => 1); 26 | delete $ENV{GIT_CONFIG}; 27 | 28 | our @EXPORT = ( 29 | qw(git_init_add_commit git_submodule_add git_submodule_update_init_recursive write_minil_toml), 30 | qw(tempdir pushd), 31 | @Minilla::Git::EXPORT, @Minilla::Util::EXPORT_OK, qw(spew), 32 | qw(catfile), 33 | qw(mkpath), 34 | ); 35 | 36 | sub git_init_add_commit() { 37 | git_init(); 38 | git_add('.'); 39 | git_commit('-m', 'initial import'); 40 | } 41 | 42 | # Cloning from local file system is disabled by default since git 2.38.1 43 | # So set 'protocol.file.allow=always' 44 | # - https://github.blog/2022-10-18-git-security-vulnerabilities-announced/#cve-2022-39253 45 | sub git_submodule_add { 46 | cmd('git', '-c', 'protocol.file.allow=always', 'submodule', 'add', @_); 47 | } 48 | 49 | sub git_submodule_update_init_recursive { 50 | cmd('git', '-c', 'protocol.file.allow=always', 'submodule', 'update', '--init', '--recursive'); 51 | } 52 | 53 | sub write_minil_toml { 54 | if ( @_ == 1 && !ref $_[0] ) { 55 | my $name = shift; 56 | $name =~ s/::/-/g; 57 | spew( 'minil.toml', qq{name = "$name"\n} ); 58 | } 59 | else { 60 | spew( 'minil.toml', to_toml(@_) ); 61 | } 62 | } 63 | 64 | 1; 65 | -------------------------------------------------------------------------------- /t/work_dir/release_test.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | 6 | use lib "t/lib"; 7 | use Util; 8 | use File::Spec::Functions qw(catfile); 9 | 10 | use Minilla::Profile::Default; 11 | use Minilla::Project; 12 | use Minilla::Git; 13 | 14 | subtest 'Contributors are included in stopwords' => sub { 15 | local %ENV = %ENV; 16 | delete $ENV{GIT_AUTHOR_NAME}; 17 | delete $ENV{GIT_AUTHOR_EMAIL}; 18 | 19 | my $guard = pushd(tempdir(CLEANUP => 1)); 20 | 21 | my $profile = Minilla::Profile::Default->new( 22 | author => 'tokuhirom', 23 | dist => 'Acme-Foo', 24 | path => 'Acme/Foo.pm', 25 | suffix => 'Foo', 26 | module => 'Acme::Foo', 27 | version => '0.01', 28 | email => 'tokuhirom@example.com', 29 | ); 30 | $profile->generate(); 31 | write_minil_toml('Acme-Foo'); 32 | 33 | git_init(); 34 | git_add('.'); 35 | git_config(qw(user.name tokuhirom)); 36 | git_config(qw(user.email tokuhirom@example.com)); 37 | Minilla::Project->new()->regenerate_files(); 38 | git_commit('-m', 'initial import'); 39 | 40 | git_config(qw(user.name Foo)); 41 | git_config(qw(user.email foo@example.com)); 42 | git_commit('--allow-empty', '-m', 'foo'); 43 | git_config(qw(user.name Bar)); 44 | git_config(qw(user.email bar@example.com)); 45 | git_commit('--allow-empty', '-m', 'bar'); 46 | 47 | my $work_dir = Minilla::Project->new()->work_dir(); 48 | $work_dir->build; 49 | my $spelling_test_file = catfile($work_dir->project->work_dir->dir, 'xt', 'minilla', 'spelling.t'); 50 | 51 | ok -f $spelling_test_file; 52 | my $spelling = slurp($spelling_test_file); 53 | my ($stopwords) = $spelling =~ /add_stopwords\(\@(.*)\);/; 54 | 55 | like $stopwords, qr(tokuhirom) or diag $spelling; 56 | like $stopwords, qr(Foo) or diag $stopwords; 57 | like $stopwords, qr(Bar); 58 | }; 59 | done_testing; 60 | -------------------------------------------------------------------------------- /t/migrate/eumm.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | 8 | package Minilla::Profile::EUMM; 9 | use parent qw(Minilla::Profile::Default); 10 | 11 | use Test::More; 12 | 13 | plan skip_all => "No git configuration" unless `git config user.email` =~ /\@/; 14 | 15 | use File::Temp qw(tempdir); 16 | use File::pushd; 17 | use Data::Section::Simple qw(get_data_section); 18 | use File::Basename qw(dirname); 19 | use File::Path qw(mkpath); 20 | 21 | use Minilla::Util qw(spew cmd slurp); 22 | use Minilla::Migrate; 23 | use Minilla::Git; 24 | 25 | subtest 'Removing committed README' => sub { 26 | my $guard = pushd(tempdir(CLEANUP => 1)); 27 | 28 | my $profile = Minilla::Profile::EUMM->new( 29 | author => 'foo', 30 | dist => 'Acme-Foo', 31 | path => 'Acme/Foo.pm', 32 | suffix => 'Foo', 33 | module => 'Acme::Foo', 34 | version => '0.01', 35 | email => 'foo@example.com', 36 | ); 37 | $profile->generate(); 38 | $profile->render('minil.toml'); 39 | $profile->render('Makefile.PL'); 40 | $profile->render('MANIFEST'); 41 | unlink 'Build.PL'; 42 | unlink 'META.json'; 43 | 44 | git_init(); 45 | git_add(); 46 | git_commit('-m', 'initial import'); 47 | 48 | Minilla::Migrate->new->run(); 49 | 50 | like(slurp('.gitignore'), qr{!LICENSE}); 51 | 52 | ok -f 'META.json'; 53 | }; 54 | 55 | done_testing; 56 | 57 | __DATA__ 58 | 59 | @@ minil.toml 60 | name = "Acme-Foo" 61 | 62 | @@ MANIFEST 63 | Makefile.PL 64 | minil.toml 65 | lib/Acme/Foo.pm 66 | cpanfile 67 | 68 | @@ Makefile.PL 69 | require 5.008001; 70 | use strict; 71 | use ExtUtils::MakeMaker; 72 | 73 | WriteMakefile( 74 | NAME => 'Acme::Foo', 75 | VERSION_FROM => 'lib/Acme/Foo.pm', 76 | ABSTRACT => 'Acme style messages', 77 | AUTHOR => 'Ghha', 78 | LICENSE => "perl", 79 | MIN_PERL_VERSION => 5.008001, 80 | PREREQ_PM => { 81 | }, 82 | ); 83 | -------------------------------------------------------------------------------- /t/module_maker/tiny/run_tests.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use Test::Requires {'Module::Build::Tiny', 0.035}; 6 | use lib "t/lib"; 7 | use Util; 8 | use FindBin; 9 | use lib "$FindBin::Bin/../../../lib"; 10 | use File::Temp qw(tempdir); 11 | use File::pushd; 12 | 13 | use Minilla::CLI; 14 | use Minilla::CLI::New; 15 | use Minilla::ReleaseTest; 16 | 17 | my $original_write_release_tests = *Minilla::ReleaseTest::write_release_tests{CODE}; 18 | undef *Minilla::ReleaseTest::write_release_tests; 19 | *Minilla::ReleaseTest::write_release_tests = sub {}; # Do nothing 20 | 21 | my $minil = File::Spec->rel2abs('script/minil'); 22 | 23 | subtest 'dist test' => sub { 24 | my $guard = pushd(tempdir(CLEANUP => 1)); 25 | 26 | Minilla::CLI::New->run('Acme::Speciality', '--username' => 'foo', '--email' => 'bar', '--profile', 'ModuleBuildTiny'); 27 | 28 | chdir 'Acme-Speciality'; 29 | 30 | { 31 | mkdir 'xt'; 32 | open my $fh, '>', 'xt/fail.t'; 33 | print $fh <<'...'; 34 | use strict; 35 | use warnings; 36 | use Test::More; 37 | ok 0; # Failure 38 | done_testing; 39 | ... 40 | system 'git add .'; 41 | } 42 | 43 | subtest 'run only t/*.t and pass all' => sub { 44 | is test_by(''), 0; 45 | is test_by('--automated'), 0; 46 | is test_by('--author'), 0; 47 | }; 48 | 49 | subtest 'run t/*.t and xt/*.t and fail' => sub { 50 | isnt test_by('--release'), 0; 51 | isnt test_by('--all'), 0; 52 | }; 53 | }; 54 | 55 | sub test_by { 56 | my $run_opt = shift; 57 | 58 | $ENV{RELEASE_TESTING} = 0; 59 | $ENV{AUTHOR_TESTING} = 0; 60 | $ENV{AUTOMATED_TESTING} = 0; 61 | 62 | my $pid = fork; 63 | fail("Fork failed") unless defined $pid; 64 | if ($pid) { 65 | waitpid($pid, 0); 66 | return $?; 67 | } 68 | else { 69 | Minilla::CLI->new->run('test', $run_opt); 70 | } 71 | } 72 | 73 | done_testing; 74 | 75 | -------------------------------------------------------------------------------- /t/project/contributors.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use Test::Requires 'Software::License'; 6 | use lib "t/lib"; 7 | use Util; 8 | 9 | use CPAN::Meta; 10 | 11 | use Minilla::Profile::Default; 12 | use Minilla::Project; 13 | use Minilla::Git; 14 | 15 | subtest 'develop deps' => sub { 16 | local %ENV = %ENV; 17 | delete $ENV{GIT_AUTHOR_NAME}; 18 | delete $ENV{GIT_AUTHOR_EMAIL}; 19 | 20 | my $guard = pushd(tempdir(CLEANUP => 1)); 21 | 22 | my $profile = Minilla::Profile::Default->new( 23 | author => 'Tokuhiro Matsuno', 24 | dist => 'Acme-Foo', 25 | path => 'Acme/Foo.pm', 26 | suffix => 'Foo', 27 | module => 'Acme::Foo', 28 | version => '0.01', 29 | email => 'tokuhirom@example.com', 30 | ); 31 | $profile->generate(); 32 | 33 | write_minil_toml('Acme-Foo'); 34 | 35 | git_init(); 36 | git_add('.'); 37 | git_config(qw(user.name), 'Tokuhiro Matsuno'); 38 | git_config(qw(user.email tokuhirom@example.com)); 39 | git_commit('-m', 'initial import'); 40 | 41 | # other name, but same address 42 | git_config(qw(user.name tokuhirom)); 43 | git_config(qw(user.email tokuhirom@example.com)); 44 | git_commit('--allow-empty', '-m', 'foo'); 45 | 46 | git_config(qw(user.name Foo)); 47 | git_config(qw(user.email foo@example.com)); 48 | git_commit('--allow-empty', '-m', 'foo'); 49 | 50 | git_config(qw(user.name Bar)); 51 | git_config(qw(user.email bar@example.com)); 52 | git_commit('--allow-empty', '-m', 'bar'); 53 | git_commit('--allow-empty', '-m', 'bar2'); 54 | 55 | my $project = Minilla::Project->new(); 56 | is_deeply( 57 | $project->contributors, 58 | ['Bar ', 59 | 'Foo '], 60 | ); 61 | $project->regenerate_files(); 62 | is_deeply( 63 | CPAN::Meta->load_file('META.json')->{x_contributors}, 64 | ['Bar ', 65 | 'Foo '], 66 | ); 67 | }; 68 | 69 | done_testing; 70 | 71 | -------------------------------------------------------------------------------- /t/cli/regenerate_BuildPL.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | use Test::Requires 'Version::Next', 'CPAN::Uploader'; 8 | use Minilla::CLI::Build; 9 | use Minilla::CLI::Dist; 10 | use Minilla::CLI::New; 11 | use Minilla::CLI::Release; 12 | use Minilla::CLI::Test; 13 | 14 | sub regenerate_BuildPL_test { 15 | my ($cli_class, %opt) = @_; 16 | my $fork = $opt{fork}; 17 | my $repo = $opt{repo}; 18 | 19 | my $guard = pushd(tempdir(CLEANUP => 1)); 20 | Minilla::CLI::New->run("Acme::Foo"); 21 | 22 | { 23 | my $guard2 = pushd("Acme-Foo"); 24 | mkdir "builder"; 25 | spew "builder/MyBuilder.pm", q( 26 | package builder::MyBuilder; 27 | use base 'Module::Build'; 28 | 1; 29 | ); 30 | 31 | write_minil_toml({ 32 | name => 'Acme-Foo', 33 | module_maker => "ModuleBuild", 34 | build => { build_class => "builder::MyBuilder" } 35 | }); 36 | 37 | git_add; 38 | git_remote('add', 'origin', "file://$repo") if $repo; 39 | 40 | if ($fork) { 41 | my $pid = fork; 42 | die "fork failed" unless defined $pid; 43 | if ($pid == 0) { 44 | $cli_class->run; 45 | die "never reach here"; 46 | } 47 | waitpid $pid, 0; 48 | } else { 49 | $cli_class->run; 50 | } 51 | 52 | like slurp("Build.PL"), qr/use\s+builder::MyBuilder/, 53 | "$cli_class\->run should regenerate Build.PL"; 54 | } 55 | } 56 | 57 | { 58 | my $repo = tempdir(CLEANUP => 1); 59 | { my $guard = pushd($repo); cmd('git', 'init', '--bare'); } 60 | local $ENV{PERL_MM_USE_DEFAULT} = 1; 61 | local $ENV{PERL_MINILLA_SKIP_CHECK_CHANGE_LOG} = 1; 62 | local $ENV{FAKE_RELEASE} = 1; 63 | regenerate_BuildPL_test "Minilla::CLI::Release", fork => 0, repo => $repo; 64 | } 65 | regenerate_BuildPL_test "Minilla::CLI::Build", fork => 0; 66 | regenerate_BuildPL_test "Minilla::CLI::Dist", fork => 0; 67 | regenerate_BuildPL_test "Minilla::CLI::Test", fork => 1; 68 | 69 | done_testing; 70 | -------------------------------------------------------------------------------- /t/migrate/tmpfiles.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use lib "t/lib"; 5 | use Util; 6 | 7 | package Minilla::Profile::Tempfiles; 8 | use parent qw(Minilla::Profile::Default); 9 | 10 | use Test::More; 11 | 12 | plan skip_all => "No git configuration" unless `git config user.email` =~ /\@/; 13 | 14 | use File::Temp qw(tempdir); 15 | use File::pushd; 16 | use Data::Section::Simple qw(get_data_section); 17 | use File::Basename qw(dirname); 18 | use File::Path qw(mkpath); 19 | 20 | use Minilla::Util qw(spew cmd); 21 | use Minilla::Migrate; 22 | use Minilla::Git; 23 | 24 | subtest 'Removing committed README' => sub { 25 | my $guard = pushd(tempdir(CLEANUP => 1)); 26 | 27 | my $profile = Minilla::Profile::Tempfiles->new( 28 | author => 'foo', 29 | dist => 'Acme-Foo', 30 | path => 'Acme/Foo.pm', 31 | suffix => 'Foo', 32 | module => 'Acme::Foo', 33 | version => '0.01', 34 | email => 'foo@example.com', 35 | ); 36 | $profile->generate(); 37 | $profile->render('minil.toml'); 38 | $profile->render('README'); 39 | 40 | git_init(); 41 | git_add(); 42 | git_commit('-m', 'initial import'); 43 | 44 | Minilla::Migrate->new->run(); 45 | 46 | ok(!-f 'README'); 47 | }; 48 | 49 | subtest 'Removing ignored README' => sub { 50 | my $guard = pushd(tempdir(CLEANUP => 1)); 51 | 52 | my $profile = Minilla::Profile::Tempfiles->new( 53 | author => 'foo', 54 | dist => 'Acme-Foo', 55 | path => 'Acme/Foo.pm', 56 | suffix => 'Foo', 57 | module => 'Acme::Foo', 58 | version => '0.01', 59 | email => 'foo@example.com', 60 | ); 61 | $profile->generate(); 62 | $profile->render('minil.toml'); 63 | $profile->render('README'); 64 | 65 | my $gi = Minilla::Gitignore->load('.gitignore'); 66 | $gi->add('/README'); 67 | $gi->save('.gitignore'); 68 | 69 | git_init(); 70 | git_add(); 71 | git_commit('-m', 'initial import'); 72 | 73 | Minilla::Migrate->new->run(); 74 | 75 | ok(!-f 'README'); 76 | }; 77 | 78 | done_testing; 79 | 80 | __DATA__ 81 | 82 | @@ minil.toml 83 | name = "Acme-Foo" 84 | 85 | @@ README 86 | AAA 87 | -------------------------------------------------------------------------------- /cpanfile: -------------------------------------------------------------------------------- 1 | requires 'perl' => '5.010001'; 2 | 3 | # Core module at recent Perl5. 4 | requires 'parent' => '0'; 5 | requires 'Archive::Tar', '1.60'; 6 | requires 'Time::Piece' => 1.16; # older Time::Piece was broken 7 | requires 'version'; 8 | requires 'CPAN::Meta', '2.132830'; # merged_requirements is 2.132830+ 9 | requires 'ExtUtils::Manifest', 1.54; # make maniskip a public routine, and allow an argument to override $mfile 10 | suggests 'Devel::PPPort'; # XS 11 | requires 'TAP::Harness::Env'; # From 5.19.5 12 | 13 | # The TOML parser 14 | requires 'TOML', 0.95; 15 | 16 | # Templating 17 | requires 'Text::MicroTemplate', '0.20'; 18 | 19 | # CPAN related 20 | requires 'App::cpanminus', '1.6902'; 21 | requires 'Module::CPANfile', '0.9025'; 22 | requires 'Module::Metadata' => '1.000037'; 23 | requires 'Pod::Markdown', '1.322'; 24 | 25 | # File operation 26 | requires 'File::pushd'; 27 | requires 'File::Which'; 28 | 29 | # OOPS 30 | requires 'Moo' => 1.001000; 31 | 32 | # Utilities 33 | requires 'Data::Section::Simple' => 0.04; 34 | requires 'Term::ANSIColor'; 35 | requires 'Term::Encoding'; 36 | requires 'Module::Runtime'; 37 | requires 'URI'; 38 | 39 | # Modules required by minil new/minil dist/minil release are optional. 40 | # It's good for contributors 41 | recommends 'Version::Next'; 42 | recommends 'Pod::Escapes'; 43 | recommends 'CPAN::Uploader'; 44 | 45 | # Core deps 46 | requires 'Try::Tiny'; 47 | requires 'Getopt::Long', 2.36; 48 | 49 | # Module required for license otherwise Perl_5 license. 50 | recommends 'Software::License', '0.103010'; 51 | 52 | # release testing 53 | recommends 'Test::Pod'; 54 | recommends 'Test::Spellunker', 'v0.2.7'; 55 | recommends 'Test::MinimumVersion::Fast' => '0.04'; 56 | recommends 'Test::CPAN::Meta'; 57 | recommends 'Test::PAUSE::Permissions'; 58 | 59 | on 'test' => sub { 60 | requires 'Test::More' => '0.98'; 61 | requires 'Test::Requires' => 0; 62 | requires 'Test::Output'; 63 | requires 'File::Copy::Recursive'; 64 | requires 'File::Temp'; 65 | recommends 'Devel::CheckLib'; 66 | suggests 'Dist::Zilla'; 67 | requires 'CPAN::Meta::Validator'; 68 | requires 'JSON'; 69 | requires 'Module::Build::Tiny', 0.035; # https://github.com/tokuhirom/Minilla/issues/151 70 | }; 71 | 72 | on 'develop' => sub { 73 | # Dependencies for developers 74 | }; 75 | -------------------------------------------------------------------------------- /t/migrate/changes.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | 8 | package Minilla::Profile::Changes; 9 | use parent qw(Minilla::Profile::Default); 10 | 11 | use Test::More; 12 | 13 | plan skip_all => "No git configuration" unless `git config user.email` =~ /\@/; 14 | 15 | use File::Temp qw(tempdir); 16 | use File::pushd; 17 | use Data::Section::Simple qw(get_data_section); 18 | use File::Basename qw(dirname); 19 | use File::Path qw(mkpath); 20 | 21 | use Minilla::Util qw(spew cmd slurp); 22 | use Minilla::Migrate; 23 | use Minilla::Git; 24 | 25 | subtest 'Insert {{$NEXT}}' => sub { 26 | my $guard = pushd(tempdir(CLEANUP => 1)); 27 | 28 | my $profile = Minilla::Profile::Changes->new( 29 | author => 'foo', 30 | dist => 'Acme-Foo', 31 | path => 'Acme/Foo.pm', 32 | suffix => 'Foo', 33 | module => 'Acme::Foo', 34 | version => '0.01', 35 | email => 'foo@example.com', 36 | ); 37 | $profile->generate(); 38 | $profile->render('minil.toml'); 39 | $profile->render('Changes'); 40 | 41 | git_init(); 42 | git_add(); 43 | git_commit('-m', 'initial import'); 44 | 45 | Minilla::Migrate->new->run(); 46 | 47 | like(slurp('Changes'), qr!\{\{\$NEXT\}\}!); 48 | }; 49 | 50 | subtest 'Do not {{$NEXT}} twice' => sub { 51 | my $guard = pushd(tempdir(CLEANUP => 1)); 52 | 53 | my $profile = Minilla::Profile::Changes->new( 54 | author => 'foo', 55 | dist => 'Acme-Foo', 56 | path => 'Acme/Foo.pm', 57 | suffix => 'Foo', 58 | module => 'Acme::Foo', 59 | version => '0.01', 60 | email => 'foo@example.com', 61 | ); 62 | $profile->generate(); 63 | $profile->render('minil.toml'); 64 | $profile->render('Changes'); 65 | 66 | git_init(); 67 | git_add(); 68 | git_commit('-m', 'initial import'); 69 | 70 | Minilla::Migrate->new->run(); 71 | Minilla::Migrate->new->run(); 72 | 73 | my $content = slurp('Changes'); 74 | my $n; 75 | $content =~ s!\{\{\$NEXT\}\}!$n++!ge; 76 | is($n, 1); 77 | }; 78 | 79 | done_testing; 80 | 81 | __DATA__ 82 | 83 | @@ minil.toml 84 | name = "Acme-Foo" 85 | 86 | @@ Changes 87 | Revision history for Perl extension Minilla 88 | 89 | 0.01 2013-02-02 90 | 91 | - foo 92 | 93 | -------------------------------------------------------------------------------- /t/module_maker/requires_external_bin.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | use Test::Requires 'Devel::CheckBin'; 8 | 9 | plan skip_all => 'Missing "tar"' unless can_run('tar'); 10 | 11 | use CPAN::Meta; 12 | 13 | use Minilla::Profile::ModuleBuild; 14 | use Minilla::Project; 15 | use Minilla::Git; 16 | 17 | subtest 'develop deps' => sub { 18 | my $guard = pushd(tempdir(CLEANUP => 1)); 19 | 20 | my $profile = Minilla::Profile::ModuleBuild->new( 21 | author => 'Tokuhiro Matsuno', 22 | dist => 'Acme-Foo', 23 | path => 'Acme/Foo.pm', 24 | suffix => 'Foo', 25 | module => 'Acme::Foo', 26 | version => '0.01', 27 | email => 'tokuhirom@example.com', 28 | ); 29 | $profile->generate(); 30 | 31 | subtest 'normal' => sub { 32 | write_minil_toml({ 33 | name => 'Acme-Foo', 34 | requires_external_bin => [ 'tar' ] 35 | }); 36 | note slurp('minil.toml'); 37 | 38 | git_init_add_commit(); 39 | 40 | my $project = Minilla::Project->new(); 41 | $project->regenerate_files(); 42 | is_deeply($project->requires_external_bin, ['tar']); 43 | is($project->module_maker->prereqs($project)->{configure}->{requires}->{'Devel::CheckBin'}, 0); 44 | $project->module_maker->generate($project); 45 | is(system($^X, 'Build.PL'), 0); 46 | }; 47 | 48 | subtest 'Failing case' => sub { 49 | write_minil_toml({ 50 | name => 'Acme-Foo', 51 | requires_external_bin => [ 'unknown_command_name_here' ] 52 | }); 53 | note slurp('minil.toml'); 54 | 55 | git_init_add_commit(); 56 | 57 | my $project = Minilla::Project->new(); 58 | $project->regenerate_files(); 59 | is_deeply($project->requires_external_bin, ['unknown_command_name_here']); 60 | is($project->module_maker->prereqs($project)->{configure}->{requires}->{'Devel::CheckBin'}, 0); 61 | $project->module_maker->generate($project); 62 | my $err = `$^X Build.PL 2>&1`; 63 | like ($err, qr/Please install 'unknown_command_name_here' seperately and try again./ms, 64 | "missing 'unknown_command_name_here'" 65 | ); 66 | }; 67 | }; 68 | 69 | done_testing; 70 | 71 | -------------------------------------------------------------------------------- /lib/Minilla/Git.pm: -------------------------------------------------------------------------------- 1 | package Minilla::Git; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use parent qw(Exporter); 7 | 8 | our @EXPORT = qw(git_ls_files git_init git_add git_rm git_commit git_config git_remote git_submodules git_submodule_files git_show_toplevel); 9 | 10 | use Minilla::Logger qw(errorf); 11 | use Minilla::Util qw(cmd); 12 | 13 | sub git_init { 14 | cmd('git', 'init'); 15 | } 16 | 17 | sub git_add { 18 | cmd('git', 'add', @_ ? @_ : '.'); 19 | } 20 | 21 | sub git_config { 22 | cmd('git', 'config', @_ ? @_ : '.'); 23 | } 24 | 25 | sub git_rm { 26 | cmd('git', 'rm', @_); 27 | } 28 | 29 | sub git_commit { 30 | cmd('git', 'commit', @_); 31 | } 32 | 33 | sub git_remote { 34 | cmd('git', 'remote', @_); 35 | } 36 | 37 | sub git_ls_files { 38 | my @files = split /\0/, `git ls-files -z`; 39 | return @files; 40 | } 41 | 42 | sub git_submodules { 43 | my @submodules = split /\n/, `git submodule status --recursive`; 44 | my @files; 45 | for (@submodules) { 46 | my ($path) = $_ =~ /^[+\-U\x20][0-9a-f]{40}\x20([^\x20]+).*$/; 47 | push @files, $path if $path; 48 | } 49 | return @files; 50 | } 51 | 52 | sub git_submodule_files { 53 | # XXX: `git ls-files -z` does *NOT* print new line in last. 54 | # So it breaks format when multiple submodules contains and combined with `git submodule foreach`. (and failed to parse.) 55 | my @output = split /\n/, `git submodule foreach --recursive "git ls-files -z"`; 56 | for (my $i = 1; $i <= @output-2; $i += 2) { 57 | $output[$i] =~ s/\0([^\0]*)$//; 58 | splice @output, $i+1, 0, $1; 59 | } 60 | 61 | my @files; 62 | while (@output) { 63 | my $submodule_line = shift @output; 64 | my ($submodule_name) = $submodule_line =~ /'(.+)'/; 65 | push @files, map "$submodule_name/$_", split /\0/, shift @output; 66 | } 67 | return @files; 68 | } 69 | 70 | sub git_show_toplevel { 71 | my $top_level = `git rev-parse --show-toplevel`; 72 | if ( $? != 0 ) { 73 | errorf("Top-level git directory could not be found for %s: %s\n", Cwd::getcwd(), 74 | $? == -1 ? "$!" : 75 | $? & 127 ? "git received signal ". ($? & 127) : "git exited ". ($? >> 8)) 76 | } 77 | chomp $top_level; 78 | return $top_level; 79 | } 80 | 81 | 1; 82 | -------------------------------------------------------------------------------- /t/module_maker/tiny/requires_external_bin.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | use Test::Requires 'Devel::CheckBin'; 8 | 9 | plan skip_all => 'Missing "tar"' unless can_run('tar'); 10 | 11 | use CPAN::Meta; 12 | 13 | use Minilla::Profile::ModuleBuildTiny; 14 | use Minilla::Project; 15 | use Minilla::Git; 16 | 17 | subtest 'develop deps' => sub { 18 | my $guard = pushd(tempdir(CLEANUP => 1)); 19 | 20 | my $profile = Minilla::Profile::ModuleBuildTiny->new( 21 | author => 'Tokuhiro Matsuno', 22 | dist => 'Acme-Foo', 23 | path => 'Acme/Foo.pm', 24 | suffix => 'Foo', 25 | module => 'Acme::Foo', 26 | version => '0.01', 27 | email => 'tokuhirom@example.com', 28 | ); 29 | $profile->generate(); 30 | 31 | subtest 'normal' => sub { 32 | write_minil_toml({ 33 | name => 'Acme-Foo', 34 | requires_external_bin => [ 'tar' ] 35 | }); 36 | note slurp('minil.toml'); 37 | 38 | git_init_add_commit(); 39 | 40 | my $project = Minilla::Project->new(); 41 | $project->regenerate_files(); 42 | is_deeply($project->requires_external_bin, ['tar']); 43 | is($project->module_maker->prereqs($project)->{configure}->{requires}->{'Devel::CheckBin'}, 0); 44 | $project->module_maker->generate($project); 45 | is(system($^X, 'Build.PL'), 0); 46 | }; 47 | 48 | subtest 'Failing case' => sub { 49 | write_minil_toml({ 50 | name => 'Acme-Foo', 51 | requires_external_bin => [ 'unknown_command_name_here' ] 52 | }); 53 | note slurp('minil.toml'); 54 | 55 | git_init_add_commit(); 56 | 57 | my $project = Minilla::Project->new(); 58 | $project->regenerate_files(); 59 | is_deeply($project->requires_external_bin, ['unknown_command_name_here']); 60 | is($project->module_maker->prereqs($project)->{configure}->{requires}->{'Devel::CheckBin'}, 0); 61 | $project->module_maker->generate($project); 62 | my $err = `$^X Build.PL 2>&1`; 63 | like ($err, qr/Please install 'unknown_command_name_here' seperately and try again./ms, 64 | "missing 'unknown_command_name_here'" 65 | ); 66 | }; 67 | }; 68 | 69 | done_testing; 70 | 71 | -------------------------------------------------------------------------------- /t/project/xsutil.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | 8 | use CPAN::Meta; 9 | 10 | use Minilla::Profile::Default; 11 | use Minilla::Project; 12 | use Minilla::Git; 13 | 14 | subtest 'No xsutil' => sub { 15 | my $guard = pushd( tempdir(CLEANUP => 1) ); 16 | 17 | make_profile(); 18 | write_minil_toml( 19 | { name => 'Acme-Foo', 20 | } 21 | ); 22 | git_init_add_commit(); 23 | 24 | my $project = Minilla::Project->new(); 25 | is $project->use_xsutil, 0; 26 | }; 27 | 28 | 29 | subtest 'Use XSUtil with default value' => sub { 30 | my $guard = pushd( tempdir(CLEANUP => 1) ); 31 | 32 | make_profile(); 33 | write_minil_toml( 34 | { name => 'Acme-Foo', 35 | module_maker => "ModuleBuild", 36 | XSUtil => {}, 37 | } 38 | ); 39 | git_init_add_commit(); 40 | 41 | my $project = Minilla::Project->new(); 42 | is $project->use_xsutil, 1; 43 | is $project->needs_compiler_c99, 0; 44 | is $project->needs_compiler_cpp, 0; 45 | is $project->generate_ppport_h, 0; 46 | is $project->generate_xshelper_h, 0; 47 | is $project->cc_warnings, 0; 48 | }; 49 | 50 | subtest 'Use XSUtil with specify value' => sub { 51 | my $guard = pushd( tempdir(CLEANUP => 1) ); 52 | 53 | make_profile(); 54 | write_minil_toml( 55 | { name => 'Acme-Foo', 56 | module_maker => "ModuleBuild", 57 | XSUtil => { 58 | needs_compiler_c99 => 1, 59 | needs_compiler_cpp => 1, 60 | generate_ppport_h => 1, 61 | generate_xshelper_h => 1, 62 | cc_warnings => 1, 63 | }, 64 | } 65 | ); 66 | git_init_add_commit(); 67 | 68 | my $project = Minilla::Project->new(); 69 | is $project->use_xsutil, 1; 70 | is $project->needs_compiler_c99, 1; 71 | is $project->needs_compiler_cpp, 1; 72 | is $project->generate_ppport_h, 1; 73 | is $project->generate_xshelper_h, 1; 74 | is $project->cc_warnings, 1; 75 | }; 76 | 77 | done_testing; 78 | 79 | sub make_profile { 80 | my $profile = Minilla::Profile::Default->new( 81 | author => 'hoge', 82 | dist => 'Acme-Foo', 83 | path => 'Acme/Foo.pm', 84 | module => 'Acme::Foo', 85 | version => '0.01', 86 | ); 87 | $profile->generate(); 88 | } 89 | -------------------------------------------------------------------------------- /lib/Minilla/Profile/XS.pm: -------------------------------------------------------------------------------- 1 | package Minilla::Profile::XS; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use parent qw(Minilla::Profile::ModuleBuild); 6 | 7 | use File::Spec::Functions qw(catfile); 8 | use File::Basename qw(dirname); 9 | use Data::Section::Simple qw(get_data_section); 10 | 11 | use Minilla::Gitignore; 12 | use Minilla::Util qw(require_optional); 13 | use Minilla::Logger; 14 | 15 | sub module_pm_src { 16 | join("\n", 17 | 'use XSLoader;', 18 | 'XSLoader::load(__PACKAGE__, $VERSION);' 19 | ); 20 | } 21 | 22 | sub generate { 23 | my $self = shift; 24 | 25 | require_optional( 'Devel/PPPort.pm', 'PPPort is required for XS support' ); 26 | 27 | $self->render('Module.pm', catfile('lib', $self->path)); 28 | $self->render('Module.xs', catfile('lib', dirname($self->path), $self->suffix . '.xs')); 29 | 30 | my $ppport = catfile(dirname(catfile('lib', $self->path)), 'ppport.h'); 31 | infof("Writing ppport.h: %s\n", $ppport); 32 | Devel::PPPort::WriteFile($ppport); 33 | 34 | $self->render('Changes'); 35 | $self->render('t/00_compile.t'); 36 | $self->render('t/01_simple.t'); 37 | $self->render('github_actions_test.yml', catfile('.github', 'workflows', 'test.yml')); 38 | 39 | $self->render('.gitignore'); 40 | my $gi = Minilla::Gitignore->load('.gitignore'); 41 | $gi->add(catfile('lib', dirname($self->path), $self->suffix . '.c')); 42 | $gi->add("!$ppport"); # Import ppport.h! 43 | $gi->save('.gitignore'); 44 | 45 | $self->write_file('LICENSE', Minilla::License::Perl_5->new( 46 | holder => sprintf('%s <%s>', $self->author, $self->email) 47 | )->fulltext); 48 | 49 | $self->render('cpanfile'); 50 | } 51 | 52 | 1; 53 | __DATA__ 54 | 55 | @@ t/01_simple.t 56 | use strict; 57 | use Test::More; 58 | 59 | use <% $module %>; 60 | 61 | is(<% $module %>::hello(), 'Hello, world!'); 62 | 63 | done_testing; 64 | 65 | @@ Module.xs 66 | #ifdef __cplusplus 67 | extern "C" { 68 | #endif 69 | 70 | #define PERL_NO_GET_CONTEXT /* we want efficiency */ 71 | #include 72 | #include 73 | #include 74 | 75 | #ifdef __cplusplus 76 | } /* extern "C" */ 77 | #endif 78 | 79 | #define NEED_newSVpvn_flags 80 | #include "ppport.h" 81 | 82 | MODULE = <% $module %> PACKAGE = <% $module %> 83 | 84 | PROTOTYPES: DISABLE 85 | 86 | void 87 | hello() 88 | CODE: 89 | { 90 | ST(0) = newSVpvs_flags("Hello, world!", SVs_TEMP); 91 | } 92 | -------------------------------------------------------------------------------- /t/cli/release_check_branch.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use Test::Requires 'Version::Next', 'CPAN::Uploader'; 6 | use lib "t/lib"; 7 | use Util; 8 | use Minilla::Profile::ModuleBuild; 9 | use Minilla::CLI::Release; 10 | 11 | subtest 'wrong release branch' => sub { 12 | my $RELEASE_BRANCH = 'GREAT_RELEASE_BRANCH'; 13 | 14 | my $repo = tempdir(CLEANUP => 1); 15 | { 16 | my $guard = pushd($repo); 17 | cmd('git', 'init', '--bare'); 18 | } 19 | 20 | my $guard = pushd(tempdir(CLEANUP => 1)); 21 | 22 | 23 | Minilla::Profile::ModuleBuild->new( 24 | author => 'hoge', 25 | dist => 'Acme-Foo', 26 | module => 'Acme::Foo', 27 | path => 'Acme/Foo.pm', 28 | version => '0.01', 29 | )->generate(); 30 | write_minil_toml({ 31 | name => 'Acme-Foo', 32 | release => { 33 | branch => $RELEASE_BRANCH, 34 | }, 35 | }); 36 | git_init_add_commit(); 37 | git_remote('add', 'origin', "file://$repo"); 38 | 39 | { 40 | local $ENV{PERL_MM_USE_DEFAULT} = 1; 41 | local $ENV{PERL_MINILLA_SKIP_CHECK_CHANGE_LOG} = 1; 42 | local $ENV{FAKE_RELEASE} = 1; 43 | eval { 44 | Minilla::CLI::Release->run(); 45 | }; 46 | my $e = $@; 47 | like $e, qr!Release branch must be $RELEASE_BRANCH!; 48 | } 49 | }; 50 | 51 | subtest 'correct release branch' => sub { 52 | my $RELEASE_BRANCH = 'main'; 53 | 54 | my $repo = tempdir(CLEANUP => 1); 55 | { 56 | my $guard = pushd($repo); 57 | cmd('git', 'init', '--bare'); 58 | } 59 | 60 | my $guard = pushd(tempdir(CLEANUP => 1)); 61 | 62 | Minilla::Profile::ModuleBuild->new( 63 | author => 'hoge', 64 | dist => 'Acme-Foo', 65 | module => 'Acme::Foo', 66 | path => 'Acme/Foo.pm', 67 | version => '0.01', 68 | )->generate(); 69 | write_minil_toml({ 70 | name => 'Acme-Foo', 71 | release => { 72 | branch => $RELEASE_BRANCH, 73 | }, 74 | }); 75 | git_init_add_commit(); 76 | git_remote('add', 'origin', "file://$repo"); 77 | 78 | cmd('git', 'branch','-M', $RELEASE_BRANCH); 79 | 80 | { 81 | local $ENV{PERL_MM_USE_DEFAULT} = 1; 82 | local $ENV{PERL_MINILLA_SKIP_CHECK_CHANGE_LOG} = 1; 83 | local $ENV{FAKE_RELEASE} = 1; 84 | Minilla::CLI::Release->run(); 85 | pass 'released.'; 86 | } 87 | }; 88 | 89 | done_testing; 90 | 91 | -------------------------------------------------------------------------------- /t/filegatherer.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | use File::Temp qw(tempdir); 8 | use File::pushd; 9 | 10 | use Minilla::Util qw(spew); 11 | use Minilla::FileGatherer; 12 | use Minilla::Git; 13 | 14 | can_ok('Minilla::FileGatherer', 'new'); 15 | 16 | subtest 'FileGatherer' => sub { 17 | my $guard = init(); 18 | 19 | subtest 'normal' => sub { 20 | my @files = Minilla::FileGatherer->new( 21 | exclude_match => ['^local/'], 22 | )->gather_files('.'); 23 | 24 | is(join(',', sort @files), 'META.json,README,foo,libbar/bar.c,libfoo/foo.c'); 25 | }; 26 | 27 | subtest include_dotfiles => sub { 28 | my @files = Minilla::FileGatherer->new( 29 | exclude_match => ['^local/'], 30 | include_dotfiles => 1, 31 | )->gather_files('.'); 32 | 33 | is(join(',', sort @files), '.dot/dot,.gitignore,.gitmodules,META.json,README,foo,libbar/bar.c,libfoo/foo.c,xtra/.dot,xtra/.dotdir/dot'); 34 | }; 35 | 36 | subtest 'MANIFEST.SKIP' => sub { 37 | spew('MANIFEST.SKIP', q{^foo$}); 38 | git_init_add_commit(); 39 | 40 | my @files = Minilla::FileGatherer->new( 41 | exclude_match => ['^local/'], 42 | )->gather_files('.'); 43 | 44 | is(join(',', sort @files), 'MANIFEST.SKIP,META.json,README,libbar/bar.c,libfoo/foo.c'); 45 | }; 46 | }; 47 | 48 | done_testing; 49 | 50 | sub init { 51 | my $guard = pushd(tempdir(CLEANUP => 1)); 52 | my %submodule_repos = map { $_ => create_submodule_repo($_) } qw/foo bar/; 53 | 54 | mkdir 'local'; 55 | mkdir '.dot'; 56 | mkpath 'xtra/.dotdir'; 57 | mkpath 'extlib/lib'; 58 | spew('local/foo', '...'); 59 | spew('extlib/lib/Foo.pm', '...'); 60 | spew('.gitignore', '...'); 61 | spew('README', 'rrr'); 62 | spew('META.json', 'mmm'); 63 | spew('foo', 'mmm'); 64 | spew('.dot/dot', 'dot'); 65 | spew('xtra/.dot', 'dot'); 66 | spew('xtra/.dotdir/dot', '...'); 67 | 68 | git_init(); 69 | git_add('.'); 70 | git_submodule_add("file://$submodule_repos{$_}", "lib$_") for keys %submodule_repos; 71 | git_commit('-m', 'foo'); 72 | 73 | $guard; 74 | } 75 | 76 | sub create_submodule_repo { 77 | my $name = shift; 78 | 79 | my $dir = tempdir(CLEANUP => 1); 80 | my $guard = pushd($dir); 81 | 82 | spew("$name.c", '...'); 83 | git_init(); 84 | git_add('.'); 85 | git_commit('-m', 'submodule'); 86 | 87 | return $dir; 88 | } 89 | -------------------------------------------------------------------------------- /t/bumpversion.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | 8 | use Module::BumpVersion; 9 | use File::Temp qw(tempdir); 10 | use Module::Metadata; 11 | 12 | my $tmpdir = tempdir(CLEANUP => 1); 13 | 14 | subtest 'normal' => sub { 15 | my $tmpfile = "$tmpdir/Foo.pm"; 16 | open my $fh, '>', $tmpfile or die $!; 17 | print {$fh} q{ 18 | package Foo; 19 | our $VERSION="v0.0.1"; 20 | 1; 21 | }; 22 | close $fh; 23 | test($tmpfile); 24 | }; 25 | 26 | subtest 'gah' => sub { 27 | my $tmpfile = "$tmpdir/Bar.pm"; 28 | open my $fh, '>', $tmpfile or die $!; 29 | print {$fh} q{ 30 | package Foo; 31 | use version; our $VERSION = version->declare("v0.0.1"); 32 | 1; 33 | }; 34 | close $fh; 35 | test($tmpfile); 36 | }; 37 | 38 | subtest 'no bumpversion' => sub { 39 | my $tmpfile = "$tmpdir/Baz.pm"; 40 | open my $fh, '>', $tmpfile or die $!; 41 | print {$fh} q{ 42 | package Baz; 43 | our $VERSION="v0.0.1"; # No BumpVersion 44 | 1; 45 | }; 46 | close $fh; 47 | 48 | # check 49 | { 50 | my $meta = Module::Metadata->new_from_file($tmpfile); 51 | is($meta->version('Baz'), 'v0.0.1'); 52 | } 53 | 54 | # bump 55 | { 56 | my $bump = Module::BumpVersion->load($tmpfile); 57 | ok($bump); 58 | ok(!$bump->find_version); 59 | $bump->set_version('v0.0.2'); 60 | } 61 | 62 | # test. 63 | { 64 | my $meta = Module::Metadata->new_from_file($tmpfile); 65 | is($meta->version('Baz'), 'v0.0.1'); 66 | } 67 | }; 68 | 69 | subtest 'package version syntax' => sub { 70 | my $tmpfile = "$tmpdir/Foo.pm"; 71 | open my $fh, '>', $tmpfile or die $!; 72 | print {$fh} q{ 73 | package Foo v0.0.1; 74 | 1; 75 | }; 76 | close $fh; 77 | test($tmpfile); 78 | }; 79 | 80 | done_testing; 81 | 82 | sub test { 83 | my $tmpfile = shift; 84 | 85 | # check 86 | { 87 | my $meta = Module::Metadata->new_from_file($tmpfile); 88 | is($meta->version('Foo'), 'v0.0.1'); 89 | } 90 | 91 | # bump 92 | { 93 | my $bump = Module::BumpVersion->load($tmpfile); 94 | ok($bump); 95 | is($bump->find_version, 'v0.0.1'); 96 | $bump->set_version('v0.0.2'); 97 | } 98 | 99 | # test. 100 | { 101 | my $meta = Module::Metadata->new_from_file($tmpfile); 102 | is($meta->version('Foo'), 'v0.0.2'); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /lib/Minilla/Release/UploadToCPAN.pm: -------------------------------------------------------------------------------- 1 | package Minilla::Release::UploadToCPAN; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use ExtUtils::MakeMaker qw(prompt); 6 | 7 | use Minilla::Util qw(require_optional); 8 | use Minilla::Logger; 9 | 10 | sub init { 11 | require_optional('CPAN/Uploader.pm', 12 | 'Release engineering'); 13 | } 14 | 15 | sub run { 16 | my ($self, $project, $opts) = @_; 17 | 18 | my $work_dir = $project->work_dir(); 19 | my $tar = $work_dir->dist; 20 | 21 | if ($opts->{dry_run} || $ENV{FAKE_RELEASE}) { 22 | infof("Dry run. You don't need the module upload to CPAN\n"); 23 | } elsif ($project->config->{release}->{do_not_upload_to_cpan}) { 24 | infof("You disabled CPAN uploading feature in minil.toml.\n"); 25 | } else { 26 | infof("Upload to CPAN\n"); 27 | 28 | my $pause_config = ($opts->{pause_config}) ? $opts->{pause_config} 29 | : ($project->config->{release}->{pause_config}) ? $project->config->{release}->{pause_config} 30 | : undef; 31 | my $config = CPAN::Uploader->read_config_file($pause_config); 32 | if (!$config || !$config->{user} || !$config->{password}) { 33 | die <{upload_uri} || 'CPAN') . ' ? [y/n] '); 47 | if ($answer =~ /y/i) { 48 | last PROMPT; 49 | } elsif ($answer =~ /n/i) { 50 | errorf("Giving up!\n"); 51 | } else { 52 | redo PROMPT; 53 | } 54 | } 55 | 56 | if ($opts->{trial}) { 57 | my $orig_file = $tar; 58 | $tar =~ s/\.(tar\.gz|tgz|tar.bz2|tbz|zip)$/-TRIAL.$1/ 59 | or die "Distfile doesn't match supported archive format: $orig_file"; 60 | infof("renaming $orig_file -> $tar for TRIAL release\n"); 61 | rename $orig_file, $tar or errorf("Renaming $orig_file -> $tar failed: $!\n"); 62 | } 63 | 64 | my $uploader = CPAN::Uploader->new(+{ 65 | tar => $tar, 66 | %$config 67 | }); 68 | $uploader->upload_file($tar); 69 | } 70 | 71 | unlink($tar) unless Minilla->debug; 72 | } 73 | 74 | 1; 75 | 76 | -------------------------------------------------------------------------------- /t/module_maker/xsutil.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | 8 | use Minilla::Profile::ModuleBuild; 9 | use Minilla::Project; 10 | 11 | test( 12 | {}, 13 | sub { 14 | my $buildpl = slurp('Build.PL'); 15 | like( $buildpl, qr!use Module::Build::XSUtil;! ); 16 | like( $buildpl, qr!needs_compiler_c99\s+=>\s+0! ); 17 | like( $buildpl, qr!needs_compiler_cpp\s+=>\s+0! ); 18 | like( $buildpl, qr!generate_ppport_h\s+=>\s+\'0\'! ); 19 | like( $buildpl, qr!generate_xshelper_h\s+=>\s+\'0\'! ); 20 | like( $buildpl, qr!cc_warnings\s+=>\s+0! ); 21 | } 22 | ); 23 | 24 | test( 25 | { needs_compiler_c99 => 1, 26 | needs_compiler_cpp => 1, 27 | generate_ppport_h => 1, 28 | generate_xshelper_h => 1, 29 | cc_warnings => 1, 30 | }, 31 | sub { 32 | my $buildpl = slurp('Build.PL'); 33 | like( $buildpl, qr!use Module::Build::XSUtil;! ); 34 | like( $buildpl, qr!needs_compiler_c99\s+=>\s+1! ); 35 | like( $buildpl, qr!needs_compiler_cpp\s+=>\s+1! ); 36 | like( $buildpl, qr!generate_ppport_h\s+=>\s+\'1\'! ); 37 | like( $buildpl, qr!generate_xshelper_h\s+=>\s+\'1\'! ); 38 | like( $buildpl, qr!cc_warnings\s+=>\s+1! ); 39 | } 40 | ); 41 | 42 | test( 43 | { 44 | generate_ppport_h => 'lib/ppport.h', 45 | generate_xshelper_h => 'lib/xshelper.h', 46 | }, 47 | sub { 48 | my $buildpl = slurp('Build.PL'); 49 | like( $buildpl, qr!use Module::Build::XSUtil;! ); 50 | like( $buildpl, qr!generate_ppport_h\s+=>\s+\'lib/ppport\.h\'! ); 51 | like( $buildpl, qr!generate_xshelper_h\s+=>\s+\'lib/xshelper\.h\'! ); 52 | } 53 | ); 54 | 55 | done_testing; 56 | 57 | sub test { 58 | my $xsutil = shift; 59 | my $code = shift; 60 | 61 | my $guard = pushd( tempdir(CLEANUP => 1) ); 62 | 63 | Minilla::Profile::ModuleBuild->new( 64 | author => 'hoge', 65 | dist => 'Acme-Foo', 66 | module => 'Acme::Foo', 67 | path => 'Acme/Foo.pm', 68 | version => '0.01', 69 | )->generate(); 70 | 71 | spew( 'MANIFEST', <<'...'); 72 | Build.PL 73 | lib/Acme/Foo.pm 74 | ... 75 | write_minil_toml( 76 | { name => 'Acme-Foo', 77 | XSUtil => $xsutil, 78 | module_maker => "ModuleBuild", 79 | } 80 | ); 81 | git_init_add_commit(); 82 | Minilla::Project->new()->regenerate_files(); 83 | git_init_add_commit(); 84 | $code->(); 85 | } 86 | -------------------------------------------------------------------------------- /contrib/bash-completion.sh: -------------------------------------------------------------------------------- 1 | # bash completion for minil 2 | # 3 | # minil-completion 4 | # ================ 5 | # 6 | # Bash completion support for [minil](https://github.com/tokuhirom/Minilla) 7 | # 8 | # 9 | # Installation 10 | # ------------- 11 | # 12 | # 1. Install bash-completion 13 | # 14 | # 2. Install this file. Either: 15 | # 16 | # a. Place it in a `bash-completion.d` folder: 17 | # 18 | # * /etc/bash-completion.d 19 | # * /usr/local/etc/bash-completion.d 20 | # * ~/bash-completion.d 21 | # 22 | # e.g. 23 | # 24 | # $ cp this-file /etc/bash-completion.d/minil 25 | # 26 | # b. Or, copy it somewhere (e.g. ~/.minil-completion.sh) and put the following line in 27 | # your .bashrc: 28 | # 29 | # source ~/.minil-completion.sh 30 | 31 | _minil() 32 | { 33 | local subcommands cur 34 | _get_comp_words_by_ref cur 35 | subcommands="new build test clean dist install release migrate help" 36 | 37 | case "${COMP_WORDS[1]}" in 38 | new) 39 | subcommands='' 40 | if [[ "${cur}" == -* ]] ; then 41 | subcommands='--profile --username --email --help' 42 | fi 43 | ;; 44 | build) 45 | subcommands='' 46 | if [[ "${cur}" == -* ]] ; then 47 | subcommands='--help' 48 | fi 49 | ;; 50 | test) 51 | subcommands='' 52 | if [[ "${cur}" == -* ]] ; then 53 | subcommands='--release --automated --author --all --help' 54 | fi 55 | ;; 56 | clean) 57 | subcommands='' 58 | if [[ "${cur}" == -* ]] ; then 59 | subcommands='--help -y' 60 | fi 61 | ;; 62 | dist) 63 | subcommands='' 64 | if [[ "${cur}" == -* ]] ; then 65 | subcommands='--help' 66 | fi 67 | ;; 68 | install) 69 | subcommands='' 70 | if [[ "${cur}" == -* ]] ; then 71 | subcommands='--no-test --help' 72 | fi 73 | ;; 74 | release) 75 | subcommands='' 76 | if [[ "${cur}" == -* ]] ; then 77 | subcommands='--no-test --trial --dry-run --pause-config --help' 78 | fi 79 | ;; 80 | migrate) 81 | subcommands='' 82 | if [[ "${cur}" == -* ]] ; then 83 | subcommands='--help' 84 | fi 85 | ;; 86 | help) 87 | return 0 88 | ;; 89 | esac 90 | COMPREPLY=($(compgen -W "${subcommands}" -- ${cur})) 91 | } 92 | complete -F _minil minil 93 | 94 | # Local variables: 95 | # mode: shell-script 96 | # sh-basic-offset: 2 97 | # sh-indent-comment: t 98 | # indent-tabs-mode: nil 99 | # End: 100 | # ex: ts=2 sw=2 et filetype=sh 101 | -------------------------------------------------------------------------------- /t/work_dir/dist.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | 6 | use lib "t/lib"; 7 | use Util; 8 | use File::Spec::Functions qw(catfile); 9 | use Archive::Tar; 10 | use Fcntl qw(:mode); 11 | 12 | use Minilla::Profile::Default; 13 | use Minilla::Project; 14 | use Minilla::Git; 15 | 16 | subtest 'dist' => sub { 17 | my $guard = pushd(tempdir(CLEANUP => 1)); 18 | 19 | my $profile = Minilla::Profile::Default->new( 20 | author => 'tokuhirom', 21 | dist => 'Acme-Foo', 22 | path => 'Acme/Foo.pm', 23 | suffix => 'Foo', 24 | module => 'Acme::Foo', 25 | version => '0.01', 26 | email => 'tokuhirom@example.com', 27 | ); 28 | $profile->generate(); 29 | write_minil_toml('Acme-Foo'); 30 | spew 'empty.txt', ''; 31 | spew 'executable.pl', "#/usr/bin/perl\n"; 32 | chmod 0755, 'executable.pl'; 33 | 34 | git_init(); 35 | git_add('.'); 36 | git_config(qw(user.name tokuhirom)); 37 | git_config(qw(user.email tokuhirom@example.com)); 38 | Minilla::Project->new()->regenerate_files(); 39 | git_commit('-m', 'initial import'); 40 | ok -f 'Build.PL'; 41 | 42 | git_config(qw(user.name Foo)); 43 | git_config(qw(user.email foo@example.com)); 44 | git_commit('--allow-empty', '-m', 'foo'); 45 | git_config(qw(user.name Bar)); 46 | git_config(qw(user.email bar@example.com)); 47 | git_commit('--allow-empty', '-m', 'bar'); 48 | git_commit('--allow-empty', '-m', 'bar2'); 49 | 50 | my $work_dir = Minilla::Project->new()->work_dir; 51 | my $dist = $work_dir->dist(); 52 | my $tar = Archive::Tar->new(); 53 | $tar->read($dist); 54 | 55 | is_deeply( 56 | [sort $tar->list_files], 57 | [do {my %h; 58 | sort 59 | grep {!$h{$_}++} 60 | map { 61 | my $x = "$_"; 62 | $x =~ s!\\!/!g; 63 | "Acme-Foo-0.01/$x" 64 | } 65 | grep /\S/, split /\n/, $tar->get_content('Acme-Foo-0.01/MANIFEST') 66 | }], 67 | "Valid MANIFEST file was generated.", 68 | ); 69 | like($tar->get_content('Acme-Foo-0.01/MANIFEST'), qr{^Build.PL$}sm, 'Contains Build.PL in MANIFEST'); 70 | 71 | like($tar->get_content('Acme-Foo-0.01/MANIFEST'), qr{^empty.txt$}sm, 'Contains empty.txt in MANIFEST'); 72 | ok($tar->contains_file('Acme-Foo-0.01/empty.txt'), 'Contains empty.txt in archive'); 73 | 74 | my ($executable) = $tar->get_files('Acme-Foo-0.01/executable.pl'); 75 | SKIP: { 76 | skip "chmod has a portability issue under Windows", 1 if $^O eq 'MSWin32'; 77 | is($executable->mode & (S_IRWXU|S_IRWXG|S_IRWXO), 0755, 'Contains executable file'); 78 | } 79 | }; 80 | 81 | done_testing; 82 | -------------------------------------------------------------------------------- /lib/Minilla/CLI/Release.pm: -------------------------------------------------------------------------------- 1 | package Minilla::CLI::Release; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use ExtUtils::MakeMaker qw(prompt); 6 | 7 | use Minilla::Util qw(edit_file require_optional parse_options); 8 | use Minilla::WorkDir; 9 | use Minilla::Logger; 10 | use Minilla::Project; 11 | 12 | sub run { 13 | my ($self, @args) = @_; 14 | 15 | my $opts = { 16 | test => 1, 17 | trial => 0, 18 | dry_run => 0, 19 | }; 20 | parse_options( 21 | \@args, 22 | 'test!' => \$opts->{test}, 23 | 'trial!' => \$opts->{trial}, 24 | 'dry-run!' => \$opts->{dry_run}, 25 | 'pause-config=s' => \$opts->{pause_config}, 26 | ); 27 | 28 | my $project = Minilla::Project->new(); 29 | unless ($project->validate()) { 30 | return; 31 | } 32 | 33 | my @steps = qw( 34 | CheckUntrackedFiles 35 | CheckOrigin 36 | CheckReleaseBranch 37 | BumpVersion 38 | CheckChanges 39 | RegenerateFiles 40 | RunHooks 41 | DistTest 42 | MakeDist 43 | 44 | UploadToCPAN 45 | 46 | RewriteChanges 47 | Commit 48 | Tag 49 | ); 50 | my @klasses; 51 | # Load all step classes. 52 | for (@steps) { 53 | my $klass = "Minilla::Release::$_"; 54 | if (eval "require ${klass}; 1") { 55 | push @klasses, $klass; 56 | $klass->init() if $klass->can('init'); 57 | } else { 58 | errorf("Error while loading %s: %s\n", $_, $@); 59 | } 60 | } 61 | # And run all steps. 62 | for my $klass (@klasses) { 63 | $klass->run($project, $opts); 64 | } 65 | } 66 | 67 | 1; 68 | __END__ 69 | 70 | =head1 NAME 71 | 72 | Minilla::CLI::Release - Release the module to CPAN! 73 | 74 | =head1 SYNOPSIS 75 | 76 | % minil release 77 | 78 | --no-test Do not run test scripts 79 | --trial Trial release 80 | --dry-run Dry run mode 81 | --pause-config Path to a CPAN::Uploader configuration file 82 | 83 | =head1 DESCRIPTION 84 | 85 | This sub-command release the module to CPAN. 86 | 87 | =head1 ENVIRONMENT VARIABLES 88 | 89 | =over 4 90 | 91 | =item FAKE_RELEASE 92 | 93 | > FAKE_RELEASE=1 minil release 94 | 95 | If this is your first conversion to Minilla and want to make sure you're not going to mess CPAN with a bad archive when something goes wrong, you can run the release command with FAKE_RELEASE environment variable. This will run all the other release process, except the UploadToCPAN step. 96 | 97 | Note, this runs C<< git tag >> and C<< git push >>. 98 | 99 | =back 100 | -------------------------------------------------------------------------------- /lib/Minilla/CLI/New.pm: -------------------------------------------------------------------------------- 1 | package Minilla::CLI::New; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use File::pushd; 6 | use File::Path qw(mkpath); 7 | 8 | use Minilla::Util qw(check_git cmd parse_options); 9 | use Minilla::Logger; 10 | 11 | sub run { 12 | my ($self, @args) = @_; 13 | 14 | my $username; 15 | my $email; 16 | my $profile = 'Default'; 17 | parse_options( 18 | \@args, 19 | 'username=s' => \$username, 20 | 'email=s' => \$email, 21 | 'p|profile=s' => \$profile, 22 | ); 23 | 24 | my $module = shift @args or errorf("Missing module name\n"); 25 | $module =~ s!-!::!g; 26 | 27 | check_git; 28 | 29 | $username ||= `git config user.name`; 30 | $username =~ s/\n$//; 31 | 32 | $email ||= `git config user.email`; 33 | $email =~ s/\n$//; 34 | 35 | my $version = '0.01'; 36 | 37 | unless ($username) { 38 | errorf("Please set user.name in git, or use `--username` option.\n"); 39 | } 40 | 41 | # $module = "Foo::Bar" 42 | # $suffix = "Bar" 43 | # $dist = "Foo-Bar" 44 | # $path = "Foo/Bar.pm" 45 | my @pkg = split /::/, $module; 46 | my $suffix = $pkg[ @pkg - 1 ]; 47 | my $dist = join "-", @pkg; 48 | my $path = join( "/", @pkg ) . ".pm"; 49 | ( my $dir = $dist ) =~ s/^App-//; 50 | 51 | if (-d $dist) { 52 | errorf("There is %s/\n", $dist); 53 | } 54 | 55 | my $author = $username; 56 | 57 | my $profile_klass = "Minilla::Profile::${profile}"; 58 | eval "require $profile_klass; 1;" or die $@; 59 | my $skelton = $profile_klass->new( 60 | dist => $dist, 61 | path => $path, 62 | author => $username, 63 | suffix => $suffix, 64 | module => $module, 65 | version => $version, 66 | email => $email, 67 | ); 68 | { 69 | mkpath($dist); 70 | my $guard = pushd($dist); 71 | $skelton->generate(); 72 | 73 | # init git repo 74 | infof("Initializing git $module\n"); 75 | cmd('git', 'init'); 76 | 77 | # generate project after initialize git repo 78 | my $project = Minilla::Project->new(); 79 | $project->generate_minil_toml($profile); 80 | cmd('git', 'add', '.'); 81 | $project->regenerate_files(); 82 | 83 | # and git add all things 84 | cmd('git', 'add', '.'); 85 | } 86 | 87 | infof("Finished to create $module\n"); 88 | } 89 | 90 | 1; 91 | __END__ 92 | 93 | =head1 NAME 94 | 95 | Minilla::CLI::New - Generate new module skeleton 96 | 97 | =head1 SYNOPSIS 98 | 99 | # Create new app using Module::Build::Tiny(default) 100 | % minil new MyApp 101 | 102 | # Create new app using XS 103 | % minil new -p XS MyApp 104 | 105 | =head1 DESCRIPTION 106 | 107 | This module creates module skeleton to current directory. 108 | 109 | =head1 OPTIONS 110 | 111 | =over 4 112 | 113 | =back 114 | -------------------------------------------------------------------------------- /contrib/zsh-completion.sh: -------------------------------------------------------------------------------- 1 | #compdef minil 2 | # ------------------------------------------------------------------------------ 3 | # Description 4 | # ----------- 5 | # 6 | # Completion script for minil (https://github.com/tokuhirom/Minilla). 7 | # 8 | # ------------------------------------------------------------------------------ 9 | # Authors 10 | # ------- 11 | # 12 | # * tokuhirom (https://github.com/tokuhirom) 13 | # * syohex (https://github.com/syohex) 14 | # * xaicron (https://github.com/xaicron) 15 | # 16 | # ------------------------------------------------------------------------------ 17 | # Instllation 18 | # ------------ 19 | # 20 | # Copy this file to your $fpath directory. 21 | # For example, if you fpath is ~/.zsh/fpath: 22 | # 23 | # $ cp this-file ~/.zsh/fpath/_minil 24 | # 25 | # You may have to force rebuild zcompdump: 26 | # 27 | # $ rm -f ~/.zcompdump; compinit 28 | # 29 | # ------------------------------------------------------------------------------- 30 | 31 | _minil() { 32 | typeset -A opt_args 33 | local context state line 34 | 35 | local -a _minil_subcommands 36 | _minil_subcommands=( 37 | 'new:Create a new dist' 38 | 'build:Build distribution' 39 | 'test:Run test cases' 40 | 'clean:Clean up directory' 41 | 'dist:Make dist tarball' 42 | 'install:Install distribution' 43 | 'release:Release distribution to CPAN' 44 | 'migrate:Migrate existed distribution repo' 45 | 'help:Help me' 46 | ) 47 | 48 | _arguments '*:: :->subcmd' 49 | 50 | if [[ "$state" == "subcmd" ]];then 51 | 52 | if (( CURRENT == 1 )); then 53 | _describe -t commands "minil command" _minil_subcommands -V1 54 | return 55 | else 56 | local opts curcontext 57 | 58 | case "$words[1]" in 59 | new) 60 | opts=( 61 | '(--username)--username[Specifies Username]:username:' 62 | '(--email)--email[Specifies Email Address]:email:' 63 | '(-p|--profile)'{-p,--profile}'[Minilla profile]: :(XS)' 64 | ) 65 | ;; 66 | clean) 67 | opts=('(-y)-y[delete files without asking]') 68 | ;; 69 | install) 70 | opts=('--no-test[Do not run test]') 71 | ;; 72 | release) 73 | opts=( 74 | '--no-test[Do not run test]' 75 | '--trial[Trial release]' 76 | '--dry-run[Dry run mode]' 77 | '--pause-config=[Path to a CPAN::Uploader configuration file]:file:_files' 78 | ) 79 | ;; 80 | test) 81 | opts=( 82 | '--release[enable the RELEASE_TESTING env variable]' 83 | '--automated[enable the AUTOMATED_TESTING env variable]' 84 | '--author[enable the AUTHOR_TESTING env variable]' 85 | '--all[enable the All env variables]' 86 | ) 87 | ;; 88 | help) 89 | local -a subcommands 90 | subcommands=(new build test clean dist install release migrate help) 91 | _arguments -s : "()"'*: :'"($subcommands)" 92 | return 0 93 | ;; 94 | *) 95 | opts=() 96 | ;; 97 | esac 98 | _arguments -s : "$opts[@]" '*::Files:_files' 99 | fi 100 | fi 101 | } 102 | -------------------------------------------------------------------------------- /t/filegatherer/submodules-recursive.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | use File::Temp qw(tempdir); 8 | use File::pushd; 9 | 10 | use Minilla::Util qw(spew); 11 | use Minilla::FileGatherer; 12 | use Minilla::Git; 13 | 14 | can_ok('Minilla::FileGatherer', 'new'); 15 | 16 | subtest 'FileGatherer' => sub { 17 | my $guard = init(); 18 | 19 | subtest 'normal' => sub { 20 | my @files = Minilla::FileGatherer->new( 21 | exclude_match => ['^local/'], 22 | )->gather_files('.'); 23 | 24 | is(join(',', sort @files), 'META.json,README,foo,libbar/bar.c,libbar/libbar/bar.c,libbar/libfoo/foo.c,libfoo/foo.c,libfoo/libbar/bar.c,libfoo/libfoo/foo.c'); 25 | }; 26 | 27 | subtest include_dotfiles => sub { 28 | my @files = Minilla::FileGatherer->new( 29 | exclude_match => ['^local/'], 30 | include_dotfiles => 1, 31 | )->gather_files('.'); 32 | 33 | is(join(',', sort @files), '.dot/dot,.gitignore,.gitmodules,META.json,README,foo,libbar/.gitmodules,libbar/bar.c,libbar/libbar/bar.c,libbar/libfoo/foo.c,libfoo/.gitmodules,libfoo/foo.c,libfoo/libbar/bar.c,libfoo/libfoo/foo.c,xtra/.dot,xtra/.dotdir/dot'); 34 | }; 35 | 36 | subtest 'MANIFEST.SKIP' => sub { 37 | spew('MANIFEST.SKIP', q{^foo$}); 38 | git_init_add_commit(); 39 | 40 | my @files = Minilla::FileGatherer->new( 41 | exclude_match => ['^local/'], 42 | )->gather_files('.'); 43 | 44 | is(join(',', sort @files), 'MANIFEST.SKIP,META.json,README,libbar/bar.c,libbar/libbar/bar.c,libbar/libfoo/foo.c,libfoo/foo.c,libfoo/libbar/bar.c,libfoo/libfoo/foo.c'); 45 | }; 46 | }; 47 | 48 | done_testing; 49 | 50 | sub init { 51 | my $guard = pushd(tempdir(CLEANUP => 1)); 52 | my %submodule_repos = map { $_ => create_deep_submodule_repo($_) } qw/foo bar/; 53 | 54 | mkdir 'local'; 55 | mkdir '.dot'; 56 | mkpath 'xtra/.dotdir'; 57 | mkpath 'extlib/lib'; 58 | spew('local/foo', '...'); 59 | spew('extlib/lib/Foo.pm', '...'); 60 | spew('.gitignore', '...'); 61 | spew('README', 'rrr'); 62 | spew('META.json', 'mmm'); 63 | spew('foo', 'mmm'); 64 | spew('.dot/dot', 'dot'); 65 | spew('xtra/.dot', 'dot'); 66 | spew('xtra/.dotdir/dot', '...'); 67 | 68 | git_init(); 69 | git_add('.'); 70 | git_submodule_add("file://$submodule_repos{$_}", "lib$_") for keys %submodule_repos; 71 | git_submodule_update_init_recursive(); 72 | git_commit('-m', 'foo'); 73 | 74 | $guard; 75 | } 76 | 77 | sub create_deep_submodule_repo { 78 | my $name = shift; 79 | 80 | my $dir = create_submodule_repo($name); 81 | my $guard = pushd($dir); 82 | 83 | my %submodule_repos = map { $_ => create_submodule_repo($_) } qw/foo bar/; 84 | 85 | git_add('.'); 86 | git_submodule_add("file://$submodule_repos{$_}", "lib$_") for keys %submodule_repos; 87 | git_commit('-m', 'deep submodule'); 88 | 89 | return $dir; 90 | } 91 | 92 | sub create_submodule_repo { 93 | my $name = shift; 94 | 95 | my $dir = tempdir(CLEANUP => 1); 96 | my $guard = pushd($dir); 97 | 98 | spew("$name.c", '...'); 99 | git_init(); 100 | git_add('.'); 101 | git_commit('-m', 'submodule'); 102 | 103 | return $dir; 104 | } 105 | -------------------------------------------------------------------------------- /lib/Minilla/ModuleMaker/ModuleBuildTiny.pm: -------------------------------------------------------------------------------- 1 | package Minilla::ModuleMaker::ModuleBuildTiny; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use Data::Section::Simple qw(get_data_section); 6 | use Text::MicroTemplate qw(render_mt); 7 | use Data::Dumper; 8 | use File::Spec::Functions qw(catdir rel2abs); 9 | use File::Find (); 10 | use TAP::Harness::Env; 11 | use Cwd; 12 | 13 | use Moo; 14 | 15 | no Moo; 16 | 17 | use Minilla::Util qw(spew_raw); 18 | 19 | sub generate { 20 | my ($self, $project) = @_; 21 | 22 | local $Data::Dumper::Terse = 1; 23 | local $Data::Dumper::Useqq = 1; 24 | local $Data::Dumper::Purity = 1; 25 | local $Data::Dumper::Indent = 0; 26 | my $content = get_data_section('Build.PL'); 27 | my $mt = Text::MicroTemplate->new(template => $content, escape_func => sub { $_[0] }); 28 | my $src = $mt->build->($project); 29 | spew_raw('Build.PL', $src); 30 | } 31 | 32 | sub validate { 33 | my $self = shift; 34 | 35 | if (-d 'bin/') { 36 | warn "[ERROR] Module::Build::Tiny doesn't install bin/ directory. You should rename bin/ to script/\n"; 37 | return 0; 38 | } 39 | return 1; # Yes. It's valid project. 40 | } 41 | 42 | sub prereqs { 43 | my ($self, $project) = @_; 44 | 45 | my %configure_requires = ( 46 | 'Module::Build::Tiny' => '0.035', 47 | ); 48 | if ( @{$project->requires_external_bin || []} ) { 49 | $configure_requires{'Devel::CheckBin'} = 0; 50 | } 51 | 52 | my $prereqs = +{ 53 | configure => { 54 | requires => { 55 | %configure_requires, 56 | } 57 | } 58 | }; 59 | 60 | for my $key (qw(tap_harness_args use_xsutil c_source allow_pureperl)) { 61 | if( $project->$key ){ 62 | die "$key does not supported by " . __PACKAGE__; 63 | } 64 | } 65 | for my $key (qw(PL_files)) { 66 | if( $project->$key && ref($project->$key) eq 'HASH' && %{$project->$key} > 0 ){ 67 | die "$key does not supported by " . __PACKAGE__; 68 | } 69 | } 70 | return $prereqs; 71 | } 72 | 73 | sub run_tests { 74 | my $harness = TAP::Harness::Env->create({ 75 | verbosity => 0, 76 | lib => [ map { rel2abs(catdir(qw/blib/, $_), cwd) } qw/arch lib/ ], 77 | color => -t STDOUT 78 | }); 79 | my @tests = sort +_find(qr/\.t$/, 't'); 80 | if ($ENV{RELEASE_TESTING}) { 81 | push @tests, sort +_find(qr/\.t$/, 'xt'); 82 | } 83 | $harness->runtests(@tests)->has_errors and die; 84 | } 85 | 86 | sub _find { 87 | my ($pattern, $dir) = @_; 88 | my @ret; 89 | File::Find::find(sub { push @ret, $File::Find::name if /$pattern/ && -f }, $dir) if -d $dir; 90 | return @ret; 91 | } 92 | 93 | 1; 94 | __DATA__ 95 | 96 | @@ Build.PL 97 | ? my $project = shift; 98 | # ========================================================================= 99 | # THIS FILE IS AUTOMATICALLY GENERATED BY MINILLA. 100 | # DO NOT EDIT DIRECTLY. 101 | # ========================================================================= 102 | 103 | use 5.008_001; 104 | use strict; 105 | 106 | use Module::Build::Tiny 0.035; 107 | 108 | ? if ( @{ $project->unsupported->os } ) { 109 | ? for my $os ( @{ $project->unsupported->os } ) { 110 | die "OS unsupported\n" if $^O eq ; 111 | ? } 112 | 113 | ? } 114 | ? if ( @{ $project->requires_external_bin || [] } ) { 115 | use Devel::CheckBin; 116 | 117 | ? for my $bin ( @{ $project->requires_external_bin } ) { 118 | check_bin(''); 119 | ? } 120 | 121 | ? } 122 | Build_PL(); 123 | 124 | -------------------------------------------------------------------------------- /t/project/static_install.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib "t/lib"; 6 | use Util; 7 | 8 | use Minilla; 9 | use Minilla::Project; 10 | use Minilla::CLI::New; 11 | use CPAN::Meta; 12 | use TOML qw(from_toml to_toml); 13 | 14 | subtest basic => sub { 15 | my $guard = pushd(tempdir(CLEANUP => 1)); 16 | Minilla::CLI::New->run('Acme::Foo'); 17 | chdir "Acme-Foo"; 18 | 19 | my ($config, $meta); 20 | $config = from_toml slurp("minil.toml"); 21 | is $config->{static_install}, "auto"; 22 | 23 | $meta = CPAN::Meta->load_file("META.json")->as_struct; 24 | is $meta->{x_static_install}, 1; 25 | 26 | $config->{static_install} = 0; 27 | spew("minil.toml", to_toml $config); 28 | Minilla::Project->new->regenerate_files; 29 | $meta = CPAN::Meta->load_file("META.json")->as_struct; 30 | is $meta->{x_static_install}, 0; 31 | 32 | $config->{static_install} = 1; 33 | spew("minil.toml", to_toml $config); 34 | Minilla::Project->new->regenerate_files; 35 | $meta = CPAN::Meta->load_file("META.json")->as_struct; 36 | is $meta->{x_static_install}, 1; 37 | }; 38 | 39 | my $static_install = sub { 40 | my $sub = shift; 41 | my $guard = pushd(tempdir(CLEANUP => 1)); 42 | Minilla::CLI::New->run('Acme::Foo'); 43 | chdir "Acme-Foo"; 44 | $sub->(); 45 | git_add("."); 46 | Minilla::Project->new->regenerate_files; 47 | git_add("."); 48 | CPAN::Meta->load_file("META.json")->as_struct->{x_static_install}; 49 | }; 50 | 51 | subtest auto_detection => sub { 52 | my $test; 53 | 54 | $test = sub { 55 | mkdir "script"; 56 | spew "script/foo", "#!perl\nprint 'hello'\n"; 57 | }; 58 | is $static_install->($test), 1; 59 | 60 | $test = sub { 61 | mkdir "share"; 62 | spew "share/foo", "this is a share file"; 63 | }; 64 | is $static_install->($test), 1; 65 | 66 | $test = sub { 67 | my $config = from_toml slurp "minil.toml"; 68 | $config->{module_maker} = "ExtUtilsMakeMaker"; 69 | spew "minil.toml", to_toml $config; 70 | }; 71 | is $static_install->($test), 1; 72 | 73 | $test = sub { 74 | mkdir "bin"; 75 | spew "bin/foo", "#!perl\nprint 'hello'\n"; 76 | }; 77 | is $static_install->($test), 0; 78 | 79 | $test = sub { 80 | spew "lib/Acme/Foo.xs", "/* this is a xs code */\n"; 81 | }; 82 | is $static_install->($test), 0; 83 | 84 | $test = sub { 85 | my $config = from_toml slurp "minil.toml"; 86 | $config->{requires_external_bin} = ["git"]; 87 | spew "minil.toml", to_toml $config; 88 | }; 89 | is $static_install->($test), 0; 90 | 91 | $test = sub { 92 | my $config = from_toml slurp "minil.toml"; 93 | $config->{unsupported}{os} = ["MSWin32"]; 94 | spew "minil.toml", to_toml $config; 95 | }; 96 | is $static_install->($test), 0; 97 | 98 | $test = sub { 99 | my $config = from_toml slurp "minil.toml"; 100 | $config->{build}{build_class} = 'builder::MyBuilder'; 101 | spew "minil.toml", to_toml $config; 102 | mkdir "builder"; 103 | spew "builder/MyBuilder", q{ 104 | package builder::MyBuilder; 105 | use base 'Module::Build'; 106 | }; 107 | }; 108 | is $static_install->($test), 0; 109 | 110 | $test = sub { 111 | spew "lib/Acme/Bar.pm.PL", "# this is a PL file\n"; 112 | }; 113 | is $static_install->($test), 0; 114 | 115 | $test = sub { 116 | my $config = from_toml slurp "minil.toml"; 117 | $config->{PL_files}{"Bar.pm.PL"} = "lib/Acme/Bar.pm"; 118 | $config->{module_maker} = "ModuleBuild"; 119 | spew "minil.toml", to_toml $config; 120 | spew "Bar.pm.PL", "# this is a PL file\n"; 121 | }; 122 | is $static_install->($test), 0; 123 | }; 124 | 125 | done_testing; 126 | -------------------------------------------------------------------------------- /lib/Minilla/ReleaseTest.pm: -------------------------------------------------------------------------------- 1 | package Minilla::ReleaseTest; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use Data::Section::Simple qw(get_data_section); 6 | use File::pushd; 7 | use File::Spec::Functions qw(catfile); 8 | use File::Path qw(mkpath); 9 | 10 | use Minilla::Logger; 11 | use Minilla::Util qw(spew); 12 | 13 | sub write_release_tests { 14 | my ($class, $project, $dir) = @_; 15 | 16 | mkpath(catfile($dir, 'xt', 'minilla')); 17 | 18 | my $stopwords = do { 19 | my $append_people_into_stopwords = sub { 20 | my $people = shift; 21 | my @stopwords; 22 | for (@{$people || +[]}) { 23 | s!<.*!!; # trim e-mail address 24 | push @stopwords, split(/\s+/, $_); 25 | } 26 | return @stopwords; 27 | }; 28 | 29 | my @stopwords; 30 | push @stopwords, $append_people_into_stopwords->($project->contributors); 31 | push @stopwords, $append_people_into_stopwords->($project->authors); 32 | local $Data::Dumper::Terse = 1; 33 | local $Data::Dumper::Useqq = 1; 34 | local $Data::Dumper::Purity = 1; 35 | local $Data::Dumper::Indent = 0; 36 | Data::Dumper::Dumper([map { split /\s+/, $_ } @stopwords]); 37 | }; 38 | 39 | my $config = $project->config->{ReleaseTest}; 40 | my $name = $project->dist_name; 41 | for my $file (qw( 42 | xt/minilla/minimum_version.t 43 | xt/minilla/cpan_meta.t 44 | xt/minilla/pod.t 45 | xt/minilla/spelling.t 46 | xt/minilla/permissions.t 47 | )) { 48 | infof("Writing release tests: %s\n", $file); 49 | 50 | if ($file eq 'xt/minilla/minimum_version.t' && ($config->{MinimumVersion}||'') eq 'false') { 51 | infof("Skipping MinimumVersion"); 52 | next; 53 | } 54 | 55 | my $content = get_data_section($file); 56 | $content =~s!<>!$name!g; 57 | $content =~s!<>!$stopwords!g; 58 | spew(catfile($dir, $file), $content); 59 | } 60 | } 61 | 62 | sub prereqs { 63 | +{ 64 | develop => { 65 | requires => { 66 | 'Test::MinimumVersion::Fast' => 0.04, 67 | 'Test::CPAN::Meta' => 0, 68 | 'Test::Pod' => 1.41, 69 | 'Test::Spellunker' => 'v0.2.7', 70 | 'Test::PAUSE::Permissions' => 0.07, 71 | }, 72 | }, 73 | }; 74 | } 75 | 76 | 1; 77 | __DATA__ 78 | 79 | @@ xt/minilla/minimum_version.t 80 | use Test::More; 81 | eval "use Test::MinimumVersion::Fast 0.04"; 82 | if ($@) { 83 | plan skip_all => "Test::MinimumVersion::Fast required for testing perl minimum version"; 84 | } 85 | all_minimum_version_from_metayml_ok(); 86 | 87 | @@ xt/minilla/cpan_meta.t 88 | use Test::More; 89 | eval "use Test::CPAN::Meta"; 90 | plan skip_all => "Test::CPAN::Meta required for testing META.yml" if $@; 91 | plan skip_all => "There is no META.yml" unless -f "META.yml"; 92 | meta_yaml_ok(); 93 | 94 | @@ xt/minilla/pod.t 95 | use strict; 96 | use Test::More; 97 | eval "use Test::Pod 1.41"; 98 | plan skip_all => "Test::Pod 1.41 required for testing POD" if $@; 99 | all_pod_files_ok(); 100 | 101 | @@ xt/minilla/spelling.t 102 | use strict; 103 | use Test::More; 104 | use File::Spec; 105 | eval q{ use Test::Spellunker v0.2.2 }; 106 | plan skip_all => "Test::Spellunker is not installed." if $@; 107 | 108 | plan skip_all => "no ENV[HOME]" unless $ENV{HOME}; 109 | my $spelltest_switchfile = ".spellunker.en"; 110 | plan skip_all => "no ~/$spelltest_switchfile" unless -e File::Spec->catfile($ENV{HOME}, $spelltest_switchfile); 111 | 112 | add_stopwords('<>'); 113 | add_stopwords(@{<>}); 114 | 115 | all_pod_files_spelling_ok('lib'); 116 | 117 | @@ xt/minilla/permissions.t 118 | use strict; 119 | use Test::More; 120 | 121 | eval q{ use Test::PAUSE::Permissions 0.04 }; 122 | plan skip_all => "Test::PAUSE::Permissions is not installed." if $@; 123 | 124 | all_permissions_ok({dev => 1}); 125 | -------------------------------------------------------------------------------- /lib/Minilla/Release/BumpVersion.pm: -------------------------------------------------------------------------------- 1 | package Minilla::Release::BumpVersion; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use ExtUtils::MakeMaker qw(prompt); 6 | 7 | use Minilla::Util qw(find_file require_optional cmd); 8 | use Minilla::Logger; 9 | use Module::BumpVersion; 10 | use version (); 11 | 12 | sub init { 13 | require_optional( 14 | 'Module/BumpVersion.pm', 'Release engineering' 15 | ); 16 | require_optional( 17 | 'Version/Next.pm', 'Release engineering' 18 | ); 19 | } 20 | 21 | sub run { 22 | my ($self, $project, $opts) = @_; 23 | 24 | if (my $ver = prompt("Next Release?", $self->default_new_version($project))) { 25 | # Do not use is_strict. is_strict rejects '5.00_01' style. 26 | if (!version::is_lax($ver)) { 27 | errorf("Sorry, version '%s' is invalid. Stopping.\n", $ver); 28 | } 29 | 30 | my $curr_ver = $project->metadata->version; 31 | if (!check_version_compatibility($curr_ver, $ver)) { 32 | my $msg = sprintf 33 | "version: %s\n" . 34 | "current: %s\n" . 35 | "The version format doesn't match the current one.\n" . 36 | "Continue the release with this version? [y/n]", $ver, $curr_ver; 37 | if (prompt($msg) !~ /y/i) { 38 | errorf("Stop the release due to version format mismatch\n"); 39 | } 40 | } 41 | 42 | my @opts; 43 | push @opts, '-set', $ver; 44 | if ($opts->{dry_run}) { 45 | push @opts, '-dryrun'; 46 | } 47 | unless ($opts->{dry_run}) { 48 | $self->bump_version($project, $ver); 49 | 50 | # clear old version information 51 | $project->clear_metadata(); 52 | my $newver = $project->metadata->version; 53 | if (exists_tag($project->format_tag($newver))) { 54 | errorf("Sorry, version '%s' is already tagged. Stopping.\n", $newver); 55 | } 56 | } 57 | } 58 | } 59 | 60 | sub bump_version { 61 | my ($self, $project, $version) = @_; 62 | 63 | for my $file ($project->perl_files) { 64 | next if $file =~ /\.t$/; 65 | next if $file =~ m{\Ashare/}; 66 | 67 | next if $file eq 'Makefile.PL' || $file eq 'Build.PL'; 68 | # copy from Menlo::CLI::Compat 69 | next if grep { $file =~ m!^$_/! } @{$project->no_index->{directory} || []}; 70 | next if grep { $file eq $_ } @{$project->no_index->{file} || []}; 71 | 72 | my $bump = Module::BumpVersion->load($file); 73 | $bump->set_version($version); 74 | } 75 | } 76 | 77 | sub default_new_version { 78 | my ($self, $project) = @_; 79 | @_==2 or die; 80 | 81 | my $curver = $project->metadata->version; 82 | if (not exists_tag($project->format_tag($curver))) { 83 | $curver; 84 | } else { 85 | # $project->metadata->version returns version.pm object. 86 | # But stringify was needed by Version::Next. 87 | return Version::Next::next_version("$curver"); 88 | } 89 | } 90 | 91 | sub check_version_compatibility { 92 | my ($curr, $next) = @_; 93 | 94 | return version_format($curr) eq version_format($next) 95 | } 96 | 97 | sub version_format { 98 | local $_ = shift; 99 | # All formats accept an optional alpha notation starting with '_'. 100 | return 101 | # ex. 0.11, 3.14, 9.4_1 102 | /^(?:0|[1-9][0-9]*)\.[0-9]+(?:_[0-9]+)?$/ ? 'decimal' : 103 | # ex. v1.2.3, v1.2.3_4, v3.3, v3.4_5 104 | /^v(?:0|[1-9][0-9]*)(?:\.[0-9]+){1,2}(?:_[0-9]+)?$/ ? 'dotted' : 105 | # ex. 0.1.2, 3.4.5_67 (to distinguish it from the decimal version, it must have exactly two dots) 106 | /^(?:0|[1-9][0-9]*)(?:\.[0-9]+){2}(?:_[0-9]+)?$/ ? 'lax dotted' : 107 | 'unknown'; 108 | } 109 | 110 | sub exists_tag { 111 | my ( $tag ) = @_; 112 | 113 | my $x = `git tag -l $tag`; 114 | chomp $x; 115 | return !!$x; 116 | } 117 | 118 | 1; 119 | -------------------------------------------------------------------------------- /lib/Minilla/ModuleMaker/ExtUtilsMakeMaker.pm: -------------------------------------------------------------------------------- 1 | package Minilla::ModuleMaker::ExtUtilsMakeMaker; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use Data::Section::Simple qw(get_data_section); 6 | use Text::MicroTemplate qw(render_mt); 7 | use Data::Dumper; 8 | use File::Spec::Functions qw(catdir rel2abs); 9 | use File::Find (); 10 | use TAP::Harness::Env; 11 | use Cwd; 12 | 13 | # This module is EXPERIMENTAL. 14 | # You can use this. But I may change the behaviour... 15 | 16 | use Moo; 17 | 18 | no Moo; 19 | 20 | use Minilla::Util qw(spew_raw); 21 | 22 | sub generate { 23 | my ($self, $project) = @_; 24 | 25 | local $Data::Dumper::Terse = 1; 26 | local $Data::Dumper::Useqq = 1; 27 | local $Data::Dumper::Purity = 1; 28 | local $Data::Dumper::Indent = 1; 29 | local $Data::Dumper::Sortkeys = 1; 30 | my $content = get_data_section('Makefile.PL'); 31 | my $mt = Text::MicroTemplate->new(template => $content, escape_func => sub { $_[0] }); 32 | my $src = $mt->build->($project); 33 | spew_raw('Makefile.PL', $src); 34 | } 35 | 36 | sub prereqs { 37 | my ($self, $project) = @_; 38 | 39 | my %configure_requires = ( 40 | 'ExtUtils::MakeMaker' => $self->_eumm_minimum_version($project), 41 | ); 42 | 43 | my $prereqs = +{ 44 | configure => { 45 | requires => { 46 | %configure_requires, 47 | } 48 | } 49 | }; 50 | 51 | for my $key (qw(tap_harness_args use_xsutil c_source allow_pureperl requires_external_bin)) { 52 | if( $project->$key ){ 53 | die "$key does not supported by " . __PACKAGE__; 54 | } 55 | } 56 | return $prereqs; 57 | } 58 | 59 | sub _eumm_minimum_version { 60 | my ($self, $project) = @_; 61 | 62 | if (@{ $project->unsupported->os }) { 63 | return '7.26'; # os_unsupported 64 | } 65 | return '6.64'; # TEST_REQUIRES (and MYMETA) 66 | } 67 | 68 | sub run_tests { 69 | my $harness = TAP::Harness::Env->create({ 70 | verbosity => 0, 71 | lib => [ map { rel2abs(catdir(qw/blib/, $_), cwd) } qw/arch lib/ ], 72 | color => -t STDOUT 73 | }); 74 | my @tests = sort +_find(qr/\.t$/, 't'); 75 | if ($ENV{RELEASE_TESTING}) { 76 | push @tests, sort +_find(qr/\.t$/, 'xt'); 77 | } 78 | $harness->runtests(@tests)->has_errors and die; 79 | } 80 | 81 | sub _find { 82 | my ($pattern, $dir) = @_; 83 | my @ret; 84 | File::Find::find(sub { push @ret, $File::Find::name if /$pattern/ && -f }, $dir) if -d $dir; 85 | return @ret; 86 | } 87 | 88 | 1; 89 | __DATA__ 90 | 91 | @@ Makefile.PL 92 | ? my $project = shift; 93 | ? use Data::Dumper; 94 | # ========================================================================= 95 | # THIS FILE IS AUTOMATICALLY GENERATED BY MINILLA. 96 | # DO NOT EDIT DIRECTLY. 97 | # ========================================================================= 98 | 99 | use 5.006; 100 | use strict; 101 | 102 | use ExtUtils::MakeMaker module_maker->_eumm_minimum_version($project) ?>; 103 | 104 | ? if ( @{ $project->unsupported->os } ) { 105 | ? for my $os ( @{ $project->unsupported->os } ) { 106 | os_unsupported if $^O eq ; 107 | ? } 108 | 109 | ? } 110 | ? if ( @{ $project->requires_external_bin || [] } ) { 111 | use Devel::CheckBin; 112 | 113 | ? for my $bin ( @{ $project->requires_external_bin } ) { 114 | check_bin(''); 115 | ? } 116 | 117 | ? } 118 | ? my $prereqs = $project->cpan_meta->effective_prereqs; 119 | ? my $d = sub { Dumper($prereqs->merged_requirements([$_[0]], ['requires'])->as_string_hash) }; 120 | my %WriteMakefileArgs = ( 121 | NAME => 'name ?>', 122 | DISTNAME => 'dist_name ?>', 123 | VERSION => 'version ?>', 124 | EXE_FILES => [script_files ?>], 125 | CONFIGURE_REQUIRES => ('configure') ?>, 126 | BUILD_REQUIRES => ('build') ?>, 127 | TEST_REQUIRES => ('test') ?>, 128 | PREREQ_PM => ('runtime') ?>, 129 | ); 130 | 131 | WriteMakefile(%WriteMakefileArgs); 132 | -------------------------------------------------------------------------------- /lib/Minilla/Util.pm: -------------------------------------------------------------------------------- 1 | package Minilla::Util; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use Carp (); 6 | use File::Basename (); 7 | use File::Spec (); 8 | use File::Which 'which'; 9 | use Minilla::Logger (); 10 | use Getopt::Long (); 11 | use Cwd(); 12 | 13 | use parent qw(Exporter); 14 | 15 | our @EXPORT_OK = qw( 16 | find_dir find_file 17 | randstr 18 | slurp slurp_utf8 slurp_raw 19 | spew spew_utf8 spew_raw 20 | edit_file require_optional 21 | cmd cmd_perl 22 | pod_escape 23 | parse_options 24 | check_git 25 | ); 26 | 27 | our %EXPORT_TAGS = ( 28 | all => \@EXPORT_OK 29 | ); 30 | 31 | sub randstr { 32 | my $len = shift; 33 | my @chars = ("a".."z","A".."Z",0..9); 34 | my $ret = ''; 35 | join('', map { $chars[int(rand(scalar(@chars)))] } 1..$len); 36 | } 37 | 38 | sub slurp { 39 | my $fname = shift; 40 | open my $fh, '<', $fname 41 | or Carp::croak("Can't open '$fname' for reading: '$!'"); 42 | scalar do { local $/; <$fh> } 43 | } 44 | 45 | sub slurp_utf8 { 46 | my $fname = shift; 47 | open my $fh, '<:encoding(UTF-8)', $fname 48 | or Carp::croak("Can't open '$fname' for reading: '$!'"); 49 | scalar do { local $/; <$fh> } 50 | } 51 | 52 | sub slurp_raw { 53 | my $fname = shift; 54 | open my $fh, '<:raw', $fname 55 | or Carp::croak("Can't open '$fname' for reading: '$!'"); 56 | scalar do { local $/; <$fh> } 57 | } 58 | 59 | sub spew($$) { 60 | my $fname = shift; 61 | open my $fh, '>', $fname 62 | or Carp::croak("Can't open '$fname' for writing: '$!'"); 63 | print {$fh} $_[0]; 64 | } 65 | 66 | sub spew_raw { 67 | my $fname = shift; 68 | open my $fh, '>:raw', $fname 69 | or Carp::croak("Can't open '$fname' for writing: '$!'"); 70 | print {$fh} $_[0]; 71 | } 72 | 73 | sub spew_utf8 { 74 | my $fname = shift; 75 | open my $fh, '>:encoding(UTF8)', $fname 76 | or Carp::croak("Can't open '$fname' for writing: '$!'"); 77 | print {$fh} $_[0]; 78 | } 79 | 80 | sub edit_file { 81 | my ($file) = @_; 82 | my $editor = $ENV{"EDITOR"} || "vi"; 83 | system( $editor, $file ); 84 | } 85 | 86 | sub find_file { 87 | my ($file) = @_; 88 | 89 | my $dir = Cwd::getcwd(); 90 | my %seen; 91 | while ( -d $dir ) { 92 | return undef if $seen{$dir}++; # guard from deep recursion 93 | if ( -f "$dir/$file" ) { 94 | return "$dir/$file"; 95 | } 96 | $dir = File::Basename::dirname($dir); 97 | } 98 | 99 | return undef; 100 | } 101 | 102 | sub find_dir { 103 | my ($file) = @_; 104 | 105 | my $dir = Cwd::getcwd(); 106 | my %seen; 107 | while ( -d $dir ) { 108 | return undef if $seen{$dir}++; # guard from deep recursion 109 | if ( -d "$dir/$file" ) { 110 | return "$dir/$file"; 111 | } 112 | $dir = File::Basename::dirname($dir); 113 | } 114 | 115 | return undef; 116 | } 117 | 118 | sub require_optional { 119 | my ( $file, $feature, $library ) = @_; 120 | 121 | return if exists $INC{$file}; 122 | unless ( eval { require $file } ) { 123 | if ( $@ =~ /^Can't locate/ ) { 124 | $library ||= do { 125 | local $_ = $file; 126 | s/ \.pm \z//xms; 127 | s{/}{::}g; 128 | $_; 129 | }; 130 | Carp::croak( "$feature requires $library, but it is not available." 131 | . " Please install $library using your preferred CPAN client" ); 132 | } 133 | else { 134 | die $@; 135 | } 136 | } 137 | } 138 | 139 | sub cmd_perl { 140 | my(@args) = @_; 141 | 142 | my @abs_inc = map { $_ eq '.' ? $_ : File::Spec->rel2abs($_) } 143 | @INC; 144 | 145 | cmd($^X, (map { "-I$_" } @abs_inc), @args); 146 | } 147 | 148 | sub cmd { 149 | Minilla::Logger::infof("[%s] \$ %s\n", File::Basename::basename(Cwd::getcwd()), "@_"); 150 | system(@_) == 0 151 | or Minilla::Logger::errorf("Giving up.\n"); 152 | } 153 | 154 | sub parse_options { 155 | my ( $args, @spec ) = @_; 156 | Getopt::Long::GetOptionsFromArray( $args, @spec ); 157 | } 158 | 159 | sub pod_escape { 160 | local $_ = shift; 161 | my %POD_ESCAPE = ( '<' => 'E', '>' => 'E' ); 162 | s!([<>])!$POD_ESCAPE{$1}!ge; 163 | $_; 164 | } 165 | 166 | sub check_git { 167 | unless (which 'git') { 168 | Minilla::Logger::errorf("The \"git\" executable has not been found.\n"); 169 | } 170 | } 171 | 172 | 1; 173 | 174 | -------------------------------------------------------------------------------- /lib/Module/BumpVersion.pm: -------------------------------------------------------------------------------- 1 | package Module::BumpVersion; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | sub load { 7 | my ($class, $name) = @_; 8 | 9 | my $lines; 10 | my $is_perl = sub { 11 | return 1 if $name =~ m{ [.] (?i: pl | pm | t ) $ }x; 12 | $lines = $class->read_file($name); 13 | return 1 if @$lines && $lines->[0] =~ m{ ^ \#\! .* perl }ix; 14 | return; 15 | }->(); 16 | $lines ||= $class->read_file($name); 17 | return unless $is_perl; 18 | return unless $lines; 19 | 20 | bless {lines => $lines, name => $name}, $class; 21 | } 22 | 23 | sub read_file { 24 | my ($class, $name) = @_; 25 | open my $fh, '<:raw', $name 26 | or die "Cannot open '$name' for readding: $!"; 27 | my @ret = <$fh>; 28 | close $fh; 29 | return \@ret; 30 | } 31 | 32 | sub set_version { 33 | my ($self, $new_version) = @_; 34 | 35 | my $versions = $self->versions; 36 | my @lines = @{$self->{lines}}; 37 | my $dirty; 38 | for my $edits ( values %$versions ) { 39 | for my $edit (@$edits) { 40 | $lines[ $edit->{line} ] = 41 | $edit->{pre} . $new_version . $edit->{post} . "\n"; 42 | $dirty++; 43 | } 44 | } 45 | return unless $dirty; 46 | 47 | open my $fh, '>:raw', $self->{name} 48 | or die "Cannot open '$self->{name}' for writing: $!"; 49 | print {$fh} $_ for @lines; 50 | close $fh; 51 | } 52 | 53 | sub find_version { 54 | my $self = shift; 55 | my ($version) = keys %{$self->versions}; 56 | return $version; 57 | } 58 | 59 | sub versions { 60 | my $self = shift; 61 | $self->{versions} ||= $self->_find_version_for_doc(); 62 | } 63 | 64 | sub _find_version_for_doc { 65 | my ( $self ) = @_; 66 | 67 | my $name = $self->{name}; 68 | 69 | my $machine = $self->scanner(); 70 | my $state = $machine->{init}; 71 | my $lines = $self->{lines}; 72 | my $ver_found = {}; 73 | 74 | LINE: 75 | for my $ln ( 0 .. @$lines - 1 ) { 76 | my $line = $lines->[$ln]; 77 | 78 | next LINE if $line =~ /# No BumpVersion/; 79 | 80 | # Bail out when we're in a state with no possible actions. 81 | last LINE unless @$state; 82 | 83 | STATE: { 84 | for my $trans (@$state) { 85 | if ( my @match = $line =~ $trans->{re} ) { 86 | if ( $trans->{mark} ) { 87 | my $ver = $2 . $3 . $4; 88 | push @{ $ver_found->{ $ver } }, 89 | { 90 | file => $name, 91 | info => $self, 92 | line => $ln, 93 | pre => $1, 94 | ver => $ver, 95 | post => $5 96 | }; 97 | } 98 | 99 | if ( my $code = $trans->{exec} ) { 100 | $code->( $machine, \@match, $line ); 101 | } 102 | 103 | if ( my $goto = $trans->{goto} ) { 104 | $state = $machine->{$goto}; 105 | redo STATE; 106 | } 107 | } 108 | } 109 | } 110 | } 111 | return $ver_found; 112 | } 113 | 114 | sub version_re_perl { 115 | my $ver_re = shift; 116 | 117 | return qr{ ^ ( .*? [\$\*] (?: \w+ (?: :: | ' ) )* VERSION \s* = 118 | \D*? ) 119 | $ver_re 120 | ( .* ) $ }x; 121 | } 122 | 123 | sub version_re_pod { 124 | my $ver_re = shift; 125 | return qr{ ^ ( .*? (?i: version ) .*? ) $ver_re ( .* ) $ }x; 126 | } 127 | 128 | sub version_re_package { 129 | my $ver_re = shift; 130 | return qr{ ^ ( package \s+ \S+ \s+ ) $ver_re (.*) $ }x; 131 | } 132 | 133 | # State machine for Perl source 134 | sub scanner{ 135 | # Perl::Version::REGEX 136 | my $ver_re = qr/ ( (?i: Revision: \s+ ) | v | ) 137 | ( \d+ (?: [.] \d+)* ) 138 | ( (?: _ \d+ )? ) /x; 139 | 140 | { 141 | init => [ 142 | { 143 | re => qr{ ^ = (?! cut ) }x, 144 | goto => 'pod', 145 | }, 146 | { 147 | re => version_re_perl($ver_re), 148 | mark => 1, 149 | }, 150 | { 151 | re => version_re_package($ver_re), 152 | mark => 1, 153 | }, 154 | ], 155 | 156 | # pod within perl 157 | pod => [ 158 | { 159 | re => qr{ ^ =head\d\s+VERSION\b }x, 160 | goto => 'version', 161 | }, 162 | { 163 | re => qr{ ^ =cut }x, 164 | goto => 'init', 165 | }, 166 | ], 167 | 168 | # version section within pod 169 | version => [ 170 | { 171 | re => qr{ ^ = (?! head\d\s+VERSION\b ) }x, 172 | goto => 'pod', 173 | }, 174 | { 175 | re => version_re_pod($ver_re), 176 | mark => 1, 177 | }, 178 | 179 | ], 180 | }; 181 | } 182 | 183 | 184 | 1; 185 | -------------------------------------------------------------------------------- /lib/Minilla/Profile/Base.pm: -------------------------------------------------------------------------------- 1 | package Minilla::Profile::Base; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use File::Spec::Functions qw(catfile); 6 | use File::Path qw(mkpath); 7 | use File::Basename qw(dirname); 8 | use Data::Section::Simple; 9 | use Time::Piece; 10 | 11 | use Minilla::Util qw(spew_raw); 12 | use Minilla::Logger; 13 | 14 | use Moo; 15 | 16 | has [qw(dist path module)] => ( 17 | is => 'ro', 18 | required => 1, 19 | ); 20 | 21 | has 'version' => ( 22 | is => 'ro', 23 | default => sub { '0.01' }, 24 | ); 25 | 26 | has suffix => ( 27 | is => 'lazy', 28 | required => 1, 29 | ); 30 | 31 | has [qw(email author)] => ( 32 | is => 'lazy', 33 | required => 1, 34 | ); 35 | 36 | no Moo; 37 | 38 | sub _build_author { 39 | my $self = shift; 40 | 41 | my $name ||= `git config user.name`; 42 | $name =~ s/\n$//; 43 | 44 | unless ($name) { 45 | errorf("You need to set user.name in git config.\nRun: git config user.name 'Your name'\n"); 46 | } 47 | 48 | $name; 49 | } 50 | 51 | sub _build_email { 52 | my $self = shift; 53 | 54 | my $email ||= `git config user.email`; 55 | $email =~ s/\n$//; 56 | 57 | unless ($email) { 58 | errorf("You need to set user.email in git config.\nRun: git config user.email 'name\@example.com'\n"); 59 | } 60 | 61 | $email; 62 | } 63 | 64 | sub _build_suffix { 65 | my $self = shift; 66 | my $suffix = $self->path; 67 | $suffix =~ s!^.+/!!; 68 | $suffix =~ s!\.pm!!; 69 | $suffix; 70 | } 71 | 72 | sub new_from_project { 73 | my ($class, $project) = @_; 74 | 75 | my $path = $project->main_module_path; 76 | $path =~ s!^lib/!!; 77 | my $self = $class->new( 78 | dist => $project->dist_name, 79 | author => $project->authors ? $project->authors->[0] : 'Unknown Author', 80 | version => $project->version, 81 | path => $path, 82 | module => $project->name, 83 | ); 84 | return $self; 85 | } 86 | 87 | sub date { 88 | gmtime->strftime('%Y-%m-%dT%H:%M:%SZ'); 89 | } 90 | 91 | sub end { '__END__' } 92 | 93 | sub module_pm_src { '' } 94 | 95 | sub render { 96 | my ($self, $tmplname, $dst) = @_; 97 | my $path = $dst || $tmplname; 98 | 99 | infof("Writing %s\n", $path); 100 | mkpath(dirname($path)); 101 | 102 | for my $pkg (@{mro::get_linear_isa(ref $self || $self)}) { 103 | my $content = Data::Section::Simple->new($pkg)->get_data_section($tmplname); 104 | next unless defined $content; 105 | $content =~ s!<%\s*\$([a-z_]+)\s*%>! 106 | $self->$1() 107 | !ge; 108 | spew_raw($path, $content); 109 | return; 110 | } 111 | errorf("Cannot find template for %s\n", $tmplname); 112 | } 113 | 114 | sub write_file { 115 | my ($self, $path, $content) = @_; 116 | 117 | infof("Writing %s\n", $path); 118 | mkpath(dirname($path)); 119 | spew_raw($path, $content); 120 | } 121 | 122 | 123 | 1; 124 | __DATA__ 125 | 126 | @@ t/00_compile.t 127 | use strict; 128 | use Test::More 0.98; 129 | 130 | use_ok $_ for qw( 131 | <% $module %> 132 | ); 133 | 134 | done_testing; 135 | 136 | @@ Module.pm 137 | package <% $module %>; 138 | use 5.008001; 139 | use strict; 140 | use warnings; 141 | 142 | our $VERSION = "<% $version %>"; 143 | 144 | <% $module_pm_src %> 145 | 146 | 1; 147 | <% $end %> 148 | 149 | =encoding utf-8 150 | 151 | =head1 NAME 152 | 153 | <% $module %> - It's new $module 154 | 155 | =head1 SYNOPSIS 156 | 157 | use <% $module %>; 158 | 159 | =head1 DESCRIPTION 160 | 161 | <% $module %> is ... 162 | 163 | =head1 LICENSE 164 | 165 | Copyright (C) <% $author %>. 166 | 167 | This library is free software; you can redistribute it and/or modify 168 | it under the same terms as Perl itself. 169 | 170 | =head1 AUTHOR 171 | 172 | <% $author %> E<% $email %>E 173 | 174 | =cut 175 | 176 | @@ github_actions_test.yml 177 | name: test 178 | on: [push, pull_request] 179 | jobs: 180 | build: 181 | runs-on: ubuntu-latest 182 | strategy: 183 | matrix: 184 | perl: 185 | [ 186 | "5.42", 187 | "5.40", 188 | "5.38", 189 | "5.36", 190 | "5.34", 191 | "5.32", 192 | "5.30", 193 | "5.28", 194 | "5.26", 195 | "5.24", 196 | "5.22", 197 | "5.20", 198 | "5.18", 199 | "5.16", 200 | "5.14", 201 | "5.12", 202 | "5.10" 203 | ] 204 | name: Perl ${{ matrix.perl }} 205 | steps: 206 | - uses: actions/checkout@v3 207 | - name: Setup perl 208 | uses: shogo82148/actions-setup-perl@v1 209 | with: 210 | perl-version: ${{ matrix.perl }} 211 | - name: Install dependencies 212 | run: cpanm -nq --installdeps --with-develop --with-recommends . 213 | - name: Run test 214 | run: prove -lr t 215 | 216 | @@ Changes 217 | Revision history for Perl extension <% $dist %> 218 | 219 | {{$NEXT}} 220 | 221 | - original version 222 | 223 | @@ .gitignore 224 | /.build/ 225 | /_build/ 226 | /Build 227 | /Build.bat 228 | /blib 229 | /Makefile 230 | /pm_to_blib 231 | 232 | /carton.lock 233 | /.carton/ 234 | /local/ 235 | 236 | nytprof.out 237 | nytprof/ 238 | 239 | cover_db/ 240 | 241 | *.bak 242 | *.old 243 | *~ 244 | *.swp 245 | *.o 246 | *.obj 247 | 248 | !LICENSE 249 | 250 | /_build_params 251 | 252 | MYMETA.* 253 | 254 | /<% $dist %>-* 255 | -------------------------------------------------------------------------------- /lib/Minilla/ModuleMaker/ModuleBuild.pm: -------------------------------------------------------------------------------- 1 | package Minilla::ModuleMaker::ModuleBuild; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use Data::Section::Simple qw(get_data_section); 6 | use Text::MicroTemplate qw(render_mt); 7 | use Data::Dumper; 8 | use Minilla::Util qw(cmd_perl); 9 | 10 | use Moo; 11 | 12 | no Moo; 13 | 14 | use Minilla::Util qw(spew_raw); 15 | 16 | sub generate { 17 | my ($self, $project) = @_; 18 | 19 | Carp::croak('Usage: $module_maker->generate($project)') unless defined $project; 20 | 21 | local $Data::Dumper::Terse = 1; 22 | local $Data::Dumper::Useqq = 1; 23 | local $Data::Dumper::Purity = 1; 24 | local $Data::Dumper::Indent = 0; 25 | my $content = get_data_section('Build.PL'); 26 | my $mt = Text::MicroTemplate->new(template => $content, escape_func => sub { $_[0] }); 27 | my $src = $mt->build->($project); 28 | spew_raw('Build.PL', $src); 29 | } 30 | 31 | sub prereqs { 32 | my ($self, $project) = @_; 33 | 34 | Carp::croak('Usage: $module_maker->prereqs($project)') unless defined $project; 35 | 36 | my %configure_requires = ( 37 | 'Module::Build' => 0.4005, # test_requires, --pureperl 38 | ); 39 | if ($project->requires_external_bin && @{$project->requires_external_bin}) { 40 | $configure_requires{'Devel::CheckBin'} = 0; 41 | } 42 | 43 | my $prereqs = +{ 44 | configure => { 45 | requires => { 46 | %configure_requires, 47 | } 48 | } 49 | }; 50 | 51 | if( $project->use_xsutil ){ 52 | $prereqs->{configure}{requires}{'Module::Build::XSUtil'} = '0.03'; 53 | } 54 | return $prereqs; 55 | } 56 | 57 | sub run_tests { 58 | cmd_perl('Build', 'test'); 59 | } 60 | 61 | 1; 62 | __DATA__ 63 | 64 | @@ Build.PL 65 | ? my $project = shift; 66 | ? use Data::Dumper; 67 | # ========================================================================= 68 | # THIS FILE IS AUTOMATICALLY GENERATED BY MINILLA. 69 | # DO NOT EDIT DIRECTLY. 70 | # ========================================================================= 71 | 72 | use 5.008_001; 73 | 74 | use strict; 75 | use warnings; 76 | use utf8; 77 | 78 | ? if ( $project->build_class !~ m/^Module::Build(?:::XSUtil)?$/ ) { 79 | BEGIN { push @INC, '.' } 80 | ? } 81 | use build_class ?>; 82 | use File::Basename; 83 | use File::Spec; 84 | 85 | ? if ( @{ $project->unsupported->os } ) { 86 | ? for my $os ( @{ $project->unsupported->os } ) { 87 | die "OS unsupported\n" if $^O eq ; 88 | ? } 89 | 90 | ? } 91 | ? if ( @{ $project->requires_external_bin || [] } ) { 92 | use Devel::CheckBin; 93 | 94 | ? for my $bin ( @{ $project->requires_external_bin } ) { 95 | check_bin(''); 96 | ? } 97 | 98 | ? } 99 | my %args = ( 100 | license => 'metadata->license->meta2_name ?>', 101 | dynamic_config => 0, 102 | 103 | configure_requires => { 104 | 'Module::Build' => '0.4005', 105 | }, 106 | 107 | requires => { 108 | ? for my $requirement ( sort keys %{ $project->cpan_meta->prereqs->{runtime}{requires} } ){ 109 | '' => 'cpan_meta->prereqs->{runtime}{requires}{$requirement} ?>', 110 | ? } 111 | }, 112 | 113 | recommends => { 114 | ? for my $recommendation ( sort keys %{ $project->cpan_meta->prereqs->{runtime}{recommends} } ){ 115 | '' => 'cpan_meta->prereqs->{runtime}{recommends}{$recommendation} ?>', 116 | ? } 117 | }, 118 | 119 | suggests => { 120 | ? for my $suggestion ( sort keys %{ $project->cpan_meta->prereqs->{runtime}{suggests} } ){ 121 | '' => 'cpan_meta->prereqs->{runtime}{suggests}{$suggestion} ?>', 122 | ? } 123 | }, 124 | 125 | build_requires => { 126 | ? for my $requirement ( sort keys %{ $project->cpan_meta->prereqs->{build}{requires} } ){ 127 | '' => 'cpan_meta->prereqs->{build}{requires}{$requirement} ?>', 128 | ? } 129 | }, 130 | 131 | test_requires => { 132 | ? for my $requirement ( sort keys %{ $project->cpan_meta->prereqs->{test}{requires} } ){ 133 | '' => 'cpan_meta->prereqs->{test}{requires}{$requirement} ?>', 134 | ? } 135 | }, 136 | 137 | name => 'dist_name ?>', 138 | module_name => 'name ?>', 139 | allow_pureperl => allow_pureperl ?>, 140 | 141 | script_files => [script_files ?>], 142 | ? if ($project->c_source) { 143 | c_source => [qw(c_source ?>)], 144 | ? } 145 | PL_files => PL_files) ?>, 146 | 147 | test_files => ((-d '.git' || $ENV{RELEASE_TESTING}) && -d 'xt') ? 't/ xt/' : 't/', 148 | recursive_test_files => 1, 149 | 150 | ? if( $project->tap_harness_args ){ 151 | tap_harness_args => tap_harness_args) ?>, 152 | ? } 153 | 154 | ? if( $project->use_xsutil ){ 155 | needs_compiler_c99 => needs_compiler_c99 ?>, 156 | needs_compiler_cpp => needs_compiler_cpp ?>, 157 | generate_ppport_h => 'generate_ppport_h ?>', 158 | generate_xshelper_h => 'generate_xshelper_h ?>', 159 | cc_warnings => cc_warnings ?>, 160 | ? } 161 | ); 162 | if (-d 'share') { 163 | $args{share_dir} = 'share'; 164 | } 165 | 166 | my $builder = build_class ?>->subclass( 167 | class => 'MyBuilder', 168 | code => q{ 169 | sub ACTION_distmeta { 170 | die "Do not run distmeta. Install Minilla and `minil install` instead.\n"; 171 | } 172 | sub ACTION_installdeps { 173 | die "Do not run installdeps. Run `cpanm --installdeps .` instead.\n"; 174 | } 175 | } 176 | )->new(%args); 177 | $builder->create_build_script(); 178 | 179 | -------------------------------------------------------------------------------- /META.json: -------------------------------------------------------------------------------- 1 | { 2 | "abstract" : "CPAN module authoring tool", 3 | "author" : [ 4 | "Tokuhiro Matsuno < tokuhirom@gmail.com >" 5 | ], 6 | "dynamic_config" : 0, 7 | "generated_by" : "Minilla/v3.1.28, CPAN::Meta::Converter version 2.150010", 8 | "license" : [ 9 | "perl_5" 10 | ], 11 | "meta-spec" : { 12 | "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", 13 | "version" : 2 14 | }, 15 | "name" : "Minilla", 16 | "no_index" : { 17 | "directory" : [ 18 | "t", 19 | "xt", 20 | "inc", 21 | "share", 22 | "eg", 23 | "examples", 24 | "author", 25 | "builder" 26 | ] 27 | }, 28 | "prereqs" : { 29 | "configure" : { 30 | "requires" : { 31 | "Module::Build::Tiny" : "0.035" 32 | } 33 | }, 34 | "develop" : { 35 | "requires" : { 36 | "Test::CPAN::Meta" : "0", 37 | "Test::MinimumVersion::Fast" : "0.04", 38 | "Test::PAUSE::Permissions" : "0.07", 39 | "Test::Pod" : "1.41", 40 | "Test::Spellunker" : "v0.2.7" 41 | } 42 | }, 43 | "runtime" : { 44 | "recommends" : { 45 | "CPAN::Uploader" : "0", 46 | "Pod::Escapes" : "0", 47 | "Software::License" : "0.103010", 48 | "Test::CPAN::Meta" : "0", 49 | "Test::MinimumVersion::Fast" : "0.04", 50 | "Test::PAUSE::Permissions" : "0", 51 | "Test::Pod" : "0", 52 | "Test::Spellunker" : "v0.2.7", 53 | "Version::Next" : "0" 54 | }, 55 | "requires" : { 56 | "App::cpanminus" : "1.6902", 57 | "Archive::Tar" : "1.60", 58 | "CPAN::Meta" : "2.132830", 59 | "Data::Section::Simple" : "0.04", 60 | "ExtUtils::Manifest" : "1.54", 61 | "File::Which" : "0", 62 | "File::pushd" : "0", 63 | "Getopt::Long" : "2.36", 64 | "Module::CPANfile" : "0.9025", 65 | "Module::Metadata" : "1.000037", 66 | "Module::Runtime" : "0", 67 | "Moo" : "1.001", 68 | "Pod::Markdown" : "1.322", 69 | "TAP::Harness::Env" : "0", 70 | "TOML" : "0.95", 71 | "Term::ANSIColor" : "0", 72 | "Term::Encoding" : "0", 73 | "Text::MicroTemplate" : "0.20", 74 | "Time::Piece" : "1.16", 75 | "Try::Tiny" : "0", 76 | "URI" : "0", 77 | "parent" : "0", 78 | "perl" : "5.010001", 79 | "version" : "0" 80 | }, 81 | "suggests" : { 82 | "Devel::PPPort" : "0" 83 | } 84 | }, 85 | "test" : { 86 | "recommends" : { 87 | "Devel::CheckLib" : "0" 88 | }, 89 | "requires" : { 90 | "CPAN::Meta::Validator" : "0", 91 | "File::Copy::Recursive" : "0", 92 | "File::Temp" : "0", 93 | "JSON" : "0", 94 | "Module::Build::Tiny" : "0.035", 95 | "Test::More" : "0.98", 96 | "Test::Output" : "0", 97 | "Test::Requires" : "0" 98 | }, 99 | "suggests" : { 100 | "Dist::Zilla" : "0" 101 | } 102 | } 103 | }, 104 | "release_status" : "unstable", 105 | "resources" : { 106 | "bugtracker" : { 107 | "web" : "https://github.com/tokuhirom/Minilla/issues" 108 | }, 109 | "homepage" : "https://github.com/tokuhirom/Minilla", 110 | "repository" : { 111 | "type" : "git", 112 | "url" : "https://github.com/tokuhirom/Minilla.git", 113 | "web" : "https://github.com/tokuhirom/Minilla" 114 | } 115 | }, 116 | "version" : "v3.1.29", 117 | "x_contributors" : [ 118 | "Alex Kapranoff ", 119 | "Alex Kapranoff ", 120 | "Alexander Karelas ", 121 | "AnaTofuZ ", 122 | "Anirvan Chatterjee ", 123 | "Chris White ", 124 | "Chris White ", 125 | "Dabrien 'Dabe' Murphy ", 126 | "Daisuke Maki ", 127 | "Daisuke Murase ", 128 | "Dan Book ", 129 | "Fitz Elliott ", 130 | "Fuji, Goro ", 131 | "GeJ ", 132 | "Graham Knop ", 133 | "Helmut Wollmersdorfer ", 134 | "Hiroyuki Akabane ", 135 | "ITO Nobuaki ", 136 | "Jason Cooper ", 137 | "Jiro Nishiguchi ", 138 | "Jose Luis Perez Diez ", 139 | "Jörg Forstreuter ", 140 | "Kazumasa Utashiro ", 141 | "Keiji, Yoshimi ", 142 | "Kenichi Ishigaki ", 143 | "Klaus Eichner ", 144 | "Manni Heumann ", 145 | "Masahiro Nagano ", 146 | "Nathaniel Nutter ", 147 | "Oleg Gashev ", 148 | "Peter Oliver ", 149 | "Pine Mizune ", 150 | "Ryo Okamoto ", 151 | "Sebastian B. Knapp ", 152 | "Shohei YOSHIDA ", 153 | "Shoichi Kaji ", 154 | "Shoichi Kaji ", 155 | "Songmu ", 156 | "TAGOMORI Satoshi ", 157 | "Takumi Akiyama ", 158 | "Tatsuhiko Miyagawa ", 159 | "Tatsuhiko Miyagawa ", 160 | "Thibault DUPONCHELLE ", 161 | "Yuji Shimada ", 162 | "Zak B. Elep ", 163 | "aereal ", 164 | "amarnus ", 165 | "hideo55 ", 166 | "iwata-motonori ", 167 | "karupanerura ", 168 | "kazhiramatsu ", 169 | "keita.iseki ", 170 | "kfly8 ", 171 | "kobaken ", 172 | "mattn ", 173 | "moznion ", 174 | "sago35 ", 175 | "tokuhirom ", 176 | "vti ", 177 | "ysasaki " 178 | ], 179 | "x_serialization_backend" : "JSON::PP version 4.16", 180 | "x_static_install" : 1 181 | } 182 | -------------------------------------------------------------------------------- /lib/Minilla/Migrate.pm: -------------------------------------------------------------------------------- 1 | package Minilla::Migrate; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use File::pushd; 7 | use CPAN::Meta; 8 | use File::Find (); 9 | use TOML 0.92 qw(to_toml); 10 | use Config; 11 | 12 | use Minilla::Gitignore; 13 | use Minilla::Util qw( 14 | slurp spew require_optional 15 | cmd cmd_perl slurp_utf8 spew_utf8 16 | slurp_raw spew_raw 17 | ); 18 | use Minilla::Logger; 19 | use Minilla::Git; 20 | use Minilla::Project; 21 | 22 | use Moo; 23 | 24 | has project => ( 25 | is => 'lazy', 26 | ); 27 | 28 | no Moo; 29 | 30 | sub _build_project { 31 | my $self = shift; 32 | Minilla::Project->new(); 33 | } 34 | 35 | sub run { 36 | my $self = shift; 37 | 38 | if (!-d '.git') { 39 | # init git repo 40 | infof("Initializing git\n"); 41 | cmd('git', 'init'); 42 | } 43 | 44 | my $guard = pushd($self->project->dir); 45 | 46 | # Generate cpanfile from Build.PL/Makefile.PL 47 | unless (-f 'cpanfile') { 48 | $self->migrate_cpanfile(); 49 | } 50 | 51 | $self->generate_license(); 52 | 53 | # TODO move top level *.pm to lib/? 54 | 55 | if (-f 'dist.ini') { 56 | $self->dist_ini2minil_toml(); 57 | } elsif (!-f 'minil.toml') { 58 | $self->project->generate_minil_toml('Default'); 59 | } 60 | 61 | $self->remove_unused_files(); 62 | $self->migrate_gitignore(); 63 | $self->project->regenerate_files(); 64 | $self->migrate_changes(); 65 | 66 | git_add('.'); 67 | } 68 | 69 | sub migrate_changes { 70 | my $self = shift; 71 | 72 | if (-f 'Changes') { 73 | # Q. Why :raw? 74 | # A. It's for windows. See dzil. 75 | my $content = slurp_raw('Changes'); 76 | unless ($content =~ qr!\{\{\$NEXT\}\}!) { 77 | $content =~ s!^(Revision history for Perl extension \S+\n\n)!$1\{\{\$NEXT\}\}\n\n!; 78 | } 79 | spew_raw('Changes', $content); 80 | } else { 81 | # Q. Why :raw? 82 | # A. It's for windows. See dzil. 83 | require Minilla::Profile::Default; 84 | Minilla::Profile::Default->new_from_project( 85 | $self->project 86 | )->render('Changes'); 87 | } 88 | } 89 | 90 | sub rm { 91 | my ($self, $file) = @_; 92 | } 93 | 94 | sub dist_ini2minil_toml { 95 | my $self = shift; 96 | 97 | infof("Converting dist.ini to minil.toml\n"); 98 | 99 | require_optional( 'Config/MVP/Reader/INI.pm', 'Migrate dzil repo' ); 100 | require_optional( 'Dist/Zilla/MVP/Assembler.pm', 'Migrate dzil repo' ); 101 | require_optional( 'Dist/Zilla/Chrome/Term.pm', 'Migrate dzil repo' ); 102 | my $sequence = Config::MVP::Reader::INI->read_into_assembler( 103 | 'dist.ini', 104 | Dist::Zilla::MVP::Assembler->new( 105 | chrome => Dist::Zilla::Chrome::Term->new(), 106 | ) 107 | ); 108 | my $conf = do { 109 | # Note. dist.ini using @Milla does not have '_' section. 110 | my $section = $sequence->section_named('_'); 111 | $section ? $section->payload : +{}; 112 | }; 113 | 114 | my $dst = +{}; 115 | for my $key (qw(name author version license)) { 116 | if ( defined(my $val = $conf->{$key}) ) { 117 | $dst->{$key} = $val; 118 | } 119 | } 120 | if (%$dst) { 121 | my $toml = to_toml($dst); 122 | spew( 'minil.toml' => $toml ); 123 | git_add('minil.toml'); 124 | } 125 | git_rm('--quiet', 'dist.ini'); 126 | 127 | $self->project->clear_metadata(); 128 | } 129 | 130 | sub generate_license { 131 | my ($self) = @_; 132 | 133 | unless (-f 'LICENSE') { 134 | spew_raw('LICENSE', $self->project->metadata->license->fulltext()); 135 | git_add(qw(-f LICENSE)); 136 | } 137 | } 138 | 139 | sub migrate_cpanfile { 140 | my ($self) = @_; 141 | 142 | my $metafile; 143 | if (-f 'Build.PL') { 144 | if (slurp('Build.PL') =~ /Module::Build::Tiny/) { 145 | infof("M::B::Tiny was detected. I hope META.json is already exists here\n"); 146 | $metafile = 'META.json'; 147 | } else { 148 | cmd_perl('Build.PL'); 149 | $metafile = 'MYMETA.json'; 150 | } 151 | } elsif (-f 'Makefile.PL') { 152 | # ExtUtils::MakeMaker or Module::Install's 153 | cmd_perl('Makefile.PL'); 154 | cmd($Config{make}, 'metafile'); 155 | $metafile = 'MYMETA.json'; 156 | } elsif (-f 'dist.ini') { 157 | my %orig = map { $_ => 1 } glob('*/META.yml'); 158 | cmd_perl('-S', 'dzil', 'build'); 159 | ($metafile) = grep { !$orig{$_} } glob('*/META.yml'); 160 | } else { 161 | errorf("There is no Build.PL/Makefile.PL/dist.ini: %s\n", Cwd::getcwd()); 162 | } 163 | 164 | unless (defined($metafile) && -f $metafile) { 165 | errorf("Build.PL/Makefile.PL does not generates %s\n", $metafile); 166 | } 167 | 168 | my $meta = CPAN::Meta->load_file($metafile); 169 | my $prereqs = $meta->effective_prereqs->as_string_hash; 170 | 171 | infof("Using Module::Build (Because this distribution uses xs)\n"); 172 | delete $prereqs->{configure}->{requires}->{'ExtUtils::MakeMaker'}; 173 | delete $prereqs->{configure}->{requires}->{'Module::Build'}; 174 | delete $prereqs->{configure}->{requires}->{'Module::Build::Tiny'}; 175 | 176 | my $cpanfile = Module::CPANfile->from_prereqs($prereqs); 177 | spew('cpanfile', $cpanfile->to_string); 178 | 179 | git_add('cpanfile'); 180 | } 181 | 182 | sub remove_unused_files { 183 | my $self = shift; 184 | 185 | # Some users put a README.pod symlink for main module. 186 | # But it's duplicated to README.md created by Minilla. 187 | 188 | # remove some unusable files 189 | for my $file (qw( 190 | Makefile.PL 191 | MANIFEST 192 | MANIFEST.SKIP 193 | .shipit 194 | xt/97_podspell.t 195 | xt/99_pod.t 196 | xt/01_podspell.t xt/03_pod.t xt/05_cpan_meta.t 197 | xt/04_minimum_version.t xt/06_meta_author.t 198 | xt/podspell.t 199 | MANIFEST.SKIP.bak 200 | MANIFEST.bak 201 | README.pod 202 | META.yml 203 | README 204 | MYMETA.json 205 | MYMETA.yml 206 | inc/Module/Install.pm 207 | ), glob('inc/Module/Install/*.pm')) { 208 | if (-e $file) { 209 | if (grep { $_ eq $file } git_ls_files()) { 210 | # committed file 211 | git_rm('--quiet', $file); 212 | } else { 213 | unlink $file; 214 | } 215 | } 216 | } 217 | } 218 | 219 | sub migrate_gitignore { 220 | my ($self) = @_; 221 | 222 | my @lines; 223 | 224 | my $gitignore = ( 225 | -f '.gitignore' 226 | ? Minilla::Gitignore->load('.gitignore') 227 | : Minilla::Gitignore->new() 228 | ); 229 | $gitignore->remove('META.json'); 230 | $gitignore->remove('/META.json'); 231 | $gitignore->remove('LICENSE'); 232 | 233 | # Add some lines 234 | $gitignore->add(sprintf('/%s-*', $self->project->dist_name)); 235 | for my $fname (qw( 236 | /.build 237 | /_build_params 238 | /Build 239 | /Build.bat 240 | !Build/ 241 | !META.json 242 | !LICENSE 243 | )) { 244 | $gitignore->add($fname); 245 | } 246 | 247 | $gitignore->save('.gitignore'); 248 | 249 | git_add(qw(.gitignore)); 250 | } 251 | 252 | 253 | 254 | 1; 255 | 256 | -------------------------------------------------------------------------------- /lib/Minilla/WorkDir.pm: -------------------------------------------------------------------------------- 1 | package Minilla::WorkDir; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use Archive::Tar; 6 | use File::pushd; 7 | use Data::Dumper; # serializer 8 | use File::Spec::Functions qw(splitdir); 9 | use File::Spec; 10 | use Time::Piece qw(gmtime); 11 | use File::Basename qw(dirname); 12 | use File::Path qw(mkpath); 13 | use File::Copy qw(copy); 14 | use Config; 15 | 16 | use Minilla::Logger; 17 | use Minilla::Util qw(randstr cmd cmd_perl slurp slurp_raw spew spew_raw pod_escape); 18 | use Minilla::FileGatherer; 19 | use Minilla::ReleaseTest; 20 | 21 | use Moo; 22 | 23 | has project => ( 24 | is => 'ro', 25 | required => 1, 26 | handles => [qw(files)], 27 | ); 28 | 29 | has dir => ( 30 | is => 'lazy', 31 | isa => sub { 32 | Carp::confess("'dir' must not be undef") unless defined $_[0]; 33 | }, 34 | ); 35 | 36 | has manifest_files => ( 37 | is => 'lazy', 38 | ); 39 | 40 | has [qw(prereq_specs)] => ( 41 | is => 'lazy', 42 | ); 43 | 44 | has 'cleanup' => ( 45 | is => 'ro', 46 | default => sub { $Minilla::DEBUG ? 0 : 1 }, 47 | ); 48 | 49 | has changes_time => ( 50 | is => 'lazy', 51 | ); 52 | 53 | no Moo; 54 | 55 | sub _build_changes_time { scalar(gmtime()) } 56 | 57 | sub DEMOLISH { 58 | my $self = shift; 59 | if ($self->cleanup) { 60 | infof("Removing %s\n", $self->dir); 61 | File::Path::rmtree($self->dir) 62 | } 63 | } 64 | 65 | sub _build_dir { 66 | my $self = shift; 67 | my $dirname = $^O eq 'MSWin32' ? '_build' : '.build'; 68 | File::Spec->catfile($self->project->dir, $dirname, randstr(8)); 69 | } 70 | 71 | sub _build_prereq_specs { 72 | my $self = shift; 73 | 74 | my $cpanfile = Module::CPANfile->load(File::Spec->catfile($self->project->dir, 'cpanfile')); 75 | return $cpanfile->prereq_specs; 76 | } 77 | 78 | sub _build_manifest_files { 79 | my $self = shift; 80 | my @files = (@{$self->files}, qw(LICENSE META.json META.yml MANIFEST)); 81 | if (-f File::Spec->catfile($self->dir, 'Makefile.PL')) { 82 | push @files, 'Makefile.PL'; 83 | } else { 84 | push @files, 'Build.PL'; 85 | } 86 | 87 | [do { 88 | my %h; 89 | grep {!$h{$_}++} @files; 90 | }]; 91 | } 92 | 93 | sub as_string { 94 | my $self = shift; 95 | $self->dir; 96 | } 97 | 98 | sub BUILD { 99 | my ($self) = @_; 100 | 101 | infof("Creating working directory: %s\n", $self->dir); 102 | 103 | # copying 104 | mkpath($self->dir); 105 | for my $src (@{$self->files}) { 106 | next if -d $src; 107 | debugf("Copying %s\n", $src); 108 | 109 | if (not -e $src) { 110 | warnf("Trying to copy non-existing file '$src', ignored\n"); 111 | next; 112 | } 113 | my $dst = File::Spec->catfile($self->dir, File::Spec->abs2rel($src, $self->project->dir)); 114 | mkpath(dirname($dst)); 115 | infof("cp %s %s\n", $src, $dst); 116 | copy($src => $dst) or die "Copying failed: $src $dst, $!\n"; 117 | chmod((stat($src))[2], $dst) or die "Cannot change mode: $dst, $!\n"; 118 | } 119 | } 120 | 121 | sub build { 122 | my ($self) = @_; 123 | 124 | return if $self->{build}++; 125 | 126 | my $guard = pushd($self->dir); 127 | 128 | infof("Building %s\n", $self->dir); 129 | 130 | # Generate meta file 131 | { 132 | my $meta = $self->project->cpan_meta(); 133 | $meta->save('META.yml', { 134 | version => 1.4, 135 | }); 136 | $meta->save('META.json', { 137 | version => 2.0, 138 | }); 139 | } 140 | 141 | { 142 | infof("Writing MANIFEST file\n"); 143 | spew('MANIFEST', join("\n", @{$self->manifest_files})); 144 | } 145 | 146 | $self->project->regenerate_files(); 147 | $self->_rewrite_changes(); 148 | $self->_rewrite_pod(); 149 | 150 | unless ($ENV{MINILLA_DISABLE_WRITE_RELEASE_TEST}) { # DO NOT USE THIS ENVIRONMENT VARIABLE. 151 | Minilla::ReleaseTest->write_release_tests($self->project, $self->dir); 152 | } 153 | 154 | if (-f 'Build.PL') { 155 | cmd_perl('Build.PL'); 156 | cmd_perl('Build', 'build'); 157 | } elsif (-f 'Makefile.PL') { 158 | cmd_perl('Makefile.PL'); 159 | cmd($Config{make}); 160 | } else { 161 | die "There is no Makefile.PL/Build.PL"; 162 | } 163 | } 164 | 165 | sub _rewrite_changes { 166 | my $self = shift; 167 | 168 | my $orig = slurp_raw('Changes'); 169 | $orig =~ s!\{\{\$NEXT\}\}! 170 | $self->project->version . ' ' . $self->changes_time->strftime('%Y-%m-%dT%H:%M:%SZ') 171 | !e; 172 | spew_raw('Changes', $orig); 173 | } 174 | 175 | sub _rewrite_pod { 176 | my $self = shift; 177 | 178 | # Disabled this feature. 179 | # my $orig =slurp_raw($self->project->main_module_path); 180 | # if (@{$self->project->contributors}) { 181 | # $orig =~ s! 182 | # (^=head \d \s+ (?:authors?)\b \s*) 183 | # (.*?) 184 | # (^=head \d \s+ | \z) 185 | # ! 186 | # ( $1 187 | # . $2 188 | # . "=head1 CONTRIBUTORS\n\n=over 4\n\n" 189 | # . join( '', map { "=item $_\n\n" } map { pod_escape($_) } @{ $self->project->contributors } ) 190 | # . "=back\n\n" 191 | # . $3 ) 192 | # !ixmse; 193 | # spew_raw($self->project->main_module_path => $orig); 194 | # } 195 | } 196 | 197 | # Return non-zero if fail 198 | sub dist_test { 199 | my ($self, @targets) = @_; 200 | 201 | $self->build(); 202 | 203 | $self->project->verify_prereqs(); 204 | 205 | eval { 206 | my $guard = pushd($self->dir); 207 | $self->project->module_maker->run_tests(); 208 | }; 209 | return $@ ? 1 : 0; 210 | } 211 | 212 | sub dist { 213 | my ($self) = @_; 214 | 215 | $self->{tarball} ||= do { 216 | $self->build(); 217 | 218 | my $guard = pushd($self->dir); 219 | 220 | # Create tar ball 221 | my $tarball = sprintf('%s-%s.tar.gz', $self->project->dist_name, $self->project->version); 222 | 223 | my $force_mode = 0; 224 | 225 | my $tar = Archive::Tar->new; 226 | for my $file (@{$self->manifest_files}) { 227 | my $filename = File::Spec->catfile($self->project->dist_name . '-' . $self->project->version, $file); 228 | my $data = slurp($file); 229 | 230 | my $mode = (stat($file))[2]; 231 | 232 | # On Windows, (stat($file))[2] * ALWAYS * results in octal 0100666 (which means it is 233 | # world writeable). World writeable files are always rejected by PAUSE. The solution is to 234 | # change a file mode octal 0100666 to octal 000664, such that it is * NOT * world 235 | # writeable. This works on Windows, as well as on other systems (Linux, Mac, etc...), because 236 | # the filemode 0100666 only occurs on Windows. (If it occurred on Linux, it would be wrong anyway) 237 | 238 | if ($mode == 0100666) { 239 | $mode = 0644; 240 | $force_mode++; 241 | } 242 | 243 | $tar->add_data($filename, $data, { mode => $mode }); 244 | } 245 | $tar->write($tarball, COMPRESS_GZIP); 246 | infof("Wrote %s\n", $tarball.($force_mode == 0 ? '' : ' --> forced to mode 000664')); 247 | 248 | File::Spec->rel2abs($tarball); 249 | }; 250 | } 251 | 252 | sub run { 253 | my ($self, @cmd) = @_; 254 | $self->build(); 255 | 256 | eval { 257 | my $guard = pushd($self->dir); 258 | cmd(@cmd); 259 | }; 260 | return $@ ? 1 : 0; 261 | } 262 | 263 | 1; 264 | -------------------------------------------------------------------------------- /lib/Minilla/Tutorial.pod: -------------------------------------------------------------------------------- 1 | =for stopwords gitignore cpanfile mymeta PL UploadToCPAN MetaCPAN 2 | 3 | =head1 NAME 4 | 5 | Minilla::Tutorial - Tutorial document for Minilla 6 | 7 | =head1 The Minilla workflow 8 | 9 | =head2 Installing 10 | 11 | > cpanm --with-recommends Minilla 12 | 13 | You can install L from CPAN. 14 | 15 | Unlike L, you don't need to do any setup. L aggregates user name and e-mail address from 16 | your C<~/.gitconfig> (You already set, isn't it?) 17 | 18 | =head2 Making new distribution 19 | 20 | Now it's time to make a new distribution. 21 | 22 | > minil new Dist-Name 23 | > cd Dist-Name/ 24 | 25 | At this point, you will have a really simple Dist-Name directory that contains your module file with as minimum boilerplate as possible. 26 | 27 | L done C and C. You need to commit it ASAP. 28 | 29 | > git commit -m 'initial import' 30 | 31 | Now start writing your code, edit the docs, tests and manage CPAN dependencies with L. 32 | 33 | > $EDITOR lib/Dist/Name.pm t/dist-name.t cpanfile 34 | 35 | =head2 Making the first release 36 | 37 | When you get confident and it's about time to ship to CPAN, use the test and release command. Before doing so, make sure your git directory is not dirty i.e. all changes are committed. 38 | 39 | > git commit -a -m "Done initial version" 40 | 41 | Minilla assumes you have a git remote setup so that you can push all your changes to. I recommend you to use either hub gem or L to create a new github repository. 42 | 43 | # Use hub rubygem 44 | > hub create 45 | 46 | # Use App::ph 47 | > ph import 48 | 49 | Alternatively, if you prefer to use GitLab you can create a project in their web interface and follow the instructions it provides to I. 50 | 51 | Now, make sure you have Changes file ready and have a new entry under C<{{$NEXT}}>, which will be expanded to the next version of your module. 52 | 53 | > $EDITOR Changes 54 | > minil test 55 | 56 | Before you proceed to release step, please ensure the C<~/.pause> file is configured correctly because Minilla uses L to upload your distribution to CPAN. You can specify the location of PAUSE configuration file on I if you want to. See L for further information. 57 | 58 | > minil release 59 | 60 | And your first release is done. The release is tagged on git and all the changes automatically made are committed to git as well. 61 | 62 | If this is your first conversion to Minilla and want to make sure you're not going to mess CPAN with a bad archive when something goes wrong, you can run the release command with FAKE_RELEASE environment variable. This will run all the other release process, except the UploadToCPAN step. 63 | 64 | > FAKE_RELEASE=1 minil release 65 | 66 | Wait for PAUSE processing it and your module showing up on MetaCPAN in a few minutes. Congratulations! 67 | 68 | =head2 Making a maintenance release 69 | 70 | You have new features, bugs, pull requests and get ready to make a next version of your module. Great, making a new release is equally easy. 71 | 72 | First, make sure all your code has been committed to git and there's no dirty files in the working directory. 73 | 74 | Then make sure to edit Changes file and contain entries for the next release under C<{{$NEXT}}>. You don't need to commit the change to the I file, yet. 75 | 76 | Now, make a release! 77 | 78 | > minil test 79 | > minil release 80 | 81 | The release command will automatically bump the version for you - if you have 0.10, the next version will be 0.11 by default, but you will be prompted to confirm that version in case you need a major bump. 82 | 83 | You can annotate any lines for which you would like the automatic version bump to be skipped by appending, `# No BumpVersion`. 84 | 85 | This will update C, C and bump C<$VERSION> in your main module. These changes made by Minilla will be automatically committed, tagged and pushed to the remote. 86 | 87 | =head1 MIGRATING 88 | 89 | This section describes how to migrate your current authoring process to Minilla. 90 | 91 | =head2 Migrate by C 92 | 93 | You just type C. 94 | 95 | It can migrate your distribution automatically. If you can't do it, please report to L. 96 | 97 | =head2 Manually migrating from other build tools 98 | 99 | =head3 Module Dependencies to cpanfile 100 | 101 | First, move the prereq declaration from Makefile.PL or Build.PL to cpanfile. 102 | 103 | The easiest way to convert existing dependencies to cpanfile is to use the command line tool mymeta-cpanfile, which is installed with L. Run the configuration with Makefile.PL for the one last time, then run the mymeta-cpanfile command: 104 | 105 | > perl Makefile.PL 106 | > mymeta-cpanfile --no-configure 107 | requires 'DBI', '1.000'; 108 | 109 | on test => sub { 110 | requires 'Test::More', '0.86'; 111 | } 112 | 113 | ... 114 | 115 | You can redirect the output to cpanfile if you like. It is important to pass --no-configure option here, since otherwise modules like ExtUtils::MakeMaker will be included. It is not required with Minilla setup, since Minilla knows which configuration tool (installer) to use and include them in META files upon the releases. You can leave that out from the cpanfile. 116 | 117 | If you decide to manually construct new cpanfile, the format is mostly compatible to Module::Install's requirement DSL. 118 | 119 | # Makefile.PL 120 | test_requires 'Test::More', 0.90; 121 | requires 'Plack', '1.000'; 122 | 123 | becomes: 124 | 125 | # cpanfile 126 | test_requires 'Test::More', 0.90; 127 | requires 'Plack', '1.000'; 128 | 129 | which is exactly the same. If you use L or L, that will be more manual process, but basically the same thing. See L for the available syntax. 130 | 131 | =head3 Remove boilerplate 132 | 133 | Next, remove unnecessary boilerplate files. 134 | 135 | > git rm {Makefile,Build}.PL MANIFEST MANIFEST.SKIP README .shipit 136 | 137 | =head3 Edit configurations 138 | 139 | Edit .gitignore and add the following lines: 140 | 141 | /Dist-Name-* 142 | /.build 143 | !META.json 144 | 145 | You're almost done, and your directory will look like: 146 | 147 | cpanfile 148 | lib/Dist/Name.pm 149 | t/... 150 | 151 | C the newly created files and commit it. 152 | 153 | =head3 Make a new build 154 | 155 | Now you're ready to make the first build. 156 | 157 | > minil build 158 | 159 | and if it was successful, you get a build in a directory called C under your current directory. They can be later removed with C command. 160 | 161 | Also, new C, C and C are added in your 162 | working directory for git-friendliness. C them and commit it. 163 | 164 | > git add Build.PL META.json README.md && git commit -m "git stuff" 165 | 166 | Now you're ready to roll a new release with Minilla. Before doing so, 167 | convert your C file format a little bit, and make sure you 168 | have a following header in the top: 169 | 170 | {{$NEXT}} 171 | 172 | - Change log entry for the next version 173 | 174 | The C<{{$NEXT}}> is a template variable that gets replaced with the 175 | version and date string, when you make a next release. This is almost 176 | the I change you're required to make in your code base. 177 | 178 | Now, run the release command: 179 | 180 | > minil release 181 | 182 | to make a new release, in the same way described above for a new Minilla 183 | setup. You can set C environment variable if this is 184 | your first conversion and want to double check what happens, before 185 | uploading to CPAN. 186 | 187 | When this is not your first release, the version number gets 188 | automatically bumped by Minilla, but you will be prompted if that is 189 | exactly the version you want, and if you want a major version up, you 190 | can specify to do so. 191 | 192 | =head1 AUTHOR 193 | 194 | Tokuhiro Matsuno 195 | 196 | Tatsuhiko Miyagawa 197 | (Most of documents are taken from L!) 198 | 199 | =head1 SEE ALSO 200 | 201 | L, L 202 | 203 | =cut 204 | --------------------------------------------------------------------------------