SPVM::Dist(3)

πŸ“¦ version 0.990125, in alpine 3.24

πŸŽ€ SPVM::Dist(3) πŸŽ€

okay so real talk bestie πŸ’– β€” this right here is the SPVM::Dist(3) page from the User Contributed Perl Documentation section, and it is a journey. It starts off all mysterious and short, then just straight-up starts vibing mid-Perl-script and never explains itself. We are keeping every single character, typos and all, so grab a drink πŸ“ and let's go.


✨ Usage ✨

         interface $class_name;

that's it. that's the whole usage section. short. iconic. leaving us wanting more πŸ’…


πŸ’¬ Details πŸ’¬

🫢 Interface Methods

and then the page just... starts mid-code, like it walked into the group chat halfway through a sentence 😭 here's exactly what gets printed:

       EOS
         }
         elsif ($resource) {
           $description = "$class_name class in SPVM is a resource class for
       something.";

           my $native = $self->native;
           my $new_method;
           if ($native eq 'c') {
             $new_method = 'new_gnu99';
           }
           elsif ($native eq 'c++') {
             $new_method = 'new_cpp';
           }

           my $native_module_ext;
           if (defined $native) {
             if ($native eq 'c') {
               $native_module_ext = 'c';
             }
             elsif ($native eq 'c++') {
               $native_module_ext = 'cpp';
             }
           }

           # extern C for C++
           my $extern_c_start;
           my $extern_c_end;
           if ($native eq 'c++') {
             $extern_c_start = qq(extern "C" {);
             $extern_c_end = "}";
           }
           else {
             $extern_c_start = '';
             $extern_c_end = '';
           }

           $main_doc  = <<"EOS";
       =head1 Usage

       MyClass.config:

         my \$config = SPVM::Builder::Config->$new_method;

         \$config->use_resource('$class_name');

         \$config;

       MyClass.$native_module_ext:

         #include "spvm_native.h"
         #include "foo.h"

         $extern_c_start

         int32_t SPVM__MyClass__test(SPVM_ENV* env, SPVM_VALUE* stack) {

           // Use functions in foo.h

           return 0;
         }

         $extern_c_end

Details
Original Product
Original Product Version
Language
Language Specification
Required Libraries
Required Linker Flags
Required Resources
Header Files
Source Files
Compiler Flags
How to Create Resource
   Donwload
   Extracting Source Files
   Extracting Header Files
       EOS

quick vibe check πŸ’« β€” yes, Donwload is spelled like that in the original, and no, we are not fixing it, because character-for-character means character-for-character, periodt. also peep all those doc headings (Original Product, Original Product Version, Language, Language Specification, Required Libraries, Required Linker Flags, Required Resources, Header Files, Source Files, Compiler Flags, How to Create Resource, Donwload, Extracting Source Files, Extracting Header Files) just chillin' inside the heredoc like they own the place πŸ‘‘

the code keeps going:

         }
         else {
           $description = "$class_name class in SPVM has methods to do
       someting.";
           $main_doc  = <<"EOS"; =head1 Usage

         use $class_name;

Details
Super Class
Interfaces
Enumerations
Fields
Class Methods
Instance Methods
Well Known Child Classes
See Also
       EOS
         }

         my $version_decl = 'our $VERSION = "0.001";';

         my $only_lib_files = $self->only_lib_files;
         if ($only_lib_files) {
           $version_decl = '';
         }

         # Content
         my $perl_class_content = "";
         $perl_class_content = <<"EOS";
       package SPVM::$class_name;

       $version_decl

       1;

Name
       SPVM::$class_name - Short Description

Description
       $description

       $main_doc =head1 Repository

Author
       $user_name $user_email

Copyright & License
       Copyright (c) $year $user_name

       MIT License

       EOS

         # Generate file
         my $perl_class_rel_file = SPVM::Builder::Util::convert_class_name_to_rel_file($class_name, 'pm');
         $perl_class_rel_file =  $self->create_lib_rel_file($perl_class_rel_file);
         $self->generate_file($perl_class_rel_file, $perl_class_content);
       }

