The Perl Toolchain Summit 2025 Needs You: You can help 🙏 Learn more

package SPVM::Builder;
use strict;
use Carp 'confess';
use File::Path 'mkpath';
use SPVM ();
# Fields
sub build_dir {
my $self = shift;
if (@_) {
$self->{build_dir} = $_[0];
return $self;
}
else {
return $self->{build_dir};
}
}
sub include_dirs {
my $self = shift;
if (@_) {
$self->{include_dirs} = $_[0];
return $self;
}
else {
return $self->{include_dirs};
}
}
sub new {
my $class = shift;
my $self = {
build_dir => $ENV{SPVM_BUILD_DIR},
include_dirs => [map { "$_/SPVM" } @INC],
@_
};
bless $self, ref $class || $class;
return $self;
}
# Instance Methods
sub build_dynamic_lib_dist_precompile {
my ($self, $class_name, $options) = @_;
$options ||= {};
$options = {%$options, category => 'precompile'};
$self->build_dynamic_lib_dist($class_name, $options);
}
sub build_dynamic_lib_dist_native {
my ($self, $class_name, $options) = @_;
$options ||= {};
$options = {%$options, category => 'native'};
$self->build_dynamic_lib_dist($class_name, $options);
}
sub build_dynamic_lib_dist {
my ($self, $class_name, $options) = @_;
$options ||= {};
$options = {%$options};
my $compiler = SPVM::Builder::Native::Compiler->new;
for my $include_dir (@{$self->include_dirs}) {
$compiler->add_include_dir($include_dir);
}
$compiler->set_start_file(__FILE__);
$compiler->set_start_line(__LINE__ + 1);
eval { $compiler->compile($class_name); };
if ($@) {
Carp::confess(join("\n", @{$compiler->get_formatted_error_messages}));
}
my $runtime = $compiler->get_runtime;
$options->{runtime} = $runtime;
$self->build_dist($class_name, $options);
}
sub build_dist {
my ($self, $class_name, $options) = @_;
$options ||= {};
$options = {%$options};
my $is_jit = 0;
my $build_dir = $self->build_dir;
my $output_dir = 'blib/lib';
$options->{output_dir} = $output_dir;
$self->build($class_name, $options);
}
sub build_jit {
my ($self, $class_name, $options) = @_;
$options ||= {};
$options = {%$options};
my $build_dir = $self->build_dir;
$options->{is_jit} = 1;
$self->build($class_name, $options);
}
sub build {
my ($self, $class_name, $options) = @_;
$options ||= {};
$options = {%$options};
my $build_dir = $self->build_dir;
my $is_jit = $options->{is_jit};
my $category = $options->{category};
my $output_dir = $options->{output_dir};
my $runtime = $options->{runtime};
# Config
my $config;
if ($category eq 'native') {
my $config_file = SPVM::Builder::Util::search_config_file($class_name);
unless (defined $config_file) {
my $config_rel_file = SPVM::Builder::Util::convert_class_name_to_rel_file($class_name, 'config');
confess("A config file \"$config_rel_file\" is not found in (@INC)");
}
$config = SPVM::Builder::Config->load_config($config_file, []);
}
elsif ($category eq 'precompile') {
$config = SPVM::Builder::Util::API::create_default_config();
}
$config->class_name($class_name);
$config->category($category);
$config->is_jit($is_jit);
$config->output_dir($output_dir);
my $cc_options = {build_dir => $build_dir};
if (exists $options->{force}) {
$cc_options->{force} = $options->{force};
}
unless (defined $build_dir) {
confess("A build directory must be defined. Perhaps the SPVM_BUILD_DIR environment variable is not set.");
}
mkpath $build_dir;
unless (-d $build_dir) {
confess("[Unexpected Error]A build directory must exists.");
}
my $cc = SPVM::Builder::CC->new(%$cc_options);
my $compile_and_link_options = {config => $config, runtime => $runtime};
# Compile source files to object files
my $object_files = $cc->compile_class($class_name, $compile_and_link_options);
# Link object files and generate a dynamic library
my $output_file = $cc->link($class_name, $object_files, $compile_and_link_options);
return $output_file;
}
1;
=encoding utf8
=head1 Name
SPVM::Builder - Build Dynamic Libraries for SPVM Distribution
=head1 Description
The SPVM::Builder class has methods to build dynamic librares for an SPVM distribution.
=head1 Copyright & License
Copyright (c) 2023 Yuki Kimoto
MIT License