Просмотр исходного кода

Resolver: dependency refs resolve to the newest provider, not the oldest cached copy

find_resource returned the FIRST satisfier from the catalog, which sorts
(name, version ASCENDING) with cached packages ahead of repository
listings — so a dependency ref resolved to the oldest cached version:
installing a high-level package like spry from scratch planned stale
dependencies until 'usm update' re-resolved them. Provider selection is
now best-match: alphabetically-first name, then the HIGHEST version,
then a repository package over a supplied/cached one (so installs pull
a fresh copy), then repository name and package path — the same shape
find_package already had.

usm-tests: 410 passed, 0 failed (newest-provider fixture across three
cached versions). MANIFEST 0.1.1 -> 0.1.2.
clanker 4 недель назад
Родитель
Сommit
a99e31a3bb
3 измененных файлов с 93 добавлено и 7 удалено
  1. 1 1
      MANIFEST.usm
  2. 40 5
      src/lib/Resolver.vala
  3. 52 1
      src/tests/TestMain.vala

+ 1 - 1
MANIFEST.usm

@@ -1,6 +1,6 @@
 {
   "name": "usm",
-  "version": "0.1.1",
+  "version": "0.1.2",
   "summary": "Universal Source Manifest",
   "licences": [ {"name": "GPLv3", "category": "libre", "text": "src/LICENSE"} ],
   "flags": ["ninjaStyleProgress"],

+ 40 - 5
src/lib/Resolver.vala

@@ -200,16 +200,51 @@ namespace Usm {
 
         /**
          * Finds a package providing the given resource, deterministically
-         * choosing the alphabetically-first package name (then version,
-         * repository name, package path).
+         * choosing the best provider: alphabetically-first package name,
+         * then the HIGHEST version (a stale cached copy of an older
+         * release must never win over a newer one), then a repository
+         * package over a supplied/cached one so installs pull a fresh
+         * copy, then repository name and package path.
          */
         public AbstractPackage? find_resource(ResourceRef resource) {
+            AbstractPackage? match = null;
             foreach(var package in catalog()) {
-                if(package.manifest.satisfies(resource)) {
-                    return package;
+                if(!package.manifest.satisfies(resource)) {
+                    continue;
+                }
+                if(match == null) {
+                    match = package;
+                    continue;
                 }
+                if(better_provider(package, match)) {
+                    match = package;
+                }
+            }
+            return match;
+        }
+
+        /**
+         * Whether {@link candidate} beats {@link incumbent} as a provider:
+         * alphabetically-first name, then highest version, then repository
+         * over supplied/cache, then repository name and package path.
+         */
+        private static bool better_provider(AbstractPackage candidate, AbstractPackage incumbent) {
+            var by_name = candidate.manifest.name.collate(incumbent.manifest.name);
+            if(by_name != 0) {
+                return by_name < 0;
+            }
+            var by_version = candidate.manifest.version.compare(incumbent.manifest.version);
+            if(by_version != 0) {
+                return by_version > 0;
+            }
+            if((candidate.repository == null) != (incumbent.repository == null)) {
+                return candidate.repository != null;
+            }
+            var by_repository = (candidate.repository?.name ?? "").collate(incumbent.repository?.name ?? "");
+            if(by_repository != 0) {
+                return by_repository < 0;
             }
-            return null;
+            return (candidate.package_path ?? "").collate(incumbent.package_path ?? "") < 0;
         }
 
         /**

+ 52 - 1
src/tests/TestMain.vala

@@ -1022,6 +1022,31 @@ esac
         return archive;
     }
 
+    /**
+     * Builds a .usmc archive for pkg-<name> at an explicit version
+     * providing bin:pkg-<name> with the given runtime refs (used for
+     * provider-selection fixtures).
+     */
+    string make_versioned_package_archive(string scratch, string name, string version, string[] runtime_refs) throws Error {
+        var source = Path.build_filename(scratch, @"src-$name-$version");
+        DirUtils.create(source, 0755);
+
+        var refs = new StringBuilder();
+        foreach(var resource_ref in runtime_refs) {
+            if(refs.len > 0) {
+                refs.append(",");
+            }
+            refs.append_printf("\"%s\"", resource_ref);
+        }
+        FileUtils.set_contents(Path.build_filename(source, "MANIFEST.usm"),
+            APP_MANIFEST.printf(@"pkg-$name", @"pkg-$name", depends_with("runtime", "[" + refs.str + "]"))
+                .replace("\"version\": \"1.0.0\"", @"\"version\": \"$version\""));
+
+        var archive = Path.build_filename(scratch, @"pkg-$name-$version.usmc");
+        Usm.Util.archive(source, archive);
+        return archive;
+    }
+
     /**
      * Builds a .usmc archive for pkg-<name> providing provides_key with the
      * given BUILD-phase refs (used for lot-admission fixtures).
@@ -1091,9 +1116,34 @@ esac
         Usm.Util.delete_tree(scratch);
     }
 
-    void test_resolve_spm_before_usm() throws Error {
+    /**
+     * A dependency ref must resolve to the HIGHEST version among its
+     * providers: the catalog is name/version-ascending and the cache
+     * sorts ahead of repository listings, so a first-match scan pulled
+     * the oldest cached copy — fresh installs of high-level packages
+     * shipped stale dependencies until `usm update` ran.
+     */
+    void test_resolve_prefers_newest_provider() throws Error {
         var scratch = make_scratch();
         var resolver = new Usm.Resolver(new Usm.ResourceFinder());
+        resolver.supply_package(make_versioned_package_archive(scratch, "x", "1.0.0", {}));
+        resolver.supply_package(make_versioned_package_archive(scratch, "x", "1.1.0", {}));
+        resolver.supply_package(make_versioned_package_archive(scratch, "x", "0.9.0", {}));
+        resolver.supply_package(make_package_archive(scratch, "app", { "bin:pkg-x" }));
+
+        var roots = new Vector<Usm.AbstractPackage>();
+        roots.add(((!)resolver.find_package("pkg-app")));
+        var result = resolver.resolve(roots, null);
+
+        var chosen = result.packages.where(p => p.manifest.name == "pkg-x").to_vector();
+        check(chosen.length == 1, "exactly one provider version is resolved for the ref");
+        check(chosen[0].manifest.version.to_string() == "1.1.0", "a dependency ref resolves to the highest provider version, not the oldest cached copy");
+
+        Usm.Util.delete_tree(scratch);
+    }
+
+    void test_resolve_spm_before_usm() throws Error {
+        var scratch = make_scratch();        var resolver = new Usm.Resolver(new Usm.ResourceFinder());
         resolver.supply_package(make_package_archive(scratch, "x", {}));
 
         var roots = new Vector<Usm.AbstractPackage>();
@@ -2961,6 +3011,7 @@ echo "Installing libmeson-pkg.so to /usr/lib"
             test_satisfied_by_constraints();
             test_manifest_satisfies_pc_fallback();
             test_resolve_diamond_usm_only();
+            test_resolve_prefers_newest_provider();
             test_resolve_spm_before_usm();
             test_resolver_constrained_lib_dep();
             test_resolver_skips_spm_for_constrained_refs();