Implements nimble run. Fixes #614.

This commit is contained in:
Dominik Picheta 2019-09-21 23:34:02 +01:00
commit c834faf60e
5 changed files with 216 additions and 59 deletions

View file

@ -4,6 +4,7 @@
import system except TResult import system except TResult
import os, tables, strtabs, json, algorithm, sets, uri, sugar, sequtils import os, tables, strtabs, json, algorithm, sets, uri, sugar, sequtils
import std/options as std_opt
import strutils except toLower import strutils except toLower
from unicode import toLower from unicode import toLower
@ -213,7 +214,10 @@ proc processDeps(pkginfo: PackageInfo, options: Options): seq[PackageInfo] =
for i in reverseDeps: for i in reverseDeps:
addRevDep(options.nimbleData, i, pkginfo) 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``. ## Builds a package as specified by ``pkgInfo``.
if pkgInfo.bin.len == 0: if pkgInfo.bin.len == 0:
raise newException(NimbleError, raise newException(NimbleError,
@ -223,6 +227,12 @@ proc buildFromDir(pkgInfo: PackageInfo, paths, args: seq[string]) =
let realDir = pkgInfo.getRealDir() let realDir = pkgInfo.getRealDir()
for path in paths: args.add("--path:\"" & path & "\" ") for path in paths: args.add("--path:\"" & path & "\" ")
for bin in pkgInfo.bin: 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) & "\"" let outputOpt = "-o:\"" & pkgInfo.getOutputDir(bin) & "\""
display("Building", "$1/$2 using $3 backend" % display("Building", "$1/$2 using $3 backend" %
[pkginfo.name, bin, pkgInfo.backend], priority = HighPriority) [pkginfo.name, bin, pkgInfo.backend], priority = HighPriority)
@ -502,12 +512,12 @@ proc build(options: Options) =
nimScriptHint(pkgInfo) nimScriptHint(pkgInfo)
let deps = processDeps(pkginfo, options) let deps = processDeps(pkginfo, options)
let paths = deps.map(dep => dep.getRealDir()) let paths = deps.map(dep => dep.getRealDir())
var args = options.action.compileOptions var args = options.getCompilationFlags()
buildFromDir(pkgInfo, paths, args) buildFromDir(pkgInfo, paths, args, options.getCompilationBinary())
proc execBackend(options: Options) = proc execBackend(options: Options) =
let let
bin = options.action.file bin = options.getCompilationBinary().get()
binDotNim = bin.addFileExt("nim") binDotNim = bin.addFileExt("nim")
if bin == "": if bin == "":
raise newException(NimbleError, "You need to specify a file.") raise newException(NimbleError, "You need to specify a file.")
@ -522,7 +532,7 @@ proc execBackend(options: Options) =
var args = "" var args = ""
for dep in deps: args.add("--path:\"" & dep.getRealDir() & "\" ") for dep in deps: args.add("--path:\"" & dep.getRealDir() & "\" ")
for option in options.action.compileOptions: for option in options.getCompilationFlags():
args.add("\"" & option & "\" ") args.add("\"" & option & "\" ")
let backend = let backend =
@ -1011,9 +1021,9 @@ proc test(options: Options) =
optsCopy.action = Action(typ: actionCompile) optsCopy.action = Action(typ: actionCompile)
optsCopy.action.file = file.path optsCopy.action.file = file.path
optsCopy.action.backend = "c" optsCopy.action.backend = "c"
optsCopy.action.compileOptions = @[] optsCopy.getCompilationFlags() = @[]
optsCopy.action.compileOptions.add("-r") optsCopy.getCompilationFlags().add("-r")
optsCopy.action.compileOptions.add("--path:.") optsCopy.getCompilationFlags().add("--path:.")
let let
binFileName = file.path.changeFileExt(ExeExt) binFileName = file.path.changeFileExt(ExeExt)
existsBefore = existsFile(binFileName) existsBefore = existsFile(binFileName)
@ -1058,6 +1068,28 @@ proc check(options: Options) =
display("Failure:", "Validation failed", Error, HighPriority) display("Failure:", "Validation failed", Error, HighPriority)
quit(QuitFailure) 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) = proc doAction(options: Options) =
if options.showHelp: if options.showHelp:
writeHelp() writeHelp()
@ -1094,6 +1126,8 @@ proc doAction(options: Options) =
listPaths(options) listPaths(options)
of actionBuild: of actionBuild:
build(options) build(options)
of actionRun:
run(options)
of actionCompile, actionDoc: of actionCompile, actionDoc:
execBackend(options) execBackend(options)
of actionInit: of actionInit:

