File Coverage

blib/lib/Module/Install/Rust.pm
Criterion Covered Total %
statement 17 61 27.8
branch 0 18 0.0
condition 0 4 0.0
subroutine 6 16 37.5
pod 6 6 100.0
total 29 105 27.6


line stmt bran cond sub pod time code
1             package Module::Install::Rust;
2              
3 1     1   12657 use 5.006;
  1         2  
4 1     1   3 use strict;
  1         1  
  1         14  
5 1     1   3 use warnings;
  1         3  
  1         22  
6              
7 1     1   342 use Module::Install::Base;
  1         1  
  1         22  
8 1     1   423 use TOML 0.97 ();
  1         17632  
  1         17  
9 1     1   5 use Config ();
  1         1  
  1         495  
10              
11             our @ISA = qw( Module::Install::Base );
12              
13             =head1 NAME
14              
15             Module::Install::Rust - Helpers to build Perl extensions written in Rust
16              
17             =head1 VERSION
18              
19             Version 0.02
20              
21             =cut
22              
23             our $VERSION = '0.04';
24              
25              
26             =head1 SYNOPSIS
27              
28             # In Makefile.PL
29             use inc::Module::Install;
30              
31             # ...
32              
33             rust_requires libc => "0.2";
34             rust_write;
35              
36             WriteAll;
37              
38             =head1 DESCRIPTION
39              
40             This package allows L to build Perl extensions written in Rust.
41              
42             =head1 COMMANDS
43              
44             =head2 rust_requires
45              
46             rust_requires libc => "0.2";
47             rust_requires internal_crate => { path => "../internal_crate" };
48              
49             This command is used to specify Rust dependencies. First argument should be a
50             crate name, second - either a version string, or a hashref with keys per Cargo
51             manifest spec.
52              
53             =cut
54              
55             sub rust_requires {
56 0     0 1   my ($self, $name, $spec) = @_;
57 0           $self->{rust_requires}{$name} = $spec;
58             }
59              
60             =head2 rust_feature
61              
62             rust_feature default => [ "some_feature" ];
63             rust_feature some_feature => [ "some-crate/feature" ];
64              
65             This command adds items to C<[features]> section of the generated C.
66              
67             =cut
68              
69             sub rust_feature {
70 0     0 1   my ($self, $name, $spec) = @_;
71 0 0         die "Feature $name is already defined" if $self->{rust_features}{$name};
72 0           $self->{rust_features}{$name} = $spec;
73             }
74              
75             =head2 rust_profile
76              
77             rust_profile debug => { "opt-level" => 1 };
78             rust_profile release => { lto => 1 };
79              
80             This command configures a C<[profile]> section to the generated C.
81              
82             =cut
83              
84             sub rust_profile {
85 0     0 1   my ($self, $name, $spec) = @_;
86 0 0         die "Profile $name is already configured" if $self->{rust_profile}{$name};
87              
88 0           $self->{rust_profile}{$name} = $spec;
89             }
90              
91             =head2 rust_use_perl_xs
92              
93             rust_use_perl_xs;
94              
95             Configure crate to use C bindings.
96              
97             =cut
98              
99             sub rust_use_perl_xs {
100 0     0 1   my ($self, $spec) = @_;
101              
102 0   0       $spec //= { version => "0" };
103              
104 0           $self->rust_requires("perl-xs", $spec);
105 0           $self->rust_clean_on_rebuild("perl-sys");
106             }
107              
108             =head2 rust_clean_on_rebuild
109              
110             rust_clean_on_rebuild;
111             # or
112             rust_clean_on_rebuild qw/crate_name/;
113              
114             If Makefile changed since last build, force C run. If crate names
115             are specified, force clean only for those packages (C).
116              
117             =cut
118              
119             sub rust_clean_on_rebuild {
120 0     0 1   my ($self, @args) = @_;
121              
122 0   0       my $crates = $self->{cargo_clean} //= [];
123 0           push @$crates, @args;
124             }
125              
126             =head2 rust_write
127              
128             rust_write;
129              
130             Writes C and sets up Makefile options as needed.
131              
132             =cut
133              
134             sub rust_write {
135 0     0 1   my $self = shift;
136              
137 0           $self->_rust_write_cargo;
138 0           $self->_rust_setup_makefile;
139             }
140              
141             sub _rust_crate_name {
142             lc shift->name
143 0     0     }
144              
145             sub _rust_target_name {
146 0     0     shift->_rust_crate_name =~ s/-/_/gr
147             }
148              
149             sub _rust_write_cargo {
150 0     0     my $self = shift;
151              
152 0           my $crate_spec = {
153             package => {
154             name => $self->_rust_crate_name,
155             description => $self->abstract,
156             version => "1.0.0", # FIXME
157             },
158              
159             lib => {
160             "crate-type" => [ "cdylib" ],
161             },
162             };
163              
164             $crate_spec->{dependencies} = $self->{rust_requires}
165 0 0         if $self->{rust_requires};
166              
167             $crate_spec->{features} = $self->{rust_features}
168 0 0         if $self->{rust_features};
169              
170             $crate_spec->{profile} = $self->{rust_profile}
171 0 0         if $self->{rust_profile};
172              
173 0 0         open my $f, ">", "Cargo.toml" or die $!;
174 0           $f->print("# This file is autogenerated\n\n");
175 0           $f->print(TOML::to_toml($crate_spec));
176 0 0         close $f or die $!;
177             }
178              
179             sub _rust_setup_makefile {
180 0     0     my $self = shift;
181 0           my $class = ref $self;
182              
183             # FIXME: don't assume libraries have "lib" prefix
184 0           my $libname = "lib" . $self->_rust_target_name;
185              
186 0           my $rustc_opts = "";
187 0           my $postproc;
188 0 0         if ($^O eq "darwin") {
189             # Linker flag to allow bundle to use symbols from the parent process.
190 0           $rustc_opts = "-C link-args='-undefined dynamic_lookup'";
191              
192             # On darwin, Perl uses special darwin-specific format for loadable
193             # modules. Normally it is produced by passing "-bundle" flag to the
194             # linker, but Rust as of 1.12 does not support that.
195             #
196             # "-C link-args=-bundle" doesn't work, because then "-bundle" conflicts
197             # with "-dylib" option used by rustc.
198             #
199             # However, it seems possible to produce correct ".bundle" file by
200             # running linker with correct options on the shared library that was
201             # created by rustc.
202 0           $postproc = <
203             \$(LD) \$(LDDLFLAGS) -o \$@ \$<
204             MAKE
205             } else {
206 0           $postproc = <
207             \$(CP) \$< \$@
208             MAKE
209             }
210              
211              
212              
213 0           $self->postamble(<
214             # --- $class section:
215              
216             INST_RUSTDYLIB = \$(INST_ARCHAUTODIR)/\$(DLBASE).\$(DLEXT)
217             RUST_TARGETDIR = target/release
218             RUST_DYLIB = \$(RUST_TARGETDIR)/$libname.\$(SO)
219             CARGO = cargo
220             CARGO_OPTS = --release
221             RUSTC_OPTS = $rustc_opts
222              
223             dynamic :: \$(INST_RUSTDYLIB)
224              
225             MAKE
226              
227 0 0         if ($self->{cargo_clean}) {
228 0           my @opts = map qq{-p "$_"}, @{$self->{cargo_clean}};
  0            
229              
230 0           $self->postamble(<
231             \$(RUST_DYLIB) ::
232             test \$(FIRST_MAKEFILE) -ot \$@ || \$(CARGO) clean \$(CARGO_OPTS) @opts
233              
234             MAKE
235             }
236              
237 0           $self->postamble(<
238             \$(RUST_DYLIB) ::
239             PERL=\$(FULLPERL) \$(CARGO) rustc \$(CARGO_OPTS) -- \$(RUSTC_OPTS)
240              
241             \$(INST_RUSTDYLIB): \$(RUST_DYLIB)
242             $postproc
243              
244             clean ::
245             \$(CARGO) clean
246             \$(RM) Cargo.toml Cargo.lock
247             MAKE
248             }
249              
250             =head1 AUTHOR
251              
252             Vickenty Fesunov, C<< >>
253              
254             =head1 BUGS
255              
256             Please report any bugs or feature requests to L.
257              
258             =head1 LICENSE AND COPYRIGHT
259              
260             Copyright 2016 Vickenty Fesunov.
261              
262             This module may be used, modified, and distributed under the same terms as Perl
263             itself. Please see the license that came with your Perl distribution for
264             details.
265              
266             =cut
267              
268             1;