Skip to content

Commit

Permalink
feat(gbuild): introduce cargo-gbuild (#3835)
Browse files Browse the repository at this point in the history
  • Loading branch information
clearloop authored Apr 26, 2024
1 parent f103875 commit e29d9c3
Show file tree
Hide file tree
Showing 15 changed files with 1,119 additions and 4 deletions.
2 changes: 2 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[alias]
gbuild = "run -p cargo-gbuild --"
26 changes: 26 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ base64 = "0.21.7"
byteorder = { version = "1.5.0", default-features = false }
blake2-rfc = { version = "0.2.18", default-features = false }
bs58 = { version = "0.5.1", default-features = false }
cargo_toml = "0.19.2"
cargo_metadata = "0.18.1"
clap = "4.5.4"
codec = { package = "parity-scale-codec", version = "3.6.4", default-features = false }
Expand Down
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,14 @@ test-gcli: node
test-gcli-release: node-release
@ ./scripts/gear.sh test gcli --release

.PHONY: test-gbuild
test-gbuild: node
@ ./scripts/gear.sh test gbuild

.PHONY: test-gbuild-release
test-gbuild-release: node-release
@ ./scripts/gear.sh test gbuild --release

.PHONY: test-pallet
test-pallet:
@ ./scripts/gear.sh test pallet
Expand Down
22 changes: 22 additions & 0 deletions utils/cargo-gbuild/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "cargo-gbuild"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true

[dependencies]
anyhow.workspace = true
cargo_toml.workspace = true
clap = { workspace = true, features = ["derive"] }
colored.workspace = true
serde = { workspace = true, features = ["derive"] }
toml.workspace = true
tracing.workspace = true
tracing-subscriber = { workspace = true, features = ["env-filter"] }
gear-wasm-builder.workspace = true

[dev-dependencies]
gtest.workspace = true
66 changes: 66 additions & 0 deletions utils/cargo-gbuild/src/artifact.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// This file is part of Gear.
//
// Copyright (C) 2024 Gear Technologies Inc.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use anyhow::{anyhow, Result};
use gear_wasm_builder::optimize::{self, OptType, Optimizer};
use std::{fs, path::PathBuf};

/// Gbuild artifact registry
///
/// This instance simply holds the paths of the built binaries
/// for re-using stuffs.
///
/// TODO: support workspace format, abstract instance for different programs (#3852)
pub struct Artifact {
/// The directory path of the artifact.
pub root: PathBuf,
/// Program name of this artifact.
pub name: String,
/// The path to the built program.
pub program: PathBuf,
}

impl Artifact {
/// Create a new artifact registry.
pub fn new(root: PathBuf, name: String) -> Result<Self> {
fs::create_dir_all(&root)
.map_err(|e| anyhow!("Failed to create the artifact directory, {e}"))?;

Ok(Self {
program: root.join(format!("{name}.wasm")),
name,
root,
})
}

/// Build artifacts with optimization.
pub fn process(&self, src: PathBuf) -> Result<()> {
optimize::optimize_wasm(
src.join(format!("{}.wasm", self.name)),
self.program.clone(),
"4",
true,
)?;
let mut optimizer = Optimizer::new(self.program.clone())?;
optimizer
.insert_stack_end_export()
.map_err(|e| anyhow!(e))?;
optimizer.strip_custom_sections();
fs::write(self.program.clone(), optimizer.optimize(OptType::Opt)?).map_err(Into::into)
}
}
66 changes: 66 additions & 0 deletions utils/cargo-gbuild/src/bin/cargo-gbuild.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// This file is part of Gear.
//
// Copyright (C) 2024 Gear Technologies Inc.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use std::env;

use anyhow::Result;
use cargo_gbuild::GBuild;
use clap::Parser;
use tracing_subscriber::filter::EnvFilter;

const CUSTOM_COMMAND_NAME: &str = "gbuild";

/// Command `gbuild` as cargo extension.
#[derive(Parser)]
#[clap(author, version)]
#[command(name = "cargo-gbuild")]
struct App {
/// The verbosity level
#[clap(short, long, action = clap::ArgAction::Count)]
pub verbose: u8,

/// The gbuild command.
#[clap(flatten)]
pub command: GBuild,
}

fn main() -> Result<()> {
let args = env::args().enumerate().filter_map(|(idx, arg)| {
if idx == 1 && arg == CUSTOM_COMMAND_NAME {
return None;
}

Some(arg)
});

let app = App::parse_from(args);

// Replace the binary name to library name.
let name = env!("CARGO_PKG_NAME").replace('-', "_");
let env = EnvFilter::try_from_default_env().unwrap_or(EnvFilter::new(match app.verbose {
0 => format!("{name}=info"),
1 => format!("{name}=debug"),
2 => "debug".into(),
_ => "trace".into(),
}));

tracing_subscriber::fmt().with_env_filter(env).init();
let artifact = app.command.run()?;
tracing::info!("The artifact has been generated at {:?}", artifact.root);
Ok(())
}
135 changes: 135 additions & 0 deletions utils/cargo-gbuild/src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// This file is part of Gear.
//
// Copyright (C) 2024 Gear Technologies Inc.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

//! TODO: Introduce a standard for the project structure of gear programs (#3866)

use crate::Artifact;
use anyhow::{anyhow, Result};
use cargo_toml::Manifest;
use clap::Parser;
use colored::Colorize;
use gear_wasm_builder::CargoCommand;
use std::{
env, fs,
path::{Path, PathBuf},
};

const ARTIFACT_DIR: &str = "gbuild";
const DEV_PROFILE: &str = "dev";
const DEBUG_ARTIFACT: &str = "debug";
const RELEASE_PROFILE: &str = "release";

/// Command `gbuild` as cargo extension.
#[derive(Parser)]
pub struct GBuild {
/// The path to the program manifest
#[clap(short, long, default_value = "Cargo.toml")]
pub manifest_path: PathBuf,

/// Space or comma separated list of features to activate
#[clap(short = 'F', long)]
pub features: Vec<String>,

/// If enables the release profile.
#[clap(short, long)]
pub release: bool,

/// Directory for all generated artifacts
///
/// If not set, the default value will be the target folder
/// of the cargo project.
#[clap(short, long)]
pub target_dir: Option<PathBuf>,

/// Build artifacts with the specified profile
#[clap(long)]
pub profile: Option<String>,
}

impl GBuild {
/// Run the gbuild command
///
/// TODO: Support `gtest::Program::current` (#3851)
pub fn run(self) -> Result<Artifact> {
// 1. Get the cargo target directory
//
// TODO: Detect if the package is part of a workspace. (#3852)
// TODO: Support target dir defined in `.cargo/config.toml` (#3852)
let absolute_root = fs::canonicalize(self.manifest_path.clone())?;
let cargo_target_dir = env::var("CARGO_TARGET_DIR").map(PathBuf::from).unwrap_or(
absolute_root
.parent()
.ok_or_else(|| anyhow!("Failed to parse the root directory."))?
.join("target"),
);

// 2. Run the cargo command, process optimizations and collect artifacts.
let cargo_artifact_dir = self.cargo(&cargo_target_dir)?;
let gbuild_artifact_dir = self
.target_dir
.unwrap_or(cargo_target_dir.clone())
.join(ARTIFACT_DIR);

let artifact = Artifact::new(
gbuild_artifact_dir,
Manifest::from_path(self.manifest_path.clone())?
.package()
.name
.replace('-', "_"),
)?;
artifact.process(cargo_artifact_dir)?;
Ok(artifact)
}

/// Process the cargo command.
///
/// TODO: Support workspace build. (#3852)
fn cargo(&self, target_dir: &Path) -> Result<PathBuf> {
let mut kargo = CargoCommand::default();
let mut artifact = DEBUG_ARTIFACT;

if let Some(profile) = &self.profile {
if self.release {
eprintln!(
"{}: conflicting usage of --profile={} and --release
The `--release` flag is the same as `--profile=release`.
Remove one flag or the other to continue.",
"error".red().bold(),
profile
);
std::process::exit(1);
}

kargo.set_profile(profile.clone());
if profile != DEV_PROFILE {
artifact = profile;
}
} else if self.release {
kargo.set_profile(RELEASE_PROFILE.into());
artifact = RELEASE_PROFILE;
}

kargo.set_manifest_path(self.manifest_path.clone());
kargo.set_target_dir(target_dir.to_path_buf());
kargo.set_features(&self.features);
kargo.run()?;

// Returns the root of the built artifact
Ok(target_dir.join(format!("wasm32-unknown-unknown/{}", artifact)))
}
}
26 changes: 26 additions & 0 deletions utils/cargo-gbuild/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// This file is part of Gear.
//
// Copyright (C) 2024 Gear Technologies Inc.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

//! Cargo extension for building gear programs.

#![deny(missing_docs)]

mod artifact;
mod cli;

pub use self::{artifact::Artifact, cli::GBuild};
Loading

0 comments on commit e29d9c3

Please sign in to comment.