package Module::Install::Rust;

use 5.006;
use strict;
use warnings;

use Module::Install::Base;
use TOML 0.97 ();
use Config ();

our @ISA = qw( Module::Install::Base );

=head1 NAME

Module::Install::Rust - Helpers to build Perl extensions written in Rust

=head1 VERSION

Version 0.01

=cut

our $VERSION = '0.01';


=head1 SYNOPSIS

    # In Makefile.PL
    use inc::Module::Install;

    # ...

    rust_requires libc => "0.2";
    rust_write;

    WriteAll;

=head1 DESCRIPTION

This package allows L<Module::Install> to build Perl extensions written in Rust.

=head1 COMMANDS

=head2 rust_requires

    rust_requires libc => "0.2";
    rust_requires internal_crate => { path => "../internal_crate" };

This command is used to specify Rust dependencies. First argument should be a
crate name, second - either a version string, or a hashref with keys per Cargo
manifest spec.

=cut

sub rust_requires {
    my ($self, $name, $spec) = @_;
    $self->{rust_requires}{$name} = $spec;
}

=head2 rust_feature

    rust_feature default => [ "some_feature" ];
    rust_feature some_feature => [ "some-crate/feature" ];

This command adds items to C<[features]> section of the generated C<Cargo.toml>.

=cut

sub rust_feature {
    my ($self, $name, $spec) = @_;
    die "Feature $name is already defined" if $self->{rust_features}{$name};
    $self->{rust_features}{$name} = $spec;
}

=head2 rust_use_perl_xs

    rust_use_perl_xs;

Configure crate to use C<perl-xs> bindings.

=cut

sub rust_use_perl_xs {
    my ($self, $spec) = @_;

    $spec //= { version => "0" };

    $self->rust_requires("perl-xs", $spec);
    $self->rust_clean_on_rebuild("perl-sys");
}

=head2 rust_clean_on_rebuild

    rust_clean_on_rebuild;
    # or
    rust_clean_on_rebuild qw/crate_name/;

If Makefile changed since last build, force C<cargo clean> run. If crate names
are specified, force clean only for those packages (C<cargo clean -p>).

=cut

sub rust_clean_on_rebuild {
    my ($self, @args) = @_;

    my $crates = $self->{cargo_clean} //= [];
    push @$crates, @args;
}

=head2 rust_write

    rust_write;

Writes C<Cargo.toml> and sets up Makefile options as needed.

=cut

sub rust_write {
    my $self = shift;

    $self->_rust_write_cargo;
    $self->_rust_setup_makefile;
}

sub _rust_crate_name {
    lc shift->name
}

sub _rust_target_name {
    shift->_rust_crate_name =~ s/-/_/gr
}

sub _rust_write_cargo {
    my $self = shift;

    my $crate_spec = {
        package => {
            name => $self->_rust_crate_name,
            description => $self->abstract,
            version => "1.0.0", # FIXME
        },

        lib => {
            "crate-type" => [ "dylib" ],
        },
    };

    $crate_spec->{dependencies} = $self->{rust_requires}
        if $self->{rust_requires};

    $crate_spec->{features} = $self->{rust_features}
        if $self->{rust_features};

    open my $f, ">", "Cargo.toml" or die $!;
    $f->print("# This file is autogenerated\n\n");
    $f->print(TOML::to_toml($crate_spec));
    close $f or die $!;
}

sub _rust_setup_makefile {
    my $self = shift;
    my $class = ref $self;

    # FIXME: don't assume libraries have "lib" prefix
    my $libname = "lib" . $self->_rust_target_name;

    $self->postamble(<<MAKE);
# --- $class section:

INST_RUSTDYLIB = \$(INST_ARCHAUTODIR)/\$(DLBASE).\$(DLEXT)
RUST_TARGETDIR = target/release
RUST_DYLIB = \$(RUST_TARGETDIR)/$libname.\$(DLEXT)
CARGO = cargo
CARGO_OPTS = --release

dynamic :: \$(INST_RUSTDYLIB)

MAKE

    if ($self->{cargo_clean}) {
        my @opts = map qq{-p "$_"}, @{$self->{cargo_clean}};

        $self->postamble(<<MAKE);
\$(RUST_DYLIB) ::
	test \$(FIRST_MAKEFILE) -ot \$@ || \$(CARGO) clean \$(CARGO_OPTS) @opts

MAKE
    }

    $self->postamble(<<MAKE);
\$(RUST_DYLIB) ::
	PERL=\$(FULLPERL) \$(CARGO) build \$(CARGO_OPTS)

\$(INST_RUSTDYLIB): \$(RUST_DYLIB)
	\$(CP) \$< \$@

clean ::
	\$(CARGO) clean
	\$(RM) Cargo.toml Cargo.lock
MAKE
}

=head1 AUTHOR

Vickenty Fesunov, C<< <kent at setattr.net> >>

=head1 BUGS

Please report any bugs or feature requests to L<https://github.com/vickenty/mi-rust>.

=head1 LICENSE AND COPYRIGHT

Copyright 2015 Vickenty Fesunov.

This module may be used, modified, and distributed under the same terms as Perl
itself. Please see the license that came with your Perl distribution for
details.

=cut

1;
