8 Commits

Author SHA1 Message Date
1974ed90be second attempt 2026-03-23 16:29:33 -05:00
8039f5e778 Revert "first attempt at fixing issue"
This reverts commit 99722302a2.
2026-03-23 16:26:30 -05:00
99722302a2 first attempt at fixing issue 2026-03-23 16:25:30 -05:00
a4b7a4d909 Fix clippy 2026-03-23 16:22:47 -05:00
9c8fccbb4c add pkgconf to the deps (#21)
issue: #20
Reviewed-on: http://192.168.1.227:3000/sfrembling/pallet/pulls/21
Co-authored-by: Shea Frembling <sfrembling@gmail.com>
Co-committed-by: Shea Frembling <sfrembling@gmail.com>
2026-03-23 12:56:43 -06:00
29cbfbac66 Add pkg-config stuff (#14)
Reviewed-on: http://192.168.1.227:3000/sfrembling/pallet/pulls/14
Co-authored-by: godsfryingpan <sfrembling@gmail.com>
Co-committed-by: godsfryingpan <sfrembling@gmail.com>
2026-03-23 11:39:46 -06:00
4a2e959535 Add ability to make compile_commands.json (#13)
issue: #8
Reviewed-on: http://192.168.1.227:3000/sfrembling/pallet/pulls/13
Co-authored-by: godsfryingpan <sfrembling@gmail.com>
Co-committed-by: godsfryingpan <sfrembling@gmail.com>
2026-03-23 11:17:42 -06:00
94d6f5d54d add change to hash and build to hash and build each file independently (#12)
issue: #10
Reviewed-on: http://192.168.1.227:3000/sfrembling/pallet/pulls/12
Co-authored-by: godsfryingpan <sfrembling@gmail.com>
Co-committed-by: godsfryingpan <sfrembling@gmail.com>
2026-03-23 11:00:40 -06:00
5 changed files with 288 additions and 47 deletions

32
Cargo.lock generated
View File

@@ -233,12 +233,24 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.183" version = "0.2.183"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]] [[package]]
name = "once_cell_polyfill" name = "once_cell_polyfill"
version = "1.70.2" version = "1.70.2"
@@ -254,6 +266,7 @@ dependencies = [
"colored", "colored",
"glob", "glob",
"serde", "serde",
"serde_json",
"sha256", "sha256",
"toml", "toml",
] ]
@@ -312,6 +325,19 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "serde_json"
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]] [[package]]
name = "serde_spanned" name = "serde_spanned"
version = "1.0.4" version = "1.0.4"
@@ -455,3 +481,9 @@ name = "winnow"
version = "1.0.0" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8"
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"

View File

@@ -10,5 +10,6 @@ clap_complete = "4.6.0"
colored = "3.1.1" colored = "3.1.1"
glob = "0.3.3" glob = "0.3.3"
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149"
sha256 = "1.6.0" sha256 = "1.6.0"
toml = "1.0.7" toml = "1.0.7"

View File

@@ -6,7 +6,7 @@ pkgdesc="A simple C project manager inspired by Cargo"
arch=('x86_64') arch=('x86_64')
url="" url=""
license=('MIT') license=('MIT')
depends=('gcc') depends=('gcc' 'pkgconf')
makedepends=('rust' 'cargo') makedepends=('rust' 'cargo')
source=() source=()

View File

@@ -9,7 +9,7 @@ use colored::Colorize;
use glob::glob; use glob::glob;
const MAIN_C: &str = include_str!("templates/main.c"); const MAIN_C: &str = include_str!("templates/main.c");
const GITIGNORE: &str = "target/"; const GITIGNORE: &str = "target/\ncompile_commands.json\n";
#[derive(clap::Parser)] #[derive(clap::Parser)]
#[clap(version, about)] #[clap(version, about)]
@@ -55,6 +55,11 @@ enum Subcommand {
}, },
/// List available build modes /// List available build modes
List, List,
/// Add a new package to the project (throguh pkg-config)
Add {
/// The package to be added
package: String,
},
} }
#[derive(clap::Subcommand)] #[derive(clap::Subcommand)]
@@ -65,6 +70,11 @@ enum UtilSubcommand {
#[arg(value_enum, long, short)] #[arg(value_enum, long, short)]
shell: ShellCompletions, shell: ShellCompletions,
}, },
/// Generate compile_commands.json for IDE support
GenCompileCommands {
/// The build mode to generate for
mode: Option<String>,
},
} }
#[derive(Clone, clap::ValueEnum)] #[derive(Clone, clap::ValueEnum)]
@@ -150,6 +160,12 @@ impl App {
), ),
} }
} }
UtilSubcommand::GenCompileCommands { mode } => {
if let Err(e) = gen_compile_commands(&mode) {
eprintln!("Error generating compile commands: {e}");
std::process::exit(1);
}
}
}, },
Subcommand::List => { Subcommand::List => {
if let Err(e) = list() { if let Err(e) = list() {
@@ -157,8 +173,116 @@ impl App {
std::process::exit(1); std::process::exit(1);
} }
} }
Subcommand::Add { package } => {
if let Err(e) = add_package(&package) {
eprintln!("Error adding package {package} to project: {e}");
std::process::exit(1);
} }
} }
}
}
}
fn add_package(package: &str) -> std::io::Result<()> {
let status = Command::new("pkg-config")
.arg("--exists")
.arg(package)
.status()
.map_err(|_| {
std::io::Error::new(
std::io::ErrorKind::NotFound,
"pkg-config not found - please install it first",
)
})?;
if !status.success() {
eprintln!(
" {} pkg-config could not find package '{package}'",
"Error".red().bold()
);
eprintln!(
" {} try installing the development package for '{package}', e.g.:",
"Hint".yellow().bold()
);
eprintln!(" Arch: sudo pacman -S {package}");
eprintln!(" Debian: sudo apt install lib{package}-dev");
std::process::exit(1);
}
let mut conf = get_config().ok_or(std::io::Error::new(
std::io::ErrorKind::NotFound,
"no Pallet.toml found in local directory",
))?;
let deps = conf.dependencies.get_or_insert_with(Vec::new);
if deps.contains(&package.to_owned()) {
println!(
" {} '{package}' is already a dependency",
"Warning".yellow().bold()
);
return Ok(());
}
deps.push(package.to_owned());
std::fs::write(
"Pallet.toml",
toml::to_string_pretty(&conf).expect("valid TOML"),
)?;
println!(" {} {package}", "Added".green().bold());
Ok(())
}
fn gen_compile_commands(mode: &Option<String>) -> std::io::Result<()> {
let conf = get_config().ok_or(std::io::Error::new(
std::io::ErrorKind::NotFound,
"no Pallet.toml found in current directory",
))?;
let build_config = conf.get_or_default(mode).ok_or(std::io::Error::new(
std::io::ErrorKind::NotFound,
"build layout not found",
))?;
let compiler = conf.compiler.as_deref().unwrap_or("gcc");
let cwd = std::env::current_dir()?;
let obj_dir = format!("target/{}/obj", build_config.name);
let source_files: Vec<PathBuf> = glob("src/*.c")
.map_err(|e| std::io::Error::new(std::io::ErrorKind::NotFound, format!("{e}")))?
.filter_map(|e| e.ok())
.collect();
let entries: Vec<serde_json::Value> = source_files
.iter()
.map(|src| {
let stem = src.file_stem().unwrap().to_string_lossy();
let obj = format!("{}/{}.o", obj_dir, stem);
let command = std::iter::once(compiler.to_string())
.chain(build_config.args.iter().cloned())
.chain(["-c".to_string(), src.to_string_lossy().to_string()])
.chain(["-o".to_string(), obj])
.collect::<Vec<_>>()
.join(" ");
serde_json::json!({
"directory": cwd.to_string_lossy(),
"file": cwd.join(src).to_string_lossy(),
"command": command
})
})
.collect();
let json = serde_json::to_string_pretty(&entries)
.map_err(|e| std::io::Error::other(format!("{e}")))?;
std::fs::write("compile_commands.json", json)?;
println!(" {} compile_commands.json", "Generated".green().bold());
Ok(())
} }
fn list() -> std::io::Result<()> { fn list() -> std::io::Result<()> {
@@ -206,7 +330,19 @@ fn create_project<P: AsRef<Path>>(directory: P) -> std::io::Result<()> {
std::fs::write("src/main.c", MAIN_C)?; std::fs::write("src/main.c", MAIN_C)?;
std::fs::write(".gitignore", GITIGNORE)?; std::fs::write(".gitignore", GITIGNORE)?;
let config = crate::config::Config::new(&pathdir.to_string_lossy()); let lossy = pathdir.to_string_lossy();
let app_name = if lossy == "." {
let root = std::env::current_dir()?;
root.file_name()
.expect("a valid file name")
.to_string_lossy()
.to_string()
} else {
lossy.to_string()
};
let config = crate::config::Config::new(&app_name);
let serial = toml::to_string_pretty(&config).expect("a valid TOML structure"); let serial = toml::to_string_pretty(&config).expect("a valid TOML structure");
@@ -248,55 +384,132 @@ fn build(mode: &Option<String>, force_recompile: bool) -> std::io::Result<()> {
let start = std::time::Instant::now(); let start = std::time::Instant::now();
std::fs::create_dir_all(format!("target/{}", build_config.name))?; let obj_dir = format!("target/{}/obj", build_config.name);
let hash = hash_src_tree()?; std::fs::create_dir_all(&obj_dir)?;
let old_compute_path = PathBuf::from(format!("target/{}/.build_hash", build_config.name)); let compiler = conf.compiler.as_deref().unwrap_or("gcc");
if old_compute_path.exists() && !force_recompile { let mut extra_compile_flags: Vec<String> = Vec::new();
let text = std::fs::read_to_string(old_compute_path)?; let mut extra_link_flags: Vec<String> = Vec::new();
if hash.trim() == text.trim() { if let Some(deps) = &conf.dependencies
&& !deps.is_empty()
{
let cflags = Command::new("pkg-config")
.arg("--cflags")
.args(deps)
.output()
.map_err(|_| {
std::io::Error::new(std::io::ErrorKind::NotFound, "pkg-config not found")
})?;
if !cflags.status.success() {
return Err(std::io::Error::other(
"pkg-config --cflags failed - is the package installed?",
));
}
let libs = Command::new("pkg-config")
.arg("--libs")
.args(deps)
.output()
.map_err(|_| {
std::io::Error::new(std::io::ErrorKind::NotFound, "pkg-config not found")
})?;
if !libs.status.success() {
return Err(std::io::Error::other(
"pkg-config --libs failed - is the pacakge installed?",
));
}
extra_compile_flags = String::from_utf8_lossy(&cflags.stdout)
.split_whitespace()
.map(str::to_owned)
.collect();
extra_link_flags = String::from_utf8_lossy(&libs.stdout)
.split_whitespace()
.map(str::to_owned)
.collect();
}
let source_files: Vec<PathBuf> = glob("src/*.c")
.map_err(|e| std::io::Error::new(std::io::ErrorKind::NotFound, format!("{e}")))?
.filter_map(|e| e.ok())
.collect();
let mut any_changed = false;
for src in &source_files {
let stem = src.file_stem().unwrap().to_string_lossy();
let obj_path = format!("{}/{}.o", obj_dir, stem);
let hash_path = format!("{}/{}.c.hash", obj_dir, stem);
let hash = hash_file(src)?;
let needs_compile = force_recompile
|| match std::fs::read_to_string(&hash_path) {
Ok(old) => old.trim() != hash.trim(),
Err(_) => true,
};
if needs_compile {
println!(" {} {}", "Compiling".green().bold(), src.display());
let status = Command::new(compiler)
.args(&build_config.args)
.args(&extra_compile_flags)
.arg("-c")
.arg(src)
.arg("-o")
.arg(&obj_path)
.status()?;
if !status.success() {
return Err(std::io::Error::other(format!(
"failed to compile {}",
src.display()
)));
}
std::fs::write(&hash_path, &hash)?;
any_changed = true;
}
}
let binary = format!("target/{}/{}", build_config.name, conf.name);
let binary_exists = PathBuf::from(&binary).exists();
if !any_changed && binary_exists {
println!( println!(
" {} (code not changed, recompile not made)", " {} (code not changed, recompile not made)",
"Finished".green().bold() "Finished".green().bold()
); );
return Ok(()); return Ok(());
} }
}
let mut command = Command::new(conf.compiler.as_deref().unwrap_or("gcc")); let obj_files: Vec<String> = source_files
.iter()
.map(|src| {
let stem = src.file_stem().unwrap().to_string_lossy();
format!("{}/{}.o", obj_dir, stem)
})
.collect();
for arg in &build_config.args { let status = Command::new(compiler)
command.arg(arg); .args(&obj_files)
} .args(&extra_link_flags)
for entry in glob("src/*.c")
.map_err(|e| std::io::Error::new(std::io::ErrorKind::NotFound, format!("{e}").as_str()))?
{
if let Ok(path) = entry {
command.arg(path.to_string_lossy().to_string());
}
}
command
.arg("-o") .arg("-o")
.arg(format!("target/{}/{}", build_config.name, conf.name)); .arg(&binary)
.status()?;
let status = command.status()?;
if !status.success() { if !status.success() {
return Err(std::io::Error::new( return Err(std::io::Error::other("linking failed"));
std::io::ErrorKind::Other,
format!("compiler exited with status {status}"),
));
} }
std::fs::write(format!("target/{}/.build_hash", build_config.name), hash)?;
let stop = start.elapsed(); let stop = start.elapsed();
gen_compile_commands(mode)?;
println!( println!(
" {} '{}' profile for project '{}' in {:.2}s", " {} '{}' profile for project '{}' in {:.2}s",
"Finished".green().bold(), "Finished".green().bold(),
@@ -308,16 +521,9 @@ fn build(mode: &Option<String>, force_recompile: bool) -> std::io::Result<()> {
Ok(()) Ok(())
} }
fn hash_src_tree() -> std::io::Result<String> { fn hash_file(path: &Path) -> std::io::Result<String> {
let mut hashes = String::new(); let contents = std::fs::read_to_string(path)?;
for entry in glob("src/**/*").expect("a valid glob pattern") { Ok(sha256::digest(contents))
if let Ok(file) = entry {
let text = std::fs::read_to_string(file)?;
let hash = sha256::digest(text);
hashes.push_str(hash.as_str());
}
}
Ok(sha256::digest(hashes))
} }
fn run(mode: &Option<String>, args: Option<Vec<String>>) -> std::io::Result<()> { fn run(mode: &Option<String>, args: Option<Vec<String>>) -> std::io::Result<()> {

View File

@@ -14,6 +14,8 @@ pub struct Config {
pub authors: Option<Vec<String>>, pub authors: Option<Vec<String>>,
/// Build configs /// Build configs
pub build: Vec<BuildConf>, pub build: Vec<BuildConf>,
/// Build dependnecies verified by pkg-config
pub dependencies: Option<Vec<String>>,
} }
impl Config { impl Config {