use crate::builder::tgt::Target;
use crate::global_config::GlobalConfig;
use crate::utils::{
self,
configs::{BuildConfig, TargetConfig},
log::{log, LogLevel},
package::Package,
};
use std::fs;
use std::io::Write;
use std::path::Path;
use std::process::{Command, Stdio};
pub fn clean(targets: &Vec<TargetConfig>) {
if Path::new(".bld_cpp").exists() {
fs::create_dir_all(".bld_cpp").unwrap_or_else(|why| {
log(
LogLevel::Error,
&format!("Could not remove binary directory: {}", why),
);
});
}
if Path::new(Target::obj_dir()).exists() {
fs::remove_dir_all(Target::obj_dir()).unwrap_or_else(|why| {
log(
LogLevel::Error,
&format!("Could not remove object directory: {}", why),
);
});
log(LogLevel::Info, &format!("Cleaning: {}", Target::obj_dir()));
}
for target in targets {
#[cfg(target_os = "windows")]
let hash_path = format!(".bld_cpp/{}.win32.hash", &target.name);
#[cfg(target_os = "linux")]
let hash_path = format!(".bld_cpp/{}.linux.hash", &target.name);
#[cfg(target_os = "android")]
let hash_path = format!(".bld_cpp/{}.linux.hash", &target.name);
if Path::new(&hash_path).exists() {
fs::remove_file(&hash_path).unwrap_or_else(|why| {
log(
LogLevel::Error,
&format!("Could not remove hash file: {}", why),
);
});
log(LogLevel::Info, &format!("Cleaning: {}", &hash_path));
}
if Path::new(Target::build_dir()).exists() {
let mut bin_name = String::new();
bin_name.push_str(Target::build_dir());
bin_name.push('/');
bin_name.push_str(&target.name);
#[cfg(target_os = "windows")]
if target.typ == "exe" {
bin_name.push_str(".exe");
} else if target.typ == "dll" {
bin_name.push_str(".dll");
}
#[cfg(target_os = "linux")]
if target.typ == "exe" {
bin_name.push_str("");
} else if target.typ == "dll" {
bin_name.push_str(".so");
}
#[cfg(target_os = "android")]
if target.typ == "exe" {
bin_name.push_str("");
} else if target.typ == "dll" {
bin_name.push_str(".so");
}
if Path::new(&bin_name).exists() {
fs::remove_file(&bin_name).unwrap_or_else(|why| {
log(
LogLevel::Error,
&format!("Could not remove binary file: {}", why),
);
});
log(LogLevel::Log, &format!("Cleaning: {}", &bin_name));
} else {
log(
LogLevel::Log,
&format!("Binary file does not exist: {}", &bin_name),
);
}
}
}
}
pub fn clean_packages(packages: &Vec<Package>) {
for pack in packages {
for target in &pack.target_configs {
#[cfg(target_os = "windows")]
let pack_bin_path = format!("{}/{}.dll", Target::build_dir(), &target.name);
#[cfg(target_os = "linux")]
let pack_bin_path = format!("{}/{}.so", Target::build_dir(), &target.name);
#[cfg(target_os = "android")]
let pack_bin_path = format!("{}/{}.so", Target::build_dir(), &target.name);
if !Path::new(&pack_bin_path).exists() {
log(
LogLevel::Log,
&format!("Package binary does not exist: {}", &pack_bin_path),
);
continue;
}
let cmd_str = format!("rm {}", &pack_bin_path);
log(LogLevel::Debug, cmd_str.as_str());
let output = Command::new("sh")
.arg("-c")
.arg(&cmd_str)
.output()
.expect("failed to execute process");
if output.status.success() {
log(
LogLevel::Log,
&format!("Cleaned package: {} of {}", &pack.name, &pack.repo),
);
} else {
log(
LogLevel::Error,
&format!("Could not clean package: {} of {}", &pack.name, &pack.repo),
);
}
}
}
}
pub fn build(
build_config: &BuildConfig,
targets: &Vec<TargetConfig>,
gen_cc: bool,
gen_vsc: bool,
packages: &Vec<Package>,
) {
if !Path::new("./.bld_cpp").exists() {
fs::create_dir(".bld_cpp").unwrap_or_else(|why| {
log(
LogLevel::Error,
&format!("Could not create bld_cpp directory: {}", why),
);
std::process::exit(1);
});
}
if gen_cc {
let mut cc_file = fs::OpenOptions::new()
.write(true)
.append(true)
.open("compile_commands.json")
.unwrap_or_else(|why| {
log(LogLevel::Error, &format!("Could not open cc file: {}", why));
std::process::exit(1);
});
cc_file.write_all(b"[").unwrap_or_else(|why| {
log(
LogLevel::Error,
&format!("Could not write to cc file: {}", why),
);
std::process::exit(1);
});
}
if gen_vsc {
let mut vsc_file = fs::OpenOptions::new()
.write(true)
.append(true)
.open(".vscode/c_cpp_properties.json")
.unwrap_or_else(|why| {
log(
LogLevel::Error,
&format!("Could not open vsc file: {}", why),
);
std::process::exit(1);
});
let mut inc_dirs: Vec<String> = targets.iter().map(|t| t.include_dir.clone()).collect();
for package in packages {
for target in &package.target_configs {
inc_dirs.push(target.include_dir.clone());
}
}
let compiler_path: String = build_config.compiler.clone();
let mut intellimode: String = String::new();
if compiler_path == "gcc" || compiler_path == "g++" {
intellimode = "gcc-x64".to_string();
} else if compiler_path == "clang" || compiler_path == "clang++" {
intellimode = "clang-x64".to_string();
} else {
log(
LogLevel::Error,
&format!("Unsupported compiler: {}", compiler_path),
);
}
#[cfg(target_os = "windows")]
let compiler_path = Command::new("sh")
.arg("-c")
.arg(&format!("where {}", &compiler_path))
.output()
.expect("failed to execute process")
.stdout;
#[cfg(target_os = "windows")]
let compiler_path = String::from_utf8(compiler_path)
.unwrap()
.split('\n')
.collect::<Vec<&str>>()[0]
.to_string()
.replace('\r', "")
.replace('\\', "/");
#[cfg(target_os = "windows")]
let vsc_json = format!(
r#"{{
"configurations": [
{{
"name": "Win32",
"includePath": [
"{}"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
],
"compilerPath": "{}",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "windows-{}"
}}
],
"version": 4
}}"#,
inc_dirs.join("\",\n\t\t\t\t\""),
compiler_path,
intellimode
);
#[cfg(target_os = "linux")]
let compiler_path = Command::new("sh")
.arg("-c")
.arg(&format!("which {}", &compiler_path))
.output()
.expect("failed to execute process")
.stdout;
#[cfg(target_os = "linux")]
let compiler_path = String::from_utf8(compiler_path).unwrap().replace('\n', "");
#[cfg(target_os = "linux")]
let vsc_json = format!(
r#"{{
"configurations": [
{{
"name": "Linux",
"includePath": [
"{}"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
],
"compilerPath": "{}",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "linux-{}"
}}
],
"version": 4
}}"#,
inc_dirs.join("\",\n\t\t\t\t\""),
compiler_path,
intellimode
);
#[cfg(target_os = "android")]
let compiler_path = Command::new("sh")
.arg("-c")
.arg(&format!("which {}", &compiler_path))
.output()
.expect("failed to execute process")
.stdout;
#[cfg(target_os = "android")]
let compiler_path = String::from_utf8(compiler_path).unwrap().replace('\n', "");
#[cfg(target_os = "android")]
let vsc_json = format!(
r#"{{
"configurations": [
{{
"name": "Linux",
"includePath": [
"{}"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
],
"compilerPath": "{}",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "linux-{}"
}}
],
"version": 4
}}"#,
inc_dirs.join("\",\n\t\t\t\t\""),
compiler_path,
intellimode
);
vsc_file
.write_all(vsc_json.as_bytes())
.unwrap_or_else(|why| {
log(
LogLevel::Error,
&format!("Could not write to vsc file: {}", why),
);
std::process::exit(1);
});
}
for target in targets {
let mut tgt = Target::new(build_config, target, targets, packages);
tgt.build(gen_cc);
}
if gen_cc {
let mut cc_file = fs::OpenOptions::new()
.write(true)
.read(true)
.append(true)
.open("compile_commands.json")
.unwrap_or_else(|why| {
log(LogLevel::Error, &format!("Could not open cc file: {}", why));
std::process::exit(1);
});
cc_file.write_all(b"]").unwrap_or_else(|why| {
log(
LogLevel::Error,
&format!("Could not write to cc file: {}", why),
);
std::process::exit(1);
});
}
log(LogLevel::Info, "Build complete");
}
pub fn run(
bin_args: Option<Vec<&str>>,
build_config: &BuildConfig,
exe_target: &TargetConfig,
targets: &Vec<TargetConfig>,
packages: &Vec<Package>,
) {
let trgt = Target::new(build_config, exe_target, targets, packages);
if !Path::new(&trgt.bin_path).exists() {
log(
LogLevel::Error,
&format!("Could not find binary: {}", &trgt.bin_path),
);
std::process::exit(1);
}
if build_config.pre_build.is_some() {
log(LogLevel::Log, "Running pre-build script...");
let mut cmd = std::process::Command::new(build_config.pre_build.clone().unwrap());
cmd.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit());
let output = cmd.output();
if output.is_ok() {
log(LogLevel::Info, " Success");
} else {
log(LogLevel::Error, " Error");
std::process::exit(1);
}
}
log(LogLevel::Log, &format!("Running: {}", &trgt.bin_path));
let mut cmd = std::process::Command::new(&trgt.bin_path);
if let Some(bin_args) = bin_args {
for arg in bin_args {
cmd.arg(arg);
}
}
cmd.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit());
let output = cmd.output();
if output.is_ok() {
log(LogLevel::Info, &format!(" Success: {}", &trgt.bin_path));
} else {
log(LogLevel::Error, &format!(" Error: {}", &trgt.bin_path));
std::process::exit(1);
}
if build_config.post_build.is_some() {
log(LogLevel::Log, "Running post-build script...");
let mut cmd = std::process::Command::new(build_config.post_build.clone().unwrap());
cmd.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit());
let output = cmd.output();
if output.is_ok() {
log(LogLevel::Info, " Success");
} else {
log(LogLevel::Error, " Error");
std::process::exit(1);
}
}
}
pub fn init(project_name: &str, is_c: Option<bool>, config: GlobalConfig) {
if Path::new(project_name).exists() {
log(LogLevel::Error, &format!("{} already exists", project_name));
log(LogLevel::Error, "Cannot initialise project");
std::process::exit(1);
}
let mut cmd = std::process::Command::new("git");
cmd.arg("init").arg(project_name);
let output = cmd.output();
if output.is_err() {
log(LogLevel::Error, "Could not initialise git repo");
log(LogLevel::Error, &format!("{}", output.err().unwrap()));
std::process::exit(1);
}
#[cfg(target_os = "windows")]
let config_file = project_name.to_owned() + "/config_win32.toml";
#[cfg(target_os = "linux")]
let config_file = project_name.to_owned() + "/config_linux.toml";
#[cfg(target_os = "android")]
let config_file = project_name.to_owned() + "/config_linux.toml";
if Path::new(&config_file).exists() {
log(LogLevel::Error, &format!("{} already exists", config_file));
log(LogLevel::Error, "Cannot initialise project");
std::process::exit(1);
}
let mut config_file = fs::OpenOptions::new()
.write(true)
.create(true)
.open(config_file)
.unwrap_or_else(|why| {
log(
LogLevel::Error,
&format!("Could not create config file: {}", why),
);
std::process::exit(1);
});
let c_compiler = match config.get_default_compiler().as_str() {
"gcc" => "gcc",
"clang" => "clang",
_ => {
log(LogLevel::Error, "Invalid default compiler");
std::process::exit(1);
}
};
let cpp_compiler = match config.get_default_compiler().as_str() {
"gcc" => "g++",
"clang" => "clang++",
_ => {
log(LogLevel::Error, "Invalid default compiler");
std::process::exit(1);
}
};
let sample_cpp_config = format!("[build]\ncompiler = \"{}\"\n\n[[targets]]\nname = \"main\"\nsrc = \"./src/\"\ninclude_dir = \"./src/include/\"\ntype = \"exe\"\ncflags = \"-g -Wall -Wextra\"\nlibs = \"\"\ndeps = [\"\"]\n", cpp_compiler);
let sample_c_config = format!("[build]\ncompiler = \"{}\"\n\n[[targets]]\nname = \"main\"\nsrc = \"./src/\"\ninclude_dir = \"./src/include/\"\ntype = \"exe\"\ncflags = \"-g -Wall -Wextra\"\nlibs = \"\"\ndeps = [\"\"]\n", c_compiler);
let sample_config = match is_c {
Some(true) => sample_c_config,
Some(false) => sample_cpp_config,
None => match config.get_default_language().as_str() {
"c" => sample_c_config,
"cpp" => sample_cpp_config,
_ => {
log(LogLevel::Error, "Invalid default language");
std::process::exit(1);
}
},
};
config_file
.write_all(sample_config.as_bytes())
.unwrap_or_else(|why| {
log(
LogLevel::Error,
&format!("Could not write to config file: {}", why),
);
std::process::exit(1);
});
let src_dir = project_name.to_owned() + "/src";
let include_dir = project_name.to_owned() + "/src/include";
if !Path::new(&src_dir).exists() {
fs::create_dir(&src_dir).unwrap_or_else(|why| {
log(LogLevel::Warn, &format!("Project name {}", project_name));
log(
LogLevel::Error,
&format!("Could not create src directory: {}", why),
);
std::process::exit(1);
});
}
if !Path::new(&include_dir).exists() {
fs::create_dir(&include_dir).unwrap_or_else(|why| {
log(
LogLevel::Error,
&format!("Could not create src/include directory: {}", why),
);
std::process::exit(1);
});
}
let main_path: String;
match is_c {
Some(true) => main_path = src_dir.to_owned() + "/main.c",
Some(false) => main_path = src_dir.to_owned() + "/main.cpp",
None => match config.get_default_language().as_str() {
"c" => main_path = src_dir.to_owned() + "/main.c",
"cpp" => main_path = src_dir.to_owned() + "/main.cpp",
_ => {
log(LogLevel::Error, "Invalid default language");
std::process::exit(1);
}
},
}
if !Path::new(&main_path).exists() {
let mut main_file = fs::OpenOptions::new()
.write(true)
.create(true)
.open(&main_path)
.unwrap_or_else(|why| {
log(
LogLevel::Error,
&format!("Could not create main.cpp: {}", why),
);
std::process::exit(1);
});
let c_sample_program =
b"#include <stdio.h>\n\nint main() {\n\tprintf(\"Hello World!\\n\");\n\treturn 0;\n}";
let cpp_sample_program = b"#include <iostream>\n\nint main() {\n\tstd::cout << \"Hello World!\" << std::endl;\n\treturn 0;\n}";
match is_c {
Some(true) => main_file.write_all(c_sample_program).unwrap_or_else(|why| {
log(
LogLevel::Error,
&format!("Could not write to main.c: {}", why),
);
std::process::exit(1);
}),
Some(false) => main_file
.write_all(cpp_sample_program)
.unwrap_or_else(|why| {
log(
LogLevel::Error,
&format!("Could not write to main.cpp: {}", why),
);
std::process::exit(1);
}),
None => match config.get_default_language().as_str() {
"c" => main_file.write_all(c_sample_program).unwrap_or_else(|why| {
log(
LogLevel::Error,
&format!("Could not write to main.c: {}", why),
);
std::process::exit(1);
}),
"cpp" => main_file
.write_all(cpp_sample_program)
.unwrap_or_else(|why| {
log(
LogLevel::Error,
&format!("Could not write to main.cpp: {}", why),
);
std::process::exit(1);
}),
_ => {
log(LogLevel::Error, "Invalid default language");
std::process::exit(1);
}
},
}
}
let gitignore_path = project_name.to_owned() + "/.gitignore";
if !Path::new(&gitignore_path).exists() {
let mut gitignore_file = fs::OpenOptions::new()
.write(true)
.create(true)
.open(&gitignore_path)
.unwrap_or_else(|why| {
log(
LogLevel::Error,
&format!("Could not create .gitignore: {}", why),
);
std::process::exit(1);
});
gitignore_file
.write_all(b".bld_cpp\ncompile_commands.json\n.cache\n")
.unwrap_or_else(|why| {
log(
LogLevel::Error,
&format!("Could not write to .gitignore: {}", why),
);
std::process::exit(1);
});
}
let mut readme_file = fs::OpenOptions::new()
.write(true)
.create(true)
.open(project_name.to_owned() + "/README.md")
.unwrap_or_else(|why| {
log(
LogLevel::Error,
&format!("Could not create README.md: {}", why),
);
std::process::exit(1);
});
readme_file
.write_all(format!("# {}", project_name).as_bytes())
.unwrap_or_else(|why| {
log(
LogLevel::Error,
&format!("Could not write to README.md: {}", why),
);
std::process::exit(1);
});
let mut license_file = fs::OpenOptions::new()
.write(true)
.create(true)
.open(project_name.to_owned() + "/LICENSE")
.unwrap_or_else(|why| {
log(
LogLevel::Error,
&format!("Could not create LICENSE: {}", why),
);
std::process::exit(1);
});
let license = config.get_license();
if license.as_str() == "NONE" {
license_file.write_all(b"No license").unwrap_or_else(|why| {
log(
LogLevel::Error,
&format!("Could not write to LICENSE: {}", why),
);
std::process::exit(1);
});
} else {
license_file
.write_all(license.as_bytes())
.unwrap_or_else(|why| {
log(
LogLevel::Error,
&format!("Could not write to LICENSE: {}", why),
);
std::process::exit(1);
});
}
log(
LogLevel::Log,
&format!("Project {} initialised", project_name),
);
}
pub fn init_project(project_name: String, is_c: Option<bool>, config: GlobalConfig) {
log(LogLevel::Log, "Initializing project...");
init(&project_name, is_c, config);
std::process::exit(0);
}
pub fn parse_config() -> (BuildConfig, Vec<TargetConfig>, Vec<Package>) {
#[cfg(target_os = "linux")]
let (build_config, targets) = utils::configs::parse_config("./config_linux.toml", true);
#[cfg(target_os = "windows")]
let (build_config, targets) = utils::configs::parse_config("./config_win32.toml", true);
#[cfg(target_os = "android")]
let (build_config, targets) = utils::configs::parse_config("./config_linux.toml", true);
let mut num_exe = 0;
let mut exe_target: Option<&TargetConfig> = None;
if targets.is_empty() {
log(LogLevel::Error, "No targets in config");
std::process::exit(1);
} else {
for target in &targets {
if target.typ == "exe" {
num_exe += 1;
exe_target = Some(target);
} else if target.typ == "dll" {
if !target.name.starts_with("lib") {
log(
LogLevel::Warn,
"Dynamic library target name must start with lib",
);
log(
LogLevel::Warn,
format!(
"Consider renaming \"{}\" to \"lib{}\"",
target.name, target.name
)
.as_str(),
);
log(
LogLevel::Log,
"Libraries are prefixed with \"lib\" and the lib is replaced by -l when linking",
);
log(LogLevel::Log, "For example, libmylib.so becomes -lmylib");
}
}
}
}
if num_exe != 1 || exe_target.is_none() {
log(
LogLevel::Error,
"Exactly one executable target must be specified",
);
std::process::exit(1);
}
#[cfg(target_os = "linux")]
let packages = Package::parse_packages("./config_linux.toml");
#[cfg(target_os = "android")]
let packages = Package::parse_packages("./config_linux.toml");
#[cfg(target_os = "windows")]
let packages = Package::parse_packages("./config_win32.toml");
(build_config, targets, packages)
}
pub fn pre_gen_cc() {
if !Path::new("./compile_commands.json").exists() {
fs::File::create(Path::new("./compile_commands.json")).unwrap();
} else {
fs::remove_file(Path::new("./compile_commands.json")).unwrap();
fs::File::create(Path::new("./compile_commands.json")).unwrap();
}
}
pub fn pre_gen_vsc() {
if !Path::new("./.vscode").exists() {
fs::create_dir(Path::new("./.vscode")).unwrap();
}
if !Path::new("./.vscode/c_cpp_properties.json").exists() {
fs::File::create(Path::new("./.vscode/c_cpp_properties.json")).unwrap();
} else {
fs::remove_file(Path::new("./.vscode/c_cpp_properties.json")).unwrap();
fs::File::create(Path::new("./.vscode/c_cpp_properties.json")).unwrap();
}
}
pub fn clean_packages_wrapper(packages: &Vec<Package>) {
log(LogLevel::Log, "Cleaning packages...");
clean_packages(packages);
}
pub fn update_packages(packages: &Vec<Package>) {
log(LogLevel::Log, "Updating packages...");
for package in packages {
package.update();
}
}
pub fn restore_packages(packages: &Vec<Package>) {
log(LogLevel::Log, "Restoring packages...");
for package in packages {
package.restore();
}
}