bp3d_os_build/
lib.rs

1// Copyright (c) 2025, BlockProject 3D
2//
3// All rights reserved.
4//
5// Redistribution and use in source and binary forms, with or without modification,
6// are permitted provided that the following conditions are met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//     * Redistributions in binary form must reproduce the above copyright notice,
11//       this list of conditions and the following disclaimer in the documentation
12//       and/or other materials provided with the distribution.
13//     * Neither the name of BlockProject 3D nor the names of its contributors
14//       may be used to endorse or promote products derived from this software
15//       without specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
21// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29use cargo_lock::Lockfile;
30use cargo_manifest::Manifest;
31use itertools::Itertools;
32use std::path::PathBuf;
33
34pub struct ModuleMain {
35    rust_code: String,
36    out_path: PathBuf,
37    crate_name: String,
38    virtual_lib: String,
39}
40
41impl Default for ModuleMain {
42    fn default() -> Self {
43        Self::new()
44    }
45}
46
47impl ModuleMain {
48    pub fn new() -> Self {
49        let crate_name = std::env::var("CARGO_PKG_NAME").unwrap().replace('-', "_");
50        let crate_version = std::env::var("CARGO_PKG_VERSION").unwrap();
51        let rustc_version = rustc_version::version().unwrap();
52        let mod_const_name = format!("BP3D_OS_MODULE_{}", crate_name.to_uppercase());
53        let mut manifest_path = PathBuf::from(
54            std::env::var_os("CARGO_MANIFEST_PATH").expect("Failed to get CARGO_MANIFEST_PATH"),
55        );
56        let package =
57            Manifest::from_path(&manifest_path).expect("Failed to read CARGO_MANIFEST_PATH");
58        manifest_path.set_extension("lock");
59        let lock_file = Lockfile::load(&manifest_path).ok();
60        let mut features = Vec::new();
61        let deps_list = package
62            .dependencies
63            .map(|v| {
64                v.iter()
65                    .map(|(k, v)| {
66                        let dep_version = lock_file
67                            .as_ref()
68                            .and_then(|v| v.packages.iter().find(|v| v.name.as_ref() == *k))
69                            .map(|v| &v.version);
70                        for feature in v.req_features() {
71                            features.push(format!("{}/{}", k, feature));
72                        }
73                        match dep_version {
74                            Some(v) => format!("{}={}", k, v),
75                            None => format!("{}={}", k, v.req()),
76                        }
77                    })
78                    .join(",")
79            })
80            .unwrap_or("".into());
81        let data = format!(
82            "\"\0BP3D_OS_MODULE|TYPE=RUST|NAME={}|VERSION={}|RUSTC={}|DEPS={}|FEATURES={}\0\"",
83            crate_name,
84            crate_version,
85            rustc_version,
86            deps_list,
87            features.join(",")
88        );
89        let rust_code = format!(
90            "
91    #[unsafe(no_mangle)]
92    #[allow(clippy::manual_c_str_literals)] // The string is enclosed in NULLs and apparently clippy
93    // does not like that...
94    static mut {mod_const_name}: *const std::ffi::c_char = {data}.as_ptr() as _;
95"
96        );
97        let virtual_lib = format!("
98    #[allow(static_mut_refs)]
99    pub static VIRTUAL_MODULE: bp3d_os::module::library::types::VirtualLibrary = bp3d_os::module::library::types::VirtualLibrary::new(\"{crate_name}\", &[
100        (\"{mod_const_name}\", unsafe {{ &{mod_const_name} as *const *const i8 as *const std::ffi::c_void }})");
101        let out_path =
102            PathBuf::from(std::env::var_os("OUT_DIR").unwrap()).join("bp3d_os_module.rs");
103        let this = Self {
104            rust_code,
105            out_path,
106            crate_name,
107            virtual_lib,
108        };
109        this.add_init().add_uninit()
110    }
111
112    pub fn add_export(mut self, func_name: impl AsRef<str>) -> Self {
113        let func_name = func_name.as_ref();
114        self.virtual_lib += &format!(",\n        (\"{func_name}\", {func_name} as _)");
115        self
116    }
117
118    fn add_init(mut self) -> Self {
119        let motherfuckingrust = "extern \"C\"";
120        let crate_name = &self.crate_name;
121        let rust_code = format!(
122            r"
123    #[unsafe(no_mangle)]
124    #[inline(never)]
125    pub {motherfuckingrust} fn bp3d_os_module_{crate_name}_init(loader: &'static std::sync::Mutex<bp3d_os::module::loader::ModuleLoader>) {{
126        bp3d_os::module::loader::ModuleLoader::install_from_existing(loader);
127    }}
128"
129        );
130        self.rust_code += &rust_code;
131        let motherfuckingrust = format!("bp3d_os_module_{crate_name}_init");
132        self.add_export(motherfuckingrust)
133    }
134
135    fn add_uninit(mut self) -> Self {
136        let motherfuckingrust = "extern \"C\"";
137        let crate_name = &self.crate_name;
138        let rust_code = format!(
139            r"
140    #[unsafe(no_mangle)]
141    #[inline(never)]
142    pub {motherfuckingrust} fn bp3d_os_module_{crate_name}_uninit() {{
143        bp3d_os::module::loader::ModuleLoader::uninstall();
144    }}
145"
146        );
147        self.rust_code += &rust_code;
148        let motherfuckingrust = format!("bp3d_os_module_{crate_name}_uninit");
149        self.add_export(motherfuckingrust)
150    }
151
152    pub fn add_open(mut self) -> Self {
153        let motherfuckingrust = "extern \"C\"";
154        let crate_name = &self.crate_name;
155        let rust_code = format!(
156            r"
157    #[unsafe(no_mangle)]
158    #[inline(never)]
159    pub {motherfuckingrust} fn bp3d_os_module_{crate_name}_open() {{
160        module_open();
161    }}
162"
163        );
164        self.rust_code += &rust_code;
165        let motherfuckingrust = format!("bp3d_os_module_{crate_name}_open");
166        self.add_export(motherfuckingrust)
167    }
168
169    pub fn add_close(mut self) -> Self {
170        let motherfuckingrust = "extern \"C\"";
171        let crate_name = &self.crate_name;
172        let rust_code = format!(
173            r"
174    #[unsafe(no_mangle)]
175    #[inline(never)]
176    pub {motherfuckingrust} fn bp3d_os_module_{crate_name}_close() {{
177        module_close();
178    }}
179"
180        );
181        self.rust_code += &rust_code;
182        let motherfuckingrust = format!("bp3d_os_module_{crate_name}_close");
183        self.add_export(motherfuckingrust)
184    }
185
186    pub fn add_bp3d_debug(mut self) -> Self {
187        let motherfuckingrust = "extern \"Rust\"";
188        let crate_name = &self.crate_name;
189        let rust_code = format!(
190            r"
191    #[unsafe(no_mangle)]
192    #[inline(never)]
193    pub {motherfuckingrust} fn bp3d_os_module_{crate_name}_init_bp3d_debug(engine: &'static dyn bp3d_debug::engine::Engine) {{
194        bp3d_debug::engine::set(engine);
195    }}
196"
197        );
198        self.rust_code += &rust_code;
199        let motherfuckingrust = format!("bp3d_os_module_{crate_name}_init_bp3d_debug");
200        self.add_export(motherfuckingrust)
201    }
202
203    pub fn build(mut self) {
204        self.virtual_lib += "\n    ]);";
205        self.rust_code += &self.virtual_lib;
206        std::fs::write(&self.out_path, self.rust_code).unwrap();
207        #[cfg(unix)]
208        {
209            let crate_name = self.crate_name;
210            #[cfg(target_vendor = "apple")]
211            println!("cargo::rustc-link-arg-cdylib=-Wl,-install_name,@rpath/lib{crate_name}.dylib");
212            #[cfg(all(unix, not(target_vendor = "apple")))]
213            println!("cargo::rustc-link-arg-cdylib=-Wl,-soname,lib{crate_name}.so");
214        }
215        println!(
216            "cargo:rustc-env=BP3D_OS_MODULE_MAIN={}",
217            self.out_path.display()
218        );
219    }
220}