Skip to content
Snippets Groups Projects
main.rs 5.40 KiB
// Copyright 2021 Axiom-Team
//
// This file is part of Substrate-Libre-Currency.
//
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Substrate-Libre-Currency 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.

mod gen_calls_doc;

use anyhow::{Context, Result};
use clap::Parser;
use std::io::{BufReader, BufWriter};
use std::path::{Path, PathBuf};
use std::process::Command;

const MIN_RUST_VERSION: &str = "1.58.0";

#[derive(Debug, clap::Parser)]
struct DuniterXTask {
    #[clap(subcommand)]
    command: DuniterXTaskCommand,
}

#[derive(Debug, clap::Subcommand)]
enum DuniterXTaskCommand {
    /// Build duniter binary
    Build {
        #[clap(long)]
        production: bool,
    },
    /// Generate calls documentation
    GenCallsDoc,
    /// Inject runtime code in raw specs
    InjectRuntimeCode {
        #[clap(short, long, parse(from_os_str))]
        /// Runtime filepath
        runtime: PathBuf,
        #[clap(short = 's', long, parse(from_os_str))]
        /// Raw spec filepath
        raw_spec: PathBuf,
    },
    /// Execute unit tests and integration tests
    /// End2tests are skipped
    Test,
}

fn main() -> Result<()> {
    let args = DuniterXTask::parse();

    if !version_check::is_min_version(MIN_RUST_VERSION).unwrap_or(false)
        && exec_should_success(Command::new("rustup").args(&["update", "stable"])).is_err()
    {
        eprintln!(
                "Duniter requires stable Rust {} or higher. If you installed the Rust toolchain via rustup, please execute the command `rustup update stable`.",
                MIN_RUST_VERSION
            );
        std::process::exit(1);
    }
    Command::new("rustc").arg("--version").status()?;
    Command::new("cargo").arg("--version").status()?;
    match args.command {
        DuniterXTaskCommand::Build { production } => build(production),
        DuniterXTaskCommand::GenCallsDoc => gen_calls_doc::gen_calls_doc(),
        DuniterXTaskCommand::InjectRuntimeCode { runtime, raw_spec } => {
            inject_runtime_code(&raw_spec, &runtime)
        }
        DuniterXTaskCommand::Test => test(),
    }
}

fn inject_runtime_code(raw_spec: &Path, runtime: &Path) -> Result<()> {
    // Read runtime code
    // SAFETY: `mmap` is fundamentally unsafe since technically the file can change
    //         underneath us while it is mapped; in practice it's unlikely to be a problem
    let file = std::fs::File::open(runtime).with_context(|| "Failed to open runtime wasm file")?;
    let runtime_code =
        unsafe { memmap2::Mmap::map(&file).with_context(|| "Failed to read runtime wasm file")? };

    // Read raw spec
    let file = std::fs::File::open(raw_spec).with_context(|| "Failed to open raw spec file")?;
    let reader = BufReader::new(file);
    let mut json: serde_json::Value =
        serde_json::from_reader(reader).with_context(|| "Failed to read raw spec file")?;
    println!("json raw specs loaded!");

    let mut hex_runtime_code = String::with_capacity(2 + (runtime_code.len() * 2));
    hex_runtime_code.push('0');
    hex_runtime_code.push('x');
    hex_runtime_code.push_str(&hex::encode(runtime_code));
    //hex::encode_to_slice(runtime_code, &mut hex_runtime_code[2..])
    //.with_context(|| "fail to convert runtime code to hex")?;

    const CODE_KEY: &str = "0x3a636f6465";

    json.as_object_mut()
        .with_context(|| "invalid raw spec file")?
        .get_mut("genesis")
        .with_context(|| "invalid raw spec file: missing field genesis")?
        .as_object_mut()
        .with_context(|| "invalid raw spec file")?
        .get_mut("raw")
        .with_context(|| "invalid raw spec file: missing field raw")?
        .as_object_mut()
        .with_context(|| "invalid raw spec file")?
        .get_mut("top")
        .with_context(|| "invalid raw spec file: missing field top")?
        .as_object_mut()
        .with_context(|| "invalid raw spec file")?
        .insert(
            CODE_KEY.to_owned(),
            serde_json::Value::String(unsafe { std::mem::transmute(hex_runtime_code) }),
        );

    // Write modified raw specs

    let file = std::fs::File::create(raw_spec)?;

    serde_json::to_writer_pretty(BufWriter::new(file), &json)
        .with_context(|| "fail to write raw specs")?;

    Ok(())
}

fn build(_production: bool) -> Result<()> {
    exec_should_success(Command::new("cargo").args(&["clean", "-p", "duniter"]))?;
    exec_should_success(Command::new("cargo").args(&["build", "--locked"]))?;
    exec_should_success(Command::new("mkdir").args(&["build"]))?;
    exec_should_success(Command::new("mv").args(&["target/debug/duniter", "build/duniter"]))?;

    Ok(())
}

fn test() -> Result<()> {
    exec_should_success(Command::new("cargo").args(&[
        "test",
        "--workspace",
        "--exclude",
        "duniter-end2end-tests",
    ]))?;

    Ok(())
}

fn exec_should_success(command: &mut Command) -> Result<()> {
    if !command.status()?.success() {
        std::process::exit(1);
    } else {
        Ok(())
    }
}