package SPVM::Builder::Config;
use strict;
use warnings;
use Config;
use Carp 'confess';
use File::Basename 'dirname';
use SPVM::Builder::Util;
use SPVM::Builder::LibInfo;
use SPVM::Builder::Resource;
# Fields
sub file_optional {
my $self = shift;
if (@_) {
$self->{file_optional} = $_[0];
return $self;
}
else {
return $self->{file_optional};
}
}
sub file {
my $self = shift;
if (@_) {
$self->{file} = $_[0];
return $self;
}
else {
return $self->{file};
}
}
sub ext {
my $self = shift;
if (@_) {
$self->{ext} = $_[0];
return $self;
}
else {
return $self->{ext};
}
}
sub quiet {
my $self = shift;
if (@_) {
$self->{quiet} = $_[0];
return $self;
}
else {
return $self->{quiet};
}
}
sub cc {
my $self = shift;
if (@_) {
$self->{cc} = $_[0];
return $self;
}
else {
return $self->{cc};
}
}
sub ccflags {
my $self = shift;
if (@_) {
$self->{ccflags} = $_[0];
return $self;
}
else {
return $self->{ccflags};
}
}
sub optimize {
my $self = shift;
if (@_) {
$self->{optimize} = $_[0];
return $self;
}
else {
return $self->{optimize};
}
}
sub ld_optimize {
my $self = shift;
if (@_) {
$self->{ld_optimize} = $_[0];
return $self;
}
else {
return $self->{ld_optimize};
}
}
sub include_dirs {
my $self = shift;
if (@_) {
$self->{include_dirs} = $_[0];
return $self;
}
else {
return $self->{include_dirs};
}
}
sub builder_include_dir {
my $self = shift;
if (@_) {
$self->{builder_include_dir} = $_[0];
return $self;
}
else {
return $self->{builder_include_dir};
}
}
sub builder_src_dir {
my $self = shift;
if (@_) {
$self->{builder_src_dir} = $_[0];
return $self;
}
else {
return $self->{builder_src_dir};
}
}
sub own_include_dir {
my $self = shift;
if (@_) {
$self->{own_include_dir} = $_[0];
return $self;
}
else {
return $self->{own_include_dir};
}
}
sub own_src_dir {
my $self = shift;
if (@_) {
$self->{own_src_dir} = $_[0];
return $self;
}
else {
return $self->{own_src_dir};
}
}
sub ld {
my $self = shift;
if (@_) {
$self->{ld} = $_[0];
return $self;
}
else {
return $self->{ld};
}
}
sub ldflags {
my $self = shift;
if (@_) {
$self->{ldflags} = $_[0];
return $self;
}
else {
return $self->{ldflags};
}
}
sub dynamic_lib_ldflags {
my $self = shift;
if (@_) {
$self->{dynamic_lib_ldflags} = $_[0];
return $self;
}
else {
return $self->{dynamic_lib_ldflags};
}
}
sub lib_dirs {
my $self = shift;
if (@_) {
$self->{lib_dirs} = $_[0];
return $self;
}
else {
return $self->{lib_dirs};
}
}
sub libs {
my $self = shift;
if (@_) {
$self->{libs} = $_[0];
return $self;
}
else {
return $self->{libs};
}
}
sub source_files {
my $self = shift;
if (@_) {
$self->{source_files} = $_[0];
return $self;
}
else {
return $self->{source_files};
}
}
sub force {
my $self = shift;
if (@_) {
$self->{force} = $_[0];
return $self;
}
else {
return $self->{force};
}
}
sub before_compile {
my $self = shift;
if (@_) {
$self->{before_compile} = $_[0];
return $self;
}
else {
return $self->{before_compile};
}
}
sub before_link {
my $self = shift;
if (@_) {
$self->{before_link} = $_[0];
return $self;
}
else {
return $self->{before_link};
}
}
sub dependent_files {
my $self = shift;
if (@_) {
$self->{dependent_files} = $_[0];
return $self;
}
else {
return $self->{dependent_files};
}
}
sub output_type {
my $self = shift;
if (@_) {
$self->{output_type} = $_[0];
return $self;
}
else {
return $self->{output_type};
}
}
# Methods
sub new {
my $class = shift;
my $self = {@_};
bless $self, ref $class || $class;
my $file_optional = $self->file_optional;
my $file = $self->file;
if (!$file_optional && !defined $file) {
confess "\"file\" option must be specified";
}
# quiet
unless (defined $self->{quiet}) {
$self->quiet(undef);
}
# force
unless (defined $self->{force}) {
$self->force(undef);
}
# ext
unless (defined $self->{ext}) {
$self->ext(undef);
}
# cc
unless (defined $self->{cc}) {
$self->cc($Config{cc});
}
my $builder_dir = SPVM::Builder::Util::get_builder_dir_from_config_module();
# builder_include_dir
unless (defined $self->{builder_include_dir}) {
my $builder_include_dir = "$builder_dir/include";
$self->builder_include_dir($builder_include_dir);
}
# builder_src_dir
unless (defined $self->{builder_src_dir}) {
my $builder_src_dir = "$builder_dir/src";
$self->builder_src_dir($builder_src_dir);
}
# include_dirs
unless (defined $self->{include_dirs}) {
$self->include_dirs([]);
}
# Resource directory
if (defined $file) {
my $resource_dir = $self->remove_ext_from_config_file($file);
$resource_dir .= '.native';
# own_include_dir
unless (defined $self->{own_include_dir}) {
my $own_include_dir = "$resource_dir/include";
$self->own_include_dir($own_include_dir);
}
# own_src_dir
unless (defined $self->{own_src_dir}) {
my $own_src_dir = "$resource_dir/src";
$self->own_src_dir($own_src_dir);
}
}
# ccflags
unless (defined $self->{ccflags}) {
$self->ccflags([]);
my @default_ccflags;
# If dynamic link libraries must link position independent codes, add -fPIC option.
if ($Config{cccdlflags} =~ /-fPIC\b/) {
push @default_ccflags, '-fPIC';
}
$self->add_ccflags(@default_ccflags);
}
# optimize
unless (defined $self->{optimize}) {
$self->optimize('-O3');
}
# ld
unless (defined $self->{ld}) {
$self->ld($Config{ld});
}
# lib_dirs
unless (defined $self->{lib_dirs}) {
$self->lib_dirs([]);
}
# resources
unless (defined $self->{resources}) {
$self->{resources} = {};
}
# source_files
unless (defined $self->{source_files}) {
$self->source_files([]);
}
# libs
unless (defined $self->{libs}) {
$self->libs([]);
}
# ld_optimize
unless (defined $self->{ld_optimize}) {
$self->ld_optimize('-O2');
}
unless (defined $self->{dependent_files}) {
$self->{dependent_files} = [];
}
unless (defined $self->output_type) {
$self->output_type('dynamic_lib');
}
# dynamic_lib_ldflags
unless (defined $self->{dynamic_lib_ldflags}) {
$self->dynamic_lib_ldflags([]);
if ($self->output_type eq 'dynamic_lib') {
my @dynamic_lib_ldflags;
# Dynamic link options
if ($^O eq 'MSWin32') {
push @dynamic_lib_ldflags, '-mdll', '-s';
}
else {
push @dynamic_lib_ldflags, '-shared';
}
$self->dynamic_lib_ldflags(\@dynamic_lib_ldflags);
}
}
# ldflags
unless (defined $self->{ldflags}) {
$self->ldflags([]);
}
return $self;
}
sub new_c {
my $class = shift;
my $self = $class->new(@_);
# NativeAPI
$self->ext('c');
return $self;
}
sub new_c99 {
my $class = shift;
my $self = $class->new_c(@_);
# C99
$self->set_std('c99');
return $self;
}
sub new_c11 {
my $class = shift;
my $self = $class->new_c(@_);
# C99
$self->set_std('c11');
return $self;
}
sub new_gnu99 {
my $class = shift;
my $self = $class->new_c(@_);
# C99
$self->set_std('gnu99');
return $self;
}
sub new_gnu11 {
my $class = shift;
my $self = $class->new_c(@_);
# C99
$self->set_std('gnu11');
return $self;
}
sub new_cpp {
my $class = shift;
my $self = $class->new(@_);
# The compiler
# [Memo]Free BSD don't have g++ in the environment clang++ exists.
# [Memo]"Clang" or "clang" is assumed.
my $config_gcc_version = $Config{gccversion};
if ($config_gcc_version =~ /\bclang\b/i) {
$self->cc('clang++');
$self->ld('clang++');
}
else {
$self->cc('g++');
$self->ld('g++');
}
# NativeAPI
$self->ext('cpp');
return $self;
}
sub new_cpp11 {
my $class = shift;
my $self = $class->new_cpp(@_);
# C++11
$self->set_std('c++11');
return $self;
}
sub new_cpp14 {
my $class = shift;
my $self = $class->new_cpp(@_);
# C++11
$self->set_std('c++14');
return $self;
}
sub new_cpp17 {
my $class = shift;
my $self = $class->new_cpp(@_);
# C++11
$self->set_std('c++17');
return $self;
}
sub add_ccflags {
my ($self, @ccflags) = @_;
push @{$self->{ccflags}}, @ccflags;
}
sub add_include_dirs {
my ($self, @include_dirs) = @_;
push @{$self->{include_dirs}}, @include_dirs;
}
sub set_std {
my ($self, $standard) = @_;
my $ccflags = $self->ccflags;
push @$ccflags, "-std=$standard";
# Add -std=foo section
$self->ccflags($ccflags);
return $self;
}
sub add_ldflags {
my ($self, @ldflags) = @_;
push @{$self->{ldflags}}, @ldflags;
}
sub add_lib_dirs {
my ($self, @lib_dirs) = @_;
push @{$self->{lib_dirs}}, @lib_dirs;
}
sub add_libs {
my ($self, @libs) = @_;
push @{$self->{libs}}, @libs;
}
sub add_static_libs {
my ($self, @libs) = @_;
my @static_libs;
for my $lib (@libs) {
my $static_lib;
if (ref $lib eq 'SPVM::Builder::LibInfo') {
$static_lib = $lib->static(1);
}
else {
my $lib_name = $lib;
$static_lib = SPVM::Builder::LibInfo->new;
$static_lib->name($lib_name);
$static_lib->static(1);
}
push @static_libs, $static_lib;
}
$self->add_libs(@static_libs);
}
sub add_source_files {
my ($self, @source_files) = @_;
push @{$self->{source_files}}, @source_files;
}
sub load_config {
my ($self, $config_file, @args) = @_;
unless (-f $config_file) {
confess "Can't find config file \"$config_file\"";
}
local @ARGV = @args;
my $config = do File::Spec->rel2abs($config_file);
if ($@) {
confess "Can't parse config file \"$config_file\": $@";
}
unless (defined $config && $config->isa('SPVM::Builder::Config')) {
confess "The config file must be a SPVM::Builder::Config object";
}
push @{$config->dependent_files}, $config_file;
return $config;
}
sub remove_ext_from_config_file {
my ($self, $config_file) = @_;
my $config_file_without_ext = $config_file;
$config_file_without_ext =~ s/(\.[a-zA-Z0-9_-]+)?\.config$//;
return $config_file_without_ext;
}
sub load_mode_config {
my ($self, $config_file, $mode, @argv) = @_;
my $mode_config_file = $self->remove_ext_from_config_file($config_file);
if (defined $mode) {
$mode_config_file .= ".$mode";
}
$mode_config_file .= ".config";
unless (-f $mode_config_file) {
confess "Can't find the config file \"$mode_config_file\"";
}
my $config = $self->load_config($mode_config_file, @argv);
return $config;
}
sub load_base_config {
my ($self, $config_file, @args) = @_;
my $config = $self->load_mode_config($config_file, undef, @args);
return $config;
}
sub use_resource {
my ($self, @args) = @_;
my $first_arg;
unless (@args % 2 == 0) {
$first_arg = shift @args;
}
my $resource;
if (ref $first_arg) {
$resource = $first_arg;
}
else {
my $class_name = $first_arg;
my %args = @args;
if (exists $args{class_name}) {
$class_name = delete $args{class_name};
}
$resource = SPVM::Builder::Resource->new(class_name => $class_name, %args);
}
my $resource_class_name = $resource->class_name;
my $resource_mode = $resource->mode;
my $resource_args = $resource->args;
my $ext = defined $resource_mode ? "$resource_mode.config" : 'config';
my $config_file_base = SPVM::Builder::Util::convert_class_name_to_rel_file($resource_class_name, $ext);
my $config_file = SPVM::Builder::Util::get_config_file_from_class_name($resource_class_name, $resource_mode);
my $config = $self->load_config($config_file, @$resource_args);
$config->file($config_file);
$resource->config($config);
my $index = keys %{$self->{resources}};
$self->{resources}->{$resource_class_name} = {resource => $resource, index => $index};
return $resource;
}
sub get_resource {
my ($self, $resource_class_name) = @_;
unless (defined $self->{resources}{$resource_class_name}) {
return;
}
my $resource = $self->{resources}{$resource_class_name}{resource};
return $resource;
}
sub get_resource_names {
my ($self) = @_;
my @resource_names = sort { $self->{resources}{$a}{index} <=> $self->{resources}{$b}{index} } keys %{$self->{resources}};
return \@resource_names;
}
1;
=head1 Name
SPVM::Builder::Config - Configurations of Compile and Link of Native Sources
=head1 Usage
use SPVM::Builder::Config;
# Create a config
my $config = SPVM::Builder::Config->new(file => __FILE__);
# Create a SPVM::Builder::Config object with "GNU99"
my $config = SPVM::Builder::Config->new_gnu99(file => __FILE__);
# Create a SPVM::Builder::Config object with "C99"
my $config = SPVM::Builder::Config->new_c99(file => __FILE__);
# Create a SPVM::Builder::Config object as "C++"
my $config = SPVM::Builder::Config->new_cpp(file => __FILE__);
# Create a SPVM::Builder::Config object with "C++11" standard of "C++"
my $config = SPVM::Builder::Config->new_cpp11(file => __FILE__);
# Optimize
$config->optimize('-O2');
# Optimize with debug mode
$config->optimize('-O0 -g');
# Add source files
$config->add_source_files('foo.c', 'bar.c', 'baz/baz.c');
# Use resource
$config->use_resource('TestCase::Resource::Zlib::V1_0_0');
$config->use_resource('TestCase::Resource::Foo1::V1_0_0', mode => 'mode1', args => ['args1', 'args2']);
# Get resouce information
my $resource = $config->get_resource('TestCase::Resource::Zlib::V1_0_0');
=head1 Description
C<SPVM::Builder::Config> is configuration of c/c++ compile and link.
=head1 Fields
=head2 ext
my $ext = $config->ext;
$config->ext($ext);
Get and set the extension of the SPVM native source.
The default is undef.
Examples:
# Foo/Bar.c
$config->ext('c');
# Foo/Bar.cpp
$config->ext('cpp');
=head2 cc
my $cc = $config->cc;
$config->cc($cc);
Get and set a compiler name. The default is the value of C<cc> of L<Config> module.
Examples:
# gcc
$config->cc('gcc');
# g++ for C++
$config->cc('g++');
# nvcc for CUDA/GUP
$config->cc('nvcc');
# cc that compiled this Perl
use Config;
$config->cc($Config{cc});
=head2 include_dirs
my $include_dirs = $config->include_dirs;
$config->include_dirs($include_dirs);
Get and set header including directories of the compiler. This is same as C<-I> option of C<gcc>.
=head2 builder_include_dir
my $builder_include_dir = $config->builder_include_dir;
$config->builder_include_dir($builder_include_dir);
Get and set the header including directory of L<SPVM::Builder>.
The default value is C<SPVM/Builder/include> of one up of directory that C<SPVM::Buidler::Config> is loaded.
=head2 builder_src_dir
my $builder_src_dir = $config->builder_src_dir;
$config->builder_src_dir($builder_src_dir);
Get and set the source directory of L<SPVM::Builder>.
The default value is C<SPVM/Builder/src> of one up of the directory that C<SPVM::Buidler::Config> is loaded.
=head2 own_include_dir
my $own_include_dir = $config->own_include_dir;
$config->own_include_dir($own_include_dir);
Get and set the header including directory of this module.
The default value is the name that removing C<[.mode].config> from the L<file|/"file"> and add C<.native/include>.
=head2 own_src_dir
my $own_src_dir = $config->own_src_dir;
$config->own_src_dir($own_src_dir);
Get and set the source directory of this module.
The default value is the name that removing C<[.mode].config> from the L<file|/"file"> and add C<.native/src>.
=head2 ccflags
my $ccflags = $config->ccflags;
$config->ccflags($ccflags);
Get and set compiler flags.
B<Default:>
# $Config{cccdlflags} has -fPIC.
['-fPIC']
# Other
[]
=head2 optimize
my $optimize = $config->optimize;
$config->optimize($optimize);
Get and set the option for optimization of the compiler.
The default is C<-O3>.
Examples:
$config->optimize('-O3');
$config->optimize('-O2');
$config->optimize('-g3 -O0');
=head2 source_files
my $source_files = $config->source_files;
$config->source_files($source_files);
Get and get source files. The file name is the relative pass from L</"own_src_dir">.
Examples:
$config->source_files(['foo.c', 'bar.c']);
=head2 ld
my $ld = $config->ld;
$config->ld($ld);
Get and set a linker. Default is C<ld> of L<Config> module.
=head2 lib_dirs
my $lib_dirs = $config->lib_dirs;
$config->lib_dirs($lib_dirs);
Get and set the directories that libraries are searched for by the linker. This is same as C<-L> option of C<gcc>.
B<Default:>
Windows
The directory that perlxxx.dll exists
Not Windows
empty list
=head2 libs
my $libs = $config->libs;
$config->libs($libs);
Get and set library names or L<SPVM::Builder::LibInfo> objects. These libraries are linked by L<SPVM::Builder::CC/"link"> method.
=head2 ldflags
my ldflags = $config->ldflags;
$config->ldflags(ldflags);
Get and set linker flags. The default value is an emtpy array reference.
=head2 dynamic_lib_ldflags
my dynamic_lib_ldflags = $config->dynamic_lib_ldflags;
$config->dynamic_lib_ldflags(dynamic_lib_ldflags);
Get and set linker flags for dynamic link.
B<Default:>
Windows
['-mdll', '-s']
Non-Windows
['-shared']
=head2 ld_optimize
my $ld_optimize = $config->ld_optimize;
$config->ld_optimize($ld_optimize);
Get and set the option for optimization of the linker such as C<-O3>, C<-O2>, C<-g3 -O0>.
The default is C<-O2>.
=head2 force
my $force = $config->force;
$config->force($force);
Get and set the flag to force compiles and links without caching. The default is undef.
undef means forcing is not determined by config.
=head2 before_compile
my $before_compile = $config->before_compile;
$config->before_compile($before_compile);
Get and set the callback that is executed before the compile. The callback receives C<SPVM::Builder::Config> object and the L<SPVM::Builder::CompileInfo> object used by the compileer.
Examples:
$config->before_compile(sub {
my ($config, $compile_info) = @_;
my $cc = $compile_info->cc;
# Do something
});
=head2 before_link
my $before_link = $config->before_link;
$config->before_link($before_link);
Get and set the callback that is executed before the link. The callback receives C<SPVM::Builder::Config> object and the L<SPVM::Builder::LinkInfo> object used by the linker.
Examples:
$config->before_link(sub {
my ($config, $link_info) = @_;
my $object_file_infos = $link_info->object_file_infos;
# Do something
});
=head2 quiet
my $quiet = $config->quiet;
$config->quiet($quiet);
Get and set the flag if the compiler and the linker output the results.
The default is undef. undef means quietness is not determined by config.
=head2 file
my $file = $config->file;
$config->file($file);
Get and set the config file path.
The default is 1.
=head2 file_optional
my $file_optional = $config->file_optional;
$config->file_optional($file_optional);
Get and set the value that indicates L<file|/"file"> field is needed for C<new|/"new"> method.
The default is 0.
=head2 output_type
my $output_type = $config->output_type;
$config->output_type($type);
=head1 Class Methods
=head2 new
my $config = SPVM::Builder::Config->new(file => __FILE__);
Create a C<SPVM::Builder::Config> object.
L</"file"> must be specified except for the case that L</"file_optional"> is set to a true value.
=head2 new_c
my $config = SPVM::Builder::Config->new_c(file => __FILE__);
Call L</"new">. After that, call L<ext('c')|/"ext">.
=head2 new_gnu99
my $config = SPVM::Builder::Config->new_gnu99(file => __FILE__);
Call L</"new_c">. After that, call L<set_std('gnu99')|/"set_std">.
=head2 new_gnu11
my $config = SPVM::Builder::Config->new_gnu11(file => __FILE__);
Call L</"new_c">. After that, call L<set_std('gnu11')|/"set_std">.
=head2 new_gnu99
my $config = SPVM::Builder::Config->new_gnu99(file => __FILE__);
Call L</"new_c">. After that, call L<set_std('gnu99')|/"set_std">.
=head2 new_gnu11
my $config = SPVM::Builder::Config->new_gnu11(file => __FILE__);
Call L</"new_c">. After that, call L<set_std('gnu11')|/"set_std">.
=head2 new_c99
my $config = SPVM::Builder::Config->new_c99(file => __FILE__);
Call L</"new_c">. After that, call L<set_std('c99')|/"set_std">.
=head2 new_c11
my $config = SPVM::Builder::Config->new_c11(file => __FILE__);
Call L</"new_c">. After that, call L<set_std('c11')|/"set_std">.
=head2 new_cpp
my $config = SPVM::Builder::Config->new_cpp(file => __FILE__);
Call L</"new">. After that, call L<ext('cpp')|/"ext"> and set L</"cc"> to C<C++> compiler, and set L</"ld"> to C<C++> linker.
=head2 new_cpp11
my $config = SPVM::Builder::Config->new_cpp11(file => __FILE__);
Call L</"new_cpp">. After that, call L<set_std('c++11')|/"set_std">.
=head2 new_cpp14
my $config = SPVM::Builder::Config->new_cpp14(file => __FILE__);
Call L</"new_cpp">. After that, call L<set_std('c++14')|/"set_std">.
=head2 new_cpp17
my $config = SPVM::Builder::Config->new_cpp17(file => __FILE__);
Call L</"new_cpp">. After that, call L<set_std('c++17')|/"set_std">.
=head1 Instance Methods
=head2 set_std
$config->set_std($std);
Add the value that is converted to C<-std=$std> after the last element of C<ccflags> field.
B<Example:>
$config->set_std('gnu99');
=head2 add_ccflags
$config->add_ccflags(@ccflags);
Add values after the last element of C<ccflags> field.
=head2 add_ldflags
$config->add_ldflags(@ldflags);
Add values after the last element of C<ldflags> field.
=head2 add_include_dirs
$config->add_include_dirs(@include_dirs);
Add values after the last element of C<include_dirs> field.
=head2 add_lib_dirs
$config->add_lib_dirs(@lib_dirs);
Add values after the last element of C<lib_dirs> field.
=head2 add_source_files
$config->add_source_files(@source_files);
Add the values after the last element of C<source_files> field.
=head2 add_libs
$config->add_libs(@libs);
Add library names or L<SPVM::Builder::LibInfo> objects after the last element of L</"libs"> field.
Examples:
$config->add_libs('gsl');
$config->add_libs('gsl', 'z');
$config->add_libs(
SPVM::Builder::LibInfo->new(name => 'gsl'),
SPVM::Builder::LibInfo->new(name => 'z', abs => 1),
);
=head2 add_static_libs
$config->add_static_libs(@libs);
Add library names or L<SPVM::Builder::LibInfo> objects after the last element of L</"libs"> field.
C<static> field is set to a true value.
Examples:
$config->add_static_libs('gsl');
$config->add_static_libs('gsl', 'z');
$config->add_static_libs(
SPVM::Builder::LibInfo->new(name => 'gsl'),
SPVM::Builder::LibInfo->new(name => 'z', abs => 1),
);
=head2 use_resource
$config->use_resource($resource);
$config->use_resource('Resource::Zlib::V1_0_0');
$config->use_resource('Resource::Zlib::V1_0_0', mode => 'prod', args => ['foo', 'bar']);
Use a resource.
The first argument is a L<SPVM::Builder::Resource> object.
If the first argument is a class name of the resource, a L<SPVM::Builder::Resource> object is created by L<SPVM::Builder::Resource|/"new"> method with C<class_name> option.
my $resource = SPVM::Builder::Resource->new(class_name => 'Resource::Zlib::V1_0_0');
$config->use_resource($resource);
If the rest arguments are used as the options of L<SPVM::Builder::Resource|/"new"> of L<SPVM::Builder::Resource>.
my $resource = SPVM::Builder::Resource->new(
class_name => 'Resource::Zlib::V1_0_0',
mode => 'prod',
args => ['foo', 'bar'],
);
$config->use_resource($resource);
=head2 get_resource
my $resource = $config->get_resource('Resource::Zlib::V1_0_0');
Get a resource. The resource is a L<SPVM::Builder::Resource> object.
=head2 get_resource_names
my $resource_names = $config->get_resource_names;
Get resource names.
=head1 Copyright & License
Copyright (c) 2023 Yuki Kimoto
MIT License