diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml new file mode 100644 index 0000000..fd0f1b0 --- /dev/null +++ b/.github/workflows/build_and_test.yml @@ -0,0 +1,29 @@ +name: Rust build and example test + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Build + run: cargo build --verbose + + - name: "C/C++ toolchain install" + run: | + sudo apt update && sudo apt install build-essential tree + + - name: "example build test" + run: | + cargo run ./example/hello_world + tree ./example/hello_world + diff --git a/.gitignore b/.gitignore index 703618d..a33a5a2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,8 @@ /build/ # example production files -/example/hello_world/bin/ -/example/hello_world/build/ \ No newline at end of file +/example/**/bin/ +/example/**/build/ +/example/**/output/ +/example/**/*.o +/example/**/*.obj \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index d653b25..e836cad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,7 +10,7 @@ checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "cxon" -version = "0.1.2" +version = "0.1.3" dependencies = [ "num_cpus", "pathdiff", diff --git a/Cargo.toml b/Cargo.toml index 7860dd8..15d8513 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "cxon" authors = ["Cora"] -version = "0.1.2" +version = "0.1.3" edition = "2024" readme = "README.md" repository = "https://github.com/CoraBlack/cxon" diff --git a/README.md b/README.md index 0bfc19b..6f06333 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,26 @@ > > - cxon is still in development, and the configrantion field is not stable yet. +## Installation + +- ### Build and insall from cargo +```sh +cargo install cxon +``` + +- ### [Install directly from github release](https://github.com/CoraBlack/cxon/releases) + ## Features(Order by priority) - [x] Build project with compiler and linker by cxon.json immediately.(unsupport custom) - [x] Build cache - [x] Mult-thread build. +- [x] Muti-build-target. +- [ ] cxon.json schema support +- [ ] Debug field support(Only debug currently). - [ ] Submodule support. - [ ] Multiple compile targets. - [ ] Platform-specific configuration. -- [ ] Build for cmake. ## What is the goal for cxon? @@ -24,7 +35,7 @@ We only require a small number of essential fields for cxon.json and make the bu { "project": "HelloWorld", // (Required) project name "target_name": "hello", // the final compiled product name, the default value is the project field - "target_type": "execuable", // (Required) build type (execuable(only currently), static_lib, shared_lib, object_lib) + "target_type": "execuable", // (Required) build type (execuable, static_lib, shared_lib, object_lib) "build_dir": "build", // the directory storing intermediate compiled product "output_dir": "bin", // the directory storing final compiled product diff --git a/example/hello_world/cxon.json b/example/hello_world/cxon.json index 2efd028..da636b2 100644 --- a/example/hello_world/cxon.json +++ b/example/hello_world/cxon.json @@ -3,17 +3,7 @@ "target_type": "executable", "build_dir": "build", "output_dir": "bin", - "toolchain": "gnu", - - "flags": [ - - ], - - "cflags": [ - - ], - "cxxflags": [ "-std=c++11" ], @@ -22,20 +12,8 @@ "./include/" ], - "defines": [ - - ], - "sources": [ "./main.cpp", "./func.cpp" - ], - - "link": [ - - ], - - "libs": [ - ] } \ No newline at end of file diff --git a/example/shared_lib/cxon.json b/example/shared_lib/cxon.json new file mode 100644 index 0000000..3ab2d2a --- /dev/null +++ b/example/shared_lib/cxon.json @@ -0,0 +1,9 @@ +{ + "project": "my_shared_lib", + "toolchain": "llvm", + "target_type": "shared_lib", + + "sources": [ + "./func.cpp" + ] +} \ No newline at end of file diff --git a/example/shared_lib/func.cpp b/example/shared_lib/func.cpp new file mode 100644 index 0000000..3a49ed2 --- /dev/null +++ b/example/shared_lib/func.cpp @@ -0,0 +1,6 @@ +#include +#include "./include/func.hpp" + +void add(int a, int b) { + std::cout << "The sum is: " << a + b << std::endl; +} \ No newline at end of file diff --git a/example/shared_lib/include/func.hpp b/example/shared_lib/include/func.hpp new file mode 100644 index 0000000..b55c486 --- /dev/null +++ b/example/shared_lib/include/func.hpp @@ -0,0 +1 @@ +void add(int a, int b); \ No newline at end of file diff --git a/example/static_lib/cxon.json b/example/static_lib/cxon.json new file mode 100644 index 0000000..a8b5527 --- /dev/null +++ b/example/static_lib/cxon.json @@ -0,0 +1,9 @@ +{ + "project": "my_static_lib", + "toolchain": "llvm", + "target_type": "static_lib", + + "sources": [ + "./func.cpp" + ] +} \ No newline at end of file diff --git a/example/static_lib/func.cpp b/example/static_lib/func.cpp new file mode 100644 index 0000000..3a49ed2 --- /dev/null +++ b/example/static_lib/func.cpp @@ -0,0 +1,6 @@ +#include +#include "./include/func.hpp" + +void add(int a, int b) { + std::cout << "The sum is: " << a + b << std::endl; +} \ No newline at end of file diff --git a/example/static_lib/include/func.hpp b/example/static_lib/include/func.hpp new file mode 100644 index 0000000..b55c486 --- /dev/null +++ b/example/static_lib/include/func.hpp @@ -0,0 +1 @@ +void add(int a, int b); \ No newline at end of file diff --git a/src/cli/arg.rs b/src/cli/arg.rs index 2942ff2..1dad34b 100644 --- a/src/cli/arg.rs +++ b/src/cli/arg.rs @@ -26,7 +26,7 @@ impl CliArgs { let project_dir = PathBuf::from(arg_col[1].clone()); if !project_dir.exists() { - Err(()).expect("cxon project dir is not available") + Err(()).expect(format!("cxon project dir is not available: {}", project_dir.display()).as_str()) } // remove cxon.json if it's included in the path diff --git a/src/cxon.rs b/src/cxon.rs index 2e2589c..6607f68 100644 --- a/src/cxon.rs +++ b/src/cxon.rs @@ -254,11 +254,11 @@ fn default_target_type() -> String { } fn default_build_dir() -> PathBuf { - PathBuf::from("./build").canonicalize().expect("Failed to canonicalize build directory") + PathBuf::from("./build") } fn default_output_dir() -> PathBuf { - PathBuf::from("./output").canonicalize().expect("Failed to canonicalize output directory") + PathBuf::from("./output") } #[test] diff --git a/src/main.rs b/src/main.rs index 993f1b0..137fcc0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use std::{collections::VecDeque, sync::{Arc, Mutex}, thread}; -use crate::{cxon::get_cxon_config, object::{output::ObjectCollection, source::Source}, toolchain::{ToolChain, ToolChainTrait, compiler, gnu::GNU, llvm::LLVM, msvc::MSVC}}; +use crate::{cxon::get_cxon_config, object::{output::ObjectCollection, source::Source}, toolchain::{ToolChain, ToolChainTrait, compiler, gnu::GNU, linker, llvm::LLVM, msvc::MSVC}}; pub mod cli { pub mod arg; @@ -14,7 +14,11 @@ pub mod utils; pub mod cxon; fn main() -> () { - let toolchain = get_cxon_config().read().unwrap().get_toolchain(); + let toolchain = get_cxon_config() + .read() + .unwrap() + .get_toolchain(); + match toolchain { ToolChain::GNU() => build_project::(), ToolChain::LLVM() => build_project::(), @@ -63,5 +67,10 @@ fn build_project() { thread.join().unwrap(); } - toolchain::linker::link_to_executable::(objects.lock().unwrap().clone()); + linker::link::(objects.lock().unwrap().clone(), + get_cxon_config() + .read() + .unwrap() + .get_target_type() + ); } diff --git a/src/toolchain/compiler.rs b/src/toolchain/compiler.rs index 1feb744..db88f05 100644 --- a/src/toolchain/compiler.rs +++ b/src/toolchain/compiler.rs @@ -19,7 +19,7 @@ pub struct CompileFuncArgs { } pub fn compile(src: Source) -> Object { - let obj_path = get_object_target_path(&src).expect("Failed to get the target path of object file"); + let obj_path = get_object_target_path::(&src).expect("Failed to get the target path of object file"); if !need_recompile(&src, &obj_path) { return Object { @@ -56,7 +56,7 @@ fn compile_handler(args: CompileFuncArgs) -> Object { .arg(T::DEBUG_FLAG) .arg(T::ONLY_COMPILE_FLAG) .arg(args.src_path.to_str().unwrap()) - .arg(T::OUTPUT_FLAG) + .arg(T::EXECUTABLE_OUTPUT_FLAG) .arg(args.obj_path.to_str().unwrap()) .args(args.includes) .args(args.defines) diff --git a/src/toolchain/gnu.rs b/src/toolchain/gnu.rs index fc9834c..4bfbc7c 100644 --- a/src/toolchain/gnu.rs +++ b/src/toolchain/gnu.rs @@ -7,9 +7,23 @@ pub struct GNU{ impl ToolChainTrait for GNU { const CC: &'static str = "gcc"; const CXX: &'static str = "g++"; - const LINKER: &'static str = "g++"; const DEBUG_FLAG: &'static str = "-g"; - const OUTPUT_FLAG: &'static str = "-o"; + + const EXECUTABLE_LINKER: &'static str = "g++"; + const STATIC_LIB_LINKER: &'static str = "ar"; + const SHARED_LIB_LINKER: &'static str = "g++"; + const OBJECT_LIB_LINKER: &'static str = "lr"; + + const EXECUTABLE_OUTPUT_FLAG: &'static str = "-o"; + const STATIC_LIB_OUTPUT_FLAG: &'static str = "rcs"; + const SHARED_LIB_OUTPUT_FLAG: &'static str = "-shared -fPIC -o"; + const OBJECT_LIB_OUTPUT_FLAG: &'static str = "-o"; + + const EXECUTABLE_EXTENSION: &'static str = ""; + const STATIC_LIB_EXTENSION: &'static str = "a"; + const SHARED_LIB_EXTENSION: &'static str = "so"; + const OBJECT_LIB_EXTENSION: &'static str = "o"; + const ONLY_COMPILE_FLAG: &'static str = "-c"; const DEFINE_FLAG_PREFIX: &'static str = "-D"; const INCLUDE_FLAG_PREFIX: &'static str = "-I"; diff --git a/src/toolchain/linker.rs b/src/toolchain/linker.rs index 5471b81..3a63801 100644 --- a/src/toolchain/linker.rs +++ b/src/toolchain/linker.rs @@ -1,30 +1,103 @@ use std::path::PathBuf; -use crate::{cxon::get_cxon_config, object::output::ObjectCollection, toolchain::ToolChainTrait}; +use crate::{cxon::get_cxon_config, object::output::ObjectCollection, toolchain::{TargetType, ToolChainTrait}}; -// pub trait Linker { -// fn get_linker() -> String; +pub struct LinkArgs { + pub linker: String, + pub output_path: PathBuf, -// // fn link_to_object(input: ObjectCollection) -> Option; -// fn link_to_execuable(input: ObjectCollection) -> (); -// // fn link_to_static_lib(input: ObjectCollection) -> Option; -// // fn link_to_dynamic_lib(input: ObjectCollection) -> Option; -// } + pub output_flag: String, + pub other_flags: Vec, + pub link_dir_args: Vec, + pub link_lib_args: Vec, +} -pub fn link_to_executable(input: ObjectCollection) -> () { - let linker = T::LINKER; +pub fn link(input: ObjectCollection, target_type: TargetType) -> () { let output_dir = &get_cxon_config().read().unwrap().output_dir; - let target_name = &get_cxon_config().read().unwrap().get_target_name(); let output_path = output_dir.join(PathBuf::from(target_name)); - std::process::Command::new(linker) + let mut other_flags = Vec::new(); + + match target_type { + TargetType::Executable => link_to_executable_cmd::(input, LinkArgs { + linker: T::EXECUTABLE_LINKER.to_string(), + output_path, + output_flag: T::EXECUTABLE_OUTPUT_FLAG.to_string(), + link_dir_args: get_cxon_config().read().unwrap().get_link_dir_args::(), + link_lib_args: get_cxon_config().read().unwrap().get_lib_args::(), + other_flags, + }), + TargetType::StaticLib => link_to_static_lib_cmd::(input, LinkArgs { + linker: T::STATIC_LIB_LINKER.to_string(), + output_path, + output_flag: T::STATIC_LIB_OUTPUT_FLAG.to_string(), + link_dir_args: get_cxon_config().read().unwrap().get_link_dir_args::(), + link_lib_args: get_cxon_config().read().unwrap().get_lib_args::(), + other_flags, + }), + TargetType::SharedLib => link_to_shared_lib_cmd::(input, LinkArgs { + linker: T::SHARED_LIB_LINKER.to_string(), + output_path, + output_flag: T::SHARED_LIB_OUTPUT_FLAG.to_string(), + link_dir_args: get_cxon_config().read().unwrap().get_link_dir_args::(), + link_lib_args: get_cxon_config().read().unwrap().get_lib_args::(), + other_flags, + }), + TargetType::ObjectLib => link_to_object_cmd::(input, LinkArgs { + linker: T::OBJECT_LIB_LINKER.to_string(), + output_path, + output_flag: T::OBJECT_LIB_OUTPUT_FLAG.to_string(), + link_dir_args: get_cxon_config().read().unwrap().get_link_dir_args::(), + link_lib_args: get_cxon_config().read().unwrap().get_lib_args::(), + other_flags, + }), + } +} + +pub fn link_to_executable_cmd(input: ObjectCollection, args: LinkArgs) -> () { + std::process::Command::new(T::EXECUTABLE_LINKER) + .arg(T::DEBUG_FLAG) + .args(input.to_args()) + .arg(T::EXECUTABLE_OUTPUT_FLAG) + .arg(args.output_path.with_added_extension(T::EXECUTABLE_EXTENSION).to_str().unwrap()) + .args(args.link_dir_args) + .args(args.link_lib_args) + .status() + .expect(format!("Failed to link executable {}", args.output_path.to_str().unwrap()).as_str()); +} + +pub fn link_to_static_lib_cmd(input: ObjectCollection, args: LinkArgs) -> () { + std::process::Command::new(T::STATIC_LIB_LINKER) + .args(T::STATIC_LIB_OUTPUT_FLAG.to_string().split(' ')) + .arg(args.output_path.with_extension(T::STATIC_LIB_EXTENSION).to_str().unwrap()) + .args(input.to_args()) + .args(args.link_dir_args) + .args(args.link_lib_args) + .status() + .expect(format!("Failed to link static library {}", args.output_path.to_str().unwrap()).as_str()); +} + +pub fn link_to_shared_lib_cmd(input: ObjectCollection, args: LinkArgs) -> () { + std::process::Command::new(T::SHARED_LIB_LINKER) + .arg(T::DEBUG_FLAG) + .args(T::SHARED_LIB_OUTPUT_FLAG.to_string().split(' ')) + .arg(args.output_path.with_extension(T::SHARED_LIB_EXTENSION).to_str().unwrap()) + .args(input.to_args()) + .args(args.link_dir_args) + .args(args.link_lib_args) + .status() + .expect(format!("Failed to link shared library {}", args.output_path.to_str().unwrap()).as_str()); +} + +pub fn link_to_object_cmd(input: ObjectCollection, args: LinkArgs) -> () { + std::process::Command::new(T::OBJECT_LIB_LINKER) .arg(T::DEBUG_FLAG) .args(input.to_args()) - .arg(T::OUTPUT_FLAG) - .arg(output_path.to_str().unwrap()) - .args(get_cxon_config().read().unwrap().get_link_dir_args::()) - .args(get_cxon_config().read().unwrap().get_lib_args::()) + .args(T::OBJECT_LIB_OUTPUT_FLAG.to_string().split(' ')) + .arg(args.output_path.with_extension(T::OBJECT_LIB_EXTENSION).to_str().unwrap()) + .args(args.link_dir_args) + .args(args.link_lib_args) .status() - .expect(format!("Failed to link executable {}", output_path.to_str().unwrap()).as_str()); + .expect(format!("Failed to link object file {}", args.output_path.to_str().unwrap()).as_str()); } \ No newline at end of file diff --git a/src/toolchain/llvm.rs b/src/toolchain/llvm.rs index 74ec87d..11b68ef 100644 --- a/src/toolchain/llvm.rs +++ b/src/toolchain/llvm.rs @@ -7,9 +7,26 @@ pub struct LLVM { impl ToolChainTrait for LLVM { const CC: &'static str = "clang"; const CXX: &'static str = "clang++"; - const LINKER: &'static str = "clang++"; const DEBUG_FLAG: &'static str = "-g"; - const OUTPUT_FLAG: &'static str = "-o"; + + const EXECUTABLE_LINKER: &'static str = "clang++"; + // #[cfg(windows)] + // const STATIC_LIB_LINKER: &'static str = "llvm-lib"; + // #[cfg(not(windows))] + const STATIC_LIB_LINKER: &'static str = "ar"; + const SHARED_LIB_LINKER: &'static str = "clang++"; + const OBJECT_LIB_LINKER: &'static str = "clang++"; + + const EXECUTABLE_OUTPUT_FLAG: &'static str = "-o"; + const STATIC_LIB_OUTPUT_FLAG: &'static str = "rcs"; + const SHARED_LIB_OUTPUT_FLAG: &'static str = "-shared -fPIC -o"; + const OBJECT_LIB_OUTPUT_FLAG: &'static str = "-o"; + + const EXECUTABLE_EXTENSION: &'static str = ""; + const STATIC_LIB_EXTENSION: &'static str = "a"; + const SHARED_LIB_EXTENSION: &'static str = "so"; + const OBJECT_LIB_EXTENSION: &'static str = "o"; + const ONLY_COMPILE_FLAG: &'static str = "-c"; const DEFINE_FLAG_PREFIX: &'static str = "-D"; const INCLUDE_FLAG_PREFIX: &'static str = "-I"; diff --git a/src/toolchain/mod.rs b/src/toolchain/mod.rs index d017198..b5ea24b 100644 --- a/src/toolchain/mod.rs +++ b/src/toolchain/mod.rs @@ -22,9 +22,23 @@ pub enum TargetType { pub trait ToolChainTrait { const CC: &'static str; const CXX: &'static str; - const LINKER: &'static str; const DEBUG_FLAG: &'static str; - const OUTPUT_FLAG: &'static str; + + const EXECUTABLE_LINKER: &'static str; + const STATIC_LIB_LINKER: &'static str; + const SHARED_LIB_LINKER: &'static str; + const OBJECT_LIB_LINKER: &'static str; + + const EXECUTABLE_OUTPUT_FLAG: &'static str; + const STATIC_LIB_OUTPUT_FLAG: &'static str; + const SHARED_LIB_OUTPUT_FLAG: &'static str; + const OBJECT_LIB_OUTPUT_FLAG: &'static str; + + const EXECUTABLE_EXTENSION: &'static str; + const STATIC_LIB_EXTENSION: &'static str; + const SHARED_LIB_EXTENSION: &'static str; + const OBJECT_LIB_EXTENSION: &'static str; + const ONLY_COMPILE_FLAG: &'static str; const DEFINE_FLAG_PREFIX: &'static str; const INCLUDE_FLAG_PREFIX: &'static str; @@ -35,5 +49,8 @@ pub trait ToolChainTrait { pub fn check_toolchain_availability() -> () { utils::check_executable_exists(T::CC); utils::check_executable_exists(T::CXX); - utils::check_executable_exists(T::LINKER); + utils::check_executable_exists(T::EXECUTABLE_LINKER); + utils::check_executable_exists(T::STATIC_LIB_LINKER); + utils::check_executable_exists(T::SHARED_LIB_LINKER); + utils::check_executable_exists(T::OBJECT_LIB_LINKER); } diff --git a/src/toolchain/msvc.rs b/src/toolchain/msvc.rs index 144aab4..8f47857 100644 --- a/src/toolchain/msvc.rs +++ b/src/toolchain/msvc.rs @@ -7,12 +7,26 @@ pub struct MSVC { impl ToolChainTrait for MSVC { const CC: &'static str = "cl"; const CXX: &'static str = "cl"; - const LINKER: &'static str = "link"; const DEBUG_FLAG: &'static str = "/Zi"; - const OUTPUT_FLAG: &'static str = "/Fe:"; + + const EXECUTABLE_LINKER: &'static str = "cl"; + const STATIC_LIB_LINKER: &'static str = "lib"; + const SHARED_LIB_LINKER: &'static str = "link"; + const OBJECT_LIB_LINKER: &'static str = "lib"; + + const EXECUTABLE_OUTPUT_FLAG: &'static str = "/Fe:"; + const STATIC_LIB_OUTPUT_FLAG: &'static str = "/OUT:"; + const SHARED_LIB_OUTPUT_FLAG: &'static str = "/OUT:"; + const OBJECT_LIB_OUTPUT_FLAG: &'static str = "/Fo:"; + + const EXECUTABLE_EXTENSION: &'static str = "exe"; + const STATIC_LIB_EXTENSION: &'static str = "lib"; + const SHARED_LIB_EXTENSION: &'static str = "dll"; + const OBJECT_LIB_EXTENSION: &'static str = "obj"; + const ONLY_COMPILE_FLAG: &'static str = "/c"; const DEFINE_FLAG_PREFIX: &'static str = "/D"; const INCLUDE_FLAG_PREFIX: &'static str = "/I"; const LINK_DIR_FLAG_PREFIX: &'static str = "/LIBPATH:"; - const LINK_LIB_FLAG_PREFIX: &'static str = ""; + const LINK_LIB_FLAG_PREFIX: &'static str = "/LD"; } \ No newline at end of file diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 7fb2ca0..e681e60 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use crate::{cli::arg, cxon::get_cxon_config, object::source::Source}; +use crate::{cli::arg, cxon::get_cxon_config, object::source::Source, toolchain::ToolChainTrait}; pub fn normalize_and_canonicalize_path(path: PathBuf) -> PathBuf { let canonicalized_path = if !path.is_absolute() { @@ -26,7 +26,7 @@ fn normalize_path(path: PathBuf) -> PathBuf { } } -pub fn get_object_target_path(src: &Source) -> Result { +pub fn get_object_target_path(src: &Source) -> Result { let src_path = src.get_path(); let obj_sub_path = pathdiff::diff_paths(&src_path, arg::get_args().project_dir); @@ -41,7 +41,7 @@ pub fn get_object_target_path(src: &Source) -> Result { std::fs::create_dir_all(obj_path.parent().unwrap()).expect("Failed to create object file directory"); } - Ok(obj_path.with_extension("o")) + Ok(obj_path.with_extension(T::OBJECT_LIB_EXTENSION)) } pub fn check_executable_exists(executable: &str) -> String {