diff --git a/.gitignore b/.gitignore index ad79931..a1a884b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,13 @@ target project/project project/target +.bsp .cache .classpath .project .settings -.bsp +# vscode related files +.bloop +.metals +.vscode +project/metals.sbt diff --git a/src/main/paradox/file-formats/index.md b/src/main/paradox/file-formats/index.md index 4624169..e1ee988 100644 --- a/src/main/paradox/file-formats/index.md +++ b/src/main/paradox/file-formats/index.md @@ -4,12 +4,15 @@ _sbt-dependency-lock_ stores lockfile information in JSON format with a version top-level object. Details of the file format can be found on these pages, and we encourage other tools to utilise the output information. -| Version | Added In | Removed In | Description | -| ---------------------: | -------: | ---------: | ---------------- | -| @ref:[1](version-1.md) | 0.1.0 | _current_ | Initial version. | +| Version | Added In | Removed In | Description | +| ---------------------: | ---------: | ---------: | ------------------------- | +| @ref:[1](version-1.md) | 0.1.0 | _current_ | Initial version. | +| @ref:[2](version-2.md) | _proposed_ | _N/A_ | Proposed enhanced format. | + Current default version is: 1 @@@ index * [Version 1](version-1.md) +* [Version 2](version-2.md) @@@ \ No newline at end of file diff --git a/src/main/paradox/file-formats/version-2.md b/src/main/paradox/file-formats/version-2.md new file mode 100644 index 0000000..02c5fdb --- /dev/null +++ b/src/main/paradox/file-formats/version-2.md @@ -0,0 +1,209 @@ +# Version 2 + +@@@warning +This version of the lockfile is currently a proposal and has not been implemented yet. + +Version 2.0.0 should add this version. +@@@ + +## New Features + +- Handles cross-built Scala dependencies. + +### Cross-Built Scala Dependencies + +There are three types of dependencies that need to be handled: + +1. Dependencies that are the same for all scala versions (eg Java libraries). +2. Dependencies that are only used in one scala version (eg `scala-library` or compatibility libraries). +3. Dependencies that are used in multiple scala versions but have different artifacts (eg Scala libraries). + +```scala +crossScalaVersions := Seq("2.12.10", "2.13.4") + +libraryDependencies ++= Seq( + "org.apache.commons" % "commons-lang3" % "3.9", // scenario 1 + "org.scala-lang.modules" %% "scala-xml" % "1.2.0" // scenario 3 +) +``` + +Dependency JSON for scenario 1: +```json +{ + "org": "org.apache.commons", + "name": "commons-lang3", + "version": "3.9", + "crossBuilt": false, + "scalaVersions": ["2.12", "2.13"], + "artifacts": [ + { + "name": "commons-lang3.jar", + "hash": "sha1:0122c7cee69b53ed4a7681c03d4ee4c0e2765da5" + } + ], + "configurations": [ + "test", + "compile" + ] +} +``` + +Dependency JSON for scenario 2 (Scala auto-library): +```json +[ + { + "org": "org.scala-lang", + "name": "scala-library", + "version": "2.12.10", + "crossBuilt": false, + "scalaVersions": [ + "2.12" + ], + "artifacts": [ + { + "name": "scala-library.jar", + "hash": "sha1:3509860bc2e5b3da001ed45aca94ffbe5694dbda" + } + ], + "configurations": [ + "test", + "compile" + ] + }, + { + "org": "org.scala-lang", + "name": "scala-library", + "version": "2.13.4", + "crossBuilt": false, + "scalaVersions": [ + "2.13" + ], + "artifacts": [ + { + "name": "scala-library.jar", + "hash": "sha1:b6781c71dfe4a3d5980a514eec8a513f693ead95" + } + ], + "configurations": [ + "test", + "compile" + ] + } +] +``` + +Dependency JSON for scenario 3: +```json +{ + "org": "org.scala-lang.modules", + "name": "scala-xml", + "version": "1.2.0", + "crossBuilt": true, + "scalaVersions": ["2.12", "2.13"], + "artifacts": [ + { + "name": "scala-xml_2.12.jar", + "hash": "sha1:5d38ac30beb8420dd395c0af447ba412158965e6", + "scalaVersion": "2.12" + }, + { + "name": "scala-xml_2.13.jar", + "hash": "sha1:f6abd60d28c189f05183b26c5363713d1d126b83", + "scalaVersion": "2.13" + } + ], + "configurations": [ + "test" + ] +} +``` + +## Example + +```json +{ + "lockVersion": 2, + "timestamp": "2021-05-11T12:00:00.000Z", + "configurations": [ + "compile", + "test" + ], + "scalaVersions": ["2.12", "2.13"], + "dependencies": [ + { + "org": "org.apache.commons", + "name": "commons-lang3", + "version": "3.9", + "crossBuilt": false, + "scalaVersions": ["2.12", "2.13"], + "artifacts": [ + { + "name": "commons-lang3.jar", + "hash": "sha1:0122c7cee69b53ed4a7681c03d4ee4c0e2765da5" + } + ], + "configurations": [ + "test", + "compile" + ] + }, + { + "org": "org.scala-lang", + "name": "scala-library", + "version": "2.12.10", + "crossBuilt": false, + "scalaVersions": ["2.12"], + "artifacts": [ + { + "name": "scala-library.jar", + "hash": "sha1:3509860bc2e5b3da001ed45aca94ffbe5694dbda" + } + ], + "configurations": [ + "test", + "compile" + ] + }, + { + "org": "org.scala-lang", + "name": "scala-library", + "version": "2.13.4", + "crossBuilt": false, + "scalaVersions": ["2.13"], + "artifacts": [ + { + "name": "scala-library.jar", + "hash": "sha1:b6781c71dfe4a3d5980a514eec8a513f693ead95" + } + ], + "configurations": [ + "test", + "compile" + ] + }, + { + "org": "org.scala-lang.modules", + "name": "scala-xml", + "version": "1.2.0", + "crossBuilt": true, + "scalaVersions": ["2.12", "2.13"], + "artifacts": [ + { + "name": "scala-xml_2.12.jar", + "hash": "sha1:5d38ac30beb8420dd395c0af447ba412158965e6", + "scalaVersion": "2.12" + }, + { + "name": "scala-xml_2.13.jar", + "hash": "sha1:f6abd60d28c189f05183b26c5363713d1d126b83", + "scalaVersion": "2.13" + } + ], + "configurations": [ + "test" + ] + } + ] + +} +``` \ No newline at end of file diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 0f2c458..30a9d5a 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -26,7 +26,6 @@ lock.status.artifacts.changed.singular=\ \ 1 dependency artifacts changed lock.status.artifacts.changed.multiple=\ \ {0} dependency artifacts changed # lockfile full status - configurations ======================================= - lock.status.full.configs.added.none= lock.status.full.configs.added.singular=\ \ 1 config added: {1} lock.status.full.configs.added.multiple=\ \ {0} configs added: {1} @@ -36,7 +35,6 @@ lock.status.full.configs.removed.singular=\ \ 1 config removed: {1} lock.status.full.configs.removed.multiple=\ \ {0} configs removed: {1} # lockfile full status - dependencies ========================================= - lock.status.full.dependencies.added.none= lock.status.full.dependencies.added.singular=\ \ 1 dependency added:\n{1} lock.status.full.dependencies.added.multiple=\ \ {0} dependencies added:\n{1} @@ -52,4 +50,9 @@ lock.status.full.dependencies.changed.multiple=\ \ {0} dependencies changed:\n{1 # lockfile full status - artifacts ============================================ lock.status.full.artifacts.changed.none= lock.status.full.artifacts.changed.singular=\ \ 1 dependency artifacts changed -lock.status.full.artifacts.changed.multiple=\ \ {0} dependency artifacts changed \ No newline at end of file +lock.status.full.artifacts.changed.multiple=\ \ {0} dependency artifacts changed + +# error messages ============================================================== +error.lockfile.missing=no lockfile found - please run dependencyLockWrite +error.lockfile.invalid=invalid lockfile - please run dependencyLockWrite +error.lockfile.version=unsupported lockfile version {0} \ No newline at end of file diff --git a/src/main/scala/software/purpledragon/sbt/lock/DependencyLockIO.scala b/src/main/scala/software/purpledragon/sbt/lock/DependencyLockIO.scala deleted file mode 100644 index c89a37a..0000000 --- a/src/main/scala/software/purpledragon/sbt/lock/DependencyLockIO.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2019 Michael Stringer - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package software.purpledragon.sbt.lock - -import java.io.File - -import io.circe.parser._ -import io.circe.syntax._ -import sbt.io.IO -import software.purpledragon.sbt.lock.model.Decoders._ -import software.purpledragon.sbt.lock.model.DependencyLockFile - -object DependencyLockIO { - def writeLockFile(lockFile: DependencyLockFile, dest: File): Unit = { - IO.write(dest, lockFile.asJson.spaces2) - } - - def readLockFile(src: File): Option[DependencyLockFile] = { - if (src.exists()) { - parse(IO.read(src)) match { - case Right(json) => - json.as[DependencyLockFile].toOption - case _ => - None - } - } else { - None - } - } -} diff --git a/src/main/scala/software/purpledragon/sbt/lock/DependencyLockPlugin.scala b/src/main/scala/software/purpledragon/sbt/lock/DependencyLockPlugin.scala index d69546a..fc2d64c 100644 --- a/src/main/scala/software/purpledragon/sbt/lock/DependencyLockPlugin.scala +++ b/src/main/scala/software/purpledragon/sbt/lock/DependencyLockPlugin.scala @@ -20,16 +20,20 @@ import sbt.Keys._ import sbt._ import sbt.internal.util.ManagedLogger import software.purpledragon.sbt.lock.DependencyLockUpdateMode._ -import software.purpledragon.sbt.lock.model.{DependencyLockFile, LockFileMatches} +import software.purpledragon.sbt.lock.io.{DependencyLockIO, LockfileException} +import software.purpledragon.sbt.lock.model.LockFileMatches +import software.purpledragon.sbt.lock.model.lockfile.v1.DependencyLockFile import software.purpledragon.sbt.lock.util.MessageUtil +import scala.util.{Failure, Success, Try} + object DependencyLockPlugin extends AutoPlugin { override def trigger: PluginTrigger = allRequirements object autoImport { val dependencyLockFile = settingKey[File]("lockfile to generate") val dependencyLockWrite = taskKey[File]("write dependencies to lockfile") - val dependencyLockRead = taskKey[Option[DependencyLockFile]]("read dependencies from lockfile") + val dependencyLockRead = taskKey[Try[DependencyLockFile]]("read dependencies from lockfile") val dependencyLockCheck = taskKey[Unit]("check if dependency lock is up to date") @@ -87,7 +91,7 @@ object DependencyLockPlugin extends AutoPlugin { logger.debug("Automatically checking lockfile") dependencyLockRead.value match { - case Some(currentFile) => + case Success(currentFile) => val updatedFile = DependencyUtils.resolve(report, thisProject.value.configurations.map(_.toConfigRef)) val changes = currentFile.findChanges(updatedFile) @@ -105,8 +109,12 @@ object DependencyLockPlugin extends AutoPlugin { // scenario shouldn't happen - failed check, but we're not checking... } - case None => - logger.warn("no lockfile found - please run dependencyLockWrite") + case Failure(error: LockfileException) => + logger.warn(error.getMessage) + + case Failure(error) => + logger.error("Unexpected error while parsing lockfile") + throw error } } diff --git a/src/main/scala/software/purpledragon/sbt/lock/DependencyUtils.scala b/src/main/scala/software/purpledragon/sbt/lock/DependencyUtils.scala index 00ae5f2..70c1e70 100644 --- a/src/main/scala/software/purpledragon/sbt/lock/DependencyUtils.scala +++ b/src/main/scala/software/purpledragon/sbt/lock/DependencyUtils.scala @@ -17,11 +17,12 @@ package software.purpledragon.sbt.lock import java.time.Instant - import sbt._ -import software.purpledragon.sbt.lock.model.{DependencyLockFile, DependencyRef, ResolvedArtifact, ResolvedDependency} +import software.purpledragon.sbt.lock.model.DependencyRef +import software.purpledragon.sbt.lock.model.lockfile.v1 +import software.purpledragon.sbt.lock.model.lockfile.v1.{DependencyLockFile, ResolvedArtifact, ResolvedDependency} -import scala.collection.{immutable, mutable, SortedSet} +import scala.collection.{SortedSet, immutable, mutable} object DependencyUtils { def resolve(updateReport: UpdateReport, configs: Seq[ConfigRef]): DependencyLockFile = { @@ -39,7 +40,7 @@ object DependencyUtils { } } - DependencyLockFile( + v1.DependencyLockFile( 1, Instant.now(), configurations.map(_.configuration.name).sorted, @@ -75,7 +76,7 @@ object DependencyUtils { ResolvedArtifact(s"${artifact.name}$qualifier.${artifact.extension}", hash) } - ResolvedDependency( + v1.ResolvedDependency( module.module.organization, module.module.name, module.module.revision, diff --git a/src/main/scala/software/purpledragon/sbt/lock/io/DependencyLockIO.scala b/src/main/scala/software/purpledragon/sbt/lock/io/DependencyLockIO.scala new file mode 100644 index 0000000..3e2c69d --- /dev/null +++ b/src/main/scala/software/purpledragon/sbt/lock/io/DependencyLockIO.scala @@ -0,0 +1,69 @@ +/* + * Copyright 2019 Michael Stringer + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package software.purpledragon.sbt.lock.io + +import io.circe.parser._ +import io.circe.syntax._ +import io.circe.{Decoder, Json} +import sbt.io.IO +import software.purpledragon.sbt.lock.model.lockfile.v1.Decoders._ +import software.purpledragon.sbt.lock.model.lockfile.v1.DependencyLockFile + +import java.io.File +import scala.util.{Failure, Success, Try} + +object DependencyLockIO { + def writeLockFile(lockFile: DependencyLockFile, dest: File): Unit = { + IO.write(dest, lockFile.asJson.spaces2) + } + + def readLockFile(src: File): Try[DependencyLockFile] = { + if (src.exists()) { + parseLockFile(IO.read(src)) + } else { + Failure(new MissingLockfileException()) + } + } + + def parseLockFile(contents: String): Try[DependencyLockFile] = { + parse(contents) match { + case Right(json) => + json.hcursor.get[Int]("lockVersion") match { + case Right(1) => + decodeJson[DependencyLockFile](json) + + case Right(version) => + Failure(new InvalidLockfileVersionException(version)) + + case _ => + // missing lockVersion field - invalid lockfile + Failure(new InvalidFormatException()) + } + + case _ => + // invalid json + Failure(new InvalidFormatException()) + } + } + + private def decodeJson[T](json: Json)(implicit d: Decoder[T]): Try[T] = { + json.as[T] match { + case Right(parsed) => Success(parsed) + case Left(_) => Failure(new InvalidFormatException()) + } + } +} diff --git a/src/main/scala/software/purpledragon/sbt/lock/io/errors.scala b/src/main/scala/software/purpledragon/sbt/lock/io/errors.scala new file mode 100644 index 0000000..92016c4 --- /dev/null +++ b/src/main/scala/software/purpledragon/sbt/lock/io/errors.scala @@ -0,0 +1,26 @@ +/* + * Copyright 2019 Michael Stringer + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package software.purpledragon.sbt.lock.io + +import software.purpledragon.sbt.lock.util.MessageUtil + +class LockfileException(messageKey: String, args: Any*) + extends Exception(MessageUtil.formatMessage(messageKey, args: _*)) + +class MissingLockfileException extends LockfileException("error.lockfile.missing") +class InvalidFormatException extends LockfileException("error.lockfile.invalid") +class InvalidLockfileVersionException(val version: Int) extends LockfileException("error.lockfile.version", version) diff --git a/src/main/scala/software/purpledragon/sbt/lock/model/ChangedDependency.scala b/src/main/scala/software/purpledragon/sbt/lock/model/ChangedDependency.scala index de9b47c..b9c616e 100644 --- a/src/main/scala/software/purpledragon/sbt/lock/model/ChangedDependency.scala +++ b/src/main/scala/software/purpledragon/sbt/lock/model/ChangedDependency.scala @@ -16,6 +16,8 @@ package software.purpledragon.sbt.lock.model +import software.purpledragon.sbt.lock.model.lockfile.v1.ResolvedArtifact + import scala.collection.SortedSet final case class ChangedDependency( diff --git a/src/main/scala/software/purpledragon/sbt/lock/model/LockFileStatus.scala b/src/main/scala/software/purpledragon/sbt/lock/model/LockFileStatus.scala index 182bced..b5c7e9f 100644 --- a/src/main/scala/software/purpledragon/sbt/lock/model/LockFileStatus.scala +++ b/src/main/scala/software/purpledragon/sbt/lock/model/LockFileStatus.scala @@ -16,6 +16,7 @@ package software.purpledragon.sbt.lock.model +import software.purpledragon.sbt.lock.model.lockfile.v1.ResolvedDependency import software.purpledragon.sbt.lock.util.MessageUtil import software.purpledragon.text.SortedTableFormatter diff --git a/src/main/scala/software/purpledragon/sbt/lock/model/Decoders.scala b/src/main/scala/software/purpledragon/sbt/lock/model/lockfile/v1/Decoders.scala similarity index 88% rename from src/main/scala/software/purpledragon/sbt/lock/model/Decoders.scala rename to src/main/scala/software/purpledragon/sbt/lock/model/lockfile/v1/Decoders.scala index 53d1c55..689f817 100644 --- a/src/main/scala/software/purpledragon/sbt/lock/model/Decoders.scala +++ b/src/main/scala/software/purpledragon/sbt/lock/model/lockfile/v1/Decoders.scala @@ -14,10 +14,10 @@ * limitations under the License. */ -package software.purpledragon.sbt.lock.model +package software.purpledragon.sbt.lock.model.lockfile.v1 -import io.circe._ -import io.circe.generic.semiauto._ +import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder} +import io.circe.{Decoder, Encoder} object Decoders { implicit val lockfileDecoder: Decoder[DependencyLockFile] = deriveDecoder[DependencyLockFile] diff --git a/src/main/scala/software/purpledragon/sbt/lock/model/DependencyLockFile.scala b/src/main/scala/software/purpledragon/sbt/lock/model/lockfile/v1/DependencyLockFile.scala similarity index 95% rename from src/main/scala/software/purpledragon/sbt/lock/model/DependencyLockFile.scala rename to src/main/scala/software/purpledragon/sbt/lock/model/lockfile/v1/DependencyLockFile.scala index 6936cc4..5b5e536 100644 --- a/src/main/scala/software/purpledragon/sbt/lock/model/DependencyLockFile.scala +++ b/src/main/scala/software/purpledragon/sbt/lock/model/lockfile/v1/DependencyLockFile.scala @@ -14,7 +14,9 @@ * limitations under the License. */ -package software.purpledragon.sbt.lock.model +package software.purpledragon.sbt.lock.model.lockfile.v1 + +import software.purpledragon.sbt.lock.model.{ChangedDependency, DependencyRef, LockFileMatches, LockFileStatus} import java.time.Instant import java.util.Objects diff --git a/src/main/scala/software/purpledragon/sbt/lock/model/ResolvedArtifact.scala b/src/main/scala/software/purpledragon/sbt/lock/model/lockfile/v1/ResolvedArtifact.scala similarity index 93% rename from src/main/scala/software/purpledragon/sbt/lock/model/ResolvedArtifact.scala rename to src/main/scala/software/purpledragon/sbt/lock/model/lockfile/v1/ResolvedArtifact.scala index f3fb55e..2a4e2e0 100644 --- a/src/main/scala/software/purpledragon/sbt/lock/model/ResolvedArtifact.scala +++ b/src/main/scala/software/purpledragon/sbt/lock/model/lockfile/v1/ResolvedArtifact.scala @@ -14,7 +14,7 @@ * limitations under the License. */ -package software.purpledragon.sbt.lock.model +package software.purpledragon.sbt.lock.model.lockfile.v1 import scala.math.Ordered.orderingToOrdered diff --git a/src/main/scala/software/purpledragon/sbt/lock/model/ResolvedDependency.scala b/src/main/scala/software/purpledragon/sbt/lock/model/lockfile/v1/ResolvedDependency.scala similarity index 95% rename from src/main/scala/software/purpledragon/sbt/lock/model/ResolvedDependency.scala rename to src/main/scala/software/purpledragon/sbt/lock/model/lockfile/v1/ResolvedDependency.scala index a736669..891073e 100644 --- a/src/main/scala/software/purpledragon/sbt/lock/model/ResolvedDependency.scala +++ b/src/main/scala/software/purpledragon/sbt/lock/model/lockfile/v1/ResolvedDependency.scala @@ -14,7 +14,7 @@ * limitations under the License. */ -package software.purpledragon.sbt.lock.model +package software.purpledragon.sbt.lock.model.lockfile.v1 import scala.collection.SortedSet import scala.math.Ordered.orderingToOrdered diff --git a/src/test/resources/lockfile/unknown-version.json b/src/test/resources/lockfile/unknown-version.json new file mode 100644 index 0000000..57ac356 --- /dev/null +++ b/src/test/resources/lockfile/unknown-version.json @@ -0,0 +1,3 @@ +{ + "lockVersion": 9999 +} \ No newline at end of file diff --git a/src/test/resources/lockfile/v1/empty-dependencies.json b/src/test/resources/lockfile/v1/empty-dependencies.json new file mode 100644 index 0000000..635c494 --- /dev/null +++ b/src/test/resources/lockfile/v1/empty-dependencies.json @@ -0,0 +1,9 @@ +{ + "lockVersion": 1, + "timestamp": "2021-05-11T12:00:00.000Z", + "configurations" : [ + "compile", + "test" + ], + "dependencies": [] +} \ No newline at end of file diff --git a/src/test/resources/lockfile/v1/invalid.json b/src/test/resources/lockfile/v1/invalid.json new file mode 100644 index 0000000..07d84f6 --- /dev/null +++ b/src/test/resources/lockfile/v1/invalid.json @@ -0,0 +1,9 @@ +{ + "lockVersion": 1, + "timestamp": "2021-05-11T12:00:00.000Z", + "configurations" : [ + "compile", + "test" + ], + "not-dependencies": [] +} \ No newline at end of file diff --git a/src/test/resources/lockfile/v1/with-dependencies.json b/src/test/resources/lockfile/v1/with-dependencies.json new file mode 100644 index 0000000..f38e4d8 --- /dev/null +++ b/src/test/resources/lockfile/v1/with-dependencies.json @@ -0,0 +1,39 @@ +{ + "lockVersion": 1, + "timestamp": "2021-05-11T12:00:00.000Z", + "configurations": [ + "compile", + "test" + ], + "dependencies": [ + { + "org": "org.scala-lang", + "name": "scala-library", + "version": "2.12.10", + "artifacts": [ + { + "name": "scala-library.jar", + "hash": "sha1:3509860bc2e5b3da001ed45aca94ffbe5694dbda" + } + ], + "configurations": [ + "compile", + "test" + ] + }, + { + "org": "org.scala-lang", + "name": "scala-reflect", + "version": "2.12.10", + "artifacts": [ + { + "name": "scala-reflect.jar", + "hash": "sha1:14cb7beb516cd8e07716133668c427792122c926" + } + ], + "configurations": [ + "test" + ] + } + ] +} \ No newline at end of file diff --git a/src/test/scala/software/purpledragon/sbt/lock/DependencyLockIOSpec.scala b/src/test/scala/software/purpledragon/sbt/lock/DependencyLockIOSpec.scala new file mode 100644 index 0000000..e2df6c1 --- /dev/null +++ b/src/test/scala/software/purpledragon/sbt/lock/DependencyLockIOSpec.scala @@ -0,0 +1,93 @@ +/* + * Copyright 2019 Michael Stringer + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package software.purpledragon.sbt.lock + +import org.scalatest.TryValues._ +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers +import software.purpledragon.sbt.lock.io.{DependencyLockIO, InvalidFormatException, InvalidLockfileVersionException} +import software.purpledragon.sbt.lock.model.lockfile.v1.{DependencyLockFile, ResolvedArtifact, ResolvedDependency} + +import java.time.Instant +import scala.collection.SortedSet +import scala.io.Source +import scala.util.Try + +class DependencyLockIOSpec extends AnyFlatSpec with Matchers { + "parseLockFile" should "return Failure if not valid JSON" in { + DependencyLockIO.parseLockFile("jibberish").failure.exception shouldBe an[InvalidFormatException] + } + + it should "return Failure if version is unknown" in { + val versionError = parseFromResource("unknown-version.json").failure.exception + + versionError shouldBe an[InvalidLockfileVersionException] + versionError.asInstanceOf[InvalidLockfileVersionException].version shouldBe 9999 + } + + "parseLockFile (v1)" should "parse empty dependencies" in { + val parsed = parseFromResource("v1/empty-dependencies.json") + + parsed.success.value.lockVersion shouldBe 1 + parsed.success.value.timestamp shouldBe Instant.parse("2021-05-11T12:00:00.000Z") + parsed.success.value.configurations shouldBe Seq("compile", "test") + parsed.success.value.dependencies shouldBe empty + } + + it should "parse lockfile with dependencies" in { + val parsed = parseFromResource("v1/with-dependencies.json") + + parsed.success.value.lockVersion shouldBe 1 + parsed.success.value.timestamp shouldBe Instant.parse("2021-05-11T12:00:00.000Z") + parsed.success.value.configurations shouldBe Seq("compile", "test") + parsed.success.value.dependencies shouldBe Seq( + ResolvedDependency( + "org.scala-lang", + "scala-library", + "2.12.10", + SortedSet( + ResolvedArtifact( + "scala-library.jar", + "sha1:3509860bc2e5b3da001ed45aca94ffbe5694dbda" + ) + ), + SortedSet("compile", "test") + ), + ResolvedDependency( + "org.scala-lang", + "scala-reflect", + "2.12.10", + SortedSet( + ResolvedArtifact( + "scala-reflect.jar", + "sha1:14cb7beb516cd8e07716133668c427792122c926" + ) + ), + SortedSet("test") + ) + ) + } + + it should "return Failure if format is incorrect" in { + parseFromResource("v1/invalid.json").failure.exception shouldBe an[InvalidFormatException] + } + + private def parseFromResource(name: String): Try[DependencyLockFile] = { + val contents = Source.fromResource(s"lockfile/$name").getLines().mkString("\n") + DependencyLockIO.parseLockFile(contents) + } +} diff --git a/src/test/scala/software/purpledragon/sbt/lock/model/LockFileStatusSpec.scala b/src/test/scala/software/purpledragon/sbt/lock/model/LockFileStatusSpec.scala index 2c63d3b..9b7ac6f 100644 --- a/src/test/scala/software/purpledragon/sbt/lock/model/LockFileStatusSpec.scala +++ b/src/test/scala/software/purpledragon/sbt/lock/model/LockFileStatusSpec.scala @@ -18,6 +18,8 @@ package software.purpledragon.sbt.lock.model import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers +import software.purpledragon.sbt.lock.model.lockfile.v1 +import software.purpledragon.sbt.lock.model.lockfile.v1.{ResolvedArtifact, ResolvedDependency} import scala.collection.SortedSet @@ -323,7 +325,7 @@ class LockFileStatusSpec extends AnyFlatSpec with Matchers { name: String = "artifact", version: String = "1.0", configs: SortedSet[String] = SortedSet("compile", "test")): ResolvedDependency = { - ResolvedDependency(org, name, version, SortedSet.empty, configs) + v1.ResolvedDependency(org, name, version, SortedSet.empty, configs) } private def testChangedDependency( diff --git a/src/test/scala/software/purpledragon/sbt/lock/model/DependencyLockFileSpec.scala b/src/test/scala/software/purpledragon/sbt/lock/model/lockfile/v1/DependencyLockFileSpec.scala similarity index 86% rename from src/test/scala/software/purpledragon/sbt/lock/model/DependencyLockFileSpec.scala rename to src/test/scala/software/purpledragon/sbt/lock/model/lockfile/v1/DependencyLockFileSpec.scala index f7e654b..5b0e560 100644 --- a/src/test/scala/software/purpledragon/sbt/lock/model/DependencyLockFileSpec.scala +++ b/src/test/scala/software/purpledragon/sbt/lock/model/lockfile/v1/DependencyLockFileSpec.scala @@ -14,18 +14,19 @@ * limitations under the License. */ -package software.purpledragon.sbt.lock.model - -import java.time.Instant +package software.purpledragon.sbt.lock.model.lockfile.v1 import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers +import software.purpledragon.sbt.lock.model.lockfile.v1 +import software.purpledragon.sbt.lock.model.{ChangedDependency, LockFileDiffers, LockFileMatches} +import java.time.Instant import scala.collection.SortedSet class DependencyLockFileSpec extends AnyFlatSpec with Matchers { private val EmptyLockFile = DependencyLockFile(1, Instant.now(), Nil, Nil) - private val TestLockFile = DependencyLockFile( + private val TestLockFile = v1.DependencyLockFile( 1, Instant.now(), Seq("test-1", "test-2"), @@ -36,7 +37,7 @@ class DependencyLockFileSpec extends AnyFlatSpec with Matchers { "1.0.0", SortedSet(ResolvedArtifact("package-1.jar", "hash-1")), SortedSet("test-1")), - ResolvedDependency("com.example", "package-2", "1.2.0", SortedSet.empty, SortedSet("test-2")) + v1.ResolvedDependency("com.example", "package-2", "1.2.0", SortedSet.empty, SortedSet("test-2")) ) ) @@ -48,7 +49,7 @@ class DependencyLockFileSpec extends AnyFlatSpec with Matchers { } it should "return LockFileMatches if timestamp differs" in { - val left = DependencyLockFile(1, Instant.now(), Nil, Nil) + val left = v1.DependencyLockFile(1, Instant.now(), Nil, Nil) val right = left.copy(timestamp = Instant.now()) left.findChanges(right) shouldBe LockFileMatches @@ -78,7 +79,7 @@ class DependencyLockFileSpec extends AnyFlatSpec with Matchers { } it should "return LockFileDiffers if dependency added" in { - val newDependency = ResolvedDependency( + val newDependency = v1.ResolvedDependency( "com.example", "package-3", "3.0", @@ -101,7 +102,7 @@ class DependencyLockFileSpec extends AnyFlatSpec with Matchers { it should "return LockFileDiffers if dependency changed" in { val left = TestLockFile val right = left.copy( - dependencies = left.dependencies.tail :+ ResolvedDependency( + dependencies = left.dependencies.tail :+ v1.ResolvedDependency( "com.example", "package-1", "2.0.0",