200 lines
5.3 KiB
Rust
200 lines
5.3 KiB
Rust
use clap::{Args, Parser, Subcommand, ValueEnum};
|
|
use paris::{error, info, success};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::error::Error;
|
|
use std::fmt;
|
|
use std::process::exit;
|
|
use tokio::io::AsyncWriteExt;
|
|
use tokio::net::TcpStream;
|
|
use url::Url;
|
|
use uuid::Uuid;
|
|
|
|
#[derive(Debug, Parser)]
|
|
#[command(name = "forge-server")]
|
|
#[command(about = "A simple remote build system", long_about = None)]
|
|
struct Cli {
|
|
#[arg(value_name = "IP")]
|
|
host: String,
|
|
|
|
#[arg(value_name = "PORT", default_value_t = 9134)]
|
|
port: u16,
|
|
|
|
#[command(subcommand)]
|
|
command: Commands,
|
|
}
|
|
|
|
#[derive(Debug, Subcommand)]
|
|
enum Commands {
|
|
/// Send a new build request
|
|
#[command(arg_required_else_help = true)]
|
|
Send {
|
|
/// Password for authentication
|
|
#[arg(long, value_name = "PASSWD")]
|
|
auth: Option<String>,
|
|
|
|
/// Whether to profile the build
|
|
#[arg(long, default_value_t = false)]
|
|
profile: bool,
|
|
|
|
/// Command(s) to run before building
|
|
#[arg(long, value_name = "CMD")]
|
|
prexec: Option<String>,
|
|
|
|
/// Remote git repository to clone from
|
|
#[arg(long, value_name = "URL")]
|
|
repo: String,
|
|
|
|
/// Build system to use
|
|
#[arg(long,require_equals = true, num_args = 0..=1, default_value_t = BuildSystem::Cargo,default_missing_value = "cargo")]
|
|
build: BuildSystem,
|
|
|
|
/// Cargo/Make subcommand to use
|
|
#[arg(long, require_equals = true, num_args = 0..=1, default_value_t = BuildSubcommand::Build, default_missing_value = "build")]
|
|
subcommand: BuildSubcommand,
|
|
|
|
/// Comma-separated cargo features
|
|
#[arg(long, value_name = "FEATURES")]
|
|
features: Option<Vec<String>>,
|
|
|
|
/// Optional cargo flag
|
|
#[arg(long, require_equals = true, num_args = 0..=1, default_value_t = Tag::Debug, default_missing_value = "debug")]
|
|
tag: Tag,
|
|
|
|
/// Basename of the repository
|
|
#[arg(long, value_name = "NAME")]
|
|
basename: String,
|
|
},
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
|
|
enum BuildSystem {
|
|
Cargo,
|
|
Make,
|
|
}
|
|
|
|
impl std::fmt::Display for BuildSystem {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
self.to_possible_value()
|
|
.expect("no values are skipped")
|
|
.get_name()
|
|
.fmt(f)
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ValueEnum, Copy, Clone, Debug, PartialEq, Eq)]
|
|
enum BuildSubcommand {
|
|
Run,
|
|
Build,
|
|
Install,
|
|
}
|
|
|
|
impl std::fmt::Display for BuildSubcommand {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
self.to_possible_value()
|
|
.expect("no values are skipped")
|
|
.get_name()
|
|
.fmt(f)
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ValueEnum, Copy, Clone, Debug, PartialEq, Eq)]
|
|
enum Tag {
|
|
Release,
|
|
Debug,
|
|
}
|
|
|
|
impl std::fmt::Display for Tag {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
self.to_possible_value()
|
|
.expect("no values are skipped")
|
|
.get_name()
|
|
.fmt(f)
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct Command {
|
|
build_system: BuildSystem,
|
|
subcommand: BuildSubcommand,
|
|
features: Option<Vec<String>>,
|
|
tag: Tag,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct Message {
|
|
authentication: Option<String>,
|
|
uuid: Uuid,
|
|
pre_exec: Option<String>,
|
|
profile: bool,
|
|
command: Command,
|
|
repository: Url,
|
|
basename: String,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<(), Box<dyn Error>> {
|
|
let args = Cli::parse();
|
|
match args.command {
|
|
Commands::Send {
|
|
auth,
|
|
repo,
|
|
profile,
|
|
prexec,
|
|
build,
|
|
subcommand,
|
|
features,
|
|
tag,
|
|
basename,
|
|
} => {
|
|
// Connect to a peer
|
|
let remote = format!("{}:{}", args.host, args.port);
|
|
let mut stream = match TcpStream::connect(&remote).await {
|
|
Ok(s) => s,
|
|
Err(e) => {
|
|
error!("Could not connect to remote host: {}", e);
|
|
exit(1);
|
|
}
|
|
};
|
|
success!("Connected to: <green>{}<//>", &remote);
|
|
|
|
let test_command = Command {
|
|
build_system: build,
|
|
subcommand,
|
|
features,
|
|
tag,
|
|
};
|
|
let test_message = Message {
|
|
authentication: auth,
|
|
uuid: Uuid::new_v4(),
|
|
pre_exec: prexec,
|
|
profile,
|
|
command: test_command,
|
|
repository: Url::parse(&repo).unwrap(),
|
|
basename,
|
|
};
|
|
|
|
info!("Message UUID: <magenta>{}<//>", &test_message.uuid);
|
|
|
|
let j = match serde_json::to_string(&test_message) {
|
|
Ok(s) => s,
|
|
Err(e) => {
|
|
error!("Could not serialize JSON message: {}", e);
|
|
exit(1);
|
|
}
|
|
};
|
|
|
|
match stream.write_all(&j.as_bytes()).await {
|
|
Ok(_) => {
|
|
info!(
|
|
"Sent JSON:\n<bright-white>{}<//>",
|
|
serde_json::to_string_pretty(&test_message)?
|
|
);
|
|
}
|
|
Err(e) => error!("Could not write to stream: {}", e),
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|