@@ -315,6 +315,13 @@ std::expected<GlobalConfig, ConfigError> load_or_init(
315315// Pretty-print resolved config for `mcpp env` command.
316316void print_env (const GlobalConfig& cfg);
317317
318+ // `[xlings.overrides]` of a parsed config.toml, keyed by package identity
319+ // (`<ns>:<name>`, the namespace defaulted to `xim`): where a declared xlings
320+ // payload comes from on this machine (mcpp#755). A pure function of the
321+ // document, so a test reads it without a home to bootstrap.
322+ std::expected<std::map<std::string, GlobalConfig::PayloadOverride>, std::string>
323+ parse_payload_overrides (const mcpp::libs::toml::Document& doc);
324+
318325// Normalize legacy index naming in a loaded config (exported for tests):
319326// org migration mcpp-community/mcpp-index -> mcpplibs/mcpp-index, index name
320327// mcpp-index -> mcpplibs, then name+url dedup. Order matters: URL first, so
@@ -652,6 +659,43 @@ void canonicalize_legacy_index_names(GlobalConfig& cfg) {
652659 cfg.indexRepos = std::move (normalized);
653660}
654661
662+ std::expected<std::map<std::string, GlobalConfig::PayloadOverride>, std::string>
663+ parse_payload_overrides (const mcpp::libs::toml::Document& doc) {
664+ std::map<std::string, GlobalConfig::PayloadOverride> out;
665+ auto * ot = doc.get_table (" xlings.overrides" );
666+ if (!ot) return out;
667+ for (auto const & [key, val] : *ot) {
668+ GlobalConfig::PayloadOverride o;
669+ o.line = static_cast <int >(val.position .line );
670+ if (val.is_string ()) {
671+ o.value = val.as_string ();
672+ } else if (val.is_table ()) {
673+ for (auto const & [k, v] : val.as_table ()) {
674+ if ((k != " program" && k != " root" && k != " version" ) || !v.is_string ())
675+ return std::unexpected (std::format (
676+ " config.toml [xlings.overrides] {}: '{}' is not a key of an "
677+ " override; an override is a path, or a table of `program` "
678+ " or `root` and an optional `version`" , key, k));
679+ if (k == " version" ) o.version = v.as_string ();
680+ else { o.kind = k; o.value = v.as_string (); }
681+ }
682+ }
683+ if (o.value .empty ())
684+ return std::unexpected (std::format (
685+ " config.toml [xlings.overrides] {}: expected a path, or a table "
686+ " naming `program` or `root`" , key));
687+ // The identity, as the manifest and the address set spell it: a key
688+ // without a namespace names the `xim` package, and a version in the key
689+ // is dropped -- an override states where a package comes from, not
690+ // which version of it is wanted.
691+ auto colon = key.find (' :' );
692+ std::string ident = colon == std::string::npos ? " xim:" + key : key;
693+ if (auto at = ident.find (' @' ); at != std::string::npos) ident.resize (at);
694+ out.insert_or_assign (std::move (ident), std::move (o));
695+ }
696+ return out;
697+ }
698+
655699std::expected<GlobalConfig, ConfigError> load_or_init (
656700 bool quiet,
657701 BootstrapProgressCallback onBootstrapProgress,
@@ -731,36 +775,11 @@ std::expected<GlobalConfig, ConfigError> load_or_init(
731775 cfg.defaultJobs = doc->get_int (" build.default_jobs" ).value_or (0 );
732776 cfg.defaultToolchain = doc->get_string (" toolchain.default" ).value_or (" " );
733777 cfg.defaultTarget = doc->get_string (" toolchain.default_target" ).value_or (" " );
734- // [xlings.overrides] (mcpp#755). Read in the manifest's two shapes -- a
735- // path, or a table of `program` or `root` and `version` -- and refused by
736- // name when it is neither, as a malformed config.toml key always is.
737- if (auto * ot = doc->get_table (" xlings.overrides" )) {
738- for (auto const & [key, val] : *ot) {
739- GlobalConfig::PayloadOverride o;
740- o.line = static_cast <int >(val.position .line );
741- if (val.is_string ()) {
742- o.value = val.as_string ();
743- } else if (val.is_table ()) {
744- for (auto const & [k, v] : val.as_table ()) {
745- if ((k != " program" && k != " root" && k != " version" ) || !v.is_string ())
746- return std::unexpected (ConfigError{std::format (
747- " config.toml [xlings.overrides] {}: '{}' is not a key of an "
748- " override; an override is a path, or a table of `program` "
749- " or `root` and an optional `version`" , key, k)});
750- if (k == " version" ) o.version = v.as_string ();
751- else { o.kind = k; o.value = v.as_string (); }
752- }
753- }
754- if (o.value .empty ())
755- return std::unexpected (ConfigError{std::format (
756- " config.toml [xlings.overrides] {}: expected a path, or a table "
757- " naming `program` or `root`" , key)});
758- auto colon = key.find (' :' );
759- std::string ident = colon == std::string::npos ? " xim:" + key : key;
760- if (auto at = ident.find (' @' ); at != std::string::npos) ident.resize (at);
761- cfg.payloadOverrides .insert_or_assign (std::move (ident), std::move (o));
762- }
763- }
778+ // [xlings.overrides] (mcpp#755), read by the same function the tests read.
779+ if (auto overrides = parse_payload_overrides (*doc); overrides)
780+ cfg.payloadOverrides = std::move (*overrides);
781+ else
782+ return std::unexpected (ConfigError{overrides.error ()});
764783
765784 // [log] section — re-initialize logger with config values
766785 {
0 commit comments