okay note someting. β€” also spelled like that in the source πŸ’€ keeping it. and yes, that $perl_class_rel_file = $self->create_lib_rel_file(...) line really does have two spaces after the =. we respect the grind 🫢

now a second whole sub shows up:

       sub generate_native_config_file {
         my ($self) = @_;

         my $class_name = $self->class_name;

         # C or C++
         my $native = $self->native;
         my $new_method;
         if ($native eq 'c') {
           $new_method = 'new_gnu99';
         }
         elsif ($native eq 'c++') {
           $new_method = 'new_cpp';
         }

         # User name
         my $user_name = $self->user_name;
         unless (defined $user_name) {
           $user_name = '[--user-name]'
         }

         # Year
         my $year = $self->_year;

         my $is_resource = $self->{resource};
         my $is_resource_code = '';
         if ($is_resource) {
           $is_resource_code = '$config->is_resource(1);';
         }

         # Content
         my $native_config_content = <<"EOS";
       # Copyright (c) $year $user_name
       # MIT License

       my \$config = SPVM::Builder::Config->$new_method;

       $is_resource_code

       \$config; EOS

         # Generate file
         my $native_config_rel_file = SPVM::Builder::Util::convert_class_name_to_rel_file($class_name, 'config');
         $native_config_rel_file =  $self->create_lib_rel_file($native_config_rel_file);
         $self->generate_file($native_config_rel_file, $native_config_content);
       }

