1+ use std:: env;
2+ use std:: path:: PathBuf ;
3+
4+ fn main ( ) {
5+ let out_dir = PathBuf :: from ( env:: var ( "OUT_DIR" ) . unwrap ( ) ) ;
6+ let target_dir = PathBuf :: from ( env:: var ( "CARGO_MANIFEST_DIR" ) . unwrap ( ) )
7+ . join ( "target" )
8+ . join ( env:: var ( "PROFILE" ) . unwrap_or ( "release" . into ( ) ) ) ;
9+
10+ // Compile the loader .so
11+ // This tiny C library:
12+ // 1. Has ZERO Python symbol dependencies (loads cleanly via Ring's loadlib)
13+ // 2. dlopen's libpython with RTLD_GLOBAL (makes Python symbols available)
14+ // 3. dlopen's libring_python_impl.so (the real Rust/PyO3 library)
15+ // 4. Forwards ringlib_init to the real library
16+ let loader_c = out_dir. join ( "loader.c" ) ;
17+ std:: fs:: write ( & loader_c, LOADER_SOURCE ) . unwrap ( ) ;
18+
19+ // Platform-specific loader compilation
20+ let target = env:: var ( "TARGET" ) . unwrap_or_default ( ) ;
21+ let is_windows = target. contains ( "windows" ) ;
22+ let is_macos = target. contains ( "apple" ) || target. contains ( "darwin" ) ;
23+
24+ if is_windows {
25+ // On Windows, the loader is compiled by the C compiler from the host
26+ // For cross-compilation from Linux CI, we skip loader compilation
27+ // and rely on the Rust cdylib being named ring_python_impl.dll
28+ // The loader .dll is built separately or by MSVC on Windows CI
29+ let loader_dll = target_dir. join ( "ring_python.dll" ) ;
30+ let status = std:: process:: Command :: new ( "cl.exe" )
31+ . args ( [
32+ "/LD" , "/O2" , "/Fe:" , loader_dll. to_str ( ) . unwrap ( ) ,
33+ loader_c. to_str ( ) . unwrap ( ) ,
34+ ] )
35+ . status ( ) ;
36+ // If cl.exe is not available (Linux CI), try gcc for Windows target
37+ if status. is_err ( ) || !status. unwrap ( ) . success ( ) {
38+ let status = std:: process:: Command :: new ( "gcc" )
39+ . args ( [
40+ "-shared" , "-O2" ,
41+ "-o" , loader_dll. to_str ( ) . unwrap ( ) ,
42+ loader_c. to_str ( ) . unwrap ( ) ,
43+ ] )
44+ . status ( )
45+ . expect ( "Failed to compile loader" ) ;
46+ assert ! ( status. success( ) , "Failed to compile loader .dll" ) ;
47+ }
48+ } else if is_macos {
49+ let loader_dylib = target_dir. join ( "libring_python.dylib" ) ;
50+ let status = std:: process:: Command :: new ( "gcc" )
51+ . args ( [
52+ "-shared" , "-fPIC" , "-O2" ,
53+ "-install_name" , "@rpath/libring_python.dylib" ,
54+ "-o" , loader_dylib. to_str ( ) . unwrap ( ) ,
55+ loader_c. to_str ( ) . unwrap ( ) ,
56+ "-ldl" ,
57+ ] )
58+ . status ( )
59+ . expect ( "Failed to compile loader" ) ;
60+ assert ! ( status. success( ) , "Failed to compile loader .dylib" ) ;
61+ } else {
62+ // Linux, FreeBSD, etc.
63+ let loader_so = target_dir. join ( "libring_python.so" ) ;
64+ let status = std:: process:: Command :: new ( "gcc" )
65+ . args ( [
66+ "-shared" , "-fPIC" , "-O2" ,
67+ "-Wl,-soname,libring_python.so" ,
68+ "-o" , loader_so. to_str ( ) . unwrap ( ) ,
69+ loader_c. to_str ( ) . unwrap ( ) ,
70+ "-ldl" ,
71+ ] )
72+ . status ( )
73+ . expect ( "Failed to compile loader" ) ;
74+ assert ! ( status. success( ) , "Failed to compile loader .so" ) ;
75+ }
76+
77+ println ! ( "cargo:rerun-if-changed=build.rs" ) ;
78+ }
79+
80+ const LOADER_SOURCE : & str = r#"
81+ /*
82+ * ring_python loader — loads libpython globally, then loads the real impl.
83+ * This file has ZERO Python dependencies so dlopen always succeeds.
84+ */
85+ #ifdef _WIN32
86+
87+ #include <windows.h>
88+ #include <stdio.h>
89+
90+ typedef void (*ringlib_init_fn)(void*);
91+ static HMODULE real_lib = NULL;
92+
93+ static HMODULE try_load(const char* name) {
94+ return LoadLibraryA(name);
95+ }
96+
97+ static void load_python(void) {
98+ /* Try python3.dll (stable ABI on Windows) */
99+ if (try_load("python3.dll")) return;
100+
101+ /* Try version-specific */
102+ char buf[64];
103+ for (int minor = 30; minor >= 7; minor--) {
104+ snprintf(buf, sizeof(buf), "python3%d.dll", minor);
105+ if (try_load(buf)) return;
106+ }
107+ }
108+
109+ void ringlib_init(void* pRingState) {
110+ load_python();
111+
112+ /* Find impl next to this loader */
113+ char path[MAX_PATH];
114+ HMODULE self;
115+ GetModuleHandleExA(
116+ GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
117+ (LPCSTR)ringlib_init, &self);
118+ GetModuleFileNameA(self, path, MAX_PATH);
119+
120+ /* Replace filename with impl name */
121+ char* slash = strrchr(path, '\\');
122+ if (!slash) slash = strrchr(path, '/');
123+ if (slash) slash[1] = '\0'; else path[0] = '\0';
124+ strcat(path, "ring_python_impl.dll");
125+
126+ real_lib = LoadLibraryA(path);
127+ if (!real_lib) {
128+ fprintf(stderr, "ring_python: cannot load %s (error %lu)\n", path, GetLastError());
129+ return;
130+ }
131+
132+ ringlib_init_fn real_init = (ringlib_init_fn)GetProcAddress(real_lib, "ringlib_init");
133+ if (!real_init) {
134+ fprintf(stderr, "ring_python: ringlib_init not found in impl\n");
135+ return;
136+ }
137+ real_init(pRingState);
138+ }
139+
140+ #else /* Unix (Linux, macOS, *BSD) */
141+
142+ #define _GNU_SOURCE
143+ #include <dlfcn.h>
144+ #include <stdio.h>
145+ #include <string.h>
146+ #include <stdlib.h>
147+
148+ typedef void (*ringlib_init_fn)(void*);
149+ static void* real_lib = NULL;
150+
151+ #ifdef __APPLE__
152+ #define RTLD_GLOBAL_FLAG 0x8
153+ #define LIBEXT "dylib"
154+ #define IMPLNAME "libring_python_impl.dylib"
155+ #else
156+ #define RTLD_GLOBAL_FLAG 0x100
157+ #define LIBEXT "so"
158+ #define IMPLNAME "libring_python_impl.so"
159+ #endif
160+
161+ static int try_dlopen(const char* name) {
162+ return dlopen(name, RTLD_LAZY | RTLD_GLOBAL_FLAG) != NULL;
163+ }
164+
165+ static void load_python_global(void) {
166+ /* Try generic name first */
167+ char buf[64];
168+ snprintf(buf, sizeof(buf), "libpython3.%s", LIBEXT);
169+ if (try_dlopen(buf)) return;
170+
171+ /* Try version-specific (newest first) */
172+ for (int minor = 30; minor >= 7; minor--) {
173+ snprintf(buf, sizeof(buf), "libpython3.%d.%s", minor, LIBEXT);
174+ if (try_dlopen(buf)) return;
175+ #ifndef __APPLE__
176+ snprintf(buf, sizeof(buf), "libpython3.%d.so.1.0", minor);
177+ if (try_dlopen(buf)) return;
178+ #endif
179+ }
180+ }
181+
182+ static void build_impl_path(const char* base, char* out, size_t sz) {
183+ const char* slash = strrchr(base, '/');
184+ if (slash) {
185+ int dirlen = (int)(slash - base);
186+ snprintf(out, sz, "%.*s/" IMPLNAME, dirlen, base);
187+ } else {
188+ snprintf(out, sz, IMPLNAME);
189+ }
190+ }
191+
192+ void ringlib_init(void* pRingState) {
193+ /* Step 1: Load libpython into global symbol table */
194+ load_python_global();
195+
196+ /* Step 2: Find and load the real impl library (next to this loader) */
197+ Dl_info info;
198+ char path[4096];
199+ real_lib = NULL;
200+
201+ if (dladdr((void*)ringlib_init, &info) && info.dli_fname) {
202+ /* Try next to the path the linker used (may be a symlink) */
203+ build_impl_path(info.dli_fname, path, sizeof(path));
204+ real_lib = dlopen(path, RTLD_LAZY | RTLD_GLOBAL_FLAG);
205+
206+ /* If not found, resolve symlinks and try next to the real file */
207+ if (!real_lib) {
208+ char resolved[4096];
209+ if (realpath(info.dli_fname, resolved)) {
210+ build_impl_path(resolved, path, sizeof(path));
211+ real_lib = dlopen(path, RTLD_LAZY | RTLD_GLOBAL_FLAG);
212+ }
213+ }
214+ }
215+
216+ /* Last resort: try bare name (relies on LD_LIBRARY_PATH / system paths) */
217+ if (!real_lib) {
218+ snprintf(path, sizeof(path), IMPLNAME);
219+ real_lib = dlopen(path, RTLD_LAZY | RTLD_GLOBAL_FLAG);
220+ }
221+
222+ if (!real_lib) {
223+ fprintf(stderr, "ring_python: cannot load " IMPLNAME ": %s\n", dlerror());
224+ return;
225+ }
226+
227+ /* Step 3: Forward to real ringlib_init */
228+ ringlib_init_fn real_init = (ringlib_init_fn)dlsym(real_lib, "ringlib_init");
229+ if (!real_init) {
230+ fprintf(stderr, "ring_python: ringlib_init not found in %s\n", path);
231+ return;
232+ }
233+ real_init(pRingState);
234+ }
235+
236+ #endif
237+ "# ;
0 commit comments