diff --git a/sdk/cargo-build-bpf/.gitignore b/sdk/cargo-build-bpf/.gitignore new file mode 100644 index 0000000000..1686a357aa --- /dev/null +++ b/sdk/cargo-build-bpf/.gitignore @@ -0,0 +1,2 @@ +/tests/crates/*/target/ +Cargo.lock diff --git a/sdk/cargo-build-bpf/src/main.rs b/sdk/cargo-build-bpf/src/main.rs index ac9e7f7545..66726f57ef 100644 --- a/sdk/cargo-build-bpf/src/main.rs +++ b/sdk/cargo-build-bpf/src/main.rs @@ -41,7 +41,8 @@ impl Default for Config<'_> { .parent() .expect("Unable to get parent directory") .to_path_buf() - .join("sdk/bpf"), + .join("sdk") + .join("bpf"), bpf_out_dir: None, dump: false, features: vec![], @@ -601,6 +602,13 @@ fn main() { let matches = App::new(crate_name!()) .about(crate_description!()) .version(crate_version!()) + .arg( + Arg::with_name("bpf_out_dir") + .long("bpf-out-dir") + .value_name("DIRECTORY") + .takes_value(true) + .help("Place final BPF build artifacts in this directory"), + ) .arg( Arg::with_name("bpf_sdk") .long("bpf-sdk") @@ -609,12 +617,39 @@ fn main() { .default_value(&default_bpf_sdk) .help("Path to the Solana BPF SDK"), ) + .arg( + Arg::with_name("cargo_args") + .help("Arguments passed directly to `cargo build`") + .multiple(true) + .last(true), + ) .arg( Arg::with_name("dump") .long("dump") .takes_value(false) .help("Dump ELF information to a text file on success"), ) + .arg( + Arg::with_name("features") + .long("features") + .value_name("FEATURES") + .takes_value(true) + .multiple(true) + .help("Space-separated list of features to activate"), + ) + .arg( + Arg::with_name("manifest_path") + .long("manifest-path") + .value_name("PATH") + .takes_value(true) + .help("Path to Cargo.toml"), + ) + .arg( + Arg::with_name("no_default_features") + .long("no-default-features") + .takes_value(false) + .help("Do not activate the `default` feature"), + ) .arg( Arg::with_name("offline") .long("offline") @@ -628,34 +663,6 @@ fn main() { .takes_value(false) .help("Use verbose output"), ) - .arg( - Arg::with_name("features") - .long("features") - .value_name("FEATURES") - .takes_value(true) - .multiple(true) - .help("Space-separated list of features to activate"), - ) - .arg( - Arg::with_name("no_default_features") - .long("no-default-features") - .takes_value(false) - .help("Do not activate the `default` feature"), - ) - .arg( - Arg::with_name("manifest_path") - .long("manifest-path") - .value_name("PATH") - .takes_value(true) - .help("Path to Cargo.toml"), - ) - .arg( - Arg::with_name("bpf_out_dir") - .long("bpf-out-dir") - .value_name("DIRECTORY") - .takes_value(true) - .help("Place final BPF build artifacts in this directory"), - ) .arg( Arg::with_name("workspace") .long("workspace") @@ -663,7 +670,6 @@ fn main() { .alias("all") .help("Build all BPF packages in the workspace"), ) - .arg(Arg::with_name("cargo_args").multiple(true).last(true)) .get_matches_from(args); let bpf_sdk = value_t_or_exit!(matches, "bpf_sdk", PathBuf); diff --git a/sdk/cargo-build-bpf/tests/crates.rs b/sdk/cargo-build-bpf/tests/crates.rs new file mode 100644 index 0000000000..2dac22a000 --- /dev/null +++ b/sdk/cargo-build-bpf/tests/crates.rs @@ -0,0 +1,70 @@ +use std::{ + env, fs, + io::{self, Write}, + process::{Command, Output}, +}; + +fn run_cargo_build(extra_args: &[&str]) -> Output { + let cwd = env::current_dir().expect("Unable to get current working directory"); + let root = cwd + .parent() + .expect("Unable to get parent directory of current working dir") + .parent() + .expect("Unable to get ../.. of current working dir"); + let toml = cwd + .join("tests") + .join("crates") + .join("noop") + .join("Cargo.toml"); + let toml = format!("{}", toml.display()); + let mut args = vec!["--bpf-sdk", "../bpf", "--manifest-path", &toml]; + for arg in extra_args { + args.push(arg); + } + let cargo_build_bpf = root.join("target").join("debug").join("cargo-build-bpf"); + Command::new(cargo_build_bpf) + .args(&args) + .output() + .expect("Error running cargo-build-bpf") +} + +#[test] +fn test_build() { + let output = run_cargo_build(&[]); + assert!(output.status.success()); +} + +// This test requires rustfilt. +// TODO: Add a check for rustfilt, and install it if not available. +#[ignore] +#[test] +fn test_dump() { + let output = run_cargo_build(&["--dump"]); + if !output.status.success() { + eprintln!("--- stdout ---"); + io::stderr().write_all(&output.stdout).unwrap(); + eprintln!("--- stderr ---"); + io::stderr().write_all(&output.stderr).unwrap(); + eprintln!("--------------"); + } + assert!(output.status.success()); + let cwd = env::current_dir().expect("Unable to get current working directory"); + let dump = cwd + .join("tests") + .join("crates") + .join("noop") + .join("target") + .join("deploy") + .join("noop-dump.txt"); + assert!(dump.exists()); +} + +#[test] +fn test_out_dir() { + let output = run_cargo_build(&["--bpf-out-dir", "tmp_out"]); + assert!(output.status.success()); + let cwd = env::current_dir().expect("Unable to get current working directory"); + let dir = cwd.join("tmp_out"); + assert!(dir.exists()); + fs::remove_dir_all("tmp_out").expect("Failed to remove tmp_out dir"); +} diff --git a/sdk/cargo-build-bpf/tests/crates/noop/Cargo.toml b/sdk/cargo-build-bpf/tests/crates/noop/Cargo.toml new file mode 100644 index 0000000000..35abfd9c37 --- /dev/null +++ b/sdk/cargo-build-bpf/tests/crates/noop/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "noop" +version = "1.8.0" +description = "Solana BPF test program written in Rust" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana" +license = "Apache-2.0" +homepage = "https://solana.com/" +edition = "2018" +publish = false + +[dependencies] +solana-program = { path = "../../../../program", version = "=1.8.0" } + +[lib] +crate-type = ["cdylib"] + +[workspace] diff --git a/sdk/cargo-build-bpf/tests/crates/noop/src/lib.rs b/sdk/cargo-build-bpf/tests/crates/noop/src/lib.rs new file mode 100644 index 0000000000..ac6c3d6c84 --- /dev/null +++ b/sdk/cargo-build-bpf/tests/crates/noop/src/lib.rs @@ -0,0 +1,15 @@ +//! @brief Example Rust-based BPF noop program + +extern crate solana_program; +use solana_program::{ + account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey, +}; + +entrypoint!(process_instruction); +fn process_instruction( + _program_id: &Pubkey, + _accounts: &[AccountInfo], + _instruction_data: &[u8], +) -> ProgramResult { + Ok(()) +}