16 Commits

6 changed files with 488 additions and 13 deletions

158
Cargo.lock generated
View File

@@ -52,6 +52,38 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "async-trait"
version = "0.1.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "bytes"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "clap"
version = "4.6.0"
@@ -74,6 +106,15 @@ dependencies = [
"strsim",
]
[[package]]
name = "clap_complete"
version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19c9f1dde76b736e3681f28cec9d5a61299cbaae0fce80a68e43724ad56031eb"
dependencies = [
"clap",
]
[[package]]
name = "clap_derive"
version = "4.6.0"
@@ -98,12 +139,60 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
name = "colored"
version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
dependencies = [
"windows-sys",
]
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "glob"
version = "0.3.3"
@@ -122,6 +211,12 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "indexmap"
version = "2.13.0"
@@ -138,6 +233,12 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "libc"
version = "0.2.183"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
@@ -146,14 +247,23 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "pallet"
version = "0.1.0"
version = "1.0.5"
dependencies = [
"clap",
"clap_complete",
"colored",
"glob",
"serde",
"sha256",
"toml",
]
[[package]]
name = "pin-project-lite"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "proc-macro2"
version = "1.0.106"
@@ -211,6 +321,30 @@ dependencies = [
"serde_core",
]
[[package]]
name = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "sha256"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f880fc8562bdeb709793f00eb42a2ad0e672c4f883bbe59122b926eca935c8f6"
dependencies = [
"async-trait",
"bytes",
"hex",
"sha2",
"tokio",
]
[[package]]
name = "strsim"
version = "0.11.1"
@@ -228,6 +362,16 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "tokio"
version = "1.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d"
dependencies = [
"bytes",
"pin-project-lite",
]
[[package]]
name = "toml"
version = "1.0.7+spec-1.1.0"
@@ -267,6 +411,12 @@ version = "1.0.7+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17aaa1c6e3dc22b1da4b6bba97d066e354c7945cac2f7852d4e4e7ca7a6b56d"
[[package]]
name = "typenum"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "unicode-ident"
version = "1.0.24"
@@ -279,6 +429,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "windows-link"
version = "0.2.1"

View File

@@ -1,10 +1,14 @@
[package]
name = "pallet"
version = "0.1.0"
version = "1.0.5"
edition = "2024"
description = "A project manager and build system for C inspired by Rust's Cargo"
[dependencies]
clap = { version = "4.6.0", features = ["derive"] }
clap_complete = "4.6.0"
colored = "3.1.1"
glob = "0.3.3"
serde = { version = "1.0.228", features = ["derive"] }
sha256 = "1.6.0"
toml = "1.0.7"

19
PKGBUILD Normal file
View File

@@ -0,0 +1,19 @@
# Maintainer: Shea Frembling <sfrembling@gmail.com>
pkgname=pallet
pkgver=1.0.5
pkgrel=1
pkgdesc="A simple C project manager inspired by Cargo"
arch=('x86_64')
url=""
license=('MIT')
depends=()
makedepends=('rust' 'cargo')
source=()
build() {
cargo build --release
}
package() {
install -Dm755 "$startdir/target/release/pallet" "$pkgdir/usr/bin/pallet"
}

View File

