// Copyright (C) 2022-2022 Fuwn // 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; use divina_config::Arch; #[derive(Debug, Clone)] struct Source { filename: String, path: String, } #[derive(Debug, Clone)] struct Package { name: String, sources: Vec, arch: Arch, compiler: String, #[allow(unused)] visual_studio: Option, } #[derive(Default, Debug, Clone)] pub struct Compiler { sources: Vec, is_package: bool, } 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.expect( "!! could not access 'Config.members' from `workspace`, this *shouldn't* be possible", ) { let mut package = Package { name: member.name.expect( "!! could not access `Config.?.name` from `workspace`, this *shouldn't* be possible", ), sources: Vec::new(), arch: member .arch .expect("!! could not access 'Config.members.?.arch', this *shouldn't* be possible"), compiler: member.compiler.unwrap_or_else(|| "yasm".to_string()), visual_studio: member.visual_studio, }; 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 .expect("!! could not access `Config.name` from `Package`, this *shouldn't* be possible"), sources: Vec::new(), arch: config .arch .expect("!! could not access 'Config.arch', this *shouldn't* be possible"), compiler: if config.compiler.is_some() { config .compiler .expect("!! could not access 'Config.compiler', this *shouldn't be possible") } else { "yasm".to_string() }, visual_studio: config.visual_studio, }; 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: source.to_string(), filename: { let mut sources = source.split('.'); // Remove the file extension sources.next_back(); let sources_no_extension = sources.collect::(); sources = sources_no_extension.split('/'); sources .next_back() .expect("!! could not get filename from source, this is an anomaly") .to_string() }, }); } }); self.sources.push(package); } self.is_package = self.sources.len() == 1; 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 { let package_out_directory = if self.is_package { "out/".to_string() } else { format!("out/{}/", package.name) }; if !std::path::Path::new(&package_out_directory).exists() { println!( ":: {} @@ creating directory '{}'", package.name, package_out_directory ); fs::create_dir_all(&package_out_directory).unwrap_or_else(|_| { panic!( "!! could not create directory '{}', check permissions", package_out_directory ) }); } for source in &package.sources { println!( ":: {} @@ {} ?? compiling source '{}'", package.name, package.compiler, source.path ); #[cfg(unix)] unix::compile( &package.compiler, if package.arch == Arch::X86 { "elf32" } else { "elf64" }, &source.path, &if self.is_package { format!("out/{}.o", source.filename) } else { format!("out/{}/{}.o", package.name, source.filename) }, ); #[cfg(windows)] windows::compile( &package.compiler, if package.arch == Arch::X86 { "win32" } else { "win64" }, &source.path, &if self.is_package { format!("out/{}.obj", source.filename) } else { format!("out/{}/{}.obj", package.name, source.filename) }, ); } } 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/{}{}.{}", &if self.is_package { "".to_string() } else { format!("{}/", &package.name) }, source.filename, { if cfg!(windows) { "obj" } else { "o" } } )); #[allow(unused)] arch = &package.arch; } #[cfg(windows)] println!( ":: {} @@ entering visual studio developer command prompt environment", package.name ); println!( ":: {} @@ linking source{}: '{}'", package.name, if filenames.len() > 1 { "s" } else { "" }, filenames.join("', '") ); #[cfg(unix)] unix::link( "ld", "/lib64/ld-linux-x86-64.so.2", &if self.is_package { format!("out/{}", package.name) } else { format!("out/{}/{}", package.name, package.name) }, &filenames.join(" "), ); #[cfg(windows)] { if let Some(visual_studio_path) = &package.visual_studio { windows::link_package_custom(&filenames.join(" "), &package.name, visual_studio_path); } else if arch == &Arch::X64 { if self.is_package { windows::link_package_64(&filenames.join(" "), &package.name); } else { windows::link_workspace_64(&filenames.join(" "), &package.name); } } else if self.is_package { windows::link_package_32(&filenames.join(" "), &package.name); } else { windows::link_workspace_32(&filenames.join(" "), &package.name); } } } } #[must_use] pub fn print_config(&self) -> &Self { println!("{:?}", self); self } } #[cfg(unix)] #[rustfmt::skip] // Preserve raw string literal positions mod unix { use shellfn::shell; #[shell] pub fn compile(compiler: &str, architecture: &str, source_path: &str, out_file: &str) -> String { r#" $COMPILER -f $ARCHITECTURE $SOURCE_PATH -o $OUT_FILE "# } #[shell] pub fn link(linker: &str, dynamic_linker: &str, out_file: &str, sources: &str) -> String { r#" $LINKER -dynamic-linker $DYNAMIC_LINKER -lc -o $OUT_FILE $SOURCES "# } } #[cfg(windows)] #[rustfmt::skip] mod windows { use shellfn::shell; #[shell(cmd = "powershell")] pub fn compile(compiler: &str, architecture: &str, sources: &str, out_file: &str) -> String { r#" $COMPILER -f $ARCHITECTURE $SOURCES -o $OUT_FILE "# } /// 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. #[shell(cmd = "powershell")] pub fn link_workspace_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\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars32.bat" "# } #[shell(cmd = "powershell")] pub fn link_workspace_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\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" "# } #[shell(cmd = "powershell")] pub fn link_package_32(objects: &str, filename: &str) -> String { r#" "link /subsystem:console /out:out/$FILENAME.exe $OBJECTS kernel32.lib msvcrt.lib legacy_stdio_definitions.lib" | cmd /k "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars32.bat" "# } #[shell(cmd = "powershell")] pub fn link_package_64(objects: &str, filename: &str) -> String { r#" "link /subsystem:console /out:out/$FILENAME.exe $OBJECTS kernel32.lib msvcrt.lib legacy_stdio_definitions.lib" | cmd /k "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" "# } #[shell(cmd = "powershell")] pub fn link_package_custom(objects: &str, filename: &str, visual_studio_path: &str) -> String { r#" "link /subsystem:console /out:out/$FILENAME.exe $OBJECTS kernel32.lib msvcrt.lib legacy_stdio_definitions.lib" | cmd /k "$VISUAL_STUDIO_PATH" "# } }