@@ -247,6 +247,26 @@ struct options {
247247 // does, and the application's own `resources` win over every library.
248248 std::vector<library> libraries;
249249
250+ // LIBRARIES THE GRAPH CONTRIBUTES (0.12.0). With mcpp 2026.9.16.1 the root
251+ // project's build program receives the resolved graph, and every package in
252+ // it other than the application that states `[package.metadata.dist-apk]`
253+ // contributes a library of the shape above (`package`, `resources`,
254+ // `manifest`, `assets`, `java_sources`, `kotlin_sources`) and archives
255+ // (`jars`, `aars`), its paths relative to that package's directory. A
256+ // framework's library therefore reaches every application that depends on
257+ // it without being listed in the application's build program.
258+ //
259+ // RANKED BELOW THE APPLICATION'S OWN ENTRIES, and among themselves a package
260+ // above the packages it depends on: the graph lists dependencies first, so
261+ // contributions are taken in reverse. An application's `libraries` still win
262+ // a resource any contribution defines, and a library wins over the framework
263+ // beneath it, which is the order a Gradle build gives the same modules.
264+ //
265+ // `false` reads no contribution, for an application that lists every
266+ // library itself. Under an older engine, or in a dependency's own build
267+ // program, there is no graph, and nothing is contributed either way.
268+ bool graph_libraries = true ;
269+
250270 // LOCAL ARCHIVES (0.11.0). A JAR joins the classpath and the dex. An AAR
251271 // contributes its classes, its resources (under the package its manifest
252272 // names), its manifest, its native libraries and its assets. Archives
@@ -1032,120 +1052,12 @@ inline bool merge_manifests(std::string& appText, const std::vector<manifest_sou
10321052 return true ;
10331053}
10341054
1035- // ─── JSON, as far as coursier's report needs it ───────────────────────────
1036-
1037- struct json_value {
1038- enum class kind { null, boolean, number, string, array, object };
1039- kind type = kind::null;
1040- std::string text; // string, number or boolean spelling
1041- std::vector<json_value> items;
1042- std::vector<std::pair<std::string, json_value>> members;
1043-
1044- const json_value* get (std::string_view key) const {
1045- for (auto const & m : members) if (m.first == key) return &m.second ;
1046- return nullptr ;
1047- }
1048- };
1049-
1050- struct json_reader {
1051- std::string_view s;
1052- std::size_t i = 0 ;
1053-
1054- void skip_space () {
1055- while (i < s.size () && (s[i] == ' ' || s[i] == ' \t ' || s[i] == ' \n ' || s[i] == ' \r ' )) ++i;
1056- }
1057- bool string (std::string& out) {
1058- if (i >= s.size () || s[i] != ' "' ) return false ;
1059- ++i;
1060- while (i < s.size () && s[i] != ' "' ) {
1061- if (s[i] != ' \\ ' ) { out += s[i++]; continue ; }
1062- if (++i >= s.size ()) return false ;
1063- const char e = s[i++];
1064- switch (e) {
1065- case ' n' : out += ' \n ' ; break ;
1066- case ' t' : out += ' \t ' ; break ;
1067- case ' r' : out += ' \r ' ; break ;
1068- case ' b' : out += ' \b ' ; break ;
1069- case ' f' : out += ' \f ' ; break ;
1070- case ' u' : {
1071- if (i + 4 > s.size ()) return false ;
1072- unsigned cp = 0 ;
1073- for (int k = 0 ; k < 4 ; ++k) {
1074- const char h = s[i++];
1075- cp <<= 4 ;
1076- if (h >= ' 0' && h <= ' 9' ) cp |= static_cast <unsigned >(h - ' 0' );
1077- else if (h >= ' a' && h <= ' f' ) cp |= static_cast <unsigned >(h - ' a' + 10 );
1078- else if (h >= ' A' && h <= ' F' ) cp |= static_cast <unsigned >(h - ' A' + 10 );
1079- else return false ;
1080- }
1081- if (cp < 0x80 ) out += static_cast <char >(cp);
1082- else if (cp < 0x800 ) { out += static_cast <char >(0xc0 | (cp >> 6 )); out += static_cast <char >(0x80 | (cp & 0x3f )); }
1083- else { out += static_cast <char >(0xe0 | (cp >> 12 )); out += static_cast <char >(0x80 | ((cp >> 6 ) & 0x3f )); out += static_cast <char >(0x80 | (cp & 0x3f )); }
1084- break ;
1085- }
1086- default : out += e;
1087- }
1088- }
1089- if (i >= s.size ()) return false ;
1090- ++i;
1091- return true ;
1092- }
1093- bool value (json_value& out) {
1094- skip_space ();
1095- if (i >= s.size ()) return false ;
1096- const char c = s[i];
1097- if (c == ' "' ) { out.type = json_value::kind::string; return string (out.text ); }
1098- if (c == ' {' ) {
1099- out.type = json_value::kind::object;
1100- ++i;
1101- skip_space ();
1102- if (i < s.size () && s[i] == ' }' ) { ++i; return true ; }
1103- for (;;) {
1104- skip_space ();
1105- std::string key;
1106- if (!string (key)) return false ;
1107- skip_space ();
1108- if (i >= s.size () || s[i] != ' :' ) return false ;
1109- ++i;
1110- json_value v;
1111- if (!value (v)) return false ;
1112- out.members .emplace_back (std::move (key), std::move (v));
1113- skip_space ();
1114- if (i < s.size () && s[i] == ' ,' ) { ++i; continue ; }
1115- if (i < s.size () && s[i] == ' }' ) { ++i; return true ; }
1116- return false ;
1117- }
1118- }
1119- if (c == ' [' ) {
1120- out.type = json_value::kind::array;
1121- ++i;
1122- skip_space ();
1123- if (i < s.size () && s[i] == ' ]' ) { ++i; return true ; }
1124- for (;;) {
1125- json_value v;
1126- if (!value (v)) return false ;
1127- out.items .push_back (std::move (v));
1128- skip_space ();
1129- if (i < s.size () && s[i] == ' ,' ) { ++i; continue ; }
1130- if (i < s.size () && s[i] == ' ]' ) { ++i; return true ; }
1131- return false ;
1132- }
1133- }
1134- const std::size_t b = i;
1135- while (i < s.size () && s[i] != ' ,' && s[i] != ' }' && s[i] != ' ]' &&
1136- s[i] != ' ' && s[i] != ' \n ' && s[i] != ' \r ' && s[i] != ' \t ' ) ++i;
1137- out.text = std::string (s.substr (b, i - b));
1138- if (out.text == " null" ) out.type = json_value::kind::null;
1139- else if (out.text == " true" || out.text == " false" ) out.type = json_value::kind::boolean;
1140- else out.type = json_value::kind::number;
1141- return !out.text .empty ();
1142- }
1143- };
1144-
1145- inline bool parse_json (std::string_view text, json_value& out) {
1146- json_reader r{text};
1147- return r.value (out);
1148- }
1055+ // ─── JSON ─────────────────────────────────────────────────────────────────
1056+ //
1057+ // coursier's report is read with the collection's shared reader, the one
1058+ // `mcpp::plugins::graph` reads the engine's graph document with.
1059+ using json_value = mcpp::plugins::json::value;
1060+ using mcpp::plugins::json::parse_json;
11491061
11501062// ─── The Maven lock ───────────────────────────────────────────────────────
11511063
@@ -1488,6 +1400,88 @@ inline plan plan_for(options opt = {}) {
14881400 }
14891401 }
14901402
1403+ // ── the libraries the graph contributes (0.12.0) ─────────────────────
1404+ //
1405+ // Appended after the application's own `libraries`, `jars` and `aars`,
1406+ // which keeps them highest; the graph's packages are taken requesters first
1407+ // (the reverse of the document's order). Their paths are made absolute
1408+ // against each package's directory here, so the manifest-relative
1409+ // resolution below leaves them as they are. `labels` names each library in
1410+ // a diagnostic: empty for the application's own.
1411+ std::vector<std::string> labels (opt.libraries .size ());
1412+ if (opt.graph_libraries ) {
1413+ auto graph = mcpp::plugins::graph::read ();
1414+ if (!graph) {
1415+ return refuse (p, " unreadable graph document" , std::format (
1416+ " mcpp.dist.apk: {}; the libraries its packages contribute cannot be collected. "
1417+ " Set options::graph_libraries = false to list every library in options::libraries." ,
1418+ graph.error ()));
1419+ }
1420+ for (std::size_t k = graph->packages .size (); k-- > 0 ;) {
1421+ const auto & pkg = graph->packages [k];
1422+ if (pkg.root ) continue ;
1423+ const json_value* t = mcpp::plugins::graph::table_of (pkg, " dist-apk" );
1424+ if (!t) continue ;
1425+ const std::string who = std::format (" the [package.metadata.dist-apk] of {}" ,
1426+ mcpp::plugins::graph::label_of (pkg));
1427+ library l;
1428+ std::vector<std::string> jarList, aarList;
1429+ for (auto const & [key, v] : t->members ) {
1430+ const bool isString = v.type == json_value::kind::string;
1431+ const bool isList = v.type == json_value::kind::array
1432+ && std::ranges::all_of (v.items , [](const json_value& e) {
1433+ return e.type == json_value::kind::string; });
1434+ const auto wants = [&](bool ok, const char * shape) -> bool {
1435+ if (!ok) {
1436+ refuse (p, " malformed graph contribution" , std::format (
1437+ " mcpp.dist.apk: {} states `{}` as something other than {}." ,
1438+ who, key, shape));
1439+ }
1440+ return ok;
1441+ };
1442+ const auto strings = [&]() {
1443+ std::vector<std::string> out;
1444+ for (auto const & e : v.items ) out.push_back (mcpp::plugins::graph::resolve (pkg, e.text ));
1445+ return out;
1446+ };
1447+ if (key == " package" ) {
1448+ if (!wants (isString, " a string" )) return p;
1449+ l.package = v.text ;
1450+ } else if (key == " resources" || key == " manifest" || key == " assets" ) {
1451+ if (!wants (isString, " a path" )) return p;
1452+ const std::string path = mcpp::plugins::graph::resolve (pkg, v.text );
1453+ (key == " resources" ? l.resources : key == " manifest" ? l.manifest : l.assets ) = path;
1454+ } else if (key == " java_sources" || key == " kotlin_sources"
1455+ || key == " jars" || key == " aars" ) {
1456+ if (!wants (isList, " a list of paths" )) return p;
1457+ auto list = strings ();
1458+ if (key == " java_sources" ) l.java_sources = std::move (list);
1459+ else if (key == " kotlin_sources" ) l.kotlin_sources = std::move (list);
1460+ else if (key == " jars" ) jarList = std::move (list);
1461+ else aarList = std::move (list);
1462+ } else {
1463+ // Warned, not refused: a package published for a newer
1464+ // collection may state a key this one does not read, and
1465+ // refusing it would break every application built with this
1466+ // version.
1467+ mcpp::warning (std::format (
1468+ " mcpp.dist.apk: {} states `{}`, which this version of the member does "
1469+ " not read; it is ignored." , who, key).c_str ());
1470+ }
1471+ }
1472+ const bool isLibrary = !l.package .empty () || !l.resources .empty () || !l.manifest .empty ()
1473+ || !l.assets .empty () || !l.java_sources .empty () || !l.kotlin_sources .empty ();
1474+ if (isLibrary) {
1475+ opt.libraries .push_back (std::move (l));
1476+ labels.push_back (std::format (" the library {} ({})" ,
1477+ opt.libraries .back ().package .empty () ? mcpp::plugins::graph::label_of (pkg)
1478+ : opt.libraries .back ().package , who));
1479+ }
1480+ for (auto & j : jarList) opt.jars .push_back (std::move (j));
1481+ for (auto & a : aarList) opt.aars .push_back (std::move (a));
1482+ }
1483+ }
1484+
14911485 // Every path option, resolved against the manifest once (`resolve_path`).
14921486 opt.resources = resolve_path (opt.resources );
14931487 resolve_paths (opt.java_sources );
@@ -1632,7 +1626,9 @@ inline plan plan_for(options opt = {}) {
16321626 for (std::size_t i = 0 ; i < opt.libraries .size (); ++i) {
16331627 const auto & l = opt.libraries [i];
16341628 library_input in;
1635- in.label = l.package .empty () ? std::format (" options::libraries[{}]" , i)
1629+ in.label = i < labels.size () && !labels[i].empty ()
1630+ ? labels[i]
1631+ : l.package .empty () ? std::format (" options::libraries[{}]" , i)
16361632 : std::format (" the library {}" , l.package );
16371633 in.package = l.package ;
16381634 if (!l.resources .empty ()) {
@@ -1642,9 +1638,14 @@ inline plan plan_for(options opt = {}) {
16421638 in.label , l.resources ));
16431639 }
16441640 if (l.package .empty ()) {
1645- return refuse (p, " library resources without a package" , std::format (
1646- " mcpp.dist.apk: options::libraries[{}] has resources and no package; its "
1647- " R class needs one. Set options::libraries[{}].package." , i, i));
1641+ return refuse (p, " library resources without a package" ,
1642+ i < labels.size () && !labels[i].empty ()
1643+ ? std::format (
1644+ " mcpp.dist.apk: {} has resources and no package; its R class needs "
1645+ " one. Set `package` in that [package.metadata.dist-apk] table." , labels[i])
1646+ : std::format (
1647+ " mcpp.dist.apk: options::libraries[{}] has resources and no package; its "
1648+ " R class needs one. Set options::libraries[{}].package." , i, i));
16481649 }
16491650 in.resources = l.resources ;
16501651 }
0 commit comments