diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 9087bd9..c92e2ef 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -1,5 +1,5 @@ # brian's standard GitHub Actions Ubuntu config for Perl 5 modules -# version 20250101.001 +# version 20250126.002 # https://github.com/briandfoy/github_workflows # https://github.com/features/actions # This file is licensed under the Artistic License 2.0 @@ -8,6 +8,24 @@ # in your repo settings. Or not. It still works if it isn't defined. # In that environment, add whatever environment variables or secrets # that you want. +# +# Variables that you can set in the "automated_testing" environment: +# +# EXTRA_CPAN_MODULES - extra arguments to the first call to cpan. +# Just use EXTRA_CPANM_MODULES though. This is +# here for legacy +# +# EXTRA_CPANM_MODULES - extra arguments to the first call to cpanm. +# this is useful to install very particular +# modules, such as DBD::mysql@4.050 +# +# UBUNTU_EXTRA_APT_GET - extra packages to install before we start +# +# UBUNTU_EXTRA_CPANM_MODULES - extra arguments to the first call to cpanm +# but only on Ubuntu. Other workflows won't use this. +# this is useful to install very particular +# modules, such as DBD::mysql@4.050 + --- name: ubuntu @@ -52,7 +70,7 @@ on: pull_request: # weekly build on the master branch just to see what CPAN is doing schedule: - - cron: "59 13 * * 0" + - cron: "37 3 * * 0" jobs: perl: environment: automated_testing @@ -81,13 +99,17 @@ jobs: - uses: actions/checkout@v3 - name: Platform check run: uname -a + - name: setup platform + run: | + apt-get upgrade + apt-get -y install curl ${{ vars.UBUNTU_EXTRA_APT_GET }} - name: Perl version check run: | perl -V perl -v | perl -0777 -ne 'm/(v5\.\d+)/ && print "PERL_VERSION=$1"' >> $GITHUB_ENV # Some older versions of Perl have trouble with hostnames in certs. I # haven't figured out why. - - name: Setup environment + - name: enhance environment run: | echo "PERL_LWP_SSL_VERIFY_HOSTNAME=0" >> $GITHUB_ENV # HTML::Tagset bumped its minimum version to v5.10 for no good reason @@ -102,13 +124,14 @@ jobs: - name: fix html-tagset for v5.8 if: env.PERL_VERSION == 'v5.8' run: | + cpan App::Cpan curl -L -O https://cpan.metacpan.org/authors/id/P/PE/PETDANCE/HTML-Tagset-3.24.tar.gz tar -xzf HTML-Tagset-3.24.tar.gz cd HTML-Tagset-3.24 rm META.* mv Makefile.PL Makefile.PL.orig perl -n -e 'next if /(^use 5)|(MIN_PERL)/; print' Makefile.PL.orig > Makefile.PL - cpan -T . + cpan -M http://www.cpan.org -T . cd .. pwd ls @@ -121,13 +144,14 @@ jobs: - name: Install cpanm and multiple modules run: | curl -L https://cpanmin.us | perl - App::cpanminus - cpanm --notest IO::Socket::SSL LWP::Protocol::https App::Cpan HTTP::Tiny ExtUtils::MakeMaker Test::Manifest Test::More + cpanm --notest IO::Socket::SSL LWP::Protocol::https App::Cpan HTTP::Tiny ExtUtils::MakeMaker Test::Manifest Test::More ${{ vars.EXTRA_CPANM_MODULES }} ${{ vars.UBUNTU_EXTRA_CPANM_MODULES }} + cpan -M http://www.cpan.org -T Test::Manifest ${{ vars.EXTRA_CPAN_MODULES }} # Install the dependencies, again not testing them. This installs the # module in the current directory, so we end up installing the module, # but that's not a big deal. - name: Install dependencies run: | - cpanm --notest --installdeps --with-suggests --with-recommends . ${{ vars.EXTRA_CPAN_MODULES }} + cpanm --notest --installdeps --with-suggests --with-recommends . - name: Show cpanm failures if: ${{ failure() }} run: | @@ -167,7 +191,7 @@ jobs: - name: Run coverage tests if: env.PERL_VERSION != 'v5.8' && env.PERL_VERSION != 'v5.10' env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | cpanm --notest Devel::Cover Devel::Cover::Report::Coveralls perl Makefile.PL diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 83fee1f..11492fc 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -1,5 +1,5 @@ # brian's standard GitHub Actions macOS config for Perl 5 modules -# version 20250101.001 +# version 20250125.002 # https://github.com/briandfoy/github_workflows # https://github.com/features/actions # This file is licensed under the Artistic License 2.0 @@ -8,6 +8,21 @@ # in your repo settings. Or not. It still works if it isn't defined. # In that environment, add whatever environment variables or secrets # that you want. +# +# Variables that you can set in the "automated_testing" environment: +# +# EXTRA_CPAN_MODULES - extra arguments to the first call to cpan. +# Just use EXTRA_CPANM_MODULES though. This is +# here for legacy +# +# EXTRA_CPANM_MODULES - extra arguments to the first call to cpanm. +# this is useful to install very particular +# modules, such as DBD::mysql@4.050 +# +# MACOS_EXTRA_CPANM_MODULES - extra arguments to the first call to cpanm +# but only on macOS. Other workflows won't use this. +# this is useful to install very particular +# modules, such as DBD::mysql@4.050 --- name: macos @@ -78,14 +93,14 @@ jobs: - name: Prepare cpan run: | openssl version - cpan -M http://www.cpan.org -T IO::Socket::SSL HTTP::Tiny - cpan -M https://www.cpan.org -T ExtUtils::MakeMaker Test::Manifest + cpan -M https://www.cpan.org -T App::cpanminus IO::Socket::SSL HTTP::Tiny ExtUtils::MakeMaker Test::Manifest ${{ vars.EXTRA_CPAN_MODULES }} + cpanm --notest Test::Manifest ${{ vars.EXTRA_CPANM_MODULES }} ${{ vars.MACOS_EXTRA_CPANM_MODULES }} # Install the dependencies, again not testing them. This installs the # module in the current directory, so we end up installing the module, # but that's not a big deal. - name: Install dependencies run: | - cpan -M https://www.cpan.org -T . ${{ vars.EXTRA_CPAN_MODULES }} + cpan -M https://www.cpan.org -T . - name: Run tests run: | perl Makefile.PL diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e430e10..c77ab28 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,5 +1,5 @@ # brian's standard GitHub Actions release config for Perl 5 modules -# version 20241118.001 +# version 20241125.002 # https://github.com/briandfoy/github_workflows # https://github.com/features/actions # This file is licensed under the Artistic License 2.0 @@ -12,6 +12,21 @@ # This requires that you configure a repository secret named # RELEASE_ACTION_TOKEN with a GitHub Personal Access Token # that has "read and write" permissions on Repository/Contents +# +# Variables that you can set in the "automated_testing" environment: +# +# EXTRA_CPAN_MODULES - extra arguments to the first call to cpan. +# Just use EXTRA_CPANM_MODULES though. This is +# here for legacy +# +# EXTRA_CPANM_MODULES - extra arguments to the first call to cpanm. +# this is useful to install very particular +# modules, such as DBD::mysql@4.050 +# +# UBUNTU_EXTRA_CPANM_MODULES - extra arguments to the first call to cpanm +# but only on Ubuntu. Other workflows won't use this. +# this is useful to install very particular +# modules, such as DBD::mysql@4.050 --- name: release @@ -74,13 +89,14 @@ jobs: - name: Install cpanm and multiple modules run: | curl -L https://cpanmin.us | perl - App::cpanminus - cpanm --notest IO::Socket::SSL HTTP::Tiny ExtUtils::MakeMaker Test::Manifest + cpanm --notest IO::Socket::SSL HTTP::Tiny ExtUtils::MakeMaker Test::Manifest ${{ vars.EXTRA_CPANM_MODULES }} ${{ vars.UBUNTU_EXTRA_CPANM_MODULES }} + cpan -M http://www.cpan.org -T Test::Manifest ${{ vars.EXTRA_CPAN_MODULES }} # Install the dependencies, again not testing them. This installs the # module in the current directory, so we end up installing the module, # but that's not a big deal. - name: Install dependencies run: | - cpanm --notest --installdeps --with-suggests --with-recommends . ${{ vars.EXTRA_CPAN_MODULES }} + cpanm --notest --installdeps --with-suggests --with-recommends . # This makes the distribution and tests it, but assumes by the time we # got here, everything else was already tested. - name: Create distro diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 37b5b2d..1d6e303 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -1,5 +1,5 @@ # brian's standard GitHub Actions Windows config for Perl 5 modules -# version 20250101.001 +# version 20250126.001 # https://github.com/briandfoy/github_workflows # https://github.com/features/actions # This file is licensed under the Artistic License 2.0 @@ -8,6 +8,19 @@ # in your repo settings. Or not. It still works if it isn't defined. # In that environment, add whatever environment variables or secrets # that you want. +# +# EXTRA_CPAN_MODULES - extra arguments to the first call to cpan. +# Just use EXTRA_CPANM_MODULES though. This is +# here for legacy +# +# EXTRA_CPANM_MODULES - extra arguments to the first call to cpanm. +# this is useful to install very particular +# modules, such as DBD::mysql@4.050 +# +# WINDOWS_EXTRA_CPANM_MODULES - extra arguments to the first call to cpanm +# but only on Windows. Other workflows won't use this. +# this is useful to install very particular +# modules, such as DBD::mysql@4.050 --- name: windows @@ -66,19 +79,32 @@ jobs: - uses: actions/checkout@v3 - name: Set up Perl run: | - choco install strawberryperl + choco uninstall strawberryperl + choco upgrade strawberryperl echo "C:\strawberry\c\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append echo "C:\strawberry\perl\site\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append echo "C:\strawberry\perl\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - name: Perl version run: perl -V +# cpan can operate with https, but we need IO::SSL::Socket, which +# does not come with Perl. +# +# When using cpan, use -M to specify the CDN-backed www.cpan.org +# mirror. I've noticed that letting CPAN.pm auto-choose mirrors +# sometimes selects things that the action can't reach. +# +# Also, use the -T option to not test the dependencies. Assume these +# mainline modules are good and save lots of CI minutes. + - name: Install cpanm and multiple modules + run: | + cpan -M http://www.cpan.org -T App::cpanminus ${{ vars.EXTRA_CPAN_MODULES }} + cpanm --notest IO::Socket::SSL LWP::Protocol::https HTTP::Tiny ExtUtils::MakeMaker Test::Manifest Test::More ${{ vars.EXTRA_CPANM_MODULES }} ${{ vars.WINDOWS_EXTRA_CPANM_MODULES }} # Install the dependencies, again not testing them. This installs the # module in the current directory, so we end up installing the module, # but that's not a big deal. - name: Install dependencies run: | cpan -M https://www.cpan.org -T . - cpan -M https://www.cpan.org -T Test::Manifest ${{ vars.EXTRA_CPAN_MODULES }} - name: Run tests run: | perl Makefile.PL diff --git a/MANIFEST b/MANIFEST index d741924..98c01f0 100644 --- a/MANIFEST +++ b/MANIFEST @@ -13,7 +13,6 @@ LICENSE Makefile.PL MANIFEST This list of files MANIFEST.SKIP -Module-Release-2.136.tar.gz README.pod script/release script/release-test diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP index 2bbe66f..e155f43 100644 --- a/MANIFEST.SKIP +++ b/MANIFEST.SKIP @@ -74,4 +74,6 @@ ToDo \.github_token \.gitattributes\b +Module-Release- + none diff --git a/lib/Module/Release.pm b/lib/Module/Release.pm index a84083e..1d48f23 100644 --- a/lib/Module/Release.pm +++ b/lib/Module/Release.pm @@ -25,12 +25,14 @@ use strict; use warnings; no warnings; -our $VERSION = '2.136'; +our $VERSION = '2.137'; use Carp qw(carp croak); use File::Basename qw(dirname); use File::Spec; +use IPC::Open3; use Scalar::Util qw(blessed); +use Symbol 'gensym'; my %Loaded_mixins = ( ); @@ -1321,26 +1323,48 @@ sub run { $self->_debug( "$command\n" ); $self->_die( "Didn't get a command!" ) unless defined $command; - open my($fh), "-|", "$command" or $self->_die( "Could not open command [$command]: $!" ); - $fh->autoflush; + my $pid = IPC::Open3::open3( + my $child_in, my $child_out, my $child_err = gensym, + $command + ); + close $child_in; + + $child_out->autoflush; + + #open my($fh), "-|", "$command" or $self->_die( "Could not open command [$command]: $!" ); + #$fh->autoflush; my $output = ''; + my $error = ''; my $buffer = ''; local $| = 1; my $readlen = $self->debug ? 1 : 256; - while( read $fh, $buffer, $readlen ) { - $output .= $_; + while( read $child_out, $buffer, $readlen ) { $self->_debug( $_, $buffer ); $output .= $buffer; } + while( read $child_err, $buffer, $readlen ) { + $self->_debug( $_, $buffer ); + $error .= $buffer; + } + + if( $error =~ m/exec of .*? failed| Windows/x ) { + $self->_warn( "Could not run <$command>: $error" ); + } $self->_debug( $self->_dashes, "\n" ); - unless( close $fh ) { + waitpid( $pid, 0 ); + my $child_exit_status = $? >> 8; + + + $self->_warn( $error ) if length $error; + + if( $child_exit_status ) { $self->_run_error_set; - $self->_warn( "Command [$command] didn't close cleanly: $?" ); + $self->_warn( "Command [$command] didn't close cleanly: $child_exit_status" ); } return $output; diff --git a/script/release b/script/release index a8d73a9..d968390 100755 --- a/script/release +++ b/script/release @@ -115,11 +115,29 @@ repository. You should be able to checkout the code from any release. Set $ENV{AUTOMATED_TESTING} to true. You can also set C in the configuration file +=item -c STRING + +=item --changes STRING + +Specify the F file entry on the command line. If STRING does +not start with a C<*>, one will be added to the start along with a space. + + release -C "some change" + +translates to the F entry with the date and one bullet item: + + 3.11 2025-02-20T22:56:28Z + * some change + +STRING can also be a multiline string, but this will only append a C<*> +to the first line if it does not already have on. All lines will be +indented under the date though. + =item -C =item --skip-changes -Skip the Changes file. This is also the C config option +Skip the F file. This is also the C config option =item -d @@ -423,6 +441,7 @@ GetOptions( 'h|help|usage|?' => sub { usage(0) }, 'a|automated-testing!' => \$opts{a}, + 'c|changes=s' => \$opts{c}, 'C|skip-changes!' => \$opts{C}, 'd|skip-debug!' => \$opts{d}, 'D|skip-dist!' => \$opts{D}, @@ -700,7 +719,9 @@ if( $opts{t} // $release->config->dry_run ) { # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # CPAN::Spec::Changes $release->_print("============ Getting Changes\n"); -unless( $release->debug or $opts{C} or $release->config->skip_changes ) { +say STDERR "OPTS{c} is $opts{c}"; + +unless( $opts{C} or $release->config->skip_changes ) { $release->show_recent_contributors; my $changes = "Changes"; @@ -708,32 +729,49 @@ unless( $release->debug or $opts{C} or $release->config->skip_changes ) { die "Changes file does not exist!\n" unless -e $changes; - $release->_print( "\n", "-" x 73, "\n", "Enter Changes section\n\n> " ); my $str = $Version . ' ' . $release->get_release_date . "\n"; - while( ) { - $_ =~ s/\A \s* \* \s*/* /x; # Make top-level bullets (*) - $_ =~ s/\A \s* - \s*/\t- /x; # Make second-level bullets (-) - $_ =~ s/\A/\t/; # always indent + my $message = do { + if( $opts{'c'} ) { + $opts{'c'} + } + else { + $release->_print( "\n", "-" x 73, "\n", "Enter Changes section\n\n> " ); + local $/; + + } + }; + + $message =~ s/^ \s* \* \s*/* /mgx; # Make top-level bullets (*) + $message =~ s/^ \s* - \s*/\t- /mgx; # Make second-level bullets (-) - $str .= $_; - $release->_print( "> " ); - } + $message =~ s/\A\s+//; - $str .= "\n"; + $message =~ s/\A[^*]/* $&/; + + $message =~ s/^/\t/mgx; # always indent + $message =~ s/\s*\z/\n\n/; + + $message = $Version . ' ' . $release->get_release_date . "\n" . $message; rename $changes, $bak or die "Could not backup $changes. $!\n"; open my $in, '<:encoding(UTF-8)', $bak or $release->_die( "Could not read old $changes file! $!\n" ); open my $out, '>:encoding(UTF-8)', $changes; + # transfer the first part of the existing changes + my $header; while( <$in> ) { - print $out $_; + $header .= $_; last unless m/\S/; } - print $out $str; + $header =~ s/\s*\z/\n\n/; + # now the new stuff + print $out $header, $message; + + # and the rest of the old stuff print $out $_ while( <$in> ); close $in; @@ -750,7 +788,7 @@ unless( $release->debug or $opts{C} or $release->config->skip_changes ) { $release->vcs_commit_message( { version => $Version } ); } else { - sprintf '* for release %s', $Version; + sprintf 'release %s', $Version; } }; @@ -759,9 +797,10 @@ unless( $release->debug or $opts{C} or $release->config->skip_changes ) { $release->_print( $vcs_commit ); } else { - $release->_print( "Skipping Changes file" ); + $release->_print( "Skipping Changes file\n" ); } +exit; # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Build the release in preparation for uploading @@ -779,7 +818,10 @@ $release->_debug( "This is where I should release stuff\n" ); while( $release->should_upload_to_pause ) { $release->load_mixin( 'Module::Release::WebUpload::Mojo' ); $release->_print("============ Uploading to PAUSE\n"); - last if $release->debug; + if( $release->debug ) { + $release->_print("debug is true, so skipping upload"); + last; + } $release->web_upload; last; } diff --git a/t/run.t b/t/run.t index fdb46f3..a7b73d1 100644 --- a/t/run.t +++ b/t/run.t @@ -12,58 +12,64 @@ require 'setup_common.pl'; my $class = 'Module::Release'; subtest setup => sub { - use_ok( $class ); - can_ok( $class, 'new' ); + use_ok $class; + can_ok $class, 'new'; }; my @subs = qw( run run_error _run_error_set _run_error_reset ); -# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # -# Create test object -my $release = $class->new; -isa_ok( $release, $class ); -can_ok( $release, @subs ); +my $release; +subtest 'make objects' => sub { + $release = $class->new; + isa_ok $release, $class; + can_ok $release, @subs; + }; -# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # -# Try setting some things -ok( ! defined $release->run_error, "run_error not set yet" ); +subtest 'settings' => sub { + ok ! defined $release->run_error, "run_error not set yet"; -ok( $release->_run_error_set, "Set run_error" ); -ok( $release->run_error, "run_error is set" ); + ok $release->_run_error_set, "Set run_error"; + ok $release->run_error, "run_error is set"; -ok( ! $release->_run_error_reset, "run_error is reset" ); -ok( ! $release->run_error, "run_error is not set" ); + ok ! $release->_run_error_reset, "run_error is reset"; + ok ! $release->run_error, "run_error is not set"; + }; -# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # -# Don't pass run a command -{ -my $rc = eval { $release->run }; -my $at = $@; -ok( defined $at, "run with no arguments dies" ); -like( $at, qr/Didn't get a command!/, "Error message with no arguments" ); -} +subtest 'no args to run' => sub { + my $rc = eval { $release->run }; + my $at = $@; + ok defined $at, "run with no arguments dies"; + like $at, qr/Didn't get a command!/, "Error message with no arguments"; + }; -# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # -# Pass run undef -{ -my $rc = eval { $release->run( undef ) }; -my $at = $@; -ok( defined $at, "run with undef argument dies" ); -like( $at, qr/Didn't get a command!/, "Error message with undef argument" ); -} +subtest 'undef arg' => sub { + my $rc = eval { $release->run( undef ) }; + my $at = $@; + ok defined $at, "run with undef argument dies"; + like $at, qr/Didn't get a command!/, "Error message with undef argument"; + }; -# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # -# Pass it a bad command -{ -local $ENV{PATH} = ''; -my $command = "foo"; -ok( ! -x $command, "$command is not executable (good)" ); - -my $message = eval { $release->run( qq|$command| ) }; -my $at = $@; -ok( defined $at, "Bad command dies" ); -like( $at, qr/Could not open command/, "Error message with bad command" ); -} +subtest 'bad command' => sub { + local $ENV{PATH} = ''; + my $warnings; + local $SIG{__WARN__} = sub { + $warnings = $_[0]; + }; + + my $command = "foo"; + ok ! -x $command, "$command is not executable (good)"; + + my $message = eval { $release->run( qq|$command| ) }; + my $at = $@; + ok defined $at, "Bad command dies"; + + if( $^O eq 'MSWin32' ) { + like $warnings, qr/didn't close cleanly/, 'Saw Windows error'; + } + else { + like $at, qr/exec of \Q$command\E failed/, "Error message with bad command"; + } + }; # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Pass it a cammand that exits with 255 (which should be bad)