@@ -104,6 +104,13 @@ struct BuildConfig {
104104 std::string cStandard;
105105};
106106
107+ // `[runtime]` — requirements needed when launching built binaries.
108+ struct RuntimeConfig {
109+ std::vector<std::filesystem::path> libraryDirs; // relative to package root
110+ std::vector<std::string> dlopenLibs; // runtime-loaded sonames
111+ std::vector<std::string> capabilities; // host/system capabilities
112+ };
113+
107114// `[target.<triple>]` — per-target overrides.
108115// Picked up when caller passes --target <triple> to build/run/test.
109116struct TargetEntry {
@@ -182,6 +189,7 @@ struct Manifest {
182189
183190 Toolchain toolchain; // optional; empty == fallback
184191 BuildConfig buildConfig;
192+ RuntimeConfig runtimeConfig;
185193
186194 // [target.<triple>] tables — empty if user didn't declare any.
187195 std::map<std::string, TargetEntry> targetOverrides;
@@ -779,6 +787,15 @@ std::expected<Manifest, ManifestError> parse_string(std::string_view content,
779787 }
780788 }
781789
790+ // [runtime] — launch-time requirements.
791+ if (auto v = doc->get_string_array (" runtime.library_dirs" )) {
792+ for (auto & s : *v) m.runtimeConfig .libraryDirs .emplace_back (s);
793+ }
794+ if (auto v = doc->get_string_array (" runtime.dlopen_libs" ))
795+ m.runtimeConfig .dlopenLibs = *v;
796+ if (auto v = doc->get_string_array (" runtime.capabilities" ))
797+ m.runtimeConfig .capabilities = *v;
798+
782799 // [lib] — library root convention (cargo-style).
783800 if (auto v = doc->get_string (" lib.path" )) {
784801 m.lib .path = *v;
@@ -1683,6 +1700,61 @@ synthesize_from_xpkg_lua(std::string_view luaContent,
16831700 auto v = cur.read_string ();
16841701 if (!v.empty ()) m.buildConfig .cStandard = v;
16851702 }
1703+ else if (key == " runtime" ) {
1704+ auto runtimeBody = cur.read_table_body ();
1705+ LuaCursor rc { runtimeBody };
1706+ rc.skip_ws_and_comments ();
1707+ while (!rc.eof ()) {
1708+ auto sub = rc.read_key ();
1709+ if (sub.empty ()) {
1710+ rc.skip_ws_and_comments ();
1711+ if (rc.eof ()) break ;
1712+ ++rc.pos ;
1713+ continue ;
1714+ }
1715+ rc.skip_ws_and_comments ();
1716+ if (!rc.consume (' =' )) {
1717+ return std::unexpected (ManifestError{
1718+ std::format (" malformed runtime segment near key '{}'" , sub),
1719+ m.sourcePath , 0 , 0 });
1720+ }
1721+ rc.skip_ws_and_comments ();
1722+ auto read_string_list = [&](std::vector<std::string>& out)
1723+ -> std::expected<void , ManifestError>
1724+ {
1725+ if (!rc.consume (' {' )) {
1726+ return std::unexpected (ManifestError{
1727+ std::format (" expected '{{' after `runtime.{} =`" , sub),
1728+ m.sourcePath , 0 , 0 });
1729+ }
1730+ rc.skip_ws_and_comments ();
1731+ while (!rc.eof () && rc.peek () != ' }' ) {
1732+ auto s = rc.read_string ();
1733+ if (!s.empty ()) out.push_back (std::move (s));
1734+ rc.skip_ws_and_comments ();
1735+ }
1736+ rc.consume (' }' );
1737+ return {};
1738+ };
1739+ if (sub == " library_dirs" ) {
1740+ std::vector<std::string> dirs;
1741+ if (auto r = read_string_list (dirs); !r) return std::unexpected (r.error ());
1742+ for (auto & d : dirs) m.runtimeConfig .libraryDirs .emplace_back (std::move (d));
1743+ } else if (sub == " dlopen_libs" ) {
1744+ if (auto r = read_string_list (m.runtimeConfig .dlopenLibs ); !r)
1745+ return std::unexpected (r.error ());
1746+ } else if (sub == " capabilities" ) {
1747+ if (auto r = read_string_list (m.runtimeConfig .capabilities ); !r)
1748+ return std::unexpected (r.error ());
1749+ } else {
1750+ rc.skip_ws_and_comments ();
1751+ if (rc.peek () == ' "' || rc.peek () == ' \' ' ) (void )rc.read_string ();
1752+ else if (rc.peek () == ' {' ) rc.skip_table ();
1753+ else (void )rc.read_bareword ();
1754+ }
1755+ rc.skip_ws_and_comments ();
1756+ }
1757+ }
16861758 else {
16871759 // Unknown key — skip the value (string / bareword / table).
16881760 cur.skip_ws_and_comments ();
0 commit comments