View file

@ -2,6 +2,8 @@
# BSD License. Look at license.txt for more info. # BSD License. Look at license.txt for more info.
import json, strutils, os, parseopt, strtabs, uri, tables, terminal import json, strutils, os, parseopt, strtabs, uri, tables, terminal
import sequtils, sugar
import std/options as std_opt
from httpclient import Proxy, newProxy from httpclient import Proxy, newProxy
import config, version, common, cli import config, version, common, cli
@ -26,12 +28,15 @@ type
continueTestsOnFailure*: bool continueTestsOnFailure*: bool
## Whether packages' repos should always be downloaded with their history. ## Whether packages' repos should always be downloaded with their history.
forceFullClone*: bool forceFullClone*: bool
# Temporary storage of flags that have not been captured by any specific Action.
unknownFlags*: seq[(CmdLineKind, string, string)]
ActionType* = enum ActionType* = enum
actionNil, actionRefresh, actionInit, actionDump, actionPublish, actionNil, actionRefresh, actionInit, actionDump, actionPublish,
actionInstall, actionSearch, actionInstall, actionSearch,
actionList, actionBuild, actionPath, actionUninstall, actionCompile, actionList, actionBuild, actionPath, actionUninstall, actionCompile,
actionDoc, actionCustom, actionTasks, actionDevelop, actionCheck actionDoc, actionCustom, actionTasks, actionDevelop, actionCheck,
actionRun
Action* = object Action* = object
case typ*: ActionType case typ*: ActionType
@ -49,7 +54,11 @@ type
of actionCompile, actionDoc, actionBuild: of actionCompile, actionDoc, actionBuild:
file*: string file*: string
backend*: string backend*: string
compileOptions*: seq[string] compileOptions: seq[string]
of actionRun:
runFile: string
compileFlags: seq[string]
runFlags*: seq[string]
of actionCustom: of actionCustom:
command*: string command*: string
arguments*: seq[string] arguments*: seq[string]
@ -76,7 +85,8 @@ Commands:
toplevel directory of the Nimble package. toplevel directory of the Nimble package.
uninstall [pkgname, ...] Uninstalls a list of packages. uninstall [pkgname, ...] Uninstalls a list of packages.
[-i, --inclDeps] Uninstall package and dependent package(s). [-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 c, cc, js [opts, ...] f.nim Builds a file inside a package. Passes options
to the Nim compiler. to the Nim compiler.
test Compiles and executes tests test Compiles and executes tests
@ -143,6 +153,8 @@ proc parseActionType*(action: string): ActionType =
result = actionPath result = actionPath
of "build": of "build":
result = actionBuild result = actionBuild
of "run":
result = actionRun
of "c", "compile", "js", "cpp", "cc": of "c", "compile", "js", "cpp", "cc":
result = actionCompile result = actionCompile
of "doc", "doc2": of "doc", "doc2":
@ -196,7 +208,7 @@ proc initAction*(options: var Options, key: string) =
options.action.command = key options.action.command = key
options.action.arguments = @[] options.action.arguments = @[]
options.action.flags = newStringTable() options.action.flags = newStringTable()
of actionPublish, actionList, actionTasks, actionCheck, of actionPublish, actionList, actionTasks, actionCheck, actionRun,
actionNil: discard actionNil: discard
proc prompt*(options: Options, question: string): bool = proc prompt*(options: Options, question: string): bool =
@ -271,18 +283,37 @@ proc parseArgument*(key: string, result: var Options) =
result.action.projName = key result.action.projName = key
of actionCompile, actionDoc: of actionCompile, actionDoc:
result.action.file = key result.action.file = key
of actionList, actionBuild, actionPublish: of actionList, actionPublish:
result.showHelp = true 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: of actionCustom:
result.action.arguments.add(key) result.action.arguments.add(key)
else: else:
discard 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) = proc parseFlag*(flag, val: string, result: var Options, kind = cmdLongOption) =
var wasFlagHandled = true
let f = flag.normalize() let f = flag.normalize()
# Global flags. # Global flags.
var isGlobalFlag = true
case f case f
of "help", "h": result.showHelp = true of "help", "h": result.showHelp = true
of "version", "v": result.showVersion = 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 "debug": result.verbosity = DebugPriority
of "nocolor": result.noColor = true of "nocolor": result.noColor = true
of "disablevalidation": result.disableValidation = true of "disablevalidation": result.disableValidation = true
else: isGlobalFlag = false
var wasFlagHandled = true
# Action-specific flags. # Action-specific flags.
else: case result.action.typ
case result.action.typ of actionSearch, actionList:
of actionSearch, actionList: case f
case f of "installed", "i":
of "installed", "i": result.queryInstalled = true
result.queryInstalled = true of "ver":
of "ver": result.queryVersions = true
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
else: else:
wasFlagHandled = false 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: if not wasFlagHandled and not isGlobalFlag:
raise newException(NimbleError, "Unknown option: --" & flag) result.unknownFlags.add((kind, flag, val))
proc initOptions*(): Options = proc initOptions(): Options =
result.action = Action(typ: actionNil) Options(
result.pkgInfoCache = newTable[string, PackageInfo]() action: Action(typ: actionNil),
result.nimbleDir = "" pkgInfoCache: newTable[string, PackageInfo](),
result.verbosity = HighPriority verbosity: HighPriority,
result.noColor = not isatty(stdout) noColor: not isatty(stdout)
)
proc parseMisc(options: var Options) = proc parseMisc(options: var Options) =
# Load nimbledata.json # Load nimbledata.json
@ -355,6 +388,20 @@ proc parseMisc(options: var Options) =
else: else:
options.nimbleData = %{"reverseDeps": newJObject()} 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 = proc parseCmdLine*(): Options =
result = initOptions() result = initOptions()
@ -371,6 +418,8 @@ proc parseCmdLine*(): Options =
parseFlag(key, val, result, kind) parseFlag(key, val, result, kind)
of cmdEnd: assert(false) # cannot happen of cmdEnd: assert(false) # cannot happen
handleUnknownFlags(result)
# Set verbosity level. # Set verbosity level.
setVerbosity(result.verbosity) setVerbosity(result.verbosity)
@ -386,6 +435,11 @@ proc parseCmdLine*(): Options =
if result.action.typ == actionNil and not result.showVersion: if result.action.typ == actionNil and not result.showVersion:
result.showHelp = true 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 = proc getProxy*(options: Options): Proxy =
## Returns ``nil`` if no proxy is specified. ## Returns ``nil`` if no proxy is specified.
var url = "" var url = ""
@ -431,4 +485,30 @@ proc shouldRemoveTmp*(options: Options, file: string): bool =
if options.verbosity <= DebugPriority: if options.verbosity <= DebugPriority:
let msg = "Not removing temporary path because of debug verbosity: " & file let msg = "Not removing temporary path because of debug verbosity: " & file
display("Warning:", msg, Warning, MediumPriority) display("Warning:", msg, Warning, MediumPriority)
return false 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

