better run [feature] (#11709)

* track the checksums of all involved Nim files for smarter 'nim c -r' recompiles
* don't recompile unless necessary for 'nim c -r' [feature]
* [feature] koch boot uses a two step process in order to free the RAM before the GCC/Clang invocations
* fixes a serious regression
This commit is contained in:
Andreas Rumpf 2019-07-11 21:36:23 +02:00 • committed by GitHub
commit 3d88d06b32
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 88 additions and 24 deletions

View file

@ -32,6 +32,9 @@
### Tool changes ### Tool changes
- The Nim compiler now does not recompile the Nim project via ``nim c -r`` if
no dependent Nim file changed. This feature can be overridden by
the ``--forceBuild`` command line option.
### Compiler changes ### Compiler changes

View file

@ -784,7 +784,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
if strutils.find(switch, '.') >= 0: options.setConfigVar(conf, switch, arg) if strutils.find(switch, '.') >= 0: options.setConfigVar(conf, switch, arg)
else: invalidCmdLineOption(conf, pass, switch, info) else: invalidCmdLineOption(conf, pass, switch, info)
template gCmdLineInfo*(): untyped = newLineInfo(config, AbsoluteFile"command line", 1, 1) template gCmdLineInfo*(): untyped = newLineInfo(commandLineIdx, 1, 1)
proc processCommand*(switch: string, pass: TCmdLinePass; config: ConfigRef) = proc processCommand*(switch: string, pass: TCmdLinePass; config: ConfigRef) =
var cmd, arg: string var cmd, arg: string

View file

@ -947,7 +947,7 @@ proc callCCompiler*(conf: ConfigRef) =
generateScript(conf, script) generateScript(conf, script)
#from json import escapeJson #from json import escapeJson
import json import json, std / sha1
proc writeJsonBuildInstructions*(conf: ConfigRef) = proc writeJsonBuildInstructions*(conf: ConfigRef) =
template lit(x: untyped) = f.write x template lit(x: untyped) = f.write x
@ -960,17 +960,17 @@ proc writeJsonBuildInstructions*(conf: ConfigRef) =
f.write escapeJson(x) f.write escapeJson(x)
proc cfiles(conf: ConfigRef; f: File; buf: var string; clist: CfileList, isExternal: bool) = proc cfiles(conf: ConfigRef; f: File; buf: var string; clist: CfileList, isExternal: bool) =
var pastStart = false var i = 0
for it in clist: for it in clist:
if CfileFlag.Cached in it.flags: continue if CfileFlag.Cached in it.flags: continue
let compileCmd = getCompileCFileCmd(conf, it) let compileCmd = getCompileCFileCmd(conf, it)
if pastStart: lit "],\L" if i > 0: lit ",\L"
lit "[" lit "["
str it.cname.string str it.cname.string
lit ", " lit ", "
str compileCmd str compileCmd
pastStart = true lit "]"
lit "]\L" inc i
proc linkfiles(conf: ConfigRef; f: File; buf, objfiles: var string; clist: CfileList; proc linkfiles(conf: ConfigRef; f: File; buf, objfiles: var string; clist: CfileList;
llist: seq[string]) = llist: seq[string]) =
@ -994,6 +994,19 @@ proc writeJsonBuildInstructions*(conf: ConfigRef) =
pastStart = true pastStart = true
lit "\L" lit "\L"
proc nimfiles(conf: ConfigRef; f: File) =
var i = 0
for it in conf.m.fileInfos:
if isAbsolute(it.fullPath.string):
if i > 0: lit "],\L"
lit "["
str it.fullPath.string
lit ", "
str $secureHashFile(it.fullPath.string)
inc i
lit "]\L"
var buf = newStringOfCap(50) var buf = newStringOfCap(50)
let jsonFile = conf.getNimcacheDir / RelativeFile(conf.projectName & ".json") let jsonFile = conf.getNimcacheDir / RelativeFile(conf.projectName & ".json")
@ -1009,9 +1022,37 @@ proc writeJsonBuildInstructions*(conf: ConfigRef) =
lit "],\L\"linkcmd\": " lit "],\L\"linkcmd\": "
str getLinkCmd(conf, conf.absOutFile, objfiles) str getLinkCmd(conf, conf.absOutFile, objfiles)
if optRun in conf.globalOptions:
lit ",\L\"nimfiles\":[\L"
nimfiles(conf, f)
lit "]\L"
lit "\L}\L" lit "\L}\L"
close(f) close(f)
proc changeDetectedViaJsonBuildInstructions*(conf: ConfigRef; projectfile: AbsoluteFile): bool =
let jsonFile = toGeneratedFile(conf, projectfile, "json")
if not fileExists(jsonFile): return true
if not fileExists(conf.absOutFile): return true
result = false
try:
let data = json.parseFile(jsonFile.string)
let nimfilesPairs = data["nimfiles"]
doAssert nimfilesPairs.kind == JArray
for p in nimfilesPairs:
doAssert p.kind == JArray
# >= 2 for forwards compatibility with potential later .json files:
doAssert p.len >= 2
let nimFilename = p[0].getStr
let oldHashValue = p[1].getStr
let newHashValue = $secureHashFile(nimFilename)
if oldHashValue != newHashValue:
result = true
except IOError, OSError, ValueError:
echo "Warning: JSON processing failed: ", getCurrentExceptionMsg()
result = true
proc runJsonBuildInstructions*(conf: ConfigRef; projectfile: AbsoluteFile) = proc runJsonBuildInstructions*(conf: ConfigRef; projectfile: AbsoluteFile) =
let jsonFile = toGeneratedFile(conf, projectfile, "json") let jsonFile = toGeneratedFile(conf, projectfile, "json")
try: try:

View file

@ -240,8 +240,10 @@ type
Severity* {.pure.} = enum ## VS Code only supports these three Severity* {.pure.} = enum ## VS Code only supports these three
Hint, Warning, Error Hint, Warning, Error
const trackPosInvalidFileIdx* = FileIndex(-2) # special marker so that no suggestions const
# are produced within comments and string literals trackPosInvalidFileIdx* = FileIndex(-2) # special marker so that no suggestions
# are produced within comments and string literals
commandLineIdx* = FileIndex(-3)
type type
MsgConfig* = object ## does not need to be stored in the incremental cache MsgConfig* = object ## does not need to be stored in the incremental cache

View file

@ -87,6 +87,15 @@ proc commandCompileToC(graph: ModuleGraph) =
semanticPasses(graph) semanticPasses(graph)
registerPass(graph, cgenPass) registerPass(graph, cgenPass)
if {optRun, optForceFullMake} * conf.globalOptions == {optRun}:
let proj = changeFileExt(conf.projectFull, "")
if not changeDetectedViaJsonBuildInstructions(conf, proj):
# nothing changed
# Little hack here in order to not lose our precious
# hintSuccessX message:
conf.notes.incl hintSuccessX
return
compileProject(graph) compileProject(graph)
if graph.config.errorCounter > 0: if graph.config.errorCounter > 0:
return # issue #9933 return # issue #9933

View file

@ -160,19 +160,25 @@ proc getInfoContext*(conf: ConfigRef; index: int): TLineInfo =
if i >=% L: result = unknownLineInfo() if i >=% L: result = unknownLineInfo()
else: result = conf.m.msgContext[i].info else: result = conf.m.msgContext[i].info
const
commandLineDesc = "command line"
template toFilename*(conf: ConfigRef; fileIdx: FileIndex): string = template toFilename*(conf: ConfigRef; fileIdx: FileIndex): string =
if fileIdx.int32 < 0 or conf == nil: if fileIdx.int32 < 0 or conf == nil:
"???" (if fileIdx == commandLineIdx: commandLineDesc else: "???")
else: else:
conf.m.fileInfos[fileIdx.int32].shortName conf.m.fileInfos[fileIdx.int32].shortName
proc toProjPath*(conf: ConfigRef; fileIdx: FileIndex): string = proc toProjPath*(conf: ConfigRef; fileIdx: FileIndex): string =
if fileIdx.int32 < 0 or conf == nil: "???" if fileIdx.int32 < 0 or conf == nil:
(if fileIdx == commandLineIdx: commandLineDesc else: "???")
else: conf.m.fileInfos[fileIdx.int32].projPath.string else: conf.m.fileInfos[fileIdx.int32].projPath.string
proc toFullPath*(conf: ConfigRef; fileIdx: FileIndex): string = proc toFullPath*(conf: ConfigRef; fileIdx: FileIndex): string =
if fileIdx.int32 < 0 or conf == nil: result = "???" if fileIdx.int32 < 0 or conf == nil:
else: result = conf.m.fileInfos[fileIdx.int32].fullPath.string result = (if fileIdx == commandLineIdx: commandLineDesc else: "???")
else:
result = conf.m.fileInfos[fileIdx.int32].fullPath.string
proc setDirtyFile*(conf: ConfigRef; fileIdx: FileIndex; filename: AbsoluteFile) = proc setDirtyFile*(conf: ConfigRef; fileIdx: FileIndex; filename: AbsoluteFile) =
assert fileIdx.int32 >= 0 assert fileIdx.int32 >= 0
@ -189,7 +195,7 @@ proc getHash*(conf: ConfigRef; fileIdx: FileIndex): string =
proc toFullPathConsiderDirty*(conf: ConfigRef; fileIdx: FileIndex): AbsoluteFile = proc toFullPathConsiderDirty*(conf: ConfigRef; fileIdx: FileIndex): AbsoluteFile =
if fileIdx.int32 < 0: if fileIdx.int32 < 0:
result = AbsoluteFile"???" result = AbsoluteFile(if fileIdx == commandLineIdx: commandLineDesc else: "???")
elif not conf.m.fileInfos[fileIdx.int32].dirtyFile.isEmpty: elif not conf.m.fileInfos[fileIdx.int32].dirtyFile.isEmpty:
result = conf.m.fileInfos[fileIdx.int32].dirtyFile result = conf.m.fileInfos[fileIdx.int32].dirtyFile
else: else:

View file

@ -97,7 +97,6 @@ proc resolveMod(conf: ConfigRef; module, relativeTo: string): FileIndex =
proc processImplicits(graph: ModuleGraph; implicits: seq[string], nodeKind: TNodeKind, proc processImplicits(graph: ModuleGraph; implicits: seq[string], nodeKind: TNodeKind,
a: var TPassContextArray; m: PSym) = a: var TPassContextArray; m: PSym) =
# XXX fixme this should actually be relative to the config file! # XXX fixme this should actually be relative to the config file!
let gCmdLineInfo = newLineInfo(FileIndex(0), 1, 1)
let relativeTo = toFullPath(graph.config, m.info) let relativeTo = toFullPath(graph.config, m.info)
for module in items(implicits): for module in items(implicits):
# implicit imports should not lead to a module importing itself # implicit imports should not lead to a module importing itself

View file

@ -427,12 +427,8 @@ proc storeRemaining*(g: ModuleGraph; module: PSym) =
stillForwarded.add s stillForwarded.add s
swap w.forwardedSyms, stillForwarded swap w.forwardedSyms, stillForwarded
transitiveClosure(g) transitiveClosure(g)
var nimid = 0
for x in items(g.config.m.fileInfos): for x in items(g.config.m.fileInfos):
# don't store the "command line" entry: storeFilename(g, x.fullPath, FileIndex(nimid))
if nimid != 0:
storeFilename(g, x.fullPath, FileIndex(nimid))
inc nimid
# ---------------- decoder ----------------------------------- # ---------------- decoder -----------------------------------

View file

@ -308,8 +308,14 @@ proc boot(args: string) =
extraOption.add " -d:nimBoostrapCsources0_19_0" extraOption.add " -d:nimBoostrapCsources0_19_0"
# remove this when csources get updated # remove this when csources get updated
exec "$# $# $# $# --nimcache:$# compiler" / "nim.nim" % # in order to use less memory, we split the build into two steps:
# --compileOnly produces a $project.json file and does not run GCC/Clang.
# jsonbuild then uses the $project.json file to build the Nim binary.
exec "$# $# $# $# --nimcache:$# --compileOnly compiler" / "nim.nim" %
[nimi, bootOptions, extraOption, args, smartNimcache] [nimi, bootOptions, extraOption, args, smartNimcache]
exec "$# jsonscript --nimcache:$# compiler" / "nim.nim" %
[nimi, smartNimcache]
if sameFileContent(output, i.thVersion): if sameFileContent(output, i.thVersion):
copyExe(output, finalDest) copyExe(output, finalDest)
echo "executables are equal: SUCCESS!" echo "executables are equal: SUCCESS!"

View file

@ -14,6 +14,10 @@ type
doc: seq[string] doc: seq[string]
buildIndex: seq[string] buildIndex: seq[string]
proc exec(cmd: string) =
if execShellCmd(cmd) != 0:
quit("FAILURE: " & cmd)
proc testNimDoc(prjDir, docsDir: string; switches: NimSwitches; fixup = false) = proc testNimDoc(prjDir, docsDir: string; switches: NimSwitches; fixup = false) =
let let
nimDocSwitches = switches.doc.join(" ") nimDocSwitches = switches.doc.join(" ")
@ -22,12 +26,10 @@ proc testNimDoc(prjDir, docsDir: string; switches: NimSwitches; fixup = false) =
putEnv("SOURCE_DATE_EPOCH", "100000") putEnv("SOURCE_DATE_EPOCH", "100000")
if nimDocSwitches != "": if nimDocSwitches != "":
if execShellCmd("nim doc $1" % [nimDocSwitches]) != 0: exec("nim doc $1" % [nimDocSwitches])
quit("FAILURE: nim doc failed")
if nimBuildIndexSwitches != "": if nimBuildIndexSwitches != "":
if execShellCmd("nim buildIndex $1" % [nimBuildIndexSwitches]) != 0: exec("nim buildIndex $1" % [nimBuildIndexSwitches])
quit("FAILURE: nim buildIndex failed")
for expected in walkDirRec(prjDir / "expected/"): for expected in walkDirRec(prjDir / "expected/"):
let produced = expected.replace('\\', '/').replace("/expected/", "/$1/" % [docsDir]) let produced = expected.replace('\\', '/').replace("/expected/", "/$1/" % [docsDir])