aboutsummaryrefslogtreecommitdiff
path: root/crates/divina_compile/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/divina_compile/src/lib.rs')
-rw-r--r--crates/divina_compile/src/lib.rs307
1 files changed, 307 insertions, 0 deletions
diff --git a/crates/divina_compile/src/lib.rs b/crates/divina_compile/src/lib.rs
new file mode 100644
index 0000000..ec37f6d
--- /dev/null
+++ b/crates/divina_compile/src/lib.rs
@@ -0,0 +1,307 @@
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+// SPDX-License-Identifier: GPL-3.0-only
+
+#![feature(stmt_expr_attributes)]
+#![deny(
+ warnings,
+ nonstandard_style,
+ unused,
+ future_incompatible,
+ rust_2018_idioms,
+ unsafe_code
+)]
+#![deny(clippy::all, clippy::nursery, clippy::pedantic)]
+#![recursion_limit = "128"]
+#![doc(
+ html_logo_url = "https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/160/twitter/282/ribbon_1f380.png",
+ html_favicon_url = "https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/160/twitter/282/ribbon_1f380.png"
+)]
+
+use std::{fs, process::Command};
+
+use divina_config::Arch;
+
+#[derive(Debug)]
+struct Source {
+ filename: String,
+ path: String,
+}
+
+#[derive(Debug)]
+struct Package {
+ name: String,
+ sources: Vec<Source>,
+ arch: Arch,
+ compiler: String,
+}
+
+#[derive(Default, Debug)]
+pub struct Compiler {
+ sources: Vec<Package>,
+}
+impl Compiler {
+ #[must_use]
+ pub fn new() -> Self { Self::default() }
+
+ pub fn find_sources(&mut self, config: &divina_config::Config) -> &Self {
+ if config.config_type == divina_config::ConfigType::Workspace {
+ for member in config.members.as_ref().expect(
+ "!! could not access 'Config.members' from `workspace`, this *shouldn't* be possible",
+ ) {
+ let mut package = Package {
+ name: member.name.clone().expect(
+ "!! could not access `Config.?.name` from `workspace`, this *shouldn't* be possible",
+ ),
+ sources: Vec::new(),
+ arch: member
+ .arch
+ .clone()
+ .expect("!! could not access 'Config.members.?.arch', this *shouldn't* be possible"),
+ compiler: member
+ .compiler
+ .clone()
+ .unwrap_or_else(|| "yasm".to_string()),
+ };
+
+ member
+ .sources
+ .as_ref()
+ .expect(
+ "!! could not access 'Config.sources' from 'workspace.members', this *shouldn't* be \
+ possible",
+ )
+ .iter()
+ .for_each(|source| {
+ if !source.is_empty() {
+ package.sources.push(Source {
+ path: format!(
+ "{}/{}",
+ member.path.as_ref().expect(
+ "!! could not access 'Config.members.?.path', this *shouldn't* be possible"
+ ),
+ source
+ ),
+ filename: {
+ let mut sources = source.split('.');
+ // Remove the file extension
+ sources.next_back();
+
+ sources.collect()
+ },
+ });
+ }
+ });
+
+ self.sources.push(package);
+ }
+ } else {
+ let mut package = Package {
+ name: config
+ .name
+ .clone()
+ .expect("!! could not access `Config.name` from `Package`, this *shouldn't* be possible"),
+ sources: Vec::new(),
+ arch: config
+ .arch
+ .clone()
+ .expect("!! could not access 'Config.arch', this *shouldn't* be possible"),
+ compiler: if config.compiler.is_some() {
+ config
+ .compiler
+ .clone()
+ .expect("!! could not access 'Config.compiler', this *shouldn't be possible")
+ } else {
+ "yasm".to_string()
+ },
+ };
+
+ config
+ .sources
+ .as_ref()
+ .expect("!! could not access 'Config.sources' from 'Package', this *shouldn't* be possible")
+ .iter()
+ .for_each(|source| {
+ if !source.is_empty() {
+ package.sources.push(Source {
+ path: format!(
+ "{}/{}",
+ config
+ .path
+ .as_ref()
+ .expect("!! could not access 'Config.path', this *shouldn't* be possible"),
+ source
+ ),
+ filename: {
+ let mut sources = source.split('.');
+ // Remove the file extension
+ sources.next_back();
+
+ sources.collect()
+ },
+ });
+ }
+ });
+
+ self.sources.push(package);
+ }
+
+ self
+ }
+
+ /// # Panics
+ /// if caller has insufficient permissions to create a directory
+ #[must_use]
+ pub fn compile(&self) -> &Self {
+ if !std::path::Path::new("out/").exists() {
+ println!(":: creating directory 'out/'");
+ fs::create_dir_all("out/").expect("!! could not create directory 'out/', check permissions");
+ }
+
+ for package in &self.sources {
+ if !std::path::Path::new(&format!("out/{}/", package.name)).exists() {
+ println!(
+ ":: {} @@ creating directory 'out/{}/'",
+ package.name, package.name
+ );
+ fs::create_dir_all(&format!("out/{}/", package.name)).unwrap_or_else(|_| {
+ panic!(
+ "!! could not create directory 'out/{}/', check permissions",
+ package.name
+ )
+ });
+ }
+
+ for source in &package.sources {
+ println!(
+ ":: {} @@ {} ?? compiling source '{}'",
+ package.name, package.compiler, source.path
+ );
+
+ #[cfg(unix)]
+ Command::new(&package.compiler)
+ .args([
+ "-f",
+ if package.arch == Arch::X86 {
+ "elf32"
+ } else {
+ "elf64"
+ },
+ &source.path,
+ "-o",
+ &format!("out/{}/{}.o", package.name, source.filename),
+ ])
+ .output()
+ .expect(&format!(
+ "!! failed to call command `{}` in `Compiler.compile`",
+ package.compiler
+ ));
+
+ #[cfg(windows)]
+ Command::new(&package.compiler)
+ .args([
+ "-f",
+ if package.arch == Arch::X86 {
+ "win32"
+ } else {
+ "win64"
+ },
+ &source.path,
+ "-o",
+ &format!("out/{}/{}.obj", package.name, source.filename),
+ ])
+ .output()
+ .unwrap_or_else(|_| {
+ panic!(
+ "!! failed to call command `{}` in `Compiler.compile`",
+ package.compiler
+ )
+ });
+ }
+ }
+
+ self
+ }
+
+ /// # Panics
+ /// if Visual Studio 2019 is not installed
+ pub fn link(&self) {
+ for package in &self.sources {
+ let mut filenames = Vec::new();
+ #[allow(unused)]
+ let mut arch = &Arch::X86;
+
+ for source in &package.sources {
+ filenames.push(format!("out/{}/{}.{}", package.name, source.filename, {
+ if cfg!(windows) {
+ "obj"
+ } else {
+ "o"
+ }
+ }));
+
+ #[allow(unused)]
+ arch = &package.arch;
+ }
+
+ #[cfg(unix)]
+ {
+ println!(
+ ":: {} @@ linking sources: '{}'",
+ package.name,
+ filenames.join("', '")
+ );
+
+ Command::new("ld")
+ .args([
+ "-dynamic-linker",
+ "/lib64/ld-linux-x86-64.so.2",
+ "-lc",
+ "-o",
+ &format!("out/{}/{}", package.name, package.name),
+ ])
+ .args(filenames.iter())
+ .output()
+ .expect("!! failed to call command `ld` in `Compiler.link`");
+ }
+
+ #[cfg(windows)]
+ {
+ println!(
+ ":: {} @@ entering visual studio 2019 developer command prompt environment",
+ package.name
+ );
+ println!(
+ ":: {} @@ linking sources: '{}'",
+ package.name,
+ filenames.join("', '")
+ );
+ if arch == &Arch::X64 {
+ windows::link_64(&filenames.join(" "), &package.name);
+ } else {
+ windows::link_32(&filenames.join(" "), &package.name);
+ }
+ }
+ }
+ }
+}
+
+#[cfg(windows)]
+#[rustfmt::skip] // Preserve raw string literal positions
+mod windows {
+ /// Thank lord for the [shellfn](https://github.com/synek317/shellfn) crate...
+ ///
+ /// I unironically spent **SIX** hours -- give or take a few minutes... --
+ /// trying to get the Windows linker working. After searching far and wide,
+ /// the shellfn crate happened to come up in my search results after a
+ /// random search, and it worked!
+ ///
+ /// Thanks, shellfn.
+ #[shellfn::shell(cmd = "powershell")]
+ pub fn link_32(objects: &str, filename: &str) -> String { r#"
+ "link /subsystem:console /out:out/$FILENAME/$FILENAME.exe $OBJECTS kernel32.lib msvcrt.lib legacy_stdio_definitions.lib" | cmd /k "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars32.bat"
+ "# }
+ #[shellfn::shell(cmd = "powershell")]
+ pub fn link_64(objects: &str, filename: &str) -> String { r#"
+ "link /subsystem:console /out:out/$FILENAME/$FILENAME.exe $OBJECTS kernel32.lib msvcrt.lib legacy_stdio_definitions.lib" | cmd /k "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat"
+ "# }
+}