From cd2c92d415417ba02b1795d9acb810163c6aac95 Mon Sep 17 00:00:00 2001 From: Noah Swerhun Date: Wed, 13 Dec 2023 10:27:29 -0600 Subject: [PATCH] robust header file locator --- .gitignore | 1 + Cargo.lock | 261 ++++++++++++++++++++++++++++++++ Cargo.toml | 2 + Makefile | 27 ++++ install.sh | 3 + src/main.rs | 416 +++++++++++++++++++++++++++++++++++++++++++++++++++ uninstall.sh | 3 + 7 files changed, 713 insertions(+) create mode 100644 Makefile create mode 100644 install.sh create mode 100644 src/main.rs create mode 100644 uninstall.sh diff --git a/.gitignore b/.gitignore index a03ed96..5c2df11 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target /testing_grounds +/build diff --git a/Cargo.lock b/Cargo.lock index 1122634..91343da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,267 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "clap" +version = "4.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + [[package]] name = "mgen" version = "0.1.0" +dependencies = [ + "clap", + "regex", +] + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "2.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" diff --git a/Cargo.toml b/Cargo.toml index cf61e08..97d7b1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +clap = { version = "4.4.10", features = ["derive"] } +regex = "1.10.2" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..64f2e07 --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +TARGET = foo +CC = gcc +CFLAGS = -std=c99 +LDLIBS = -lm +#=mgen_start=# +BUILDDIR = ./build +OBJDIR = $(BUILDDIR)/obj +$(BUILDDIR)/$(TARGET): $(OBJDIR)/main.o $(OBJDIR)/ops.o $(OBJDIR)/traps.o | $(OBJDIR) $(BUILDDIR) + $(CC) $(LDFLAGS) -o $(BUILDDIR)/$(TARGET) $(OBJDIR)/main.o $(OBJDIR)/ops.o $(OBJDIR)/traps.o $(LDLIBS) +$(OBJDIR)/main.o: ./testing_grounds/src/main.c ./testing_grounds/src/main.h ./testing_grounds/src/ops.h ./testing_grounds/src/traps.h | $(OBJDIR) + $(CC) $(CFLAGS) -o $(OBJDIR)/main.o -c ./testing_grounds/src/main.c +$(OBJDIR)/ops.o: ./testing_grounds/src/ops.c ./testing_grounds/src/main.h | $(OBJDIR) + $(CC) $(CFLAGS) -o $(OBJDIR)/ops.o -c ./testing_grounds/src/ops.c +$(OBJDIR)/traps.o: ./testing_grounds/src/traps.c ./testing_grounds/src/traps.h ./testing_grounds/src/main.h | $(OBJDIR) + $(CC) $(CFLAGS) -o $(OBJDIR)/traps.o -c ./testing_grounds/src/traps.c +$(BUILDDIR): + mkdir -p $(BUILDDIR) +$(OBJDIR): + mkdir -p $(OBJDIR) +.PHONY: clean +clean: + rm -r $(OBJDIR) + rm -r $(BUILDDIR) +.PHONY: run +run: $(BUILDDIR)/$(TARGET) + ./$(BUILDDIR)/$(TARGET) +#=mgen_end=# diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..fcf1cc7 --- /dev/null +++ b/install.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +cp ./target/release/mgen /usr/bin/mgen diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..a189340 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,416 @@ +use std::path::PathBuf; +use std::io::ErrorKind; +use std::fs::{self, DirEntry}; + +use regex::Regex; +use clap::Parser; + +const START_KEY: &str = "#=mgen_start=#\n"; +const END_KEY: &str = "#=mgen_end=#"; + +#[derive(Parser, Debug)] +struct Args { + /// Replace default make output with nice build messages (not yet implemented) + #[arg(short, long)] + pretty: bool, + + /// Path to makefile + #[arg(short, long, default_value = "./Makefile")] + makefile: String, +} + +#[derive(Debug)] +#[allow(dead_code)] +enum BuildRuleKind { + Object(T), + Target(T), + Directory(T), + Convenience(T), +} + +#[derive(Debug,Clone)] +struct BuildRule { + target: String, + prerequisites: Vec, + recipe_commands: Vec, +} + + +#[derive(Debug)] +#[allow(dead_code)] +struct Makefile { + path: String, + prologue: String, + mgen_zone: String, + epilogue: String, + include_dirs: Vec, +} + +#[derive(Debug)] +struct RuleList(Vec>); + +impl BuildRule { + fn get_headers_from_c_file(path: &str, makefile: &Makefile) -> Vec { + let mut headers: Vec = Vec::new(); + let file_contents = fs::read_to_string(path).unwrap_or_else(|e| { + panic!("error: could not read file {path}: {e}"); + }); + + let file_path = PathBuf::from(path); + let file_dir = file_path.parent().unwrap(); + + let includes_regex = Regex::new(r#"#include\s+"(.*?)""#).unwrap(); + let caps = includes_regex.captures_iter(&file_contents); + for (_, [include_file]) in caps.map(|c| c.extract()) { + match file_dir.join(PathBuf::from(include_file)).canonicalize() { + Ok(path) => { + headers.push(String::from(path.to_str().unwrap())); + continue; + }, + Err(_) => (), + } + + let mut done = false; + for dir in makefile.include_dirs.iter() { + let dir = PathBuf::from(&dir); + match dir.join(PathBuf::from(include_file)).canonicalize() { + Ok(path) => { + headers.push(String::from(path.to_str().unwrap())); + done = true; + break; + }, + Err(_) => (), + } + } + if done { + continue; + } + let path = file_dir.join(PathBuf::from(include_file)); + headers.push(String::from(path.to_str().unwrap())); + } + + headers + } + fn new_from_c_file(path: &str, makefile: &Makefile) -> BuildRule { + + let mut prerequisites: Vec = Vec::new(); + + prerequisites.push(String::from(path)); + + prerequisites.append(&mut BuildRule::get_headers_from_c_file(path, makefile)); + + prerequisites.push(String::from("| $(OBJDIR)")); + + let file_path = PathBuf::from(path); + + let obj_name = file_path.with_extension("o"); + let obj_name = obj_name.file_name().unwrap(); + + let obj_path = PathBuf::from("$(OBJDIR)").join(obj_name); + let obj_path = obj_path.to_str().unwrap(); + + let compile_command = format!("$(CC) $(CFLAGS) -o {} -c {}", obj_path, path); + + BuildRule { target: String::from(obj_path), prerequisites, recipe_commands: vec![compile_command] } + } +} + +impl ToString for BuildRule { + fn to_string(&self) -> String { + let mut ret = String::new(); + ret.push_str(&format!("{}:", self.target)); + for prereq in &self.prerequisites { + ret.push_str(&format!(" {}", prereq)); + } + ret.push_str(&format!("\n")); + for recipe_comm in &self.recipe_commands { + ret.push_str(&format!("\t{}\n", recipe_comm)); + } + ret + } +} + +impl Makefile { + fn new_from_path(path: &str) -> Makefile { + let contents = match fs::read_to_string(path) { + Ok(v) => v, + Err(e) => match e.kind() { + // doesn't matter if the file doesn't exist, it will be created + // later. + ErrorKind::NotFound => return Makefile { + path: String::from(path), prologue: String::from(""), + mgen_zone: String::from(""), epilogue: String::from(""), + include_dirs: vec![String::from("")] }, + _ => panic!("error: cannot read makefile: {e}") + } + }; + + let contains_start = contents.find(START_KEY).is_some(); + let contains_end = contents.find(END_KEY).is_some(); + + if (contains_start && !contains_end) || (!contains_start && + contains_end) { + panic!("error: makefile contains one mgen guard but not the other. cannot continue."); + } + + if ! contains_start && contains_end { + let include_dirs = Makefile::get_include_dirs(&contents); + return Makefile { path: String::from(path), prologue: contents, + mgen_zone: String::from(""), epilogue: String::from(""), include_dirs }; + } + + // contains the part before the start key and everything after + let pre_and_after: Vec<&str> = contents.split(START_KEY).collect(); + // contains the space between start and end keys and also the space after + let gen_and_post: Vec<&str> = pre_and_after[1].split(END_KEY).collect(); + + let include_dirs = Makefile::get_include_dirs(&contents); + + Makefile { + path: String::from(path), + prologue: String::from(pre_and_after[0]), + mgen_zone: String::from(gen_and_post[0]), + epilogue: String::from(gen_and_post[1]), + include_dirs + } + } + + fn get_include_dirs(search: &String) -> Vec { + let makefile_regex = Regex::new(r"^INCS\s*=\s*(.*?)$").unwrap(); + let caps = makefile_regex.captures_iter(search); + let (_, [dirlist]) = caps.map(|c| c.extract()).last().unwrap(); + + let mut include_dirs: Vec = Vec::new(); + + let incdir_regex = Regex::new(r"-s*-I\s*(.*?)\s*").unwrap(); + let caps = incdir_regex.captures_iter(&dirlist); + for (_, [dir]) in caps.map(|c| c.extract()) { + include_dirs.push(String::from(dir)); + } + + include_dirs + } + + fn contains_builddir_def(&self) -> bool { + let re = Regex::new(r"\s+BUILDDIR = ").unwrap(); + re.is_match(&(self.prologue)) || re.is_match(&(self.epilogue)) + } + + fn contains_objdir_def(&self) -> bool { + let re = Regex::new(r"\s+OBJDIR = ").unwrap(); + re.is_match(&(self.prologue)) || re.is_match(&(self.epilogue)) + } + + fn write_new_generated_content(&self, generated_content: String) -> std::io::Result<()> { + let content = [&self.prologue, START_KEY, &generated_content, END_KEY, &self.epilogue].concat(); + + fs::write(&self.path, &content)?; + + Ok(()) + } +} + +fn join_build_rule_vec(vector: &Vec, seperator: &str) -> String{ + let mut ret = String::new(); + + for (i, rule) in vector.iter().enumerate() { + if i != 0 { + ret.push_str(seperator); + } + ret.push_str(&rule.to_string()); + } + + ret +} + +impl ToString for RuleList { + fn to_string(&self) -> String { + let targets = join_build_rule_vec(&self.get_target_rules(), ""); + let objects = join_build_rule_vec(&self.get_object_rules(), ""); + let directories = join_build_rule_vec(&self.get_directory_rules(), ""); + let conveniences = join_build_rule_vec(&self.get_convenience_rules(), ""); + + [targets, objects, directories, conveniences].concat() + } +} + +impl RuleList { + fn new() -> RuleList{ + RuleList(vec![]) + } + + fn recursively_gen_objects(&mut self, proj_dir_path: &str, makefile: &Makefile) -> std::io::Result<()>{ + let files = fs::read_dir(proj_dir_path)?; + for file in files { + let file = file?; + let is_dir = file.metadata()?.is_dir(); + + // ignore hidden files + if file_is_hidden(&file) { + continue; + } + + let path = file.path(); + + if is_dir { + self.recursively_gen_objects(path.to_str().unwrap(), makefile)?; + } else { + match path.extension() { + Some(v) => { + let v = v.to_str().unwrap(); + if v != "c" { + continue; + } + let path = path.to_str().unwrap(); + self.0.push(BuildRuleKind::Object(BuildRule::new_from_c_file(path, makefile))); + }, + None => { + continue; + }, + } + } + } + Ok(()) + } + + fn get_obj_targets(&self) -> Vec{ + let mut ret: Vec = Vec::new(); + for rule in &self.0 { + match rule { + BuildRuleKind::Object(r) => ret.push(r.target.clone()), + _ => break, + } + } + ret + } + + fn add_target_rule(&mut self) { + let mut objects = self.get_obj_targets(); + let target_comm = String::from( + format!("$(CC) $(LDFLAGS) -o $(BUILDDIR)/$(TARGET) {} $(LDLIBS)", objects.join(" ")) + ); + + objects.push(String::from("| $(OBJDIR) $(BUILDDIR)")); + let prereqs = objects; + + self.0.push(BuildRuleKind::Target(BuildRule { + target: String::from("$(BUILDDIR)/$(TARGET)"), + prerequisites: prereqs, + recipe_commands: vec![target_comm], + })) + } + + fn add_directory_rule(&mut self, dir: &str) { + self.0.push(BuildRuleKind::Directory(BuildRule { + target: dir.to_string(), + prerequisites: vec![], + recipe_commands: vec![format!("mkdir -p {dir}").to_string()] + })); + } + + fn add_clean_rule(&mut self) { + self.0.push(BuildRuleKind::Convenience(BuildRule { + target: String::from(".PHONY: clean\nclean"), + prerequisites: vec![], + recipe_commands: vec![String::from("rm -r $(OBJDIR)"), String::from("rm -r $(BUILDDIR)")] + })); + } + + fn add_run_rule(&mut self) { + self.0.push(BuildRuleKind::Convenience(BuildRule { + target: String::from(".PHONY: run\nrun"), + prerequisites: vec![String::from("$(BUILDDIR)/$(TARGET)")], + recipe_commands: vec![String::from("./$(BUILDDIR)/$(TARGET)")] + })); + } + + fn get_target_rules(&self) -> Vec { + let mut ret: Vec = Vec::new(); + for rule in &self.0 { + match rule { + BuildRuleKind::Target(r) => ret.push(r.clone()), + _ => continue, + } + } + ret + } + + fn get_object_rules(&self) -> Vec { + let mut ret: Vec = Vec::new(); + for rule in &self.0 { + match rule { + BuildRuleKind::Object(r) => ret.push(r.clone()), + _ => continue, + } + } + ret + } + + fn get_directory_rules(&self) -> Vec { + let mut ret: Vec = Vec::new(); + for rule in &self.0 { + match rule { + BuildRuleKind::Directory(r) => ret.push(r.clone()), + _ => continue, + } + } + ret + } + + fn get_convenience_rules(&self) -> Vec { + let mut ret: Vec = Vec::new(); + for rule in &self.0 { + match rule { + BuildRuleKind::Convenience(r) => ret.push(r.clone()), + _ => continue, + } + } + ret + } +} + +fn file_is_hidden(file: &DirEntry) -> bool{ + let file_name = file.file_name(); + let str_name = file_name.to_str().unwrap(); + let first_char = str_name.chars().nth(0).unwrap(); + if first_char == '.' { + true + } else { + false + } +} + +fn main() { + let args = Args::parse(); + let makefile = Makefile::new_from_path(&args.makefile); + + let mut generated_content = String::new(); + + // Only include directory defaults if the user has not specified them + if ! makefile.contains_builddir_def() { + generated_content.push_str("BUILDDIR = ./build\n"); + } + + if ! makefile.contains_objdir_def() { + generated_content.push_str("OBJDIR = $(BUILDDIR)/obj\n"); + } + + let mut rule_list = RuleList::new(); + rule_list.recursively_gen_objects(".", &makefile).unwrap_or_else(|e| { + panic!("error: problem in recursive search: {e}"); + }); + + rule_list.add_target_rule(); + + rule_list.add_directory_rule("$(BUILDDIR)"); + rule_list.add_directory_rule("$(OBJDIR)"); + rule_list.add_clean_rule(); + rule_list.add_run_rule(); + + generated_content.push_str(&rule_list.to_string()); + + // Write makefile + makefile.write_new_generated_content(generated_content).unwrap_or_else(|e| { + panic!("error: could not write makefile at {}: {e}", makefile.path); + }); +} diff --git a/uninstall.sh b/uninstall.sh new file mode 100644 index 0000000..6d8cc03 --- /dev/null +++ b/uninstall.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +rm /usr/bin/mgen