diff --git a/lm-coursier/src/main/scala/lmcoursier/CoursierDependencyResolution.scala b/lm-coursier/src/main/scala/lmcoursier/CoursierDependencyResolution.scala index 87e4fb3f1..ea5901324 100644 --- a/lm-coursier/src/main/scala/lmcoursier/CoursierDependencyResolution.scala +++ b/lm-coursier/src/main/scala/lmcoursier/CoursierDependencyResolution.scala @@ -382,7 +382,7 @@ class CoursierDependencyResolution( .groupBy(_._1) .view .mapValues(_.map { case (_, pub, art, _) => - val originalUrl = CoursierDependencyResolution.cacheFileToOriginalUrl(art.url, cache) + val originalUrl = lmcoursier.internal.CacheUrlConversion.cacheFileToOriginalUrl(art.url, cache) (originalUrl, pub.classifier.value, pub.ext.value) }) .toMap @@ -459,32 +459,6 @@ object CoursierDependencyResolution { def defaultCacheLocation: File = CacheDefaults.location - private[lmcoursier] def cacheFileToOriginalUrl(fileUrl: String, cacheDir: File): String = { - val filePrefix = "file:" - if (fileUrl.startsWith(filePrefix)) { - val filePath = fileUrl.stripPrefix(filePrefix).replaceFirst("^/+", "/") - val cachePaths = Seq( - cacheDir.getAbsolutePath, - cacheDir.getCanonicalPath - ).distinct.map(p => if (p.endsWith("/")) p else p + "/") - - def extractHttpUrl(relativePath: String): Option[String] = { - val protocolSepIndex = relativePath.indexOf('/') - if (protocolSepIndex > 0) { - val protocol = relativePath.substring(0, protocolSepIndex) - val rest = relativePath.substring(protocolSepIndex + 1) - Some(s"$protocol://$rest") - } else None - } - - cachePaths - .collectFirst { - case cachePath if filePath.startsWith(cachePath) => - val relativePath = filePath.stripPrefix(cachePath) - extractHttpUrl(relativePath) - } - .flatten - .getOrElse(s"$${CSR_CACHE}$filePath") - } else fileUrl - } + private[lmcoursier] def cacheFileToOriginalUrl(fileUrl: String, cacheDir: File): String = + lmcoursier.internal.CacheUrlConversion.cacheFileToOriginalUrl(fileUrl, cacheDir) } diff --git a/lm-coursier/src/main/scala/lmcoursier/internal/CacheUrlConversion.scala b/lm-coursier/src/main/scala/lmcoursier/internal/CacheUrlConversion.scala new file mode 100644 index 000000000..f53ed5811 --- /dev/null +++ b/lm-coursier/src/main/scala/lmcoursier/internal/CacheUrlConversion.scala @@ -0,0 +1,47 @@ +/* + * sbt + * Copyright 2023, Scala center + * Copyright 2011 - 2022, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package lmcoursier.internal + +import java.io.File + +object CacheUrlConversion { + + final val FileUrlPrefix = "file:" + final val UnconvertiblePrefix = "${CSR_CACHE}" + + def cacheFileToOriginalUrl(fileUrl: String, cacheDir: File): String = { + if (!fileUrl.startsWith(FileUrlPrefix)) return fileUrl + val filePath = fileUrl.stripPrefix(FileUrlPrefix).replaceFirst("^/+", "/") + val cachePaths = Seq( + cacheDir.getAbsolutePath, + cacheDir.getCanonicalPath + ).distinct.map(p => if (p.endsWith("/")) p else p + "/") + + def extractHttpUrl(relativePath: String): Option[String] = { + val protocolSepIndex = relativePath.indexOf('/') + if (protocolSepIndex > 0) { + val protocol = relativePath.substring(0, protocolSepIndex) + val rest = relativePath.substring(protocolSepIndex + 1) + Some(s"$protocol://$rest") + } else None + } + + cachePaths + .collectFirst { + case cachePath if filePath.startsWith(cachePath) => + val relativePath = filePath.stripPrefix(cachePath) + extractHttpUrl(relativePath) + } + .flatten + .getOrElse(s"$UnconvertiblePrefix$filePath") + } + + def isPortableUrl(url: String): Boolean = + !url.startsWith(FileUrlPrefix) && !url.contains(UnconvertiblePrefix) +} diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index ec048292d..d289f90d6 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -3828,13 +3828,15 @@ object Classpaths { val resolverNames = fullResolvers.value.map(_.name) val buildClock = DependencyLockFile.computeBuildClock(deps, resolverNames) + val cacheDir = csrCacheDirectory.value val lock = DependencyLockManager.createFromUpdateReport( projectId, report, sv, scalaV, buildClock, - log + log, + Some(cacheDir) ) DependencyLockManager.write(lockFile, lock, log) diff --git a/main/src/main/scala/sbt/internal/librarymanagement/DependencyLockManager.scala b/main/src/main/scala/sbt/internal/librarymanagement/DependencyLockManager.scala index 7f5df2ed6..75a23e459 100644 --- a/main/src/main/scala/sbt/internal/librarymanagement/DependencyLockManager.scala +++ b/main/src/main/scala/sbt/internal/librarymanagement/DependencyLockManager.scala @@ -44,19 +44,40 @@ object DependencyLockManager: isValid } + private def artifactUrlForLock(rawUrl: String, cacheDir: Option[File], moduleDesc: String): String = + if !rawUrl.startsWith(CacheUrlConversion.FileUrlPrefix) then rawUrl + else + cacheDir match + case None => + throw new RuntimeException( + s"Cannot create dependency lock file: artifact has file URL (e.g. local cache path). " + + s"Lock files must use portable repository URLs. Module: $moduleDesc. " + + s"Run 'update' first or ensure dependencies are resolved from remote repositories." + ) + case Some(dir) => + val converted = CacheUrlConversion.cacheFileToOriginalUrl(rawUrl, dir) + if !CacheUrlConversion.isPortableUrl(converted) then + throw new RuntimeException( + s"Cannot create dependency lock file: artifact path is not under Coursier cache. " + + s"Module: $moduleDesc. URL: $rawUrl" + ) + converted + def createFromUpdateReport( projectId: String, report: UpdateReport, sbtVersion: String, scalaVersion: Option[String], buildClock: String, - log: Logger + log: Logger, + cacheDir: Option[File] = None ): LockFileData = val configurations = report.configurations.map { configReport => val deps = configReport.modules.map { moduleReport => val artifacts = moduleReport.artifacts.map { case (artifact, file) => + val url = artifactUrlForLock(file.toURI.toString, cacheDir, moduleReport.module.toString) ArtifactLock( - url = file.toURI.toString, + url = url, classifier = artifact.classifier, extension = artifact.extension, tpe = artifact.`type`