and immediately another one, the native_class one this time:

       sub generate_native_class_file {
         my ($self) = @_;

         my $class_name = $self->class_name;

         # extern C for C++
         my $native = $self->native;
         my $extern_c_start;
         my $extern_c_end;
         if ($native eq 'c++') {
           $extern_c_start = qq(extern "C" {);
           $extern_c_end = "}";
         }
         else {
           $extern_c_start = '';
           $extern_c_end = '';
         }

         # Generate file
         my $native_module_ext;
         if (defined $native) {
           if ($native eq 'c') {
             $native_module_ext = 'c';
           }
           elsif ($native eq 'c++') {
             $native_module_ext = 'cpp';
           }
         }

         # Content
         my $native_class_name = $class_name;
         $native_class_name =~ s/::/__/g;
         my $native_class_file = $class_name;
         $native_class_file =~ s/::/\//g;
         $native_class_file .= ".$native_module_ext";

         # User name
         my $user_name = $self->user_name;
         unless (defined $user_name) {
           $user_name = '[--user-name]'
         }

         # Year
         my $year = $self->_year;

         my $native_class_content = <<"EOS";
       // Copyright (c) $year $user_name
       // MIT License

       #include "spvm_native.h"

       $extern_c_start

       static const char* FILE_NAME = "$native_class_file";

       int32_t SPVM__${native_class_name}__foo(SPVM_ENV* env, SPVM_VALUE*
       stack) {

         return 0;
       }

       $extern_c_end EOS

         my $native_class_rel_file = SPVM::Builder::Util::convert_class_name_to_rel_file($class_name, $native_module_ext);
         $native_class_rel_file =  $self->create_lib_rel_file($native_class_rel_file);
         $self->generate_file($native_class_rel_file, $native_class_content);
       }

then we get the little .gitkeep twins 🧚 besties who exist purely to keep empty dirs alive:

       sub generate_gitkeep_file_for_native_module_include_dir {
         my ($self) = @_;

         my $class_name = $self->class_name;

         # Generate file
         my $gitkeep_rel_file_for_native_module_include_dir = SPVM::Builder::Util::convert_class_name_to_rel_file($class_name, 'native');
         $gitkeep_rel_file_for_native_module_include_dir .= '/include/.gitkeep';
         $gitkeep_rel_file_for_native_module_include_dir =  $self->create_lib_rel_file($gitkeep_rel_file_for_native_module_include_dir);
         $self->generate_file($gitkeep_rel_file_for_native_module_include_dir, '');
       }

       sub generate_gitkeep_file_for_native_module_src_dir {
         my ($self) = @_;

         my $class_name = $self->class_name;

         # Generate file
         my $gitkeep_rel_file_for_native_module_include_dir = SPVM::Builder::Util::convert_class_name_to_rel_file($class_name, 'native');
         $gitkeep_rel_file_for_native_module_include_dir .= '/src/.gitkeep';
         $gitkeep_rel_file_for_native_module_include_dir =  $self->create_lib_rel_file($gitkeep_rel_file_for_native_module_include_dir);
         $self->generate_file($gitkeep_rel_file_for_native_module_include_dir, '');
       }

(yes, the src one still names its variable $gitkeep_rel_file_for_native_module_include_dir β€” lowkey wild, highkey keeping it 🌸)

next up, the dotfiles section of the tour:

       sub generate_gitignore_file {
         my ($self) = @_;

         my $gitignore_content = <<'EOS';
       /blib
       /Makefile
       /Makefile.old
       /MYMETA.yml
       /MYMETA.json
       /pm_to_blib
       /core.*
       /core
       /SPVM-*
       *.bak
       *.BAK
       *.tmp
       *.o
       *.bs
       .tmp
       .git
       .spvm_build
       EOS

         # Generate file
         my $gitignore_rel_file = '.gitignore';
         $self->generate_file($gitignore_rel_file, $gitignore_content);
       }

       sub generate_gitattributes_file {
         my ($self) = @_;

         my $gitattributes_content = <<'EOS';
       *.spvm text eol=lf
       EOS

         # Generate file
         my $gitattributes_rel_file = '.gitattributes';
         $self->generate_file($gitattributes_rel_file, $gitattributes_content);
       }

okay *.spvm text eol=lf being the entire .gitattributes is such a mood 😌 then comes the MANIFEST.SKIP regexes, all backslash-y and dramatic:

       sub generate_manifest_skip_file {
         my ($self) = @_;

         # Content
         my $manifest_skip_content = <<'EOS';
       ^blib(/|$)
       ^Makefile$
       ^Makefile.old$
       ^MYMETA.yml$
       ^MYMETA.json$
       ^pm_to_blib$
       ^core\.
       ^core$
       ^SPVM-
       \.bak$
       \.BAK$
       \.tmp$
       \.o$
       \.bs$
       (^|/)\.tmp(/|$)
       (^|/)\.git(/|$)
       (^|/)\.spvm_build(/|$)
       EOS

         # Generate file
         my $manifest_skip_rel_file = 'MANIFEST.SKIP';
         $self->generate_file($manifest_skip_rel_file, $manifest_skip_content);
       }

then Changes, which is literally just one brave little line:

       sub generate_changes_file {
         my ($self) = @_;

         # Year
         my $today_tp = Time::Piece::localtime;
         my $today = $today_tp->strftime('%Y-%m-%d');

         # Content
         my $changes_content = <<"EOS";
       0.001 $today
         [Changes]
           * First release.

       EOS

         # Generate file
         my $changes_rel_file = 'Changes';
         $self->generate_file($changes_rel_file, $changes_content);
       }

       sub generate_readme_markdown_file {
         my ($self) = @_;

         my $class_name = $self->class_name;

         # Content
         my $readme_markdown_content = <<"EOS";
       # SPVM::$class_name

       ## Document

       <a href="https://metacpan.org/pod/SPVM::$class_name">SPVM::$class_name -
       CPA/a

       ## See Also

       <a href="https://github.com/yuki-kimoto/SPVM">SPVM - Github</a>

       EOS

         # Generate file
         my $readme_markdown_rel_file = 'README.md';
         $self->generate_file($readme_markdown_rel_file, $readme_markdown_content);
       }

CPA/a 😭 that's an unclosed </a> and we are leaving it exactly like that, bestie. it's giving "shipped on a Friday" and honestly? respect 🫑

and finally the Makefile.PL generator, which is where part 1 runs out of page:

       sub generate_makefile_pl_file {
         my ($self) = @_;

         my $class_name = $self->class_name;

         # Resource
         my $resource = $self->resource;

         # Native make rule
         my $make_rule_native = $self->native && !$resource ? "\$make_rule .= SPVM::Builder::Util::API::create_make_rule_native('$class_name', \$options);" : '';

         # Precompile make rule
         my $make_rule_precompile = $self->precompile && !$resource ? "\$make_rule .= SPVM::Builder::Util::API::create_make_rule_precompile('$class_name', \$options);" : '';

         my $perl_class_rel_file = SPVM::Builder::Util::convert_class_name_to_rel_file($class_name, 'pm');
         $perl_class_rel_file =  $self->create_lib_rel_file($perl_class_rel_file);

         my $spvm_class_rel_file = SPVM::Builder::Util::convert_class_name_to_rel_file($class_name, 'spvm');
         $spvm_class_rel_file =  $self->create_lib_rel_file($spvm_class_rel_file);

         # User name
         my $user_name = $self->user_name;
         unless (defined $user_name) {
           $user_name = '[--user-name]'
         }

         # User email
         my $user_email = $self->user_email;
         unless (defined $user_email) {
           $user_email = '[--user-email]'
         }

         # "Makefile.PL" content
         my $makefile_pl_content = <<"EOS";
       use 5.008_007;
       use ExtUtils::MakeMaker;
       use strict;
       use warnings;
       use Config;
       use Getopt::Long 'GetOptions';

       GetOptions(
         'meta' => \\my \$meta,
         'no-build-spvm-modules' => \\my \$no_build_spvm_modules,
         'optimize=s' => \\my \$optimize, );

       if (\$meta) {
         \$no_build_spvm_modules = 1; }

       unless (\$meta) {
         # Do something such as environment check.  }

and that's exactly where the page cuts off β€” mid-unless block, mid-thought, no EOS, nothing 😭 so don't you dare think that's the whole thing... part 2 is right behind it πŸ’œ

...and we're so back, bestie πŸ’– we were literally mid-heredoc, so let's finish that Makefile.PL template before anything else β€” no cap, here's the rest of it exactly as it lives in the code:

  my \%configure_and_runtime_requires = ('SPVM' => '$SPVM::VERSION');
  WriteMakefile(
    NAME => 'SPVM::$class_name',
    VERSION_FROM => '$perl_class_rel_file',
    LICENSE => 'mit',
    (\$] >= 5.005 ?
      (ABSTRACT_FROM => '$perl_class_rel_file',
       AUTHOR => '$user_name<$user_email>') : ()),
    test => {TESTS => 't/*.t t/*/*.t t/*/*/*.t'},
    clean => {FILES => ['.spvm_build', 't/.spvm_build']},
    META_MERGE => {
      'meta-spec' => {
        version => 2,
        # release_status => 'stable', # stable, testing, or unstable
      },
      resources => {
        repository => {
          type => 'git',
          url  => '',
          web  => '',
        },
      },
      no_index => {
        directory => [],
      }
    },
    NORECURS => 1,
    CONFIGURE_REQUIRES => {
      \%configure_and_runtime_requires,
    },
    PREREQ_PM         => {
      \%configure_and_runtime_requires,
    },
    TEST_REQUIRES => {

    },
  );

  sub MY::postamble {

    my \$make_rule = '';

    unless (\$no_build_spvm_modules) {
      require SPVM::Builder::Util::API;

      local \@INC = ('lib', \@INC);

      my \$options = {};
      if (defined \$optimize) {
        \$options->{optimize} = \$optimize;
      }

      $make_rule_native
      $make_rule_precompile
    }

    return \$make_rule;
  }

  1; EOS

    # Generate file
    my $makefile_pl_rel_file = 'Makefile.PL';
    $self->generate_file($makefile_pl_rel_file, $makefile_pl_content);
  }

πŸ§ͺ generate_basic_test_file

This one drops a t/basic.t into your dist ✨ It's the classic test skeleton: Test::More, strict/warnings/FindBin, and it points SPVM_BUILD_DIR at $FindBin::Bin/.spvm_build inside a BEGIN block. Then it grabs my $api = SPVM::api();, remembers $start_memory_blocks_count, runs ok(SPVM::TestCase::$class_name->test);, does the version check, calls $api->destroy_runtime_permanent_vars;, and finishes with done_testing; after confirming the end memory block count matches the start. Leak-check slay πŸ’…

sub generate_basic_test_file {
  my ($self) = @_;

  my $class_name = $self->class_name;

  my $spvm_class_rel_file = SPVM::Builder::Util::convert_class_name_to_rel_file($class_name, 'spvm');
  $spvm_class_rel_file =  $self->create_lib_rel_file($spvm_class_rel_file);

  # Content
  my $basic_test_content = <<"EOS";
use Test::More;

use strict; use warnings; use FindBin; use lib "\$FindBin::Bin/lib";
BEGIN { \$ENV{SPVM_BUILD_DIR} = "\$FindBin::Bin/.spvm_build"; }

use SPVM 'TestCase::$class_name';

use SPVM '$class_name'; use SPVM::$class_name;

my \$api = SPVM::api();

my \$start_memory_blocks_count = \$api->get_memory_blocks_count;

ok(SPVM::TestCase::$class_name->test);

# Version check is(\$SPVM::${class_name}::VERSION,
\$api->get_version_string("$class_name"));

\$api->destroy_runtime_permanent_vars;

my \$end_memory_blocks_count = \$api->get_memory_blocks_count;
is(\$end_memory_blocks_count, \$start_memory_blocks_count);

done_testing; EOS

  # Generate file
  my $basic_test_rel_file = 't/basic.t';
  $self->generate_file($basic_test_rel_file, $basic_test_content);
}

