use std::{collections::HashMap, fs::create_dir_all, path::PathBuf, process::Command};
use clap::{Parser, value_parser};
use volo_build::{
config_builder::InitBuilder,
model::{DEFAULT_FILENAME, Entry},
util::{
DEFAULT_CONFIG_FILE, create_git_service, detect_protocol, git_repo_init, init_git_repo,
init_local_service, modify_local_init_service_path_relative_to_yml,
},
};
use crate::command::CliCommand;
#[derive(Parser, Debug)]
#[command(about = "init your thrift or grpc project")]
pub struct Init {
#[arg(help = "The name of project")]
pub name: String,
#[arg(
long,
help = "Specify the git repo name for repo.\nExample: cloudwego_volo"
)]
pub repo: Option<String>,
#[arg(
short = 'g',
long = "git",
help = "Specify the git repo for idl.\nShould be in the format of \
\"git@domain:path/repo.git\".\nExample: git@github.com:cloudwego/volo.git"
)]
pub git: Option<String>,
#[arg(
short = 'r',
long = "ref",
requires = "git",
help = "Specify the git repo ref(branch) for idl.\nExample: main / $TAG"
)]
pub r#ref: Option<String>,
#[arg(
short = 'i',
long = "includes",
help = "Specify the include dirs for idl.\nIf -g or --git is specified, then this should \
be the path in the specified git repo."
)]
pub includes: Vec<PathBuf>,
#[arg(
value_parser = value_parser!(PathBuf),
help = "Specify the path for idl.\nIf -g or --git is specified, then this should be the \
path in the specified git repo.\nExample: \t-g not \
specified:\t./idl/server.thrift\n\t\t-g specified:\t\t/path/to/idl/server.thrift"
)]
pub idl: PathBuf,
}
impl Init {
pub fn is_grpc_project(&self) -> bool {
if let Some(ext) = self.idl.extension() {
ext == "proto"
} else {
false
}
}
fn init_gen(&self, config_entry: Entry) -> anyhow::Result<(String, String)> {
InitBuilder::new(config_entry).init()
}
fn copy_grpc_template(&self, config_entry: Entry) -> anyhow::Result<()> {
let (service_global_name, methods) = self.init_gen(config_entry)?;
let name = self.name.replace(['.', '-'], "_");
let cwd = std::env::current_dir()?;
let folder = cwd.as_path();
crate::templates_to_target_file!(folder, "templates/grpc/gitignore", ".gitignore");
crate::templates_to_target_file!(
folder,
"templates/grpc/cargo_toml",
"Cargo.toml",
name = &name
);
create_dir_all(folder.join("src/bin"))?;
crate::templates_to_target_file!(
folder,
"templates/grpc/src/bin/server_rs",
"src/bin/server.rs",
name = &name,
service_global_name = &service_global_name,
);
crate::templates_to_target_file!(
folder,
"templates/grpc/src/lib_rs",
"src/lib.rs",
service_global_name = &service_global_name,
methods = &methods,
);
create_dir_all(folder.join("volo-gen/src"))?;
crate::templates_to_target_file!(
folder,
"templates/grpc/volo-gen/build_rs",
"volo-gen/build.rs"
);
crate::templates_to_target_file!(
folder,
"templates/grpc/volo-gen/cargo_toml",
"volo-gen/Cargo.toml",
);
crate::templates_to_target_file!(
folder,
"templates/grpc/volo-gen/src/lib_rs",
"volo-gen/src/lib.rs",
);
Ok(())
}
fn copy_thrift_template(&self, config_entry: Entry) -> anyhow::Result<()> {
let (service_global_name, methods) = self.init_gen(config_entry)?;
let name = self.name.replace(['.', '-'], "_");
let cwd = std::env::current_dir()?;
let folder = cwd.as_path();
crate::templates_to_target_file!(folder, "templates/thrift/gitignore", ".gitignore");
crate::templates_to_target_file!(
folder,
"templates/thrift/cargo_toml",
"Cargo.toml",
name = &name
);
create_dir_all(folder.join("src/bin"))?;
crate::templates_to_target_file!(
folder,
"templates/thrift/src/bin/server_rs",
"src/bin/server.rs",
name = &name,
service_global_name = &service_global_name,
);
crate::templates_to_target_file!(
folder,
"templates/thrift/src/lib_rs",
"src/lib.rs",
service_global_name = &service_global_name,
methods = &methods,
);
create_dir_all(folder.join("volo-gen/src"))?;
crate::templates_to_target_file!(
folder,
"templates/thrift/volo-gen/build_rs",
"volo-gen/build.rs"
);
crate::templates_to_target_file!(
folder,
"templates/thrift/volo-gen/cargo_toml",
"volo-gen/Cargo.toml",
);
crate::templates_to_target_file!(
folder,
"templates/thrift/volo-gen/src/lib_rs",
"volo-gen/src/lib.rs",
);
Ok(())
}
fn check_config_files(&self) -> anyhow::Result<()> {
let paths = [
PathBuf::from(DEFAULT_CONFIG_FILE),
PathBuf::from("./volo-gen/").join(DEFAULT_CONFIG_FILE),
];
for path in paths {
if let Ok(metadata) = std::fs::metadata(&path) {
if metadata.len() > 0 {
eprintln!(
"{DEFAULT_CONFIG_FILE} already exists, the initialization is not allowed!"
);
std::process::exit(1);
}
}
}
Ok(())
}
}
impl CliCommand for Init {
fn run(&self, cx: crate::context::Context) -> anyhow::Result<()> {
self.check_config_files()?;
volo_build::util::with_config(|config| {
let mut repos = HashMap::new();
let mut service = if let Some(git) = self.git.as_ref() {
let (repo_name, repo) = init_git_repo(&self.repo, git, &self.r#ref)?;
repos.insert(repo_name.clone(), repo);
create_git_service(&repo_name, self.idl.as_path(), &self.includes)
} else {
init_local_service(self.idl.as_path(), &self.includes)?
};
let mut entry = Entry {
filename: PathBuf::from(DEFAULT_FILENAME),
protocol: detect_protocol(service.idl.path.as_path()),
repos,
services: vec![service.clone()],
common_option: Default::default(),
};
if self.is_grpc_project() {
self.copy_grpc_template(entry.clone())?;
} else {
self.copy_thrift_template(entry.clone())?;
}
modify_local_init_service_path_relative_to_yml(&mut service);
entry.services = vec![service];
config.entries.insert(cx.entry_name.clone(), entry);
Ok(())
})?;
std::fs::rename(
DEFAULT_CONFIG_FILE,
PathBuf::from("./volo-gen/").join(DEFAULT_CONFIG_FILE),
)?;
let _ = Command::new("cargo").arg("fmt").arg("--all").output()?;
let cwd = std::env::current_dir()?;
git_repo_init(&cwd)?;
Ok(())
}
}