⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/pkg/sdk/
pkg_config.rs

1//! Package Configuration Support
2//!
3//! Provides pkg-config compatible metadata generation for installed packages,
4//! allowing build systems to discover compiler and linker flags required to
5//! use a given library.
6//!
7//! NOTE: Many types in this module are forward declarations for user-space
8//! APIs. They will be exercised when user-space process execution is
9//! functional. See TODO(user-space) markers for specific activation points.
10
11// User-space SDK forward declarations -- see module doc TODO(user-space)
12
13#[cfg(feature = "alloc")]
14use alloc::{format, string::String, vec::Vec};
15
16/// A single include search path.
17#[cfg(feature = "alloc")]
18#[derive(Debug, Clone)]
19pub struct IncludePath {
20    /// Filesystem path to the include directory.
21    pub path: String,
22    /// Whether this is a system include path (uses `-isystem` instead of `-I`).
23    pub system: bool,
24}
25
26/// A library dependency.
27#[cfg(feature = "alloc")]
28#[derive(Debug, Clone)]
29pub struct LibraryPath {
30    /// Directory containing the library.
31    pub path: String,
32    /// Library name (without `lib` prefix or file extension).
33    pub name: String,
34}
35
36/// Package configuration metadata, compatible with the `pkg-config` format.
37///
38/// Stores the compiler and linker flags needed to build against a particular
39/// installed library package.
40#[cfg(feature = "alloc")]
41#[derive(Debug, Clone)]
42pub struct PkgConfig {
43    /// Package name.
44    pub name: String,
45    /// Package version string.
46    pub version: String,
47    /// Human-readable description.
48    pub description: String,
49    /// Include search directories.
50    pub include_dirs: Vec<String>,
51    /// Library search directories.
52    pub lib_dirs: Vec<String>,
53    /// Libraries to link against (short names, e.g. "veridian").
54    pub libs: Vec<String>,
55    /// Additional compiler flags.
56    pub cflags: Vec<String>,
57}
58
59#[cfg(feature = "alloc")]
60impl PkgConfig {
61    /// Generate a pkg-config compatible `.pc` file contents.
62    ///
63    /// The output follows the standard pkg-config format with `Name`,
64    /// `Version`, `Description`, `Cflags`, and `Libs` fields.
65    pub fn generate_pkg_config(&self) -> String {
66        let sysroot = super::get_sysroot();
67
68        let mut output = String::new();
69
70        // Variable definitions
71        output.push_str(&format!("prefix={}\n", sysroot));
72        output.push_str("exec_prefix=${prefix}\n");
73        output.push_str("libdir=${exec_prefix}/lib\n");
74        output.push_str("includedir=${prefix}/include\n");
75        output.push('\n');
76
77        // Metadata fields
78        output.push_str(&format!("Name: {}\n", self.name));
79        output.push_str(&format!("Version: {}\n", self.version));
80        output.push_str(&format!("Description: {}\n", self.description));
81
82        // Cflags line
83        let mut cflags_parts: Vec<String> = Vec::new();
84        for dir in &self.include_dirs {
85            cflags_parts.push(format!("-I{}", dir));
86        }
87        for flag in &self.cflags {
88            cflags_parts.push(flag.clone());
89        }
90        if !cflags_parts.is_empty() {
91            output.push_str("Cflags:");
92            for part in &cflags_parts {
93                output.push(' ');
94                output.push_str(part);
95            }
96            output.push('\n');
97        }
98
99        // Libs line
100        let mut libs_parts: Vec<String> = Vec::new();
101        for dir in &self.lib_dirs {
102            libs_parts.push(format!("-L{}", dir));
103        }
104        for lib in &self.libs {
105            libs_parts.push(format!("-l{}", lib));
106        }
107        if !libs_parts.is_empty() {
108            output.push_str("Libs:");
109            for part in &libs_parts {
110                output.push(' ');
111                output.push_str(part);
112            }
113            output.push('\n');
114        }
115
116        output
117    }
118
119    /// Look up the pkg-config metadata for an installed package by name.
120    ///
121    /// In a full implementation this would query the VFS for `.pc` files under
122    /// the sysroot. Currently returns built-in configurations for core
123    /// VeridianOS libraries.
124    pub fn find_package(name: &str) -> Option<PkgConfig> {
125        let sysroot = super::get_sysroot();
126        let triple = super::get_target_triple();
127
128        match name {
129            "veridian" => Some(PkgConfig {
130                name: String::from("veridian"),
131                version: String::from("0.4.0"),
132                description: String::from("VeridianOS core system library"),
133                include_dirs: alloc::vec![
134                    format!("{}/include", sysroot),
135                    format!("{}/include/veridian", sysroot),
136                ],
137                lib_dirs: alloc::vec![format!("{}/lib/{}", sysroot, triple)],
138                libs: alloc::vec![String::from("veridian")],
139                cflags: alloc::vec![
140                    String::from("-ffreestanding"),
141                    format!("--target={}", triple),
142                ],
143            }),
144            "veridian-ipc" => Some(PkgConfig {
145                name: String::from("veridian-ipc"),
146                version: String::from("0.4.0"),
147                description: String::from("VeridianOS IPC library"),
148                include_dirs: alloc::vec![format!("{}/include/veridian/ipc", sysroot)],
149                lib_dirs: alloc::vec![format!("{}/lib/{}", sysroot, triple)],
150                libs: alloc::vec![String::from("veridian-ipc"), String::from("veridian"),],
151                cflags: alloc::vec![format!("--target={}", triple)],
152            }),
153            "veridian-cap" => Some(PkgConfig {
154                name: String::from("veridian-cap"),
155                version: String::from("0.4.0"),
156                description: String::from("VeridianOS capability library"),
157                include_dirs: alloc::vec![format!("{}/include/veridian/cap", sysroot)],
158                lib_dirs: alloc::vec![format!("{}/lib/{}", sysroot, triple)],
159                libs: alloc::vec![String::from("veridian-cap"), String::from("veridian"),],
160                cflags: alloc::vec![format!("--target={}", triple)],
161            }),
162            _ => {
163                // Try to read a .pc file from the VFS pkgconfig directory.
164                // Full .pc file parsing (variable substitution, Requires lines)
165                // is deferred to Phase 6.
166                let pc_path = format!("{}/lib/pkgconfig/{}.pc", sysroot, name);
167                if let Some(vfs_lock) = crate::fs::try_get_vfs() {
168                    let vfs = vfs_lock.read();
169                    if let Ok(node) = vfs.resolve_path(&pc_path) {
170                        let mut buf = alloc::vec![0u8; 4096];
171                        if let Ok(n) = node.read(0, &mut buf) {
172                            if let Ok(content) = core::str::from_utf8(&buf[..n]) {
173                                return Self::parse_pc_basic(content, name, sysroot, triple);
174                            }
175                        }
176                    }
177                }
178                None
179            }
180        }
181    }
182
183    /// Create `IncludePath` entries from the stored include directories.
184    pub fn include_paths(&self) -> Vec<IncludePath> {
185        self.include_dirs
186            .iter()
187            .map(|p| IncludePath {
188                path: p.clone(),
189                system: false,
190            })
191            .collect()
192    }
193
194    /// Create `LibraryPath` entries from the stored library directories and
195    /// names.
196    pub fn library_paths(&self) -> Vec<LibraryPath> {
197        let mut result = Vec::new();
198        for dir in &self.lib_dirs {
199            for lib in &self.libs {
200                result.push(LibraryPath {
201                    path: dir.clone(),
202                    name: lib.clone(),
203                });
204            }
205        }
206        result
207    }
208
209    /// Parse a basic subset of a .pc file (Name, Version, Description,
210    /// Cflags, Libs lines).  Variable substitution and Requires are not
211    /// supported.
212    fn parse_pc_basic(content: &str, name: &str, sysroot: &str, triple: &str) -> Option<PkgConfig> {
213        let mut version = String::from("0.0.0");
214        let mut description = String::new();
215        let mut cflags = Vec::new();
216        let mut libs = Vec::new();
217        let mut lib_dirs = Vec::new();
218        let mut include_dirs = Vec::new();
219
220        for line in content.lines() {
221            let line = line.trim();
222            if let Some(val) = line.strip_prefix("Version:") {
223                version = String::from(val.trim());
224            } else if let Some(val) = line.strip_prefix("Description:") {
225                description = String::from(val.trim());
226            } else if let Some(val) = line.strip_prefix("Cflags:") {
227                for token in val.split_whitespace() {
228                    if let Some(inc) = token.strip_prefix("-I") {
229                        include_dirs.push(String::from(inc));
230                    }
231                    cflags.push(String::from(token));
232                }
233            } else if let Some(val) = line.strip_prefix("Libs:") {
234                for token in val.split_whitespace() {
235                    if let Some(dir) = token.strip_prefix("-L") {
236                        lib_dirs.push(String::from(dir));
237                    } else if let Some(lib) = token.strip_prefix("-l") {
238                        libs.push(String::from(lib));
239                    }
240                }
241            }
242        }
243
244        // Fall back to sysroot defaults if the .pc didn't specify paths
245        if include_dirs.is_empty() {
246            include_dirs.push(format!("{}/include", sysroot));
247        }
248        if lib_dirs.is_empty() {
249            lib_dirs.push(format!("{}/lib/{}", sysroot, triple));
250        }
251
252        Some(PkgConfig {
253            name: String::from(name),
254            version,
255            description,
256            include_dirs,
257            lib_dirs,
258            libs,
259            cflags,
260        })
261    }
262}
263
264#[cfg(feature = "alloc")]
265impl core::fmt::Display for PkgConfig {
266    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
267        write!(f, "{}", self.generate_pkg_config())
268    }
269}
270
271#[cfg(feature = "alloc")]
272impl core::fmt::Display for IncludePath {
273    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
274        if self.system {
275            write!(f, "-isystem {}", self.path)
276        } else {
277            write!(f, "-I{}", self.path)
278        }
279    }
280}
281
282#[cfg(feature = "alloc")]
283impl core::fmt::Display for LibraryPath {
284    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
285        write!(f, "-L{} -l{}", self.path, self.name)
286    }
287}