Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Manifest and cli #72

Merged
merged 3 commits into from
Nov 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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