ihciah / rust2go

Call Golang from Rust
https://en.ihcblog.com/rust2go/
Other
45 stars 10 forks source link

Support for cross compilation #2

Open hellais opened 2 months ago

hellais commented 2 months ago

First of all, really great work, thanks for putting this together!

I was wondering if you would be interested in accepting a patch to add support for running the go compilation step with cross compiler support.

My use case is that I would like to use rust2go to build a rust library that calls a go library that's then wrapped with JNI to be included inside of an Android application (yeah it's basically inception).

In order to achieve that I would need to be able to set certain environment variables (and possibly command line options) when the go build call is placed here: https://github.com/ihciah/rust2go/blob/master/rust2go/src/build.rs#L123.

You can check this cmake file and Makefile to understand more in detail what kind of configuration is needed to cross compile a golang shared library to be included inside of an android application.

Effectively you need to set GOOS, GOARCH, CC, CXX, CGO_CFLAGS, CGO_LDFLAGS and then add some extra parameters to the build command like: -tags linux.

I think the patch would not be too big, depending on how it's done. I see a few approaches for to doing it:

  1. Extend struct Builder to accept an additional set of parameters such as the extra environment variables and extra parameters to the go build call. The issue is that if the call to the go build changes, you need to careful of what are the default variables. Maybe there is an idiomatic way in rust to have a default set of environment variables that you can allow a user to extend or replace entirely.
  2. The most popular way to build a rust library in Android, AFAIK, is to use the mozilla rust-android-gradle plugin for it. Another approach could be to check the currently set environment variables and see if rust is being built from within the context of this kind of cross compilation (see: https://github.com/mozilla/rust-android-gradle/blob/master/plugin/src/main/kotlin/com/nishtahir/CargoBuildTask.kt#L208). Basically checking if something like CARGO_TARGET_* is set and extract from there the necessary things to map the rust specific targets into the golang syntax.

I think 2 would be nicer in that it would make the whole thing work out of the box without requiring changes to support cross compilation, however it's not clear to me if these CARGO_TARGET_* are standards or if it's something specific to the mozilla rust-android-gradle.

If you let me know which approach you think is best, I'm happy to draft a PR for it.

ihciah commented 2 months ago

Thank you for your attention to this project! Previously I faced env related issues too, and I set env before cargo build. I works but it only works for env. To make it more flexible, I think it can be implemented as adding a trait GoCompiler and let user implement and set it into builder(we can provide a default impl in rust2go itself). In this way users can compile go in any way they want.

hellais commented 2 months ago

To make it more flexible, I think it can be implemented as adding a trait GoCompiler and let user implement and set it into builder(we can provide a default impl in rust2go itself). In this way users can compile go in any way they want.

I think that would be a great idea! Let me know if you need some help with it, at the very least I can commit to reviewing your code if that's helpful. My rust is quite rusty, so it's a good excuse to practice it a bit more.

If you implement that trait I can work an example on how to hook it up to make a cross platform build for Android.

ihciah commented 1 month ago

I've just pushed a change https://github.com/ihciah/rust2go/commit/be684f6776cc3959617af805981b746295e20512 , but not published a version yet. You can check if it meets your need. Please give me some results, if it not works well, I'll update it.

nguquen commented 2 weeks ago

Hi @ihciah i tried your latest change and it works. However, i want to make an improvement on GoCompiler, to make build as default implementation in GoCompiler trait, please help to review https://github.com/ihciah/rust2go/pull/8

hellais commented 1 week ago

Thank you both so much for working on this! You are doing a great work.

Would it be helpful if I provider a review to #8 and/or review what was landed inside of #5?

ihciah commented 1 week ago

Thanks for @nguquen , I've merged the PR #8 and publish new version 0.3.11.

BTW, I also added a new big feature to implement call based on shared memory to avoid CGO high cost. Welcome to checkout the new example projects for how to use it(but it only works on linux).

hellais commented 5 days ago

Thank you both so much for working on this! It's really greatly appreciated! 🙏 🙇

hellais commented 5 days ago

@ihciah @nguquen would it perhaps be possible for you to put together an example on how to implement the build or go_build in the use-case of somebody making use of rust2go?

I made some attempts, but my rust knowledge is quite limited, and am running into the problem of "impl for type defined outside of crate.".

Even some pointers in the right direction would be greatly appreciated.

Apologies for the noob question.

nguquen commented 4 days ago

hi @hellais , here's what i'm using for build.rs:

fn main() {
    rust2go::Builder::new()
        .with_go_compiler(CrossGoCompiler {})
        .with_go_src("./go")
        .build();
}

#[derive(Debug, Clone, Copy)]
pub struct CrossGoCompiler;

impl GoCompiler for CrossGoCompiler {
    fn go_build(&self, go_src: &Path, link: LinkType, output: &Path) {
        let mut go_build = Command::new("go");

        let build_linker = env::var("RUSTC_LINKER").ok();
        if let Some(cc) = build_linker {
            go_build.env("CC", cc.clone());
        }

        go_build
            .env("GO111MODULE", "on")
            .current_dir(go_src)
            .arg("build")
            .arg(if link == LinkType::Static {
                "-buildmode=c-archive"
            } else {
                "-buildmode=c-shared"
            })
            .arg("-o")
            .arg(output)
            .arg(".");

        go_build.status().expect("Go build failed");
    }
}

To support cross compilation, actually we also need other env vars like CGO_ENABLED, GOOS, GOARCH, but i refer to set them via env vars rather than in build.rs. For example, i'm using cargo-make to set them before build:

[tasks."build:release:aarch64-unknown-linux-gnu"]
env = { BUILD_TARGET = "aarch64-unknown-linux-gnu", CGO_ENABLED = "1", GOOS = "linux", GOARCH = "arm64" }
...