@@ -1,15 +1,24 @@
# Pallet
Pallet is a project manager and build system for C inspired by Cargo for Rust.
Pallet is a project manager and build system for C inspired by Rust's Cargo.
This is a toy project not meant to be taken very seriously.
## Requirements for Use
[GCC](https://en.wikipedia.org/wiki/GNU_Compiler_Collection) is required as it is currently the back-end tool used to compile the C code.
This tool calls `gcc` internally, so without it, it would fail.
At some point I would like to update `Pallet.toml` to instead allow more configuring of how the build should be performed.
## Usage
- `pallet new <project>`: initializes a new project at `project`
- `pallet init`: initializes a new project in the current directory
- `pallet run`: runs the local project
- `pallet build`: builds the local project
- `pallet clean`: cleans the local project's build artifacts
## Configuring
@@ -17,4 +26,31 @@ You can configure options by editing `Pallet.toml`
### Options
TBD
- `name`: the name of the output executable
- `default_build`: the name of the default build profile to use
Additionally, one can define one or more build profiles with the following parameters:
- `name`: the name of the build profile
- `args`: the args to supply to gcc when using this profile
For example:
```toml
name = "my-app"
default_build = "debug"
[[build]]
name = "debug"
args = [
"-g",
"-O0",
]
[[build]]
name = "release"
args = [
"-DNDEBUG",
"-O3",
]
```

View File

@@ -1,11 +1,18 @@
use std::{
env::set_current_dir,
path::{Path, PathBuf},
process::Command,
};
use clap::CommandFactory;
use colored::Colorize;
use glob::glob;
const MAIN_C: &str = include_str!("templates/main.c");
const GITIGNORE: &str = "target/";
#[derive(clap::Parser)]
#[clap(version, about)]
pub struct App {
#[clap(subcommand)]
command: Subcommand,
@@ -24,38 +31,156 @@ enum Subcommand {
Run {
/// The build mode to use
mode: Option<String>,
/// Arguments to pass to the project binary
#[arg(long, short)]
args: Option<Vec<String>>,
},
/// Build the local project
Build {
/// The build mode to use
mode: Option<String>,
},
/// Clean all in progress files
Clean,
/// Utility functions
Utils {
#[clap(subcommand)]
command: UtilSubcommand,
},
/// List available build modes
List,
}
#[derive(clap::Subcommand)]
enum UtilSubcommand {
/// Generate shell completions
Completions {
/// The shell to generate completions for
#[arg(value_enum, long, short)]
shell: ShellCompletions,
},
}
#[derive(Clone, clap::ValueEnum)]
enum ShellCompletions {
Bash,
Fish,
PowerShell,
Zsh,
}
impl App {
pub fn run(self) {
match self.command {
Subcommand::New { name } => match create_project(&name) {
Ok(_) => println!("Successfully created new project '{name}'."),
Err(e) => {
eprintln!("Error creating project '{name}': {e}");
std::process::exit(1);
}
_ => {}
},
Subcommand::Init => match create_project(".") {
Ok(_) => println!("Successfully initialized new project."),
Err(e) => {
eprintln!("Error initializing project: {e}");
std::process::exit(1);
}
_ => {}
},
Subcommand::Run { mode, args } => {
match build(&mode) {
Err(e) => {
eprintln!("Error building project: {e}");
std::process::exit(1);
}
_ => {}
};
if let Err(e) = run(&mode, args) {
eprintln!("Error running project: {e}");
std::process::exit(1);
}
}
Subcommand::Build { mode } => match build(&mode) {
Err(e) => {
eprintln!("Error building project: {e}");
std::process::exit(1);
}
_ => {}
},
Subcommand::Clean => match clean() {
Err(e) => {
eprintln!("Error cleaning project: {e}");
std::process::exit(1);
}
_ => {}
},
Subcommand::Utils { command } => match command {
UtilSubcommand::Completions { shell } => {
let name = env!("CARGO_PKG_NAME");
let mut command = App::command();
match shell {
ShellCompletions::Bash => clap_complete::generate(
clap_complete::shells::Bash,
&mut command,
name,
&mut std::io::stdout(),
),
ShellCompletions::Fish => clap_complete::generate(
clap_complete::shells::Fish,
&mut command,
name,
&mut std::io::stdout(),
),
ShellCompletions::PowerShell => clap_complete::generate(
clap_complete::shells::PowerShell,
&mut command,
name,
&mut std::io::stdout(),
),
ShellCompletions::Zsh => clap_complete::generate(
clap_complete::shells::Zsh,
&mut command,
name,
&mut std::io::stdout(),
),
}
}
},
Subcommand::List => match list() {
Err(e) => {
eprintln!("Error listing build profiles: {e}");
std::process::exit(1);
}
_ => {}
},
Subcommand::Run { mode } => todo!(),
Subcommand::Build { mode } => todo!(),
}
}
}
fn list() -> std::io::Result<()> {
let conf = get_config().ok_or(std::io::Error::new(
std::io::ErrorKind::NotFound,
"no Pallet.toml found in local directory",
))?;
for build in conf.build {
println!(" - {}: {:?}", build.name.green().bold(), build.args);
}
Ok(())
}
fn create_project<P: AsRef<Path>>(directory: P) -> std::io::Result<()> {
let name = if directory.as_ref().to_string_lossy() == "." {
String::new()
} else {
format!(" '{}'", directory.as_ref().to_string_lossy())
};
println!(
" {} binary (application){}",
"Creating".green().bold(),
name
);
let pathdir = directory.as_ref();
if pathdir.exists() && pathdir.to_string_lossy() != "." {
@@ -74,6 +199,7 @@ fn create_project<P: AsRef<Path>>(directory: P) -> std::io::Result<()> {
std::fs::create_dir("src")?;
std::fs::write("src/main.c", MAIN_C)?;
std::fs::write(".gitignore", GITIGNORE)?;
let config = crate::config::Config::new(&pathdir.to_string_lossy());
@@ -94,7 +220,7 @@ fn get_config() -> Option<crate::config::Config> {
toml::from_str(&raw).ok()
}
fn build(mode: Option<String>) -> std::io::Result<()> {
fn build(mode: &Option<String>) -> std::io::Result<()> {
let conf = match get_config() {
Some(conf) => conf,
None => {
@@ -108,5 +234,132 @@ fn build(mode: Option<String>) -> std::io::Result<()> {
"build layout not found",
))?;
todo!()
println!(
" {} '{}' profile for project '{}'",
"Building".green().bold(),
build_config.name,
conf.name
);
let start = std::time::Instant::now();
std::fs::create_dir_all(format!("target/{}", build_config.name))?;
let hash = hash_src_tree()?;
let old_compute_path = PathBuf::from(format!("target/{}/.build_hash", build_config.name));
if old_compute_path.exists() {
let text = std::fs::read_to_string(old_compute_path)?;
if hash.trim() == text.trim() {
println!(
" {} (code not changed, recompile not made)",
"Finished".green().bold()
);
return Ok(());
}
}
let mut command = Command::new("gcc");
for arg in &build_config.args {
command.arg(arg);
}
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(format!("target/{}/{}", build_config.name, conf.name));
let mut child = command.spawn()?;
child.wait()?;
std::fs::write(format!("target/{}/.build_hash", build_config.name), hash)?;
let stop = start.elapsed();
println!(
" {} '{}' profile for project '{}' in {:.2}s",
"Finished".green().bold(),
build_config.name,
conf.name,
stop.as_secs_f64()
);
Ok(())
}
fn hash_src_tree() -> std::io::Result<String> {
let mut hashes = String::new();
for entry in glob("src/**/*").expect("a valid glob pattern") {
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<()> {
let conf = match get_config() {
Some(conf) => conf,
None => {
eprintln!("Error opening config file. No Pallet.toml in current directory.");
std::process::exit(1);
}
};
let build_config = conf.get_or_default(mode).ok_or(std::io::Error::new(
std::io::ErrorKind::NotFound,
"build layout not found",
))?;
println!(
" {} '{}' profile for project '{}'",
"Running".green().bold(),
build_config.name,
conf.name
);
let mut command = Command::new(format!("target/{}/{}", build_config.name, conf.name));
if let Some(args) = args {
for arg in args {
command.arg(arg);
}
}
let mut child = command.spawn()?;
child.wait()?;
Ok(())
}
fn clean() -> std::io::Result<()> {
if let None = get_config() {
return Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"no Pallet.toml found in local dir",
));
}
std::fs::remove_dir_all("target/")?;
println!(
" {} removed files in target/ directory",
"Successfully".green().bold()
);
Ok(())
}

View File

@@ -1,9 +1,15 @@
#[derive(serde::Deserialize, serde::Serialize)]
#[derive(serde::Deserialize, serde::Serialize, Default)]
pub struct Config {
/// The name of the output binary
pub name: String,
/// The default build to use
pub default_build: String,
/// A brief description
pub description: Option<String>,
/// The version of the project
pub version: Option<String>,
/// The authors of the project
pub authors: Option<Vec<String>>,
/// Build configs
pub build: Vec<BuildConf>,
}
@@ -14,12 +20,13 @@ impl Config {
name: name.to_owned(),
default_build: "debug".to_owned(),
build: vec![BuildConf::debug(), BuildConf::release()],
..Default::default()
}
}
pub fn get_or_default(&self, mode: Option<String>) -> Option<&BuildConf> {
pub fn get_or_default(&self, mode: &Option<String>) -> Option<&BuildConf> {
if let Some(mode) = mode {
self.build.iter().find(|bc| bc.name == mode)
self.build.iter().find(|bc| bc.name == *mode)
} else {
self.build.iter().find(|bc| bc.name == self.default_build)
}