2020-12-16 10:31:49 -08:00
|
|
|
use {
|
|
|
|
clap::{
|
|
|
|
crate_description, crate_name, crate_version, value_t, value_t_or_exit, values_t, App, Arg,
|
|
|
|
},
|
|
|
|
solana_sdk::signature::{write_keypair_file, Keypair},
|
|
|
|
std::{
|
|
|
|
env,
|
|
|
|
ffi::OsStr,
|
|
|
|
fs,
|
|
|
|
path::{Path, PathBuf},
|
|
|
|
process::exit,
|
|
|
|
process::Command,
|
|
|
|
},
|
2020-10-20 17:50:20 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Config {
|
2020-11-04 13:52:03 -08:00
|
|
|
bpf_out_dir: Option<PathBuf>,
|
2020-11-04 18:29:26 -08:00
|
|
|
bpf_sdk: PathBuf,
|
2020-10-20 17:50:20 -07:00
|
|
|
dump: bool,
|
|
|
|
features: Vec<String>,
|
|
|
|
no_default_features: bool,
|
2020-11-12 16:39:33 -08:00
|
|
|
offline: bool,
|
2020-11-04 18:29:26 -08:00
|
|
|
verbose: bool,
|
2020-11-06 10:50:28 -08:00
|
|
|
workspace: bool,
|
2020-10-20 17:50:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Config {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
bpf_sdk: env::current_exe()
|
2020-10-22 17:10:42 -07:00
|
|
|
.expect("Unable to get current executable")
|
2020-10-20 17:50:20 -07:00
|
|
|
.parent()
|
|
|
|
.expect("Unable to get parent directory")
|
|
|
|
.to_path_buf()
|
|
|
|
.join("sdk/bpf"),
|
2020-11-04 13:52:03 -08:00
|
|
|
bpf_out_dir: None,
|
2020-11-04 18:29:26 -08:00
|
|
|
dump: false,
|
2020-10-20 17:50:20 -07:00
|
|
|
features: vec![],
|
|
|
|
no_default_features: false,
|
2020-11-12 16:39:33 -08:00
|
|
|
offline: false,
|
2020-11-04 18:29:26 -08:00
|
|
|
verbose: false,
|
2020-11-06 10:50:28 -08:00
|
|
|
workspace: false,
|
2020-10-20 17:50:20 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn spawn<I, S>(program: &Path, args: I)
|
|
|
|
where
|
|
|
|
I: IntoIterator<Item = S>,
|
|
|
|
S: AsRef<OsStr>,
|
|
|
|
{
|
|
|
|
let args = args.into_iter().collect::<Vec<_>>();
|
|
|
|
print!("Running: {}", program.display());
|
|
|
|
for arg in args.iter() {
|
|
|
|
print!(" {}", arg.as_ref().to_str().unwrap_or("?"));
|
|
|
|
}
|
|
|
|
println!();
|
|
|
|
|
|
|
|
let mut child = Command::new(program)
|
|
|
|
.args(&args)
|
|
|
|
.spawn()
|
|
|
|
.unwrap_or_else(|err| {
|
|
|
|
eprintln!("Failed to execute {}: {}", program.display(), err);
|
|
|
|
exit(1);
|
|
|
|
});
|
|
|
|
|
|
|
|
let exit_status = child.wait().expect("failed to wait on child");
|
|
|
|
if !exit_status.success() {
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-06 10:50:28 -08:00
|
|
|
fn build_bpf_package(
|
|
|
|
config: &Config,
|
|
|
|
target_directory: &PathBuf,
|
|
|
|
package: &cargo_metadata::Package,
|
|
|
|
) {
|
2020-10-20 17:50:20 -07:00
|
|
|
let program_name = {
|
2020-11-06 10:50:28 -08:00
|
|
|
let cdylib_targets = package
|
2020-10-20 17:50:20 -07:00
|
|
|
.targets
|
|
|
|
.iter()
|
|
|
|
.filter_map(|target| {
|
|
|
|
if target.crate_types.contains(&"cdylib".to_string()) {
|
|
|
|
Some(&target.name)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
match cdylib_targets.len() {
|
|
|
|
0 => {
|
|
|
|
println!(
|
|
|
|
"Note: {} crate does not contain a cdylib target",
|
2020-11-06 10:50:28 -08:00
|
|
|
package.name
|
2020-10-20 17:50:20 -07:00
|
|
|
);
|
|
|
|
None
|
|
|
|
}
|
|
|
|
1 => Some(cdylib_targets[0].replace("-", "_")),
|
|
|
|
_ => {
|
|
|
|
eprintln!(
|
|
|
|
"{} crate contains multiple cdylib targets: {:?}",
|
2020-11-06 10:50:28 -08:00
|
|
|
package.name, cdylib_targets
|
2020-10-20 17:50:20 -07:00
|
|
|
);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-11-06 10:50:28 -08:00
|
|
|
let legacy_program_feature_present = package.name == "solana-sdk";
|
|
|
|
let root_package_dir = &package.manifest_path.parent().unwrap_or_else(|| {
|
2020-10-20 17:50:20 -07:00
|
|
|
eprintln!(
|
|
|
|
"Unable to get directory of {}",
|
2020-11-06 10:50:28 -08:00
|
|
|
package.manifest_path.display()
|
2020-10-20 17:50:20 -07:00
|
|
|
);
|
|
|
|
exit(1);
|
|
|
|
});
|
|
|
|
|
2020-11-04 13:52:03 -08:00
|
|
|
let bpf_out_dir = config
|
|
|
|
.bpf_out_dir
|
2020-11-06 10:50:28 -08:00
|
|
|
.as_ref()
|
|
|
|
.cloned()
|
|
|
|
.unwrap_or_else(|| target_directory.join("deploy"));
|
2020-11-04 13:52:03 -08:00
|
|
|
|
2020-11-06 10:50:28 -08:00
|
|
|
let target_build_directory = target_directory.join("bpfel-unknown-unknown/release");
|
2020-10-20 17:50:20 -07:00
|
|
|
|
|
|
|
env::set_current_dir(&root_package_dir).unwrap_or_else(|err| {
|
|
|
|
eprintln!(
|
|
|
|
"Unable to set current directory to {}: {}",
|
|
|
|
root_package_dir.display(),
|
|
|
|
err
|
|
|
|
);
|
|
|
|
exit(1);
|
|
|
|
});
|
|
|
|
|
|
|
|
println!("BPF SDK: {}", config.bpf_sdk.display());
|
|
|
|
if config.no_default_features {
|
|
|
|
println!("No default features");
|
|
|
|
}
|
|
|
|
if !config.features.is_empty() {
|
|
|
|
println!("Features: {}", config.features.join(" "));
|
|
|
|
}
|
|
|
|
if legacy_program_feature_present {
|
|
|
|
println!("Legacy program feature detected");
|
|
|
|
}
|
|
|
|
|
|
|
|
let xargo_build = config.bpf_sdk.join("rust/xargo-build.sh");
|
2020-11-04 18:29:26 -08:00
|
|
|
let mut xargo_build_args = vec![];
|
2020-10-20 17:50:20 -07:00
|
|
|
|
|
|
|
if config.no_default_features {
|
2020-11-04 18:29:26 -08:00
|
|
|
xargo_build_args.push("--no-default-features");
|
2020-10-20 17:50:20 -07:00
|
|
|
}
|
|
|
|
for feature in &config.features {
|
2020-11-04 18:29:26 -08:00
|
|
|
xargo_build_args.push("--features");
|
|
|
|
xargo_build_args.push(feature);
|
2020-10-20 17:50:20 -07:00
|
|
|
}
|
|
|
|
if legacy_program_feature_present {
|
|
|
|
if !config.no_default_features {
|
2020-11-04 18:29:26 -08:00
|
|
|
xargo_build_args.push("--no-default-features");
|
2020-10-20 17:50:20 -07:00
|
|
|
}
|
2020-11-04 18:29:26 -08:00
|
|
|
xargo_build_args.push("--features=program");
|
|
|
|
}
|
|
|
|
if config.verbose {
|
|
|
|
xargo_build_args.push("--verbose");
|
2020-10-20 17:50:20 -07:00
|
|
|
}
|
2020-11-04 18:29:26 -08:00
|
|
|
spawn(&config.bpf_sdk.join(xargo_build), &xargo_build_args);
|
2020-10-20 17:50:20 -07:00
|
|
|
|
|
|
|
if let Some(program_name) = program_name {
|
|
|
|
let program_unstripped_so = target_build_directory.join(&format!("{}.so", program_name));
|
2020-11-04 13:52:03 -08:00
|
|
|
let program_dump = bpf_out_dir.join(&format!("{}-dump.txt", program_name));
|
|
|
|
let program_so = bpf_out_dir.join(&format!("{}.so", program_name));
|
2020-12-16 10:31:49 -08:00
|
|
|
let program_keypair = bpf_out_dir.join(&format!("{}-keypair.json", program_name));
|
2020-10-20 17:50:20 -07:00
|
|
|
|
2020-11-05 08:56:35 -08:00
|
|
|
fn file_older_or_missing(prerequisite_file: &Path, target_file: &Path) -> bool {
|
|
|
|
let prerequisite_metadata = fs::metadata(prerequisite_file).unwrap_or_else(|err| {
|
|
|
|
eprintln!(
|
|
|
|
"Unable to get file metadata for {}: {}",
|
|
|
|
prerequisite_file.display(),
|
|
|
|
err
|
|
|
|
);
|
|
|
|
exit(1);
|
|
|
|
});
|
|
|
|
|
|
|
|
if let Ok(target_metadata) = fs::metadata(target_file) {
|
|
|
|
use std::time::UNIX_EPOCH;
|
|
|
|
prerequisite_metadata.modified().unwrap_or(UNIX_EPOCH)
|
|
|
|
> target_metadata.modified().unwrap_or(UNIX_EPOCH)
|
|
|
|
} else {
|
|
|
|
true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-16 10:31:49 -08:00
|
|
|
if !program_keypair.exists() {
|
|
|
|
write_keypair_file(&Keypair::new(), &program_keypair).unwrap_or_else(|err| {
|
|
|
|
eprintln!(
|
|
|
|
"Unable to get create {}: {}",
|
|
|
|
program_keypair.display(),
|
|
|
|
err
|
|
|
|
);
|
|
|
|
exit(1);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-11-05 08:56:35 -08:00
|
|
|
if file_older_or_missing(&program_unstripped_so, &program_so) {
|
|
|
|
spawn(
|
|
|
|
&config.bpf_sdk.join("scripts/strip.sh"),
|
|
|
|
&[&program_unstripped_so, &program_so],
|
|
|
|
);
|
|
|
|
}
|
2020-10-20 17:50:20 -07:00
|
|
|
|
2020-11-05 08:56:35 -08:00
|
|
|
if config.dump && file_older_or_missing(&program_unstripped_so, &program_dump) {
|
2020-10-20 17:50:20 -07:00
|
|
|
spawn(
|
|
|
|
&config.bpf_sdk.join("scripts/dump.sh"),
|
|
|
|
&[&program_unstripped_so, &program_dump],
|
|
|
|
);
|
|
|
|
}
|
2020-11-04 13:52:03 -08:00
|
|
|
|
|
|
|
println!();
|
|
|
|
println!("To deploy this program:");
|
2020-12-21 13:02:53 -08:00
|
|
|
println!(" $ solana program deploy {}", program_so.display());
|
2020-10-20 17:50:20 -07:00
|
|
|
} else if config.dump {
|
|
|
|
println!("Note: --dump is only available for crates with a cdylib target");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-06 10:50:28 -08:00
|
|
|
fn build_bpf(config: Config, manifest_path: Option<PathBuf>) {
|
|
|
|
let mut metadata_command = cargo_metadata::MetadataCommand::new();
|
|
|
|
if let Some(manifest_path) = manifest_path {
|
|
|
|
metadata_command.manifest_path(manifest_path);
|
|
|
|
}
|
2020-11-12 16:39:33 -08:00
|
|
|
if config.offline {
|
|
|
|
metadata_command.other_options(vec!["--offline".to_string()]);
|
|
|
|
}
|
2020-11-06 10:50:28 -08:00
|
|
|
|
|
|
|
let metadata = metadata_command.exec().unwrap_or_else(|err| {
|
|
|
|
eprintln!("Failed to obtain package metadata: {}", err);
|
|
|
|
exit(1);
|
|
|
|
});
|
|
|
|
|
|
|
|
if let Some(root_package) = metadata.root_package() {
|
|
|
|
if !config.workspace {
|
|
|
|
build_bpf_package(&config, &metadata.target_directory, root_package);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let all_bpf_packages = metadata
|
|
|
|
.packages
|
|
|
|
.iter()
|
|
|
|
.filter(|package| {
|
|
|
|
package.manifest_path.with_file_name("Xargo.toml").exists()
|
|
|
|
&& metadata.workspace_members.contains(&package.id)
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
for package in all_bpf_packages {
|
|
|
|
build_bpf_package(&config, &metadata.target_directory, package);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-20 17:50:20 -07:00
|
|
|
fn main() {
|
2020-10-22 17:10:42 -07:00
|
|
|
let default_config = Config::default();
|
|
|
|
let default_bpf_sdk = format!("{}", default_config.bpf_sdk.display());
|
2020-10-20 17:50:20 -07:00
|
|
|
|
|
|
|
let mut args = env::args().collect::<Vec<_>>();
|
|
|
|
// When run as a cargo subcommand, the first program argument is the subcommand name.
|
|
|
|
// Remove it
|
|
|
|
if let Some(arg1) = args.get(1) {
|
|
|
|
if arg1 == "build-bpf" {
|
|
|
|
args.remove(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let matches = App::new(crate_name!())
|
|
|
|
.about(crate_description!())
|
|
|
|
.version(crate_version!())
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("bpf_sdk")
|
|
|
|
.long("bpf-sdk")
|
|
|
|
.value_name("PATH")
|
|
|
|
.takes_value(true)
|
|
|
|
.default_value(&default_bpf_sdk)
|
|
|
|
.help("Path to the Solana BPF SDK"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("dump")
|
|
|
|
.long("dump")
|
|
|
|
.takes_value(false)
|
|
|
|
.help("Dump ELF information to a text file on success"),
|
|
|
|
)
|
2020-11-12 16:39:33 -08:00
|
|
|
.arg(
|
|
|
|
Arg::with_name("offline")
|
|
|
|
.long("offline")
|
|
|
|
.takes_value(false)
|
|
|
|
.help("Run without accessing the network"),
|
|
|
|
)
|
2020-11-04 18:29:26 -08:00
|
|
|
.arg(
|
|
|
|
Arg::with_name("verbose")
|
|
|
|
.short("v")
|
|
|
|
.long("verbose")
|
|
|
|
.takes_value(false)
|
|
|
|
.help("Use verbose output"),
|
|
|
|
)
|
2020-10-20 17:50:20 -07:00
|
|
|
.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"),
|
|
|
|
)
|
2020-10-22 17:10:42 -07:00
|
|
|
.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"),
|
|
|
|
)
|
2020-11-06 10:50:28 -08:00
|
|
|
.arg(
|
|
|
|
Arg::with_name("workspace")
|
|
|
|
.long("workspace")
|
|
|
|
.takes_value(false)
|
|
|
|
.alias("all")
|
|
|
|
.help("Build all BPF packages in the workspace"),
|
|
|
|
)
|
2020-10-20 17:50:20 -07:00
|
|
|
.get_matches_from(args);
|
|
|
|
|
|
|
|
let bpf_sdk = value_t_or_exit!(matches, "bpf_sdk", PathBuf);
|
2020-11-04 13:52:03 -08:00
|
|
|
let bpf_out_dir = value_t!(matches, "bpf_out_dir", PathBuf).ok();
|
2020-10-20 17:50:20 -07:00
|
|
|
|
|
|
|
let config = Config {
|
|
|
|
bpf_sdk: fs::canonicalize(&bpf_sdk).unwrap_or_else(|err| {
|
|
|
|
eprintln!(
|
|
|
|
"BPF SDK path does not exist: {}: {}",
|
|
|
|
bpf_sdk.display(),
|
|
|
|
err
|
|
|
|
);
|
|
|
|
exit(1);
|
|
|
|
}),
|
2020-11-04 13:52:03 -08:00
|
|
|
bpf_out_dir: bpf_out_dir.map(|bpf_out_dir| {
|
|
|
|
if bpf_out_dir.is_absolute() {
|
|
|
|
bpf_out_dir
|
|
|
|
} else {
|
|
|
|
env::current_dir()
|
|
|
|
.expect("Unable to get current working directory")
|
|
|
|
.join(bpf_out_dir)
|
|
|
|
}
|
|
|
|
}),
|
2020-10-20 17:50:20 -07:00
|
|
|
dump: matches.is_present("dump"),
|
|
|
|
features: values_t!(matches, "features", String)
|
|
|
|
.ok()
|
|
|
|
.unwrap_or_else(Vec::new),
|
|
|
|
no_default_features: matches.is_present("no_default_features"),
|
2020-11-12 16:39:33 -08:00
|
|
|
offline: matches.is_present("offline"),
|
2020-11-04 18:29:26 -08:00
|
|
|
verbose: matches.is_present("verbose"),
|
2020-11-06 10:50:28 -08:00
|
|
|
workspace: matches.is_present("workspace"),
|
2020-10-20 17:50:20 -07:00
|
|
|
};
|
2020-11-06 10:50:28 -08:00
|
|
|
let manifest_path = value_t!(matches, "manifest_path", PathBuf).ok();
|
|
|
|
build_bpf(config, manifest_path);
|
2020-10-20 17:50:20 -07:00
|
|
|
}
|