From 35ecb09fd825b4dd14dd35a27620eda0bcb79174 Mon Sep 17 00:00:00 2001 From: Anton Sviridov Date: Sun, 30 Jul 2023 16:10:37 +0100 Subject: [PATCH] Clang/Clang++ command (#136) * Don't use install-manifest anymore * Remove install-manifest from docs --- .github/workflows/ci.yml | 15 +- .github/workflows/fixtures/test-sqlite.c | 18 +++ README.md | 104 ++++++++------ build.sbt | 3 +- docs/README.in.md | 60 ++++++-- .../cli/src/main/scala/CommandLineApp.scala | 129 ++++++++++++------ 6 files changed, 237 insertions(+), 92 deletions(-) create mode 100644 .github/workflows/fixtures/test-sqlite.c diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 52bfd4d..5869718 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,15 +61,22 @@ jobs: curl -fLo cs https://github.com/coursier/launchers/raw/master/coursier && chmod +x cs + + ./cs bootstrap com.indoorvivants.vcpkg:sn-vcpkg_3:$(cat version) -f -o local_cli - ./cs launch com.indoorvivants.vcpkg:sn-vcpkg_3:$(cat version) -- bootstrap + ./local_cli bootstrap - ./cs launch com.indoorvivants.vcpkg:sn-vcpkg_3:$(cat version) -- install -v libpq s2n + ./local_cli install -v libpq s2n echo '{"name": "my-application","version": "0.15.2","dependencies": ["sqlite3"]}' > test-vcpkg.json - ./cs launch com.indoorvivants.vcpkg:sn-vcpkg_3:$(cat version) -- install-manifest -v test-vcpkg.json + ./local_cli install --manifest test-vcpkg.json + + ./local_cli clang sqlite3 -- .github/workflows/fixtures/test-sqlite.c + ./local_cli clang --manifest test-vcpkg.json -- .github/workflows/fixtures/test-sqlite.c + + ./local_cli clang++ sqlite3 -- .github/workflows/fixtures/test-sqlite.c + ./local_cli clang++ --manifest test-vcpkg.json -- .github/workflows/fixtures/test-sqlite.c - ./cs launch com.indoorvivants.vcpkg:sn-vcpkg_3:$(cat version) -- install -q -c -l libpq s2n windows_build: diff --git a/.github/workflows/fixtures/test-sqlite.c b/.github/workflows/fixtures/test-sqlite.c new file mode 100644 index 0000000..2d81da0 --- /dev/null +++ b/.github/workflows/fixtures/test-sqlite.c @@ -0,0 +1,18 @@ +#include +#include + +int main(int argc, char* argv[]) { + sqlite3 *db; + char *zErrMsg = 0; + int rc; + + rc = sqlite3_open("test.db", &db); + + if( rc ) { + fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db)); + return(0); + } else { + fprintf(stderr, "Opened database successfully\n"); + } + sqlite3_close(db); +} diff --git a/README.md b/README.md index 951a4b5..37a0c23 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ - [CLI](#cli) - [`bootstrap`](#bootstrap) - [`install`](#install) - - [`install-manifest`](#install-manifest) + - [`clang` and `clang++`](#clang-and-clang) - [Core](#core) - [VcpkgRootInit](#vcpkgrootinit) - [VcpkgNativeConfig](#vcpkgnativeconfig) @@ -75,7 +75,7 @@ There are several modules of interest: You can quickly test it by running: ``` - $ cs launch com.indoorvivants.vcpkg:sn-vcpkg_3:0.0.11 -- install libpq -l -q -c + $ cs launch com.indoorvivants.vcpkg:sn-vcpkg_3:0.0.13 -- install libpq -l -q -c -I<...>/sbt-vcpkg/vcpkg-install/arm64-osx/lib/pkgconfig/../../include -L<...>/sbt-vcpkg/vcpkg-install/arm64-osx/lib/pkgconfig/../../lib -L<...>/sbt-vcpkg/vcpkg-install/arm64-osx/lib/pkgconfig/../../lib/pkgconfig/../../lib @@ -101,7 +101,7 @@ There are several modules of interest: For SBT, add this to your `project/plugins.sbt`: ```scala -addSbtPlugin("com.indoorvivants.vcpkg" % "sbt-vcpkg" % "0.0.11") +addSbtPlugin("com.indoorvivants.vcpkg" % "sbt-vcpkg" % "0.0.13") ``` And in your build.sbt: @@ -137,7 +137,7 @@ Tasks and settings (find them all by doing `help vcpkg*` in SBT shell): Add dependency to your `build.sc`: ```scala -import $ivy.`com.indoorvivants.vcpkg::mill-vcpkg:0.0.11` +import $ivy.`com.indoorvivants.vcpkg::mill-vcpkg:0.0.13` ``` And use the `VcpkgModule` mixin: @@ -164,7 +164,7 @@ The Mill tasks are the same as in the SBT plugin In `project/plugins.sbt`: ```scala -addSbtPlugin("com.indoorvivants.vcpkg" % "sbt-vcpkg-native" % "0.0.11") +addSbtPlugin("com.indoorvivants.vcpkg" % "sbt-vcpkg-native" % "0.0.13") ``` In `build.sbt`: @@ -194,7 +194,7 @@ For real world usage, see [Examples](#examples). Add dependency to your `build.sc`: ```scala -import $ivy.`com.indoorvivants.vcpkg::mill-vcpkg-native:0.0.11` +import $ivy.`com.indoorvivants.vcpkg::mill-vcpkg-native:0.0.13` ``` And use the `VcpkgNativeModule` mixin: @@ -268,18 +268,24 @@ Options and flags: #### `install` -Install one or several dependencies, and optionally output linking/compilation flags for all of them. +Install one or several dependencies, by name or from a manifest file, and optionally output linking/compilation flags for all of them. -Example: `sn-vcpkg install libgit2 cjson -l -c` +Examples: +- `sn-vcpkg install libgit2 cjson -l -c` +- `sn-vcpkg install --manifest vcpkg.json -l -c` ``` -Usage: sn-vcpkg install [--output-compilation] [--output-linking] [--vcpkg-root-manual [--no-bootstrap] | --vcpkg-root-env [--no-bootstrap] | --no-bootstrap] [--vcpkg-install ] [--no-bootstrap] [--verbose] [--quiet] ... +Usage: + sn-vcpkg install --manifest [--output-compilation] [--output-linking] [--vcpkg-root-manual [--no-bootstrap] | --vcpkg-root-env [--no-bootstrap] | --no-bootstrap] [--vcpkg-install ] [--no-bootstrap] [--verbose] [--quiet] + sn-vcpkg install [--output-compilation] [--output-linking] [--vcpkg-root-manual [--no-bootstrap] | --vcpkg-root-env [--no-bootstrap] | --no-bootstrap] [--vcpkg-install ] [--no-bootstrap] [--verbose] [--quiet] ... Install a list of vcpkg dependencies Options and flags: --help Display this help text. + --manifest + vcpkg manifest file --output-compilation, -c Output (to STDOUT) compilation flags for installed libraries, one per line --output-linking, -l @@ -298,39 +304,59 @@ Options and flags: Only error logging ``` -#### `install-manifest` +#### `clang` and `clang++` -Install dependencies from a manifest file, and optionally output linking/compilation flags for all of them. +These commands invoke clang or clang++ with all the configuration +flags required [^1] to run the specified dependencies. -Example: `sn-vcpkg install-manifest vcpkg.json -l -c` +For example, say you have a snippet of C code that needs sqlite3 dependency: +```c +#include +#include + +int main(int argc, char* argv[]) { + sqlite3 *db; + char *zErrMsg = 0; + int rc; + + rc = sqlite3_open("test.db", &db); + + if( rc ) { + fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db)); + return(0); + } else { + fprintf(stderr, "Opened database successfully\n"); + } + sqlite3_close(db); +} ``` -Usage: sn-vcpkg install-manifest [--output-compilation] [--output-linking] [--vcpkg-root-manual [--no-bootstrap] | --vcpkg-root-env [--no-bootstrap] | --no-bootstrap] [--vcpkg-install ] [--no-bootstrap] [--verbose] [--quiet] -Install vcpkg dependencies from a manifest file (like vcpkg.json) +You can compile it directly by running -Options and flags: - --help - Display this help text. - --output-compilation, -c - Output (to STDOUT) compilation flags for installed libraries, one per line - --output-linking, -l - Output (to STDOUT) linking flags for installed libraries, one per line - --vcpkg-root-manual - Initialise vcpkg in this location - --no-bootstrap - Allow bootstrapping vcpkg from scratch - --vcpkg-root-env - Pick up vcpkg root from the environment variable - --vcpkg-install - folder where packages will be installed - --verbose, -v - Verbose logging - --quiet, -q - Only error logging +``` +sn-vcpkg clang sqlite3 -- test-sqlite.c ``` +Or if you have a vcpkg manifest file: +```json +{ + "name": "my-application", + "version": "0.15.2", + "dependencies": ["sqlite3"] +} +``` + +You can use that as well: + +``` +sn-vcpkg clang --manifest vcpkg.json -- test-sqlite.c +``` + +All the arguments after `--` will be passed to clang/clang++ without modification (_before_ the flags calculated for dependencies) + +[^1]: as long as the dependencies themselves provide a well configured pkg-config file, of course ### Core @@ -359,7 +385,7 @@ compilation arguments from installed vcpkg dependencies. **Defaults** ```scala VcpkgNativeConfig() -// res3: VcpkgNativeConfig = Vcpkg NativeConfig: +// res2: VcpkgNativeConfig = Vcpkg NativeConfig: // | approximate = true // | autoConfigure = true // | prependCompileOptions = true @@ -404,7 +430,7 @@ VcpkgNativeConfig().withPrependLinkingOptions(true) ```scala // Completely overwrite VcpkgNativeConfig().withRenamedLibraries(Map("cjson" -> "libcjson", "cmark" -> "libcmark")) -// res8: VcpkgNativeConfig = Vcpkg NativeConfig: +// res7: VcpkgNativeConfig = Vcpkg NativeConfig: // | approximate = true // | autoConfigure = true // | prependCompileOptions = true @@ -414,7 +440,7 @@ VcpkgNativeConfig().withRenamedLibraries(Map("cjson" -> "libcjson", "cmark" -> " // Append only VcpkgNativeConfig().addRenamedLibrary("cjson", "libcjson") -// res9: VcpkgNativeConfig = Vcpkg NativeConfig: +// res8: VcpkgNativeConfig = Vcpkg NativeConfig: // | approximate = true // | autoConfigure = true // | prependCompileOptions = true @@ -431,7 +457,7 @@ Specification for vcpkg dependencies. Can be either: ```scala VcpkgDependencies("cmark", "cjson") -// res10: VcpkgDependencies = Names( +// res9: VcpkgDependencies = Names( // deps = List( // Dependency(name = "cmark", features = Set()), // Dependency(name = "cjson", features = Set()) @@ -443,14 +469,14 @@ VcpkgDependencies("cmark", "cjson") ```scala VcpkgDependencies(new java.io.File("./vcpkg.json")) -// res11: VcpkgDependencies = ManifestFile(path = ./vcpkg.json) +// res10: VcpkgDependencies = ManifestFile(path = ./vcpkg.json) ``` - a list of detailed dependency specs: ```scala VcpkgDependencies.Names(List(Dependency("libpq", Set("arm-build")), Dependency.parse("cpprestsdk[boost]"))) -// res12: Names = Names( +// res11: Names = Names( // deps = List( // Dependency(name = "libpq", features = Set("arm-build")), // Dependency(name = "cpprestsdk", features = Set("boost")) diff --git a/build.sbt b/build.sbt index e188385..aeda62e 100644 --- a/build.sbt +++ b/build.sbt @@ -76,7 +76,7 @@ lazy val docs = .dependsOn(core.jvm(V.scala3), cli.jvm(V.scala3)) .settings(scalaVersion := V.scala3) .settings( - mdocVariables := Map("VERSION" -> "0.0.11", "SCALA3_VERSION" -> V.scala3) + mdocVariables := Map("VERSION" -> "0.0.13", "SCALA3_VERSION" -> V.scala3) ) lazy val docsDrifted = taskKey[Boolean]("") @@ -153,6 +153,7 @@ lazy val cli = projectMatrix .settings( name := "sn-vcpkg", run / fork := true, + run / baseDirectory := (ThisBuild / baseDirectory).value, testFrameworks += new TestFramework("weaver.framework.CatsEffect"), libraryDependencies += "com.monovore" %% "decline" % V.decline, libraryDependencies += "io.circe" %% "circe-parser" % V.circe, diff --git a/docs/README.in.md b/docs/README.in.md index 3b0c695..a5cf8c0 100644 --- a/docs/README.in.md +++ b/docs/README.in.md @@ -14,7 +14,7 @@ - [CLI](#cli) - [`bootstrap`](#bootstrap) - [`install`](#install) - - [`install-manifest`](#install-manifest) + - [`clang` and `clang++`](#clang-and-clang) - [Core](#core) - [VcpkgRootInit](#vcpkgrootinit) - [VcpkgNativeConfig](#vcpkgnativeconfig) @@ -252,9 +252,11 @@ println(s"```\n$help\n```") #### `install` -Install one or several dependencies, and optionally output linking/compilation flags for all of them. +Install one or several dependencies, by name or from a manifest file, and optionally output linking/compilation flags for all of them. -Example: `sn-vcpkg install libgit2 cjson -l -c` +Examples: +- `sn-vcpkg install libgit2 cjson -l -c` +- `sn-vcpkg install --manifest vcpkg.json -l -c` ```scala mdoc:passthrough val helpInstall = com.indoorvivants.vcpkg.cli.Options.opts.parse(Array("install", "--help")).fold(identity, _ => ???) @@ -262,19 +264,59 @@ val helpInstall = com.indoorvivants.vcpkg.cli.Options.opts.parse(Array("install" println(s"```\n$helpInstall\n```") ``` -#### `install-manifest` +#### `clang` and `clang++` -Install dependencies from a manifest file, and optionally output linking/compilation flags for all of them. +These commands invoke clang or clang++ with all the configuration +flags required [^1] to run the specified dependencies. -Example: `sn-vcpkg install-manifest vcpkg.json -l -c` +For example, say you have a snippet of C code that needs sqlite3 dependency: -```scala mdoc:passthrough -val helpManifest = com.indoorvivants.vcpkg.cli.Options.opts.parse(Array("install-manifest", "--help")).fold(identity, _ => ???) +```c +#include +#include + +int main(int argc, char* argv[]) { + sqlite3 *db; + char *zErrMsg = 0; + int rc; + + rc = sqlite3_open("test.db", &db); + + if( rc ) { + fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db)); + return(0); + } else { + fprintf(stderr, "Opened database successfully\n"); + } + sqlite3_close(db); +} +``` + +You can compile it directly by running + +``` +sn-vcpkg clang sqlite3 -- test-sqlite.c +``` + +Or if you have a vcpkg manifest file: + +```json +{ + "name": "my-application", + "version": "0.15.2", + "dependencies": ["sqlite3"] +} +``` -println(s"```\n$helpManifest\n```") +You can use that as well: + +``` +sn-vcpkg clang --manifest vcpkg.json -- test-sqlite.c ``` +All the arguments after `--` will be passed to clang/clang++ without modification (_before_ the flags calculated for dependencies) +[^1]: as long as the dependencies themselves provide a well configured pkg-config file, of course ### Core diff --git a/modules/cli/src/main/scala/CommandLineApp.scala b/modules/cli/src/main/scala/CommandLineApp.scala index 3960aa2..2ff27ac 100644 --- a/modules/cli/src/main/scala/CommandLineApp.scala +++ b/modules/cli/src/main/scala/CommandLineApp.scala @@ -8,10 +8,23 @@ import io.circe.Codec import io.circe.CodecDerivation import io.circe.Decoder +enum Compiler: + case Clang, ClangPP + enum Action: + case Install(dependencies: VcpkgDependencies, out: OutputOptions) + extends Action + case InvokeCompiler( + compiler: Compiler, + dependencies: VcpkgDependencies, + args: Seq[String] + ) extends Action case Bootstrap - case Install(dependencies: Seq[String], out: OutputOptions) extends Action - case InstallManifest(file: File, out: OutputOptions) extends Action +end Action + +case class SuspendedAction(apply: Seq[String] => Action) +object SuspendedAction: + def immediate(action: Action) = SuspendedAction(_ => action) case class OutputOptions(compile: Boolean, linking: Boolean) @@ -107,26 +120,23 @@ object Options extends VcpkgPluginImpl: private val out = (outputCompile, outputLinking).mapN(OutputOptions.apply) - private val actionInstall = - ( - Opts - .arguments[String](metavar = "dep") - .map(_.toList), - out, - ) - .mapN(Action.Install.apply) - - private val actionInstallManifest = - ( - Opts - .argument[String]( - "vcpkg manifest file" - ) - .map(new File(_)) - .validate("File should exist")(f => f.exists() && f.isFile()), - out - ) - .mapN(Action.InstallManifest.apply) + private val deps = + Opts + .option[String]( + "manifest", + help = "vcpkg manifest file" + ) + .map(new File(_)) + .validate("File should exist")(f => f.exists() && f.isFile()) + .map(VcpkgDependencies.apply) + .orElse( + Opts + .arguments[String](metavar = "dep") + .map(_.toList) + .map(deps => VcpkgDependencies.apply(deps*)) + ) + + private val actionInstall = (deps, out).mapN(Action.Install.apply) val logger = ExternalLogger( debug = scribe.debug(_), @@ -150,23 +160,37 @@ object Options extends VcpkgPluginImpl: private val install = Opts.subcommand("install", "Install a list of vcpkg dependencies")( - (actionInstall, configOpts).tupled + (actionInstall.map(SuspendedAction.immediate), configOpts).tupled ) private val bootstrap = Opts.subcommand("bootstrap", "Bootstrap vcpkg")( - configOpts.map(Action.Bootstrap -> _) + configOpts.map(SuspendedAction.immediate(Action.Bootstrap) -> _) ) - private val installManifest = Opts.subcommand( - "install-manifest", - "Install vcpkg dependencies from a manifest file (like vcpkg.json)" - )( - (actionInstallManifest, configOpts).tupled + private def compilerCommand(compiler: Compiler) = deps.map(d => + SuspendedAction(rest => Action.InvokeCompiler(compiler, d, rest)) ) + private val clang = + Opts.subcommand( + "clang", + "Invoke clang with the correct flags for passed dependencies" + + "The format of the command is [sn-vcpkg clang -- " + )( + (compilerCommand(Compiler.Clang), configOpts).tupled + ) + private val clangPP = + Opts.subcommand( + "clang++", + "Invoke clang++ with the correct flags for passed dependencies. \n" + + "The format of the command is [sn-vcpkg clang++ -- " + )( + (compilerCommand(Compiler.ClangPP), configOpts).tupled + ) + val opts = - Command(name, header)(install orElse installManifest orElse bootstrap) + Command(name, header)(install orElse bootstrap orElse clang orElse clangPP) end Options @@ -209,14 +233,17 @@ object VcpkgCLI extends VcpkgPluginImpl, VcpkgPluginNativeImpl: .clearHandlers() .withHandler(writer = scribe.writer.SystemErrWriter) .replace() - opts.parse(args) match + val arguments = args.takeWhile(_ != "--") + val rest = args.drop(arguments.length + 1) + opts.parse(arguments) match case Left(help) => val (modified, code) = if help.errors.nonEmpty then help.copy(body = Nil) -> -1 else help -> 0 System.err.println(modified) if code != 0 then sys.exit(code) - case Right((action, config)) => + case Right((suspended, config)) => + val action = suspended.apply(rest) import config.* if verbose then scribe.Logger.root.withMinimumLevel(scribe.Level.Trace).replace() @@ -268,13 +295,7 @@ object VcpkgCLI extends VcpkgPluginImpl, VcpkgPluginNativeImpl: case Right(value) => val bin = vcpkgBinaryImpl(value, logger) scribe.info(s"Vcpkg bootstrapped in $bin") - case action: (Action.Install | Action.InstallManifest) => - val (output, deps) = action match - case Action.Install(dependencies, out) => - out -> VcpkgDependencies(dependencies*) - case Action.InstallManifest(file, out) => - out -> VcpkgDependencies(file) - + case Action.Install(deps, output) => val result = vcpkgInstallImpl(deps, manager, logger) import io.circe.parser.decode @@ -290,6 +311,36 @@ object VcpkgCLI extends VcpkgPluginImpl, VcpkgPluginNativeImpl: allDeps, result.filter((k, v) => allDeps.contains(k)) ).foreach(println) + case Action.InvokeCompiler(compiler, deps, rest) => + val result = vcpkgInstallImpl(deps, manager, logger) + import io.circe.parser.decode + import C.given + val allDeps = + deps.dependencies(str => + decode[VcpkgManifestFile](str).fold(throw _, identity) + ) + val compilerArgs = List.newBuilder[String] + + compilerArgs += (compiler match + case Compiler.Clang => "clang" + case Compiler.ClangPP => "clang++" + ) + + compilerArgs.addAll(rest) + + printOutput( + OutputOptions(compile = true, linking = true), + allDeps, + result.filter((k, v) => allDeps.contains(k)) + ).foreach(compilerArgs.addOne) + + + scribe.debug(s"Invoking ${compiler} with arguments: [${compilerArgs.result().mkString(" ")}]") + + val exitCode = scala.sys.process.Process(compilerArgs.result()).! + + sys.exit(exitCode) + end match end match end main