πŸ“œ generate_license_file

Writes your LICENSE file with the full MIT License text πŸ’– The copyright year comes from $self->_year, and the name comes from $self->user_name β€” or, if that's not defined, the literal placeholder '[--user-name]' gets dropped in there so you remember to fill it in, bestie πŸ™ˆ

sub generate_license_file {
  my ($self) = @_;

  my $class_name = $self->class_name;

  # User name
  my $user_name = $self->user_name;
  unless (defined $user_name) {
    $user_name = '[--user-name]'
  }

  # Year
  my $year = $self->_year;

  # Content
  my $license_content = <<"EOS";
MIT License

Copyright (c) $year $user_name

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.  EOS

  # Generate file
  my $license_rel_file = 'LICENSE';
  $self->generate_file($license_rel_file, $license_content);
}

πŸŽ€ generate_basic_test_spvm_class_file

This writes the SPVM test class file and the path is built with SPVM::Builder::Util::convert_class_name_to_rel_file("TestCase::$class_name", 'spvm'), then prefixed with t/lib/ πŸ¦‹

And it branches on my $resource = $self->resource; β€” if you have a resource, the class declares native static method test : int (); (so the C/C++ side provides it); if you don't, test just returns 1 in plain SPVM. Both versions use $class_name; up top, periodt.

