From c834faf60e1dbdd8ae46456e1fb2dc2db05e4e91 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sat, 21 Sep 2019 23:34:02 +0100 Subject: [PATCH] Implements `nimble run`. Fixes #614. --- src/nimble.nim | 50 +++++++++-- src/nimblepkg/options.nim | 180 +++++++++++++++++++++++++++----------- tests/run/run.nimble | 14 +++ tests/run/src/run.nim | 4 + tests/tester.nim | 27 +++++- 5 files changed, 216 insertions(+), 59 deletions(-) create mode 100644 tests/run/run.nimble create mode 100644 tests/run/src/run.nim diff --git a/src/nimble.nim b/src/nimble.nim index 666a60f..7eb5ee4 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -4,6 +4,7 @@ import system except TResult import os, tables, strtabs, json, algorithm, sets, uri, sugar, sequtils +import std/options as std_opt import strutils except toLower from unicode import toLower @@ -213,7 +214,10 @@ proc processDeps(pkginfo: PackageInfo, options: Options): seq[PackageInfo] = for i in reverseDeps: addRevDep(options.nimbleData, i, pkginfo) -proc buildFromDir(pkgInfo: PackageInfo, paths, args: seq[string]) = +proc buildFromDir( + pkgInfo: PackageInfo, paths, args: seq[string], + binToBuild: Option[string] = none[string]() +) = ## Builds a package as specified by ``pkgInfo``. if pkgInfo.bin.len == 0: raise newException(NimbleError, @@ -223,6 +227,12 @@ proc buildFromDir(pkgInfo: PackageInfo, paths, args: seq[string]) = let realDir = pkgInfo.getRealDir() for path in paths: args.add("--path:\"" & path & "\" ") for bin in pkgInfo.bin: + # Check if this is the only binary that we want to build. + if binToBuild.isSome() and binToBuild.get() != bin: + let binToBuild = binToBuild.get() + if bin.extractFilename().changeFileExt("") != binToBuild: + continue + let outputOpt = "-o:\"" & pkgInfo.getOutputDir(bin) & "\"" display("Building", "$1/$2 using $3 backend" % [pkginfo.name, bin, pkgInfo.backend], priority = HighPriority) @@ -502,12 +512,12 @@ proc build(options: Options) = nimScriptHint(pkgInfo) let deps = processDeps(pkginfo, options) let paths = deps.map(dep => dep.getRealDir()) - var args = options.action.compileOptions - buildFromDir(pkgInfo, paths, args) + var args = options.getCompilationFlags() + buildFromDir(pkgInfo, paths, args, options.getCompilationBinary()) proc execBackend(options: Options) = let - bin = options.action.file + bin = options.getCompilationBinary().get() binDotNim = bin.addFileExt("nim") if bin == "": raise newException(NimbleError, "You need to specify a file.") @@ -522,7 +532,7 @@ proc execBackend(options: Options) = var args = "" for dep in deps: args.add("--path:\"" & dep.getRealDir() & "\" ") - for option in options.action.compileOptions: + for option in options.getCompilationFlags(): args.add("\"" & option & "\" ") let backend = @@ -1011,9 +1021,9 @@ proc test(options: Options) = optsCopy.action = Action(typ: actionCompile) optsCopy.action.file = file.path optsCopy.action.backend = "c" - optsCopy.action.compileOptions = @[] - optsCopy.action.compileOptions.add("-r") - optsCopy.action.compileOptions.add("--path:.") + optsCopy.getCompilationFlags() = @[] + optsCopy.getCompilationFlags().add("-r") + optsCopy.getCompilationFlags().add("--path:.") let binFileName = file.path.changeFileExt(ExeExt) existsBefore = existsFile(binFileName) @@ -1058,6 +1068,28 @@ proc check(options: Options) = display("Failure:", "Validation failed", Error, HighPriority) quit(QuitFailure) +proc run(options: Options) = + # Verify parameters. + let binary = options.getCompilationBinary().get() + if binary.len == 0: + raiseNimbleError("Please specify a binary to run") + + var pkgInfo = getPkgInfo(getCurrentDir(), options) + if binary notin pkgInfo.bin: + raiseNimbleError( + "Binary '$#' is not defined in '$#' package." % [binary, pkgInfo.name] + ) + + let binaryPath = pkgInfo.getOutputDir(binary) + + # Build the binary. + build(options) + + # Now run it. + let args = options.action.runFlags.join(" ") + + doCmd("$# $#" % [binaryPath, args], showOutput = true) + proc doAction(options: Options) = if options.showHelp: writeHelp() @@ -1094,6 +1126,8 @@ proc doAction(options: Options) = listPaths(options) of actionBuild: build(options) + of actionRun: + run(options) of actionCompile, actionDoc: execBackend(options) of actionInit: diff --git a/src/nimblepkg/options.nim b/src/nimblepkg/options.nim index 80a649d..28560e7 100644 --- a/src/nimblepkg/options.nim +++ b/src/nimblepkg/options.nim @@ -2,6 +2,8 @@ # BSD License. Look at license.txt for more info. import json, strutils, os, parseopt, strtabs, uri, tables, terminal +import sequtils, sugar +import std/options as std_opt from httpclient import Proxy, newProxy import config, version, common, cli @@ -26,12 +28,15 @@ type continueTestsOnFailure*: bool ## Whether packages' repos should always be downloaded with their history. forceFullClone*: bool + # Temporary storage of flags that have not been captured by any specific Action. + unknownFlags*: seq[(CmdLineKind, string, string)] ActionType* = enum actionNil, actionRefresh, actionInit, actionDump, actionPublish, actionInstall, actionSearch, actionList, actionBuild, actionPath, actionUninstall, actionCompile, - actionDoc, actionCustom, actionTasks, actionDevelop, actionCheck + actionDoc, actionCustom, actionTasks, actionDevelop, actionCheck, + actionRun Action* = object case typ*: ActionType @@ -49,7 +54,11 @@ type of actionCompile, actionDoc, actionBuild: file*: string backend*: string - compileOptions*: seq[string] + compileOptions: seq[string] + of actionRun: + runFile: string + compileFlags: seq[string] + runFlags*: seq[string] of actionCustom: command*: string arguments*: seq[string] @@ -76,7 +85,8 @@ Commands: toplevel directory of the Nimble package. uninstall [pkgname, ...] Uninstalls a list of packages. [-i, --inclDeps] Uninstall package and dependent package(s). - build Builds a package. + build [opts, ...] Builds a package. + run [opts, ...] bin Builds and runs a package. c, cc, js [opts, ...] f.nim Builds a file inside a package. Passes options to the Nim compiler. test Compiles and executes tests @@ -143,6 +153,8 @@ proc parseActionType*(action: string): ActionType = result = actionPath of "build": result = actionBuild + of "run": + result = actionRun of "c", "compile", "js", "cpp", "cc": result = actionCompile of "doc", "doc2": @@ -196,7 +208,7 @@ proc initAction*(options: var Options, key: string) = options.action.command = key options.action.arguments = @[] options.action.flags = newStringTable() - of actionPublish, actionList, actionTasks, actionCheck, + of actionPublish, actionList, actionTasks, actionCheck, actionRun, actionNil: discard proc prompt*(options: Options, question: string): bool = @@ -271,18 +283,37 @@ proc parseArgument*(key: string, result: var Options) = result.action.projName = key of actionCompile, actionDoc: result.action.file = key - of actionList, actionBuild, actionPublish: + of actionList, actionPublish: result.showHelp = true + of actionBuild: + result.action.file = key + of actionRun: + if result.action.runFile.len == 0: + result.action.runFile = key + else: + result.action.runFlags.add(key) of actionCustom: result.action.arguments.add(key) else: discard +proc getFlagString(kind: CmdLineKind, flag, val: string): string = + let prefix = + case kind + of cmdShortOption: "-" + of cmdLongOption: "--" + else: "" + if val == "": + return prefix & flag + else: + return prefix & flag & ":" & val + proc parseFlag*(flag, val: string, result: var Options, kind = cmdLongOption) = - var wasFlagHandled = true + let f = flag.normalize() # Global flags. + var isGlobalFlag = true case f of "help", "h": result.showHelp = true of "version", "v": result.showVersion = true @@ -293,54 +324,56 @@ proc parseFlag*(flag, val: string, result: var Options, kind = cmdLongOption) = of "debug": result.verbosity = DebugPriority of "nocolor": result.noColor = true of "disablevalidation": result.disableValidation = true + else: isGlobalFlag = false + + var wasFlagHandled = true # Action-specific flags. - else: - case result.action.typ - of actionSearch, actionList: - case f - of "installed", "i": - result.queryInstalled = true - of "ver": - result.queryVersions = true - else: - wasFlagHandled = false - of actionInstall: - case f - of "depsonly", "d": - result.depsOnly = true - of "passnim", "p": - result.action.passNimFlags.add(val) - else: - wasFlagHandled = false - of actionUninstall: - case f - of "incldeps", "i": - result.uninstallRevDeps = true - else: - wasFlagHandled = false - of actionCompile, actionDoc, actionBuild: - let prefix = if kind == cmdShortOption: "-" else: "--" - if val == "": - result.action.compileOptions.add(prefix & flag) - else: - result.action.compileOptions.add(prefix & flag & ":" & val) - of actionCustom: - if result.action.command.normalize == "test": - if f == "continue" or f == "c": - result.continueTestsOnFailure = true - result.action.flags[flag] = val + case result.action.typ + of actionSearch, actionList: + case f + of "installed", "i": + result.queryInstalled = true + of "ver": + result.queryVersions = true else: wasFlagHandled = false + of actionInstall: + case f + of "depsonly", "d": + result.depsOnly = true + of "passnim", "p": + result.action.passNimFlags.add(val) + else: + wasFlagHandled = false + of actionUninstall: + case f + of "incldeps", "i": + result.uninstallRevDeps = true + else: + wasFlagHandled = false + of actionCompile, actionDoc, actionBuild: + if not isGlobalFlag: + result.action.compileOptions.add(getFlagString(kind, flag, val)) + of actionRun: + result.action.runFlags.add(getFlagString(kind, flag, val)) + of actionCustom: + if result.action.command.normalize == "test": + if f == "continue" or f == "c": + result.continueTestsOnFailure = true + result.action.flags[flag] = val + else: + wasFlagHandled = false - if not wasFlagHandled: - raise newException(NimbleError, "Unknown option: --" & flag) + if not wasFlagHandled and not isGlobalFlag: + result.unknownFlags.add((kind, flag, val)) -proc initOptions*(): Options = - result.action = Action(typ: actionNil) - result.pkgInfoCache = newTable[string, PackageInfo]() - result.nimbleDir = "" - result.verbosity = HighPriority - result.noColor = not isatty(stdout) +proc initOptions(): Options = + Options( + action: Action(typ: actionNil), + pkgInfoCache: newTable[string, PackageInfo](), + verbosity: HighPriority, + noColor: not isatty(stdout) + ) proc parseMisc(options: var Options) = # Load nimbledata.json @@ -355,6 +388,20 @@ proc parseMisc(options: var Options) = else: options.nimbleData = %{"reverseDeps": newJObject()} +proc handleUnknownFlags(options: var Options) = + if options.action.typ == actionRun: + options.action.compileFlags = + map(options.unknownFlags, x => getFlagString(x[0], x[1], x[2])) + options.unknownFlags = @[] + + # Any unhandled flags? + if options.unknownFlags.len > 0: + let flag = options.unknownFlags[0] + raise newException( + NimbleError, + "Unknown option: " & getFlagString(flag[0], flag[1], flag[2]) + ) + proc parseCmdLine*(): Options = result = initOptions() @@ -371,6 +418,8 @@ proc parseCmdLine*(): Options = parseFlag(key, val, result, kind) of cmdEnd: assert(false) # cannot happen + handleUnknownFlags(result) + # Set verbosity level. setVerbosity(result.verbosity) @@ -386,6 +435,11 @@ proc parseCmdLine*(): Options = if result.action.typ == actionNil and not result.showVersion: result.showHelp = true + if result.action.typ != actionNil and result.showVersion: + # We've got another command that should be handled. For example: + # nimble run foobar -v + result.showVersion = false + proc getProxy*(options: Options): Proxy = ## Returns ``nil`` if no proxy is specified. var url = "" @@ -431,4 +485,30 @@ proc shouldRemoveTmp*(options: Options, file: string): bool = if options.verbosity <= DebugPriority: let msg = "Not removing temporary path because of debug verbosity: " & file display("Warning:", msg, Warning, MediumPriority) - return false \ No newline at end of file + return false + +proc getCompilationFlags*(options: var Options): var seq[string] = + case options.action.typ + of actionBuild, actionDoc, actionCompile: + return options.action.compileOptions + of actionRun: + return options.action.compileFlags + else: + assert false + +proc getCompilationFlags*(options: Options): seq[string] = + var opt = options + return opt.getCompilationFlags() + +proc getCompilationBinary*(options: Options): Option[string] = + case options.action.typ + of actionBuild, actionDoc, actionCompile: + let file = options.action.file.changeFileExt("") + if file.len > 0: + return some(file) + of actionRun: + let runFile = options.action.runFile.changeFileExt("") + if runFile.len > 0: + return some(runFile) + else: + discard \ No newline at end of file diff --git a/tests/run/run.nimble b/tests/run/run.nimble new file mode 100644 index 0000000..4b09bdd --- /dev/null +++ b/tests/run/run.nimble @@ -0,0 +1,14 @@ +# Package + +version = "0.1.0" +author = "Dominik Picheta" +description = "A new awesome nimble package" +license = "MIT" +srcDir = "src" +bin = @["run"] + + + +# Dependencies + +requires "nim >= 0.20.0" diff --git a/tests/run/src/run.nim b/tests/run/src/run.nim new file mode 100644 index 0000000..af98995 --- /dev/null +++ b/tests/run/src/run.nim @@ -0,0 +1,4 @@ +import os + +when isMainModule: + echo("Testing `nimble run`: ", commandLineParams()) diff --git a/tests/tester.nim b/tests/tester.nim index bc9722e..c70c5b7 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -33,8 +33,8 @@ template cd*(dir: string, body: untyped) = proc execNimble(args: varargs[string]): tuple[output: string, exitCode: int] = var quotedArgs = @args + quotedArgs.insert("--nimbleDir:" & installDir) quotedArgs.insert(nimblePath) - quotedArgs.add("--nimbleDir:" & installDir) quotedArgs = quotedArgs.map((x: string) => ("\"" & x & "\"")) let path {.used.} = getCurrentDir().parentDir() / "src" @@ -49,6 +49,7 @@ proc execNimble(args: varargs[string]): tuple[output: string, exitCode: int] = cmd = "DYLD_LIBRARY_PATH=/usr/local/opt/openssl@1.1/lib " & cmd result = execCmdEx(cmd) + checkpoint(cmd) checkpoint(result.output) proc execNimbleYes(args: varargs[string]): tuple[output: string, exitCode: int]= @@ -882,6 +883,30 @@ test "Passing command line arguments to a task (#633)": check exitCode == QuitSuccess check output.contains("Got it") +suite "nimble run": + test "Invalid binary": + cd "run": + var (output, exitCode) = execNimble( + "--debug", # Flag to enable debug verbosity in Nimble + "run", # Run command invokation + "blahblah", # The command to run + ) + check exitCode == QuitFailure + check output.contains("Binary 'blahblah' is not defined in 'run' package.") + + test "Parameters passed to executable": + cd "run": + var (output, exitCode) = execNimble( + "--debug", # Flag to enable debug verbosity in Nimble + "run", # Run command invokation + "run", # The command to run + "--debug", # First argument passed to the executed command + "check" # Second argument passed to the executed command. + ) + check exitCode == QuitSuccess + check output.contains("tests/run/run --debug check") + check output.contains("""Testing `nimble run`: @["--debug", "check"]""") + test "compilation without warnings": const buildDir = "./buildDir/" const filesToBuild = [