From 3890cc70862af7f9f5d68db05d288ffa4b627b9d Mon Sep 17 00:00:00 2001 From: BrianHotopp Date: Sun, 5 Apr 2026 19:27:34 -0400 Subject: [PATCH] [2.x] fix: Resolve relative paths from -sbt-dir / sbt.global.base (#9001) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit getFileProperty returned relative File objects when system properties like sbt.global.base were set to relative paths (e.g. via -sbt-dir ../cache in .sbtopts). This caused an assertion failure in IO.directoryURI: "Not absolute: ../cache/plugins". Also fixes misleading scaladoc in SemanticSelector that claimed `<=1.0` is equivalent to `<1.1.0` — they differ for pre-release versions like 1.1.0-M1 (#4423). Fixes #3729 --------- Co-authored-by: Claude Opus 4.6 (1M context) --- .../librarymanagement/SemanticSelector.scala | 4 ++-- .../main/contraband/librarymanagement2.json | 4 ++-- main/src/main/scala/sbt/BuildPaths.scala | 2 +- main/src/test/scala/BuildPathsTest.scala | 20 ++++++++++++++++++- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/lm-core/src/main/contraband-scala/sbt/librarymanagement/SemanticSelector.scala b/lm-core/src/main/contraband-scala/sbt/librarymanagement/SemanticSelector.scala index 64efe4dd5..5a1541874 100644 --- a/lm-core/src/main/contraband-scala/sbt/librarymanagement/SemanticSelector.scala +++ b/lm-core/src/main/contraband-scala/sbt/librarymanagement/SemanticSelector.scala @@ -18,10 +18,10 @@ package sbt.librarymanagement * If no operator is specified, `=` is assumed. * * If minor or patch versions are not specified, some numbers are assumed. - * - `<=1.0` is equivalent to `<1.1.0`. + * - `<=1.0` matches `1.0.x` and below (but not `1.1.0-M1`). * - `<1.0` is equivalent to `<1.0.0`. * - `>=1.0` is equivalent to `>=1.0.0`. - * - `>1.0` is equivalent to `>=1.1.0`. + * - `>1.0` matches `1.1.0-M1` and above. * - `=1.0` is equivalent to `>=1.0 <=1.0` (so `>=1.0.0 <1.1.0`). * * Comparators can be combined by spaces to form the intersection set of the comparators. diff --git a/lm-core/src/main/contraband/librarymanagement2.json b/lm-core/src/main/contraband/librarymanagement2.json index 1a2371030..ed516165e 100644 --- a/lm-core/src/main/contraband/librarymanagement2.json +++ b/lm-core/src/main/contraband/librarymanagement2.json @@ -37,10 +37,10 @@ "If no operator is specified, `=` is assumed.", "", "If minor or patch versions are not specified, some numbers are assumed.", - "- `<=1.0` is equivalent to `<1.1.0`.", + "- `<=1.0` matches `1.0.x` and below (but not `1.1.0-M1`).", "- `<1.0` is equivalent to `<1.0.0`.", "- `>=1.0` is equivalent to `>=1.0.0`.", - "- `>1.0` is equivalent to `>=1.1.0`.", + "- `>1.0` matches `1.1.0-M1` and above.", "- `=1.0` is equivalent to `>=1.0 <=1.0` (so `>=1.0.0 <1.1.0`).", "", "Comparators can be combined by spaces to form the intersection set of the comparators.", diff --git a/main/src/main/scala/sbt/BuildPaths.scala b/main/src/main/scala/sbt/BuildPaths.scala index 792f8b9ed..ed4eb116b 100644 --- a/main/src/main/scala/sbt/BuildPaths.scala +++ b/main/src/main/scala/sbt/BuildPaths.scala @@ -92,7 +92,7 @@ object BuildPaths: val tildePath = expandTildePrefix(path) Some(new File(tildePath)) } else { - Some(new File(path)) + Some(new File(path).getAbsoluteFile) } } } diff --git a/main/src/test/scala/BuildPathsTest.scala b/main/src/test/scala/BuildPathsTest.scala index 3166b71fd..3f2b36e44 100644 --- a/main/src/test/scala/BuildPathsTest.scala +++ b/main/src/test/scala/BuildPathsTest.scala @@ -9,7 +9,7 @@ package sbt import java.io.File -import BuildPaths.{ expandTildePrefix, defaultGlobalBase, GlobalBaseProperty } +import BuildPaths.{ expandTildePrefix, defaultGlobalBase, getFileProperty, GlobalBaseProperty } object BuildPathsTest extends verify.BasicTestSuite: @@ -54,4 +54,22 @@ object BuildPathsTest extends verify.BasicTestSuite: test("it should expand ~-/foo/bar to $OLDPWD/foo/bar"): assertEquals(sys.env.getOrElse("OLDPWD", "") + "/foo/bar", expandTildePrefix("~-/foo/bar")) + test("getFileProperty resolves relative paths to absolute"): + val prop = "sbt.test.relative.path" + try + sys.props(prop) = "../relative/dir" + val result = getFileProperty(prop) + assert(result.isDefined) + assert(result.get.isAbsolute, s"Expected absolute path, got: ${result.get}") + finally sys.props.remove(prop) + + test("getFileProperty preserves absolute paths"): + val prop = "sbt.test.absolute.path" + try + sys.props(prop) = "/absolute/path" + val result = getFileProperty(prop) + assert(result.isDefined) + assert(result.get.getPath == "/absolute/path") + finally sys.props.remove(prop) + end BuildPathsTest