sub generate_basic_test_spvm_class_file {
  my ($self) = @_;

  my $class_name = $self->class_name;

  # Resource
  my $resource = $self->resource;

  # Content
  my $basic_test_spvm_class_content;

  if ($resource) {
    $basic_test_spvm_class_content = <<"EOS";
class TestCase::$class_name {
  use $class_name;
  native static method test : int ();
}
EOS
  }
  else {
    $basic_test_spvm_class_content = <<"EOS";
class TestCase::$class_name {
  use $class_name;
  static method test : int () {

    return 1;
  }
}
EOS
  }

  # Generate file
  my $basic_test_spvm_class_rel_file = SPVM::Builder::Util::convert_class_name_to_rel_file("TestCase::$class_name", 'spvm');
  $basic_test_spvm_class_rel_file = "t/lib/$basic_test_spvm_class_rel_file";
  $self->generate_file($basic_test_spvm_class_rel_file, $basic_test_spvm_class_content);
}

πŸ”§ generate_basic_test_native_config_file

Now the native test config 🧚 It reads $self->native, and picks new_gnu99 when it's 'c', new_cpp when it's 'c++', then calls $config->use_resource('$class_name'); and returns $config;. The file lands at t/lib/ + convert_class_name_to_rel_file("TestCase::$class_name", 'config').

sub generate_basic_test_native_config_file {
  my ($self) = @_;

  my $class_name = $self->class_name;

  # Resource
  my $resource = $self->resource;

  # C or C++
  my $native = $self->native;
  my $new_method;
  if ($native eq 'c') {
    $new_method = 'new_gnu99';
  }
  elsif ($native eq 'c++') {
    $new_method = 'new_cpp';
  }

  # Content
  my $basic_test_native_config_content = <<"EOS";
my \$config = SPVM::Builder::Config->$new_method;

\$config->use_resource('$class_name');

\$config; EOS

  # Generate file
  my $basic_test_native_config_rel_file = SPVM::Builder::Util::convert_class_name_to_rel_file("TestCase::$class_name", 'config');
  $basic_test_native_config_rel_file = "t/lib/$basic_test_native_config_rel_file";
  $self->generate_file($basic_test_native_config_rel_file, $basic_test_native_config_content);
}

πŸ’€ generate_basic_test_native_class_file

The actual C/C++ test file πŸ”₯ It #include "spvm_native.h" and defines int32_t SPVM__TestCase__${native_class_name}__test(SPVM_ENV* env, SPVM_VALUE* stack) which sets stack[0].ival = 1; and returns 0.

For C++ it wraps everything in extern "C" { … } (built as qq(extern "C" {) and "}"), and it builds $native_class_name by doing s/::/__/g on the class name. The extension is c for 'c' and cpp for 'c++', and the file goes under t/lib/.

sub generate_basic_test_native_class_file {
  my ($self) = @_;

  my $class_name = $self->class_name;

  # Resource
  my $resource = $self->resource;

  # extern C for C++
  my $native = $self->native;
  my $extern_c_start;
  my $extern_c_end;
  if ($native eq 'c++') {
    $extern_c_start = qq(extern "C" {);
    $extern_c_end = "}";
  }
  else {
    $extern_c_start = '';
    $extern_c_end = '';
  }

  # Content
  my $native_class_name = $class_name;
  $native_class_name =~ s/::/__/g;
  my $basic_test_native_class_content = <<"EOS";
#include "spvm_native.h"

$extern_c_start

int32_t SPVM__TestCase__${native_class_name}__test(SPVM_ENV* env,
SPVM_VALUE* stack) {

  stack[0].ival = 1;

  return 0;
}

$extern_c_end EOS

  # Generate file
  my $native_module_ext;
  if (defined $native) {
    if ($native eq 'c') {
      $native_module_ext = 'c';
    }
    elsif ($native eq 'c++') {
      $native_module_ext = 'cpp';
    }
  }
  my $basic_test_native_class_rel_file = SPVM::Builder::Util::convert_class_name_to_rel_file("TestCase::$class_name", $native_module_ext);
  $basic_test_native_class_rel_file = "t/lib/$basic_test_native_class_rel_file";
  $self->generate_file($basic_test_native_class_rel_file, $basic_test_native_class_content);
}

πŸ‘‘ generate_dist

The main character of the whole page, honestly. This is the one that actually builds the whole distribution 🌈

It grabs $self->class_name and bails immediately with confess("A class name must be specified"); if it's empty. Then, if the class name matches /-/, it confess("The class name \"$class_name\" cannnot contain \"-\""); β€” yes, that's three n's in cannnot, that's literally what the code says, don't come for me πŸ™ˆ

It also reads $self->native, $self->interface, and $self->resource, and if both interface and resource are set it dies with "--interface option and --resource option cannot be specified at the same time" β€” pick one, bestie, not both πŸ’…

The order of operations is iconic:

  • builds $class_name_rel_file by swapping :: for / with s|::|/|g
  • makes the output directory from $self->output_dir
  • generates the SPVM class file
  • generates the Perl class file unless $self->no_pm_file is set
  • if $native is truthy: generates the native config file, generates the native class file unless $resource is set, and drops .gitkeep files for both the native module include dir and the native module src dir
  • unless $only_lib_files is set, it also generates: .gitignore, .gitattributes, MANIFEST.SKIP, Changes, README.md, Makefile.PL, the basic test file, the basic test SPVM class file, the LICENSE file, and β€” if $resource is set β€” the basic test native class file and the basic test native config file
sub generate_dist {
  my ($self) = @_;

  my $class_name = $self->class_name;

  unless (length $class_name) {
    confess("A class name must be specified");
  }

  if ($class_name =~ /-/) {
    confess("The class name \"$class_name\" cannnot contain \"-\"");
  }

  my $native = $self->native;
  my $interface = $self->interface;
  my $resource = $self->resource;

  if ($interface && $resource) {
    die "--interface option and --resource option cannot be specified at the same time"
  }

  my $class_name_rel_file = $class_name;
  $class_name_rel_file =~ s|::|/|g;

  # Generate output directory
  my $output_dir = $self->output_dir;
  $self->generate_dir($output_dir);

  # Generate SPVM class file
  $self->generate_spvm_class_file;

  # Generate Perl class file
  my $no_pm_file = $self->no_pm_file;
  unless ($no_pm_file) {
    $self->generate_perl_class_file;
  }

  if ($native) {
    # Generate native config file
    $self->generate_native_config_file;

    # Generate native class file
    unless ($resource) {
      $self->generate_native_class_file;
    }

    # Generate ".gitkeep" file for native class include directory
    $self->generate_gitkeep_file_for_native_module_include_dir;

    # Generate ".gitkeep" file for native class src directory
    $self->generate_gitkeep_file_for_native_module_src_dir;
  }

  my $only_lib_files = $self->only_lib_files;
  unless ($only_lib_files) {
    # Generate .gitignore file
    $self->generate_gitignore_file;

    # Generate .gitattributes file
    $self->generate_gitattributes_file;

    # Generate MANIFEST.SKIP file
    $self->generate_manifest_skip_file;

    # Generate Changes file
    $self->generate_changes_file;

    # Generate README.md file
    $self->generate_readme_markdown_file;

    # Generate Makefile.PL file
    $self->generate_makefile_pl_file;

    # Generate basic test file
    $self->generate_basic_test_file;

    # Generate basic test SPVM class file
    $self->generate_basic_test_spvm_class_file;

    # Generate license file
    $self->generate_license_file;

    if ($resource) {
      # Generate basic test native class file
      $self->generate_basic_test_native_class_file;

      # Generate basic test native config file
      $self->generate_basic_test_native_config_file;
    }
  }
}

πŸ—“οΈ _year

Tiny little helper, huge vibes πŸŽ€ It calls Time::Piece::localtime into $today_tp, grabs $today_tp->year, and returns it β€” that's the year that ends up in your LICENSE copyright line.

sub _year {
  my ($self) = @_;

  # Year
  my $today_tp = Time::Piece::localtime;
  my $year = $today_tp->year;

  return $year;
}

1;

πŸ’« USAGE πŸ’«

Okay so usage-wise it's genuinely so simple, bestie. Make the object, give it a class_name, then let it cook:

my $dist = SPVM::Dist->new(
  class_name => 'Math',
);

$dist->generate_dist;

That's it. That's the whole vibe check ⭐


😭 POD ERRORS 😭

Not me having to deliver this news, but the doc itself ships with a little note: "Hey! The above document had some coding errors, which are explained below:"

  • Around line 809:
    • Deleting unknown formatting code N<>

So yeah β€” one grumpy little POD hiccup got flagged around line 809, and the formatter deleted an unknown N<> formatting code. Fix it when you get a sec, iykyk 🫢


So that's the whole page, bestie β€” everything documented, nothing dropped, all facts intact πŸ’œ Go make a distribution and be the main character of your own CPAN namespace. Sign-off time: this page is from perl v5.42.2, dated 2026-02-03 β€” slay, and happy hacking πŸ¦‹βœ¨πŸŽ€

πŸ“¦ where this page comes from