From aed904d5ad0d15f116740a0001775dfb63419d07 Mon Sep 17 00:00:00 2001 From: Fabrizio Ferrai Date: Sat, 28 Feb 2026 16:27:34 +0100 Subject: [PATCH] Do not constrain (but do warn) the version for local packages --- CHANGELOG.md | 3 ++ src/Spago/Command/Fetch.purs | 43 +++++++++++++++++-- .../consumer/spago.yaml | 15 +++++++ .../consumer/src/Main.purs | 14 ++++++ .../local-lib-match/spago.yaml | 8 ++++ .../local-lib-match/src/LocalLibMatch.purs | 4 ++ .../local-lib-mismatch/spago.yaml | 8 ++++ .../src/LocalLibMismatch.purs | 4 ++ .../local-lib-no-version/spago.yaml | 5 +++ .../src/LocalLibNoVersion.purs | 4 ++ test/Spago/Install.purs | 16 ++++++- 11 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 test-fixtures/1338-extra-packages-version/consumer/spago.yaml create mode 100644 test-fixtures/1338-extra-packages-version/consumer/src/Main.purs create mode 100644 test-fixtures/1338-extra-packages-version/local-lib-match/spago.yaml create mode 100644 test-fixtures/1338-extra-packages-version/local-lib-match/src/LocalLibMatch.purs create mode 100644 test-fixtures/1338-extra-packages-version/local-lib-mismatch/spago.yaml create mode 100644 test-fixtures/1338-extra-packages-version/local-lib-mismatch/src/LocalLibMismatch.purs create mode 100644 test-fixtures/1338-extra-packages-version/local-lib-no-version/spago.yaml create mode 100644 test-fixtures/1338-extra-packages-version/local-lib-no-version/src/LocalLibNoVersion.purs diff --git a/CHANGELOG.md b/CHANGELOG.md index e5a9c75b7..b09260dab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Bugfixes: * Preserve TTY properties for child processes in `spago run` (#1341) * Do not repeatedly request the same log line from the Registry server (#1381) +* Fix solver failing for local packages in `extraPackages` with version constraints (#1338) + - Solver now widens ranges for non-registry extra packages so they are always accepted + - Warns when a local package's `publish.version` doesn't match the declared constraint ## [1.0.3] - 2026-02-01 diff --git a/src/Spago/Command/Fetch.purs b/src/Spago/Command/Fetch.purs index f448444ac..a2fc3d653 100644 --- a/src/Spago/Command/Fetch.purs +++ b/src/Spago/Command/Fetch.purs @@ -782,13 +782,21 @@ getTransitiveDeps workspacePackage = do getTransitiveDepsFromRegistry :: forall a. Map PackageName Range -> PackageMap -> Spago (FetchEnv a) (Map PackageName Version) getTransitiveDepsFromRegistry depsRanges extraPackages = do let + -- Widen ranges for non-registry extra packages (local/git/workspace overrides). + widenIfExtra :: Map PackageName Range -> Map PackageName Range + widenIfExtra = mapWithIndex \name range -> + case Map.lookup name extraPackages of + Just (RegistryVersion _) -> range + Just _ -> Config.widestRange + Nothing -> range + loader :: PackageName -> Spago (FetchEnv a) (Map Version (Map PackageName Range)) loader packageName = do -- First look up in the extra packages, as they are the workspace ones, and overrides case Map.lookup packageName extraPackages of Just p -> do deps <- getPackageDependencies packageName p - let coreDeps = deps <#> _.core # fromMaybe Map.empty + let coreDeps = widenIfExtra $ deps <#> _.core # fromMaybe Map.empty pure $ Map.singleton (getVersionFromPackage p) coreDeps Nothing -> do maybeMetadata <- Registry.getMetadata packageName @@ -798,10 +806,26 @@ getTransitiveDepsFromRegistry depsRanges extraPackages = do Left _err -> [] map (Map.fromFoldable :: Array _ -> Map _ _) $ for versions \v -> do maybeManifest <- Registry.getManifestFromIndex packageName v - let deps = fromMaybe Map.empty $ map (_.dependencies <<< unwrap) maybeManifest + let deps = widenIfExtra $ fromMaybe Map.empty $ map (_.dependencies <<< unwrap) maybeManifest pure (Tuple v deps) - maybePlan <- Registry.Solver.loadAndSolve loader depsRanges + -- Warn when a non-registry extra package's version doesn't match the declared constraint + for_ (Map.toUnfoldable (Map.intersectionWith Tuple depsRanges extraPackages) :: Array _) + \(Tuple name (Tuple range pkg)) -> case pkg of + RegistryVersion _ -> pure unit + _ -> do + maybeVersion <- extraPackageVersion pkg + case maybeVersion of + Just version | not (Range.includes range version) -> + logWarn $ "Extra package " <> PackageName.print name <> " has version " + <> Version.print version + <> ", which doesn't satisfy constraint " + <> Range.print range + _ -> pure unit + + let widenedDepsRanges = widenIfExtra depsRanges + + maybePlan <- Registry.Solver.loadAndSolve loader widenedDepsRanges case maybePlan of Left errs -> die @@ -901,6 +925,19 @@ getVersionFromPackage = case _ of RegistryVersion v -> v _ -> unsafeFromRight $ Version.parse "0.0.0" +-- | Extract the version from a non-registry package's config if available. +extraPackageVersion :: forall a. Package -> Spago (FetchEnv a) (Maybe Version) +extraPackageVersion = case _ of + RegistryVersion v -> pure $ Just v + WorkspacePackage wp -> pure $ wp.package.publish <#> _.version + LocalPackage p -> do + result <- Config.readConfig (Path.global p.path "spago.yaml") + pure $ case result of + Right { yaml: { package: Just { publish: Just { version } } } } -> Just version + _ -> Nothing + -- Git packages would require cloning to read their config, so no version warning is possible. + GitPackage _ -> pure Nothing + notInPackageSetError :: PackageName -> TransitiveDepsResult -> TransitiveDepsResult notInPackageSetError dep result = result { errors { notInPackageSet = Set.insert dep result.errors.notInPackageSet } } diff --git a/test-fixtures/1338-extra-packages-version/consumer/spago.yaml b/test-fixtures/1338-extra-packages-version/consumer/spago.yaml new file mode 100644 index 000000000..a43cc2600 --- /dev/null +++ b/test-fixtures/1338-extra-packages-version/consumer/spago.yaml @@ -0,0 +1,15 @@ +package: + name: consumer + dependencies: + - local-lib-match: ">=1.0.0 <2.0.0" + - local-lib-mismatch: ">=2.0.0 <3.0.0" + - local-lib-no-version: ">=5.0.0 <6.0.0" + +workspace: + extraPackages: + local-lib-match: + path: ../local-lib-match + local-lib-mismatch: + path: ../local-lib-mismatch + local-lib-no-version: + path: ../local-lib-no-version diff --git a/test-fixtures/1338-extra-packages-version/consumer/src/Main.purs b/test-fixtures/1338-extra-packages-version/consumer/src/Main.purs new file mode 100644 index 000000000..2dd72bb79 --- /dev/null +++ b/test-fixtures/1338-extra-packages-version/consumer/src/Main.purs @@ -0,0 +1,14 @@ +module Main where + +import LocalLibMatch as Match +import LocalLibMismatch as Mismatch +import LocalLibNoVersion as NoVersion + +match :: String +match = Match.hello + +mismatch :: String +mismatch = Mismatch.hello + +noVersion :: String +noVersion = NoVersion.hello diff --git a/test-fixtures/1338-extra-packages-version/local-lib-match/spago.yaml b/test-fixtures/1338-extra-packages-version/local-lib-match/spago.yaml new file mode 100644 index 000000000..02e3624e0 --- /dev/null +++ b/test-fixtures/1338-extra-packages-version/local-lib-match/spago.yaml @@ -0,0 +1,8 @@ +package: + name: local-lib-match + dependencies: [] + publish: + version: 1.0.0 + license: MIT + +workspace: {} diff --git a/test-fixtures/1338-extra-packages-version/local-lib-match/src/LocalLibMatch.purs b/test-fixtures/1338-extra-packages-version/local-lib-match/src/LocalLibMatch.purs new file mode 100644 index 000000000..c2a54f89c --- /dev/null +++ b/test-fixtures/1338-extra-packages-version/local-lib-match/src/LocalLibMatch.purs @@ -0,0 +1,4 @@ +module LocalLibMatch where + +hello :: String +hello = "Hello from local-lib-match" diff --git a/test-fixtures/1338-extra-packages-version/local-lib-mismatch/spago.yaml b/test-fixtures/1338-extra-packages-version/local-lib-mismatch/spago.yaml new file mode 100644 index 000000000..06ba305d5 --- /dev/null +++ b/test-fixtures/1338-extra-packages-version/local-lib-mismatch/spago.yaml @@ -0,0 +1,8 @@ +package: + name: local-lib-mismatch + dependencies: [] + publish: + version: 1.0.0 + license: MIT + +workspace: {} diff --git a/test-fixtures/1338-extra-packages-version/local-lib-mismatch/src/LocalLibMismatch.purs b/test-fixtures/1338-extra-packages-version/local-lib-mismatch/src/LocalLibMismatch.purs new file mode 100644 index 000000000..5d7715e9d --- /dev/null +++ b/test-fixtures/1338-extra-packages-version/local-lib-mismatch/src/LocalLibMismatch.purs @@ -0,0 +1,4 @@ +module LocalLibMismatch where + +hello :: String +hello = "Hello from local-lib-mismatch" diff --git a/test-fixtures/1338-extra-packages-version/local-lib-no-version/spago.yaml b/test-fixtures/1338-extra-packages-version/local-lib-no-version/spago.yaml new file mode 100644 index 000000000..cfd607515 --- /dev/null +++ b/test-fixtures/1338-extra-packages-version/local-lib-no-version/spago.yaml @@ -0,0 +1,5 @@ +package: + name: local-lib-no-version + dependencies: [] + +workspace: {} diff --git a/test-fixtures/1338-extra-packages-version/local-lib-no-version/src/LocalLibNoVersion.purs b/test-fixtures/1338-extra-packages-version/local-lib-no-version/src/LocalLibNoVersion.purs new file mode 100644 index 000000000..54526250a --- /dev/null +++ b/test-fixtures/1338-extra-packages-version/local-lib-no-version/src/LocalLibNoVersion.purs @@ -0,0 +1,4 @@ +module LocalLibNoVersion where + +hello :: String +hello = "Hello from local-lib-no-version" diff --git a/test/Spago/Install.purs b/test/Spago/Install.purs index e09d888c4..7cf52d691 100644 --- a/test/Spago/Install.purs +++ b/test/Spago/Install.purs @@ -23,7 +23,7 @@ import Test.Spec (Spec) import Test.Spec as Spec import Test.Spec.Assertions as Assert import Test.Spec.Assertions as Assertions -import Test.Spec.Assertions.String (shouldContain) +import Test.Spec.Assertions.String (shouldContain, shouldNotContain) spec :: Spec Unit spec = Spec.around withTempDir do @@ -340,6 +340,20 @@ spec = Spec.around withTempDir do spago [ "install", "either" ] >>= shouldBeSuccess checkFixture (testCwd "spago.yaml") (fixture "spago-install-solver-ranges.yaml") + Spec.it "widens solver ranges for non-registry extra packages (#1338)" \{ spago, fixture, testCwd } -> do + FS.copyTree { src: fixture "1338-extra-packages-version", dst: testCwd } + Paths.chdir $ testCwd "consumer" + -- Three local extra packages in one workspace: + -- local-lib-match: version 1.0.0, constraint >=1.0.0 <2.0.0 (satisfies, no warning) + -- local-lib-mismatch: version 1.0.0, constraint >=2.0.0 <3.0.0 (warns, still builds) + -- local-lib-no-version: no publish.version, constraint >=5.0.0 <6.0.0 (widened, no warning) + result <- spago [ "build" ] + result # shouldBeSuccess + let stderr = either _.stderr _.stderr result + stderr `shouldContain` "Extra package local-lib-mismatch has version 1.0.0, which doesn't satisfy constraint >=2.0.0 <3.0.0" + stderr `shouldNotContain` "local-lib-match" + stderr `shouldNotContain` "local-lib-no-version" + insertConfigDependencies :: Config -> Dependencies -> Dependencies -> Config insertConfigDependencies config core test = ( config