Skip to content

Commit

Permalink
Manifest and cli (#72)
Browse files Browse the repository at this point in the history
* CLI module and manifest file support
  • Loading branch information
keynmol authored Nov 21, 2022
1 parent 5703964 commit 4b7f822
Show file tree
Hide file tree
Showing 14 changed files with 318 additions and 24 deletions.
16 changes: 12 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,19 @@ jobs:
cd example
SBT_VCPKG_VERSION=$(cat ../version) sbt example/run
- name: CLI tests
shell: bash
run: |
set -e
curl -fLo cs https://github.com/coursier/launchers/raw/master/coursier &&
chmod +x cs
./cs launch com.indoorvivants.vcpkg:scala-vcpkg_3:$(cat version) -- install -v libpq s2n
echo '{"name": "my-application","version": "0.15.2","dependencies": ["sqlite3"]}' > test-vcpkg.json
./cs launch com.indoorvivants.vcpkg:scala-vcpkg_3:$(cat version) -- install-manifest -v test-vcpkg.json
windows_build:
name: Windows CI
Expand Down Expand Up @@ -79,10 +91,6 @@ jobs:
- name: Test
run: sbt test

- name: Cold start tests (docker)
run: docker build . -t sbt-vcpkg-tests
if: matrix.os == 'ubuntu-20.04'

summary:
name: Build summary
runs-on: ubuntu-latest
Expand Down
12 changes: 11 additions & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
version = "3.6.1"
runner.dialect = scala213
runner.dialect = scala213

fileOverride {
"glob:**/cli/**/*.scala" {
runner.dialect = scala3
rewrite.scala3.insertEndMarkerMinLines = 10
rewrite.scala3.removeOptionalBraces = true
rewrite.scala3.convertToNewSyntax = true
align.preset = more
}
}
41 changes: 32 additions & 9 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ val V = new {

val b2s = "0.3.17"

val decline = "2.4.0"

val scribe = "3.10.5"

val supportedScalaVersions = List(scala213, scala212, scala3)
}

Expand All @@ -51,9 +55,10 @@ lazy val publishing = Seq(

lazy val root = project
.in(file("."))
.aggregate(
(core.projectRefs ++ `sbt-plugin`.projectRefs ++ `mill-plugin`.projectRefs) *
)
.aggregate(core.projectRefs *)
.aggregate(`sbt-plugin`.projectRefs *)
.aggregate(`mill-plugin`.projectRefs *)
.aggregate(cli.projectRefs *)
.settings(
publish / skip := true
)
Expand All @@ -64,17 +69,31 @@ lazy val core = projectMatrix
.settings(publishing)
.settings(
name := "vcpkg-core",
libraryDependencies += "dev.dirs" % "directories" % V.dirs,
libraryDependencies += "com.indoorvivants.detective" %% "platform" % V.detective,
crossScalaVersions := V.supportedScalaVersions,
libraryDependencies += "org.eclipse.jgit" % "org.eclipse.jgit" % V.eclipseGit,
libraryDependencies += "com.disneystreaming" %% "weaver-cats" % V.weaver,
libraryDependencies ++= Seq(
"dev.dirs" % "directories" % V.dirs,
"com.indoorvivants.detective" %% "platform" % V.detective,
"org.eclipse.jgit" % "org.eclipse.jgit" % V.eclipseGit,
"com.disneystreaming" %% "weaver-cats" % V.weaver % Test
),
testFrameworks += new TestFramework("weaver.framework.CatsEffect"),
scalacOptions ++= {
if (!scalaVersion.value.startsWith("3.")) Seq("-Xsource:3") else Seq.empty
}
)

lazy val cli = projectMatrix
.jvmPlatform(scalaVersions = Seq(V.scala3))
.defaultAxes(VirtualAxis.scalaABIVersion(V.scala3), VirtualAxis.jvm)
.dependsOn(core)
.in(file("cli"))
.settings(publishing)
.settings(
name := "scala-vcpkg",
testFrameworks += new TestFramework("weaver.framework.CatsEffect"),
libraryDependencies += "com.monovore" %% "decline" % V.decline,
libraryDependencies += "com.outr" %% "scribe" % V.scribe
)

lazy val `sbt-plugin` = projectMatrix
.jvmPlatform(scalaVersions = Seq(V.scala212))
.in(file("sbt-plugin"))
Expand All @@ -101,7 +120,11 @@ lazy val `mill-plugin` = projectMatrix
name := """mill-vcpkg""",
libraryDependencies += "com.lihaoyi" %% "mill-scalalib" % V.mill,
libraryDependencies += "com.lihaoyi" %% "utest" % V.utest % Test,
testFrameworks += new TestFramework("utest.runner.Framework")
testFrameworks += new TestFramework("utest.runner.Framework"),
Test / fork := true,
Test / envVars := Map(
"MILL_VCPKG_ROOT" -> ((ThisBuild / baseDirectory).value / "mill-plugin" / "src" / "test").toString
)
)

Global / onChangedBuildSource := ReloadOnSourceChanges
Expand Down
163 changes: 163 additions & 0 deletions cli/src/main/scala/CommandLineApp.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package com.indoorvivants.vcpkg
package cli

import cats.implicits.*
import com.monovore.decline.*
import java.io.File

enum Action:
case Install(dependencies: Seq[String])
case InstallManifest(file: File)

object Options extends VcpkgPluginImpl:
private val name = "scala-vcpkg"

private val header = """
|Bootstraps and installs vcpkg dependencies in a way compatible with
|the build tool plugins for SBT or Mill
""".stripMargin.trim

private val vcpkgAllowBootstrap =
Opts
.flag(
"no-bootstrap",
visibility = Visibility.Normal,
help = "Allow bootstrapping vcpkg from scratch"
)
.orTrue

private val envInit = Opts
.option[String](
"vcpkg-root-env",
metavar = "env-var",
visibility = Visibility.Normal,
help = "Pick up vcpkg root from the environment variable"
)
.product(vcpkgAllowBootstrap)
.map[VcpkgRootInit](VcpkgRootInit.FromEnv(_, _))

private val manualInit = Opts
.option[String](
"vcpkg-root-manual",
metavar = "location",
visibility = Visibility.Normal,
help = "Initialise vcpkg in this location"
)
.map(fname => new java.io.File(fname))
.product(vcpkgAllowBootstrap)
.map[VcpkgRootInit](VcpkgRootInit.Manual(_, _))

private val vcpkgRootInit = manualInit
.orElse(envInit)
.orElse(
vcpkgAllowBootstrap
.map[VcpkgRootInit](allow => VcpkgRootInit.SystemCache(allow))
)

private val vcpkgInstallDir = Opts
.option[String](
"vcpkg-install",
metavar = "dir",
help = "folder where packages will be installed"
)
.map(new File(_))
.withDefault(defaultInstallDir)

private val verbose = Opts
.flag(
long = "verbose",
short = "v",
visibility = Visibility.Normal,
help = "Verbose logging"
)
.orFalse

private val actionInstall =
Opts
.arguments[String](metavar = "dep")
.map(_.toList)
.map(Action.Install(_))

private val actionInstallManifest =
Opts
.argument[String](
"vcpkg manifest file"
)
.map(new File(_))
.validate("File should exist")(f => f.exists() && f.isFile())
.map(Action.InstallManifest(_))

val logger = ExternalLogger(
debug = scribe.debug(_),
info = scribe.info(_),
warn = scribe.warn(_),
error = scribe.error(_)
)

case class Config(
rootInit: VcpkgRootInit,
installDir: File,
allowBootstrap: Boolean,
verbose: Boolean
)

private val configOpts =
(vcpkgRootInit, vcpkgInstallDir, vcpkgAllowBootstrap, verbose).mapN(
Config.apply
)

private val install =
Opts.subcommand("install", "Install a list of vcpkg dependencies")(
(actionInstall, configOpts).tupled
)

private val installManifest = Opts.subcommand(
"install-manifest",
"Install vcpkg dependencies from a manifest file (like vcpkg.json)"
)(
(actionInstallManifest, configOpts).tupled
)

val opts = Command(name, header)(install orElse installManifest)

end Options

object VcpkgCLI extends VcpkgPluginImpl:
import Options.*

def main(args: Array[String]): Unit =
opts.parse(args) match
case Left(help) =>
System.err.println(help)
if help.errors.nonEmpty then sys.exit(1)
else sys.exit(0)
case Right((action, config)) =>
import config.*
if verbose then
scribe.Logger.root.withMinimumLevel(scribe.Level.Trace).replace()

val root = rootInit.locate(logger).fold(sys.error(_), identity)
scribe.debug(s"Locating/bootstrapping vcpkg in ${root.file}")

val binary = vcpkgBinaryImpl(root, logger)
scribe.debug(s"Binary is $binary")

val manager = VcpkgBootstrap.manager(binary, installDir, logger)

action match
case Action.Install(dependencies) =>
scribe.info(
"Installed dependencies: ",
vcpkgInstallImpl(dependencies.toSet, manager, logger)
.map(_._1.name)
.mkString(", ")
)
case Action.InstallManifest(file) =>
scribe.info(
"Installed dependencies: ",
vcpkgInstallManifestImpl(file, manager, logger)
.map(_._1.name)
.mkString(", ")
)
end match
end VcpkgCLI
10 changes: 8 additions & 2 deletions core/src/main/scala/com/indoorvivants/vcpkg/Vcpkg.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ class Vcpkg(
private def cmdSeq(args: Seq[String]) =
Seq(binary.toString) ++ args ++ Seq(localArg, rootArg)

private def getLines(args: Seq[String]): Vector[String] = {
private def getLines(args: Seq[String], cwd: File = root): Vector[String] = {
import sys.process.Process
val logs = Logs.logCollector(
out = Set(Logs.Buffer, Logs.Redirect(logger.debug)),
err = Set(Logs.Buffer, Logs.Redirect(logger.debug))
)
logger.debug(s"Executing ${args.mkString("[", " ", "]")}")
val p = Process.apply(args, cwd = root).run(logs.logger).exitValue()
val p = Process.apply(args, cwd = cwd).run(logs.logger).exitValue()

if (p != 0) {
logs.dump(logger.error)
Expand All @@ -55,6 +55,12 @@ class Vcpkg(
def install(name: String): Vector[String] =
getLines(cmd("install", name, s"--triplet=$vcpkgTriplet", "--recurse"))

def installManifest(file: File): Vector[String] =
getLines(
cmd("install", s"--triplet=$vcpkgTriplet"),
cwd = file.getParentFile()
)

def installAll(names: Seq[String]): Vector[String] =
getLines(
cmdSeq(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

33 changes: 31 additions & 2 deletions core/src/main/scala/com/indoorvivants/vcpkg/VcpkgPluginImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import scala.util.Try
import scala.util.Failure
import scala.util.Success
import com.indoorvivants.detective.Platform
import java.nio.file.Files

/** A bunch of build-tool agnostic functions. The trait can be mixed in SBT's or
* Mill's native plugin constructs, which can then delegate to these functions,
Expand Down Expand Up @@ -60,6 +61,34 @@ trait VcpkgPluginImpl {
go(maxAttempts)
}

protected def vcpkgInstallManifestImpl(
manifest: File,
manager: Vcpkg,
logger: ExternalLogger
): Map[Dependency, FilesInfo] = {
val tempDir = Files.createTempDirectory("vcpkg-manifest-install")
val manifestFile =
Files.copy(manifest.toPath, tempDir.resolve("vcpkg.json"))

logger.debug(
s"Installing dependencies from manifest file $manifest (using a working directory $tempDir)"
)

VcpkgPluginImpl.synchronized {
manager.installManifest(manifestFile.toFile)

InstalledList.parse(manager.list(), logger).deps.toSet

InstalledList
.parse(manager.list(), logger)
.deps
.map { dep =>
dep -> files(dep.name, manager.config)
}
.toMap
}
}

protected def vcpkgInstallImpl(
dependencies: Set[String],
manager: Vcpkg,
Expand Down Expand Up @@ -93,7 +122,7 @@ trait VcpkgPluginImpl {
val dependenciesToInstall =
allActualDependencies.filterNot(allInstalledDependencies.contains(_))

logger.info(
logger.debug(
"Already installed dependencies: " + allInstalledDependencies
.map(_.short)
.toList
Expand Down Expand Up @@ -167,4 +196,4 @@ trait VcpkgPluginImpl {

}

object VcpkgPluginImpl
object VcpkgPluginImpl extends VcpkgPluginImpl
Loading

0 comments on commit 4b7f822

Please sign in to comment.