Skip to main content

bp3d_os_build/
lib.rs

1// Copyright (c) 2026, 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                        let dep_name = k.replace("-", "_");
71                        for feature in v.req_features() {
72                            features.push(format!("{}/{}", dep_name, feature));
73                        }
74                        match dep_version {
75                            Some(v) => format!("{}={}", dep_name, v),
76                            None => format!("{}={}", dep_name, v.req()),
77                        }
78                    })
79                    .join(",")
80            })
81            .unwrap_or("".into());
82        let data = format!(
83            "\"\0BP3D_OS_MODULE|TYPE=RUST|NAME={}|VERSION={}|RUSTC={}|DEPS={}|FEATURES={}\0\"",
84            crate_name,
85            crate_version,
86            rustc_version,
87            deps_list,
88            features.join(",")
89        );
90        let rust_code = format!(
91            "
92    #[unsafe(no_mangle)]
93    #[allow(clippy::manual_c_str_literals)] // The string is enclosed in NULLs and apparently clippy
94    // does not like that...
95    static mut {mod_const_name}: *const std::ffi::c_char = {data}.as_ptr() as _;
96"
97        );
98        let virtual_lib = format!("
99    #[allow(static_mut_refs)]
100    pub static VIRTUAL_MODULE: bp3d_os::module::library::types::VirtualLibrary = bp3d_os::module::library::types::VirtualLibrary::new(\"{crate_name}\", &[
101        (\"{mod_const_name}\", unsafe {{ &{mod_const_name} as *const *const i8 as *const std::ffi::c_void }})");
102        let out_path =
103            PathBuf::from(std::env::var_os("OUT_DIR").unwrap()).join("bp3d_os_module.rs");
104        let this = Self {
105            rust_code,
106            out_path,
107            crate_name,
108            virtual_lib,
109        };
110        this.add_init().add_uninit()
111    }
112
113    pub fn add_export(mut self, func_name: impl AsRef<str>) -> Self {
114        let func_name = func_name.as_ref();
115        self.virtual_lib += &format!(",\n        (\"{func_name}\", {func_name} as _)");
116        self
117    }
118
119    fn add_init(mut self) -> Self {
120        let motherfuckingrust = "extern \"C\"";
121        let crate_name = &self.crate_name;
122        let rust_code = format!(
123            r"
124    #[unsafe(no_mangle)]
125    #[inline(never)]
126    pub {motherfuckingrust} fn bp3d_os_module_{crate_name}_init(loader: &'static std::sync::Mutex<bp3d_os::module::loader::ModuleLoader>) {{
127        bp3d_os::module::loader::ModuleLoader::install_from_existing(loader);
128    }}
129"
130        );
131        self.rust_code += &rust_code;
132        let motherfuckingrust = format!("bp3d_os_module_{crate_name}_init");
133        self.add_export(motherfuckingrust)
134    }
135
136    fn add_uninit(mut self) -> Self {
137        let motherfuckingrust = "extern \"C\"";
138        let crate_name = &self.crate_name;
139        let rust_code = format!(
140            r"
141    #[unsafe(no_mangle)]
142    #[inline(never)]
143    pub {motherfuckingrust} fn bp3d_os_module_{crate_name}_uninit() {{
144        bp3d_os::module::loader::ModuleLoader::uninstall();
145    }}
146"
147        );
148        self.rust_code += &rust_code;
149        let motherfuckingrust = format!("bp3d_os_module_{crate_name}_uninit");
150        self.add_export(motherfuckingrust)
151    }
152
153    pub fn add_open(mut self) -> Self {
154        let motherfuckingrust = "extern \"C\"";
155        let crate_name = &self.crate_name;
156        let rust_code = format!(
157            r"
158    #[unsafe(no_mangle)]
159    #[inline(never)]
160    pub {motherfuckingrust} fn bp3d_os_module_{crate_name}_open() {{
161        module_open();
162    }}
163"
164        );
165        self.rust_code += &rust_code;
166        let motherfuckingrust = format!("bp3d_os_module_{crate_name}_open");
167        self.add_export(motherfuckingrust)
168    }
169
170    pub fn add_close(mut self) -> Self {
171        let motherfuckingrust = "extern \"C\"";
172        let crate_name = &self.crate_name;
173        let rust_code = format!(
174            r"
175    #[unsafe(no_mangle)]
176    #[inline(never)]
177    pub {motherfuckingrust} fn bp3d_os_module_{crate_name}_close() {{
178        module_close();
179    }}
180"
181        );
182        self.rust_code += &rust_code;
183        let motherfuckingrust = format!("bp3d_os_module_{crate_name}_close");
184        self.add_export(motherfuckingrust)
185    }
186
187    pub fn add_bp3d_debug(mut self) -> Self {
188        let motherfuckingrust = "extern \"Rust\"";
189        let crate_name = &self.crate_name;
190        let rust_code = format!(
191            r"
192    #[unsafe(no_mangle)]
193    #[inline(never)]
194    pub {motherfuckingrust} fn bp3d_os_module_{crate_name}_init_bp3d_debug(engine: &'static dyn bp3d_debug::engine::Engine) {{
195        bp3d_debug::engine::set(engine);
196    }}
197"
198        );
199        self.rust_code += &rust_code;
200        let motherfuckingrust = format!("bp3d_os_module_{crate_name}_init_bp3d_debug");
201        self.add_export(motherfuckingrust)
202    }
203
204    pub fn build(mut self) {
205        self.virtual_lib += "\n    ]);";
206        self.rust_code += &self.virtual_lib;
207        std::fs::write(&self.out_path, self.rust_code).unwrap();
208        #[cfg(unix)]
209        {
210            let crate_name = self.crate_name;
211            #[cfg(target_vendor = "apple")]
212            println!("cargo::rustc-link-arg-cdylib=-Wl,-install_name,@rpath/lib{crate_name}.dylib");
213            #[cfg(all(unix, not(target_vendor = "apple")))]
214            println!("cargo::rustc-link-arg-cdylib=-Wl,-soname,lib{crate_name}.so");
215        }
216        println!(
217            "cargo:rustc-env=BP3D_OS_MODULE_MAIN={}",
218            self.out_path.display()
219        );
220    }
221}