14
tests/run/run.nimble Normal file
View file

@ -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"

4
tests/run/src/run.nim Normal file
View file

@ -0,0 +1,4 @@
import os
when isMainModule:
echo("Testing `nimble run`: ", commandLineParams())

View file

@ -33,8 +33,8 @@ template cd*(dir: string, body: untyped) =
proc execNimble(args: varargs[string]): tuple[output: string, exitCode: int] = proc execNimble(args: varargs[string]): tuple[output: string, exitCode: int] =
var quotedArgs = @args var quotedArgs = @args
quotedArgs.insert("--nimbleDir:" & installDir)
quotedArgs.insert(nimblePath) quotedArgs.insert(nimblePath)
quotedArgs.add("--nimbleDir:" & installDir)
quotedArgs = quotedArgs.map((x: string) => ("\"" & x & "\"")) quotedArgs = quotedArgs.map((x: string) => ("\"" & x & "\""))
let path {.used.} = getCurrentDir().parentDir() / "src" 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 cmd = "DYLD_LIBRARY_PATH=/usr/local/opt/openssl@1.1/lib " & cmd
result = execCmdEx(cmd) result = execCmdEx(cmd)
checkpoint(cmd)
checkpoint(result.output) checkpoint(result.output)
proc execNimbleYes(args: varargs[string]): tuple[output: string, exitCode: int]= 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 exitCode == QuitSuccess
check output.contains("Got it") 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": test "compilation without warnings":
const buildDir = "./buildDir/" const buildDir = "./buildDir/"
const filesToBuild = [ const filesToBuild = [