mirror of
https://github.com/nim-lang/Nim.git
synced 2026-01-02 19:22:40 +00:00
[caas] first version that actually works (still has a lot of logical memory leaks on recompilation)
This commit is contained in:
@@ -44,9 +44,12 @@ const
|
||||
cpsStmts: "NIM_merge_PROC_BODY"
|
||||
]
|
||||
NimMergeEndMark = "/*\tNIM_merge_END:*/"
|
||||
|
||||
|
||||
template mergeSectionsEnabled: expr =
|
||||
{optCaasEnabled, optSymbolFiles} * gGlobalOptions != {}
|
||||
|
||||
proc genSectionStart*(fs: TCFileSection): PRope =
|
||||
if optSymbolFiles in gGlobalOptions:
|
||||
if mergeSectionsEnabled:
|
||||
result = toRope(tnl)
|
||||
app(result, "/*\t")
|
||||
app(result, CFileSectionNames[fs])
|
||||
@@ -54,11 +57,11 @@ proc genSectionStart*(fs: TCFileSection): PRope =
|
||||
app(result, tnl)
|
||||
|
||||
proc genSectionEnd*(fs: TCFileSection): PRope =
|
||||
if optSymbolFiles in gGlobalOptions:
|
||||
if mergeSectionsEnabled:
|
||||
result = toRope(NimMergeEndMark & tnl)
|
||||
|
||||
proc genSectionStart*(ps: TCProcSection): PRope =
|
||||
if optSymbolFiles in gGlobalOptions:
|
||||
if mergeSectionsEnabled:
|
||||
result = toRope(tnl)
|
||||
app(result, "/*\t")
|
||||
app(result, CProcSectionNames[ps])
|
||||
@@ -66,7 +69,7 @@ proc genSectionStart*(ps: TCProcSection): PRope =
|
||||
app(result, tnl)
|
||||
|
||||
proc genSectionEnd*(ps: TCProcSection): PRope =
|
||||
if optSymbolFiles in gGlobalOptions:
|
||||
if mergeSectionsEnabled:
|
||||
result = toRope(NimMergeEndMark & tnl)
|
||||
|
||||
proc writeTypeCache(a: TIdTable, s: var string) =
|
||||
|
||||
@@ -59,6 +59,12 @@ var
|
||||
proc initTypeTables() =
|
||||
for i in countup(low(TTypeKind), high(TTypeKind)): InitIdTable(gTypeTable[i])
|
||||
|
||||
proc resetCaches* =
|
||||
## XXX: fix that more properly
|
||||
initTypeTables()
|
||||
for i in low(gCanonicalTypes)..high(gCanonicalTypes):
|
||||
gCanonicalTypes[i] = nil
|
||||
|
||||
when false:
|
||||
proc echoStats*() =
|
||||
for i in countup(low(TTypeKind), high(TTypeKind)):
|
||||
|
||||
@@ -37,6 +37,10 @@ proc addForwardedProc(m: BModule, prc: PSym) =
|
||||
m.forwardedProcs.add(prc)
|
||||
inc(gForwardedProcsCounter)
|
||||
|
||||
proc getCgenModule(s: PSym): BModule =
|
||||
result = if s.position >= 0 and s.position < gModules.len: gModules[s.position]
|
||||
else: nil
|
||||
|
||||
proc findPendingModule(m: BModule, s: PSym): BModule =
|
||||
var ms = getModule(s)
|
||||
result = gModules[ms.position]
|
||||
@@ -1013,18 +1017,67 @@ proc rawNewModule(module: PSym, filename: string): BModule =
|
||||
result.nimTypesName = getTempName()
|
||||
result.PreventStackTrace = sfSystemModule in module.flags
|
||||
|
||||
proc nullify[T](arr: var T) =
|
||||
for i in low(arr)..high(arr):
|
||||
arr[i] = nil
|
||||
|
||||
proc resetModule*(m: var BModule) =
|
||||
# between two compilations in CAAS mode, we can throw
|
||||
# away all the data that was written to disk
|
||||
InitLinkedList(m.headerFiles)
|
||||
m.declaredProtos = initIntSet()
|
||||
initIdTable(m.forwTypeCache)
|
||||
m.initProc = newProc(nil, m)
|
||||
m.initProc.options = gOptions
|
||||
m.preInitProc = newProc(nil, m)
|
||||
initNodeTable(m.dataCache)
|
||||
m.typeStack = @[]
|
||||
m.forwardedProcs = @[]
|
||||
m.typeNodesName = getTempName()
|
||||
m.nimTypesName = getTempName()
|
||||
m.PreventStackTrace = sfSystemModule in m.module.flags
|
||||
nullify m.s
|
||||
m.usesThreadVars = false
|
||||
m.typeNodes = 0
|
||||
m.nimTypes = 0
|
||||
nullify m.extensionLoaders
|
||||
|
||||
# indicate that this is now cached module
|
||||
# the cache will be invalidated by nullifying gModules
|
||||
m.fromCache = true
|
||||
|
||||
# we keep only the "merge info" information for the module
|
||||
# and the properties that can't change:
|
||||
# m.filename
|
||||
# m.cfilename
|
||||
# m.isHeaderFile
|
||||
# m.module ?
|
||||
# m.typeCache
|
||||
# m.declaredThings
|
||||
# m.typeInfoMarker
|
||||
# m.labels
|
||||
# m.FrameDeclared
|
||||
|
||||
proc resetCgenModules* =
|
||||
for m in cgenModules(): resetModule(m)
|
||||
|
||||
proc rawNewModule(module: PSym): BModule =
|
||||
result = rawNewModule(module, module.filename)
|
||||
|
||||
proc newModule(module: PSym): BModule =
|
||||
result = rawNewModule(module)
|
||||
if gModules.len <= module.position: gModules.setLen(module.position + 1)
|
||||
gModules[module.position] = result
|
||||
result = getCgenModule(module)
|
||||
if result == nil:
|
||||
result = rawNewModule(module)
|
||||
growCache gModules, module.position
|
||||
gModules[module.position] = result
|
||||
|
||||
if (optDeadCodeElim in gGlobalOptions):
|
||||
if (sfDeadCodeElim in module.flags):
|
||||
InternalError("added pending module twice: " & module.filename)
|
||||
else:
|
||||
echo "CGEN CACHED MODULE: ", result.filename
|
||||
assert optCaasEnabled in gGlobalOptions
|
||||
|
||||
if (optDeadCodeElim in gGlobalOptions):
|
||||
if (sfDeadCodeElim in module.flags):
|
||||
InternalError("added pending module twice: " & module.filename)
|
||||
|
||||
proc myOpen(module: PSym): PPassContext =
|
||||
result = newModule(module)
|
||||
if optGenIndex in gGlobalOptions and generatedHeader == nil:
|
||||
@@ -1056,7 +1109,8 @@ proc writeHeader(m: BModule) =
|
||||
proc getCFile(m: BModule): string =
|
||||
result = changeFileExt(completeCFilePath(m.cfilename), cExt)
|
||||
|
||||
proc myOpenCached(module: PSym, rd: PRodReader): PPassContext =
|
||||
proc myOpenCached(module: PSym, rd: PRodReader): PPassContext =
|
||||
assert optSymbolFiles in gGlobalOptions
|
||||
var m = newModule(module)
|
||||
readMergeInfo(getCFile(m), m)
|
||||
result = m
|
||||
@@ -1130,8 +1184,28 @@ proc writeModule(m: BModule, pending: bool) =
|
||||
# ``system.c`` but then compilation fails due to an error. This means
|
||||
# that ``system.o`` is missing, so we need to call the C compiler for it:
|
||||
addFileToCompile(cfilenoext)
|
||||
|
||||
addFileToLink(cfilenoext)
|
||||
|
||||
proc updateCachedModule(m: BModule) =
|
||||
let cfile = getCFile(m)
|
||||
let cfilenoext = changeFileExt(cfile, "")
|
||||
|
||||
if mergeRequired(m):
|
||||
echo "MERGE REQUIRED FOR ", m.filename
|
||||
mergeFiles(cfile, m)
|
||||
genInitCode(m)
|
||||
finishTypeDescriptions(m)
|
||||
var code = genModule(m, cfilenoext)
|
||||
writeRope(code, cfile)
|
||||
addFileToCompile(cfilenoext)
|
||||
|
||||
addFileToLink(cfilenoext)
|
||||
|
||||
proc cgenCaasUpdate* =
|
||||
for m in cgenModules():
|
||||
if m.fromCache: m.updateCachedModule
|
||||
|
||||
proc myClose(b: PPassContext, n: PNode): PNode =
|
||||
result = n
|
||||
if b == nil or passes.skipCodegen(n): return
|
||||
@@ -1150,17 +1224,13 @@ proc myClose(b: PPassContext, n: PNode): PNode =
|
||||
# deps are allowed (and the system module is processed in the wrong
|
||||
# order anyway)
|
||||
if generatedHeader != nil: finishModule(generatedHeader)
|
||||
while gForwardedProcsCounter > 0:
|
||||
for i in countup(0, high(gModules)):
|
||||
# some modules (like stdin) may exist only in memory
|
||||
# they won't have a cgen BModule for them and we must
|
||||
# skip them
|
||||
if gModules[i] != nil:
|
||||
finishModule(gModules[i])
|
||||
for i in countup(0, high(gModules)):
|
||||
# see above
|
||||
if gModules[i] != nil:
|
||||
writeModule(gModules[i], pending=true)
|
||||
while gForwardedProcsCounter > 0:
|
||||
for m in cgenModules():
|
||||
if not m.fromCache:
|
||||
finishModule(m)
|
||||
for m in cgenModules():
|
||||
if not m.fromCache:
|
||||
writeModule(m, pending=true)
|
||||
writeMapping(gMapping)
|
||||
if generatedHeader != nil: writeHeader(generatedHeader)
|
||||
|
||||
|
||||
@@ -141,3 +141,9 @@ proc newProc*(prc: PSym, module: BModule): BProc =
|
||||
newSeq(result.blocks, 1)
|
||||
result.nestedTryStmts = @[]
|
||||
|
||||
iterator cgenModules*: var BModule =
|
||||
for i in 0..high(gModules):
|
||||
# some modules (like stdin) may exist only in memory.
|
||||
# they won't have a cgen BModule for them and we must skip them.
|
||||
if gModules[i] != nil: yield gModules[i]
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ type
|
||||
|
||||
proc ProcessCommand*(switch: string, pass: TCmdLinePass)
|
||||
proc processSwitch*(switch, arg: string, pass: TCmdlinePass, info: TLineInfo)
|
||||
|
||||
# implementation
|
||||
|
||||
const
|
||||
|
||||
@@ -379,9 +379,18 @@ proc toObjFile*(filenameWithoutExt: string): string =
|
||||
# Object file for compilation
|
||||
result = changeFileExt(filenameWithoutExt, cc[ccompiler].objExt)
|
||||
|
||||
proc addFileToCompile*(filename: string) =
|
||||
proc addFileToCompile*(filename: string) =
|
||||
appendStr(toCompile, filename)
|
||||
|
||||
proc resetCompilationLists* =
|
||||
initLinkedList(toCompile)
|
||||
## XXX: we must associate these with their originating module
|
||||
# when the module is loaded/unloaded it adds/removes its items
|
||||
# That's because we still need to CRC check the external files
|
||||
# Maybe we can do that in checkDep on the other hand?
|
||||
initLinkedList(externalToCompile)
|
||||
initLinkedList(toLink)
|
||||
|
||||
proc footprint(filename: string): TCrc32 =
|
||||
result = crcFromFile(filename) ><
|
||||
platform.OS[targetOS].name ><
|
||||
@@ -405,11 +414,11 @@ proc externalFileChanged(filename: string): bool =
|
||||
f.writeln($currentCrc)
|
||||
close(f)
|
||||
|
||||
proc addExternalFileToCompile*(filename: string) =
|
||||
proc addExternalFileToCompile*(filename: string) =
|
||||
if optForceFullMake in gGlobalOptions or externalFileChanged(filename):
|
||||
appendStr(externalToCompile, filename)
|
||||
|
||||
proc addFileToLink*(filename: string) =
|
||||
proc addFileToLink*(filename: string) =
|
||||
prependStr(toLink, filename)
|
||||
# BUGFIX: was ``appendStr``
|
||||
|
||||
@@ -540,7 +549,7 @@ proc CompileCFile(list: TLinkedList, script: var PRope, cmds: var TStringSeq,
|
||||
app(script, tnl)
|
||||
it = PStrEntry(it.next)
|
||||
|
||||
proc CallCCompiler*(projectfile: string) =
|
||||
proc CallCCompiler*(projectfile: string) =
|
||||
var
|
||||
linkCmd, buildgui, builddll: string
|
||||
if gGlobalOptions * {optCompileOnly, optGenScript} == {optCompileOnly}:
|
||||
|
||||
@@ -14,9 +14,9 @@ import
|
||||
llstream, strutils, ast, astalgo, lexer, syntaxes, renderer, options, msgs,
|
||||
os, lists, condsyms, rodread, rodwrite, ropes, trees, times,
|
||||
wordrecg, sem, semdata, idents, passes, docgen, extccomp,
|
||||
cgen, ecmasgen,
|
||||
cgen, ecmasgen, cgendata,
|
||||
platform, nimconf, importer, passaux, depends, evals, types, idgen,
|
||||
tables, docgen2, service, magicsys, parser, crc
|
||||
tables, docgen2, service, magicsys, parser, crc, ccgutils
|
||||
|
||||
const
|
||||
has_LLVM_Backend = false
|
||||
@@ -29,11 +29,15 @@ proc MainCommand*()
|
||||
# ------------------ module handling -----------------------------------------
|
||||
|
||||
type
|
||||
TNeedRecompile = enum Maybe, No, Yes, Probing, Recompiled
|
||||
TCrcStatus = enum crcNotTaken, crcCached, crcHasChanged, crcNotChanged
|
||||
|
||||
TModuleInMemory = object
|
||||
compiledAt: float
|
||||
crc: int
|
||||
crc: TCrc32
|
||||
deps: seq[int32] ## XXX: slurped files are not currently tracked
|
||||
needsRecompile: bool
|
||||
needsRecompile: TNeedRecompile
|
||||
crcStatus: TCrcStatus
|
||||
|
||||
var
|
||||
gCompiledModules: seq[PSym] = @[]
|
||||
@@ -53,24 +57,76 @@ template compiledAt(x: PSym): expr =
|
||||
template crc(x: PSym): expr =
|
||||
gMemCacheData[x.position].crc
|
||||
|
||||
template addDep(x: Psym, dep: int32) =
|
||||
gMemCacheData[x.position].deps.add(dep)
|
||||
proc crcChanged(fileIdx: int32): bool =
|
||||
InternalAssert fileIdx >= 0 and fileIdx < gMemCacheData.len
|
||||
|
||||
template updateStatus =
|
||||
gMemCacheData[fileIdx].crcStatus = if result: crcHasChanged
|
||||
else: crcNotChanged
|
||||
# echo "TESTING CRC: ", fileIdx.toFilename, " ", result
|
||||
|
||||
case gMemCacheData[fileIdx].crcStatus:
|
||||
of crcHasChanged:
|
||||
result = true
|
||||
of crcNotChanged:
|
||||
result = false
|
||||
of crcCached:
|
||||
let newCrc = crcFromFile(fileIdx.toFilename)
|
||||
result = newCrc != gMemCacheData[fileIdx].crc
|
||||
gMemCacheData[fileIdx].crc = newCrc
|
||||
updateStatus()
|
||||
of crcNotTaken:
|
||||
gMemCacheData[fileIdx].crc = crcFromFile(fileIdx.toFilename)
|
||||
result = true
|
||||
updateStatus()
|
||||
|
||||
proc checkDepMem(fileIdx: int32): bool =
|
||||
proc doCRC(fileIdx: int32) =
|
||||
if gMemCacheData[fileIdx].crcStatus == crcNotTaken:
|
||||
# echo "FIRST CRC: ", fileIdx.ToFilename
|
||||
gMemCacheData[fileIdx].crc = crcFromFile(fileIdx.toFilename)
|
||||
|
||||
proc safeAdd*[T](x: var seq[T], y: T) {.noSideEffect.} =
|
||||
if x == nil: x = @[y]
|
||||
else: x.add(y)
|
||||
|
||||
proc safeAdd*(x: var string, y: char) =
|
||||
if x == nil: x = ""
|
||||
x.add(y)
|
||||
|
||||
proc safeAdd*(x: var string, y: string) =
|
||||
if x == nil: x = y
|
||||
else: x.add(y)
|
||||
|
||||
proc addDep(x: Psym, dep: int32) =
|
||||
growCache gMemCacheData, dep
|
||||
gMemCacheData[x.position].deps.safeAdd(dep)
|
||||
|
||||
proc checkDepMem(fileIdx: int32): TNeedRecompile =
|
||||
template markDirty =
|
||||
gMemCacheData[fileIdx].needsRecompile = true
|
||||
return true
|
||||
echo "HARD RESETTING ", fileIdx.toFilename
|
||||
gMemCacheData[fileIdx].needsRecompile = Yes
|
||||
gCompiledModules[fileIdx] = nil
|
||||
cgendata.gModules[fileIdx] = nil
|
||||
|
||||
return Yes
|
||||
|
||||
if gMemCacheData[fileIdx].needsRecompile != Maybe:
|
||||
return gMemCacheData[fileIdx].needsRecompile
|
||||
|
||||
if optForceFullMake in gGlobalOptions or
|
||||
curCaasCmd != lastCaasCmd: markDirty
|
||||
curCaasCmd != lastCaasCmd or
|
||||
crcChanged(fileIdx): markDirty
|
||||
|
||||
let crc = crcFromFile(fileIdx.toFilename)
|
||||
if crc != gMemCacheData[fileIdx].crc: markDirty
|
||||
|
||||
for dep in gMemCacheData[fileIdx].deps:
|
||||
if checkDepMem(dep): markDirty
|
||||
|
||||
return false
|
||||
if gMemCacheData[fileIdx].deps != nil:
|
||||
gMemCacheData[fileIdx].needsRecompile = Probing
|
||||
for dep in gMemCacheData[fileIdx].deps:
|
||||
let d = checkDepMem(dep)
|
||||
if d in { Yes, Recompiled }:
|
||||
echo fileIdx.toFilename, " depends on ", dep.toFilename, " ", d
|
||||
markDirty
|
||||
|
||||
gMemCacheData[fileIdx].needsRecompile = No
|
||||
return No
|
||||
|
||||
proc newModule(fileIdx: int32): PSym =
|
||||
# We cannot call ``newSym`` here, because we have to circumvent the ID
|
||||
@@ -94,10 +150,12 @@ proc newModule(fileIdx: int32): PSym =
|
||||
incl(result.flags, sfUsed)
|
||||
initStrTable(result.tab)
|
||||
StrTableAdd(result.tab, result) # a module knows itself
|
||||
|
||||
|
||||
proc compileModule(fileIdx: int32, flags: TSymFlags): PSym =
|
||||
result = getModule(fileIdx)
|
||||
if result == nil:
|
||||
growCache gMemCacheData, fileIdx
|
||||
gMemCacheData[fileIdx].needsRecompile = Probing
|
||||
result = newModule(fileIdx)
|
||||
var rd = handleSymbolFile(result)
|
||||
result.flags = result.flags + flags
|
||||
@@ -109,12 +167,12 @@ proc compileModule(fileIdx: int32, flags: TSymFlags): PSym =
|
||||
else:
|
||||
result.id = getID()
|
||||
processModule(result, nil, rd)
|
||||
gMemCacheData[fileIdx].compiledAt = gLastCmdTime
|
||||
gMemCacheData[fileIdx].needsRecompile = false
|
||||
if optCaasEnabled in gGlobalOptions:
|
||||
gMemCacheData[fileIdx].compiledAt = gLastCmdTime
|
||||
gMemCacheData[fileIdx].needsRecompile = Recompiled
|
||||
doCRC fileIdx
|
||||
else:
|
||||
InternalAssert optCaasEnabled in gGlobalOptions
|
||||
if checkDepMem(fileIdx):
|
||||
gCompiledModules[fileIdx] = nil
|
||||
if checkDepMem(fileIdx) == Yes:
|
||||
result = CompileModule(fileIdx, flags)
|
||||
else:
|
||||
result = gCompiledModules[fileIdx]
|
||||
@@ -124,17 +182,17 @@ proc compileModule(filename: string, flags: TSymFlags): PSym =
|
||||
|
||||
proc importModule(s: PSym, fileIdx: int32): PSym =
|
||||
# this is called by the semantic checking phase
|
||||
result = getModule(fileIdx)
|
||||
if result == nil:
|
||||
# compile the module
|
||||
result = compileModule(fileIdx, {})
|
||||
if optCaasEnabled in gGlobalOptions: addDep(s, fileIdx)
|
||||
elif sfSystemModule in result.flags:
|
||||
result = compileModule(fileIdx, {})
|
||||
if optCaasEnabled in gGlobalOptions: addDep(s, fileIdx)
|
||||
if sfSystemModule in result.flags:
|
||||
LocalError(result.info, errAttemptToRedefine, result.Name.s)
|
||||
|
||||
proc includeModule(s: PSym, fileIdx: int32): PNode =
|
||||
result = syntaxes.parseFile(fileIdx)
|
||||
if optCaasEnabled in gGlobalOptions: addDep(s, fileIdx)
|
||||
if optCaasEnabled in gGlobalOptions:
|
||||
growCache gMemCacheData, fileIdx
|
||||
addDep(s, fileIdx)
|
||||
doCrc(fileIdx)
|
||||
|
||||
proc `==^`(a, b: string): bool =
|
||||
try:
|
||||
@@ -191,11 +249,55 @@ proc CommandCompileToC =
|
||||
registerPass(cgenPass)
|
||||
rodPass()
|
||||
#registerPass(cleanupPass())
|
||||
if optCaasEnabled in gGlobalOptions:
|
||||
# echo "BEFORE CHECK DEP"
|
||||
# discard checkDepMem(gProjectMainIdx)
|
||||
# echo "CHECK DEP COMPLETE"
|
||||
|
||||
compileProject()
|
||||
|
||||
if optCaasEnabled in gGlobalOptions:
|
||||
cgenCaasUpdate()
|
||||
|
||||
if gCmd != cmdRun:
|
||||
extccomp.CallCCompiler(changeFileExt(gProjectFull, ""))
|
||||
# caas will keep track only of the compilation commands
|
||||
lastCaasCmd = curCaasCmd
|
||||
|
||||
if optCaasEnabled in gGlobalOptions:
|
||||
# caas will keep track only of the compilation commands
|
||||
lastCaasCmd = curCaasCmd
|
||||
resetCgenModules()
|
||||
for i in 0 .. <gMemCacheData.len:
|
||||
gMemCacheData[i].crcStatus = crcCached
|
||||
gMemCacheData[i].needsRecompile = Maybe
|
||||
|
||||
# XXX: clean these global vars
|
||||
# ccgstmts.gBreakpoints
|
||||
# ccgthreadvars.nimtv
|
||||
# ccgthreadvars.nimtVDeps
|
||||
# ccgthreadvars.nimtvDeclared
|
||||
# cgendata
|
||||
# cgmeth?
|
||||
# condsyms?
|
||||
# depends?
|
||||
# lexer.gLinesCompiled
|
||||
# msgs - error counts
|
||||
# magicsys, when system.nim changes
|
||||
# rodread.rodcompilerProcs
|
||||
# rodread.gTypeTable
|
||||
# rodread.gMods
|
||||
|
||||
# !! ropes.cache
|
||||
# !! semdata.gGenericsCache
|
||||
# semthreads.computed?
|
||||
#
|
||||
# suggest.usageSym
|
||||
#
|
||||
# XXX: can we run out of IDs?
|
||||
# XXX: detect config reloading (implement as error/require restart)
|
||||
# XXX: options are appended (they will accumulate over time)
|
||||
resetCompilationLists()
|
||||
ccgutils.resetCaches()
|
||||
GC_fullCollect()
|
||||
|
||||
when has_LLVM_Backend:
|
||||
proc CommandCompileToLLVM =
|
||||
|
||||
@@ -795,7 +795,7 @@ proc GetCRC*(fileIdx: int32): TCrc32 =
|
||||
gMods[fileIdx].crc = result
|
||||
|
||||
template growCache*(cache, pos) =
|
||||
if cache.len <= fileIdx: cache.setLen(pos+1)
|
||||
if cache.len <= pos: cache.setLen(pos+1)
|
||||
|
||||
proc checkDep(fileIdx: int32): TReasonForRecompile =
|
||||
assert fileIdx != InvalidFileIDX
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import
|
||||
sockets,
|
||||
times, commands, options, msgs, nimconf,
|
||||
extccomp, strutils, os, platform, main, parseopt
|
||||
extccomp, strutils, os, platform, parseopt
|
||||
|
||||
# We cache modules and the dependency graph. However, we don't check for
|
||||
# file changes but expect the client to tell us about them, otherwise the
|
||||
|
||||
Reference in New Issue
Block a user