diff options
| author | Ganesh Viswanathan <dev@genotrance.com> | 2018-07-12 01:36:15 -0500 |
|---|---|---|
| committer | Ganesh Viswanathan <dev@genotrance.com> | 2018-07-12 01:36:15 -0500 |
| commit | cf629494a40fa569ab4316b417dfb27856ea2034 (patch) | |
| tree | 798ee0237d0035a1dae2f30f98cd7e5436126a2c | |
| parent | 293e8cca850be17b1a27c47daedfbbc322c2f2d5 (diff) | |
| download | nimgen-cf629494a40fa569ab4316b417dfb27856ea2034.tar.gz nimgen-cf629494a40fa569ab4316b417dfb27856ea2034.zip | |
Split into multiple files
| -rw-r--r-- | nimgen.nim | 886 | ||||
| -rw-r--r-- | nimgen.nimble | 4 | ||||
| -rw-r--r-- | src/nimgen.nim | 7 | ||||
| -rw-r--r-- | src/nimgen/config.nim | 229 | ||||
| -rw-r--r-- | src/nimgen/file.nim | 100 | ||||
| -rw-r--r-- | src/nimgen/fileops.nim | 110 | ||||
| -rw-r--r-- | src/nimgen/gencore.nim | 295 | ||||
| -rw-r--r-- | src/nimgen/globals.nim | 40 | ||||
| -rw-r--r-- | src/nimgen/prepare.nim | 117 |
9 files changed, 900 insertions, 888 deletions
diff --git a/nimgen.nim b/nimgen.nim deleted file mode 100644 index e3a0883..0000000 --- a/nimgen.nim +++ /dev/null @@ -1,886 +0,0 @@ -import os, ospaths, osproc, parsecfg, pegs, regex, ropes, sequtils, streams, strutils, tables - -const - cCompilerEnv = "CC" - cppCompilerEnv = "CPP" - defaultCCompiler = "gcc" - defaultCppCompiler = "g++" - -var - gDoneRecursive: seq[string] = @[] - gDoneInline: seq[string] = @[] - - gProjectDir = "" - gConfig: Config - gFilter = "" - gQuotes = true - gCppCompiler = getEnv(cppCompilerEnv, defaultCCompiler) - gCCompiler = getEnv(cCompilerEnv, defaultCppCompiler) - gOutput = "" - gIncludes: seq[string] = @[] - gExcludes: seq[string] = @[] - gRenames = initTable[string, string]() - gWildcards = newConfig() - -type - c2nimConfigObj = object - flags, ppflags: string - recurse, inline, preprocess, ctags, defines: bool - dynlib, compile, pragma: seq[string] - -const DOC = """ -Nimgen is a helper for c2nim to simpilfy and automate the wrapping of C libraries - -Usage: - nimgen [options] <file.cfg>... - -Options: - -f delete all artifacts and regenerate -""" - -# ### -# Helpers - -proc addEnv(str: string): string = - var newStr = str - for pair in envPairs(): - try: - newStr = newStr % [pair.key, pair.value.string] - except ValueError: - # Ignore if there are no values to replace. We - # want to continue anyway - discard - - try: - newStr = newStr % ["output", gOutput] - except ValueError: - # Ignore if there are no values to replace. We - # want to continue anyway - discard - - # if there are still format args, print a warning - if newStr.contains("${"): - echo "WARNING: \"", newStr, "\" still contains an uninterpolated value!" - - return newStr - -proc `[]`(table: OrderedTableRef[string, string], key: string): string = - ## Gets table values with env vars inserted - tables.`[]`(table, key).addEnv - -proc execProc(cmd: string): string = - result = "" - var - p = startProcess(cmd, options = {poStdErrToStdOut, poUsePath, poEvalCommand}) - - outp = outputStream(p) - line = newStringOfCap(120).TaintedString - - while true: - if outp.readLine(line): - result.add(line) - result.add("\n") - elif not running(p): break - - var x = p.peekExitCode() - if x != 0: - echo "Command failed: " & $x - echo cmd - echo result - quit(1) - -proc extractZip(zipfile: string) = - var cmd = "unzip -o $#" - if defined(Windows): - cmd = "powershell -nologo -noprofile -command \"& { Add-Type -A " & - "'System.IO.Compression.FileSystem'; " & - "[IO.Compression.ZipFile]::ExtractToDirectory('$#', '.'); }\"" - - setCurrentDir(gOutput) - defer: setCurrentDir(gProjectDir) - - echo "Extracting " & zipfile - discard execProc(cmd % zipfile) - -proc downloadUrl(url: string) = - let - file = url.extractFilename() - ext = file.splitFile().ext.toLowerAscii() - - var cmd = "curl $# -o $#" - if defined(Windows): - cmd = "powershell wget $# -OutFile $#" - - if not (ext == ".zip" and fileExists(gOutput/file)): - echo "Downloading " & file - discard execProc(cmd % [url, gOutput/file]) - - if ext == ".zip": - extractZip(file) - -proc gitReset() = - echo "Resetting Git repo" - - setCurrentDir(gOutput) - defer: setCurrentDir(gProjectDir) - - discard execProc("git reset --hard HEAD") - -proc gitCheckout(filename: string) {.used.} = - echo "Resetting file: $#" % [filename] - - setCurrentDir(gOutput) - defer: setCurrentDir(gProjectDir) - - let adjustedFile = filename.replace(gOutput & $DirSep, "") - - discard execProc("git checkout $#" % [adjustedFile]) - -proc gitRemotePull(url: string, pull=true) = - if dirExists(gOutput/".git"): - if pull: - gitReset() - return - - setCurrentDir(gOutput) - defer: setCurrentDir(gProjectDir) - - echo "Setting up Git repo" - discard execProc("git init .") - discard execProc("git remote add origin " & url) - - if pull: - echo "Checking out artifacts" - discard execProc("git pull --depth=1 origin master") - -proc gitSparseCheckout(plist: string) = - let sparsefile = ".git/info/sparse-checkout" - if fileExists(gOutput/sparsefile): - gitReset() - return - - setCurrentDir(gOutput) - defer: setCurrentDir(gProjectDir) - - discard execProc("git config core.sparsecheckout true") - writeFile(sparsefile, plist) - - echo "Checking out artifacts" - discard execProc("git pull --depth=1 origin master") - -proc doCopy(flist: string) = - for pair in flist.split(","): - let spl = pair.split("=") - if spl.len() != 2: - echo "Bad copy syntax: " & flist - quit(1) - - let - lfile = spl[0].strip() - rfile = spl[1].strip() - - copyFile(lfile, rfile) - echo "Copied $# to $#" % [lfile, rfile] - -proc getKey(ukey: string): tuple[key: string, val: bool] = - var kv = ukey.replace(re"\..*", "").split("-", 1) - if kv.len() == 1: - kv.add("") - - if (kv[1] == "") or - (kv[1] == "win" and defined(Windows)) or - (kv[1] == "lin" and defined(Linux)) or - (kv[1] == "osx" and defined(MacOSX)): - return (kv[0], true) - - return (kv[0], false) - -# ### -# File loction - -proc getNimout(file: string, rename=true): string = - result = file.splitFile().name.replace(re"[\-\.]", "_") & ".nim" - if gOutput != "": - result = gOutput/result - - if not rename: - return - - if gRenames.hasKey(file): - result = gRenames[file] - - if not dirExists(parentDir(result)): - createDir(parentDir(result)) - -proc exclude(file: string): bool = - for excl in gExcludes: - if excl in file: - return true - return false - -proc search(file: string): string = - if exclude(file): - return "" - - result = file - if file.splitFile().ext == ".nim": - result = getNimout(file) - elif not fileExists(result) and not dirExists(result): - var found = false - for inc in gIncludes: - result = inc/file - if fileExists(result) or dirExists(result): - found = true - break - if not found: - echo "File doesn't exist: " & file - quit(1) - - # Only keep relative directory - return result.multiReplace([("\\", $DirSep), ("//", $DirSep), (gProjectDir & $DirSep, "")]) - -# ### -# Loading / unloading - -proc openRetry(file: string, mode: FileMode = fmRead): File = - while true: - try: - result = open(file, mode) - break - except IOError: - sleep(100) - -template withFile(file: string, body: untyped): untyped = - if fileExists(file): - var f = openRetry(file) - - var contentOrig = f.readAll() - f.close() - var content {.inject.} = contentOrig - - body - - if content != contentOrig: - f = openRetry(file, fmWrite) - write(f, content) - f.close() - else: - echo "Missing file " & file - -# ### -# Manipulating content - -proc prepend(file: string, data: string, search="") = - withFile(file): - if search == "": - content = data & content - else: - let idx = content.find(search) - if idx != -1: - content = content[0..<idx] & data & content[idx..<content.len()] - -proc pipe(file: string, command: string) = - let cmd = command % ["file", file] - let commandResult = execProc(cmd).strip() - if commandResult != "": - withFile(file): - content = commandResult - -proc append(file: string, data: string, search="") = - withFile(file): - if search == "": - content &= data - else: - let idx = content.find(search) - let idy = idx + search.len() - if idx != -1: - content = content[0..<idy] & data & content[idy..<content.len()] - -proc freplace(file: string, pattern: string, repl="") = - withFile(file): - if pattern in content: - content = content.replace(pattern, repl) - -proc freplace(file: string, pattern: Regex, repl="") = - withFile(file): - var m: RegexMatch - if content.find(pattern, m): - if "$#" in repl: - content = content.replace(pattern, - proc (m: RegexMatch, s: string): string = repl % s[m.group(0)[0]]) - else: - content = content.replace(pattern, repl) - -proc comment(file: string, pattern: string, numlines: string) = - let - ext = file.splitFile().ext.toLowerAscii() - cmtchar = if ext == ".nim": "#" else: "//" - - withFile(file): - var - idx = content.find(pattern) - num = 0 - - try: - num = numlines.parseInt() - except ValueError: - echo "Bad comment value, should be integer: " & numlines - if idx != -1: - for i in 0 .. num-1: - if idx >= content.len(): - break - content = content[0..<idx] & cmtchar & content[idx..<content.len()] - while idx < content.len(): - idx += 1 - if content[idx] == '\L': - idx += 1 - break - -proc removeStatic(filename: string) = - ## Replace static function bodies with a semicolon and commented - ## out body - withFile(filename): - content = content.replace( - re"(?m)(static inline.*?\))(\s*\{(\s*?.*?$)*[\n\r]\})", - proc (m: RegexMatch, s: string): string = - let funcDecl = s[m.group(0)[0]] - let body = s[m.group(1)[0]].strip() - result = "" - - result.add("$#;" % [funcDecl]) - result.add(body.replace(re"(?m)^", "//")) - ) - -proc reAddStatic(filename: string) = - ## Uncomment out the body and remove the semicolon. Undoes - ## removeStatic - withFile(filename): - content = content.replace( - re"(?m)(static inline.*?\));(\/\/\s*\{(\s*?.*?$)*[\n\r]\/\/\})", - proc (m: RegexMatch, s: string): string = - let funcDecl = s[m.group(0)[0]] - let body = s[m.group(1)[0]].strip() - result = "" - - result.add("$# " % [funcDecl]) - result.add(body.replace(re"(?m)^\/\/", "")) - ) - -proc rename(file: string, renfile: string) = - if file.splitFile().ext == ".nim": - return - - var - nimout = getNimout(file, false) - newname = renfile.replace("$nimout", extractFilename(nimout)) - - if newname =~ peg"(!\$.)*{'$replace'\s*'('\s*{(!\)\S)+}')'}": - var final = nimout.extractFilename() - for entry in matches[1].split(","): - let spl = entry.split("=") - if spl.len() != 2: - echo "Bad replace syntax: " & renfile - quit(1) - - let - srch = spl[0].strip() - repl = spl[1].strip() - - final = final.replace(srch, repl) - newname = newname.replace(matches[0], final) - - gRenames[file] = gOutput/newname - -proc compile(dir="", file=""): string = - proc fcompile(file: string): string = - return "{.compile: \"$#\".}" % file.replace("\\", "/") - - var data = "" - if dir != "" and dirExists(dir): - for f in walkFiles(dir / "*.c"): - data &= fcompile(f) & "\n" - - if file != "" and fileExists(file): - data &= fcompile(file) & "\n" - - return data - -proc fixFuncProtos(file: string) = - withFile(file): - content = content.replace(re"(?m)(^.*?)[ ]*\(\*(.*?)\((.*?)\)\)[ \r\n]*\((.*?[\r\n]*.*?)\);", - proc (m: RegexMatch, s: string): string = - (("typedef $# (*type_$#)($#);\n" % [s[m.group(0)[0]], s[m.group(1)[0]], s[m.group(3)[0]]]) & - ("type_$# $#($#);" % [s[m.group(1)[0]], s[m.group(1)[0]], s[m.group(2)[0]]])) - ) - -# ### -# Convert to Nim - -proc getIncls(file: string, inline=false): seq[string] = - result = @[] - if inline and file in gDoneInline: - return - - let curPath = splitFile(expandFileName(file)).dir - withFile(file): - for f in content.findAll(re"(?m)^\s*#\s*include\s+(.*?)$"): - var inc = content[f.group(0)[0]].strip() - if ((gQuotes and inc.contains("\"")) or (gFilter != "" and gFilter in inc)) and (not exclude(inc)): - let addInc = inc.replace(re"""[<>"]""", "").replace(re"\/[\*\/].*$", "").strip() - try: - # Try searching for a local library. expandFilename will throw - # OSError if the file does not exist - let - finc = expandFileName(curPath / addInc) - fname = finc.replace(curPath & $DirSep, "") - - if fname.len() > 0: - # only add if the file is non-empty - result.add(fname.search()) - except OSError: - # If it's a system library - result.add(addInc) - - result = result.deduplicate() - - gDoneInline.add(file) - - if inline: - var sres = newSeq[string]() - for incl in result: - let sincl = search(incl) - if sincl == "": - continue - - sres.add(getIncls(sincl, inline)) - result.add(sres) - - result = result.deduplicate() - -proc getDefines(file: string, inline=false): string = - result = "" - if inline: - var incls = getIncls(file, inline) - for incl in incls: - let sincl = search(incl) - if sincl != "": - echo "Inlining " & sincl - result &= getDefines(sincl) - withFile(file): - for def in content.findAll(re"(?m)^(\s*#\s*define\s+[\w\d_]+\s+[\d\-.xf]+)(?:\r|//|/*).*?$"): - result &= content[def.group(0)[0]] & "\n" - -proc runPreprocess(file, ppflags, flags: string, inline: bool): string = - var - pproc = if flags.contains("cpp"): gCppCompiler else: gCCompiler - cmd = "$# -E $# $#" % [pproc, ppflags, file] - - for inc in gIncludes: - cmd &= " -I " & inc - - # Run preprocessor - var data = execProc(cmd) - - # Include content only from file - var - rdata: Rope - start = false - sfile = file.replace("\\", "/") - - if inline: - sfile = sfile.parentDir() - for line in data.splitLines(): - if line.strip() != "": - if line[0] == '#' and not line.contains("#pragma"): - start = false - if sfile in line.replace("\\", "/").replace("//", "/"): - start = true - if not ("\\" in line) and not ("/" in line) and extractFilename(sfile) in line: - start = true - else: - if start: - rdata.add( - line.replace("_Noreturn", "") - .replace("(())", "") - .replace("WINAPI", "") - .replace("__attribute__", "") - .replace("extern \"C\"", "") - .replace(re"\(\([_a-z]+?\)\)", "") - .replace(re"\(\(__format__[\s]*\(__[gnu_]*printf__, [\d]+, [\d]+\)\)\);", ";") & "\n" - ) - return $rdata - -proc runCtags(file: string): string = - var - cmd = "ctags -o - --fields=+S+K --c-kinds=+p --file-scope=no " & file - fps = execProc(cmd) - fdata = "" - - for line in fps.splitLines(): - var spl = line.split(re"\t") - if spl.len() > 4: - if spl[0] != "main" and spl[3] != "member": - var fn = "" - var match: RegexMatch - if spl[2].find(re"/\^(.*?)\(", match): - fn = spl[2][match.group(0)[0]] - fn &= spl[4].replace("signature:", "") & ";" - fdata &= fn & "\n" - - return fdata - -proc runFile(file: string, cfgin: OrderedTableRef) - -template relativePath(path: untyped): untyped = - path.multiReplace([(gOutput, ""), ("\\", "/"), ("//", "/")]) - -proc c2nim(fl, outfile: string, c2nimConfig: c2nimConfigObj) = - var file = search(fl) - if file == "": - return - - if file in gDoneRecursive: - return - - echo "Processing $# => $#" % [file, outfile] - gDoneRecursive.add(file) - - fixFuncProtos(file) - - var incout = "" - if c2nimConfig.recurse: - var - incls = getIncls(file) - cfg = newOrderedTable[string, string]() - - for name, value in c2nimConfig.fieldPairs: - when value is string: - cfg[name] = value - when value is bool: - cfg[name] = $value - - for i in c2nimConfig.dynlib: - cfg["dynlib." & i] = i - - for inc in incls: - runFile(inc, cfg) - incout &= "import $#\n" % inc.search().getNimout()[0 .. ^5] - - var cfile = file - if c2nimConfig.preprocess: - cfile = "temp-$#.c" % [outfile.extractFilename()] - writeFile(cfile, runPreprocess(file, c2nimConfig.ppflags, c2nimConfig.flags, c2nimConfig.inline)) - elif c2nimConfig.ctags: - cfile = "temp-$#.c" % [outfile.extractFilename()] - writeFile(cfile, runCtags(file)) - - if c2nimConfig.defines and (c2nimConfig.preprocess or c2nimConfig.ctags): - prepend(cfile, getDefines(file, c2nimConfig.inline)) - - var - extflags = "" - passC = "" - outlib = "" - outpragma = "" - - passC = "import ospaths, strutils\n" - - for inc in gIncludes: - if inc.isAbsolute(): - passC &= ("""{.passC: "-I\"$#\"".}""" % [inc]) & "\n" - else: - passC &= ( - """{.passC: "-I\"" & currentSourcePath().splitPath().head & "$#\"".}""" % - inc.relativePath() - ) & "\n" - - for prag in c2nimConfig.pragma: - outpragma &= "{." & prag & ".}\n" - - let fname = file.splitFile().name.replace(re"[\.\-]", "_") - - if c2nimConfig.dynlib.len() != 0: - let - win = "when defined(Windows):\n" - lin = "when defined(Linux):\n" - osx = "when defined(MacOSX):\n" - - var winlib, linlib, osxlib: string = "" - for dl in c2nimConfig.dynlib: - let - lib = " const dynlib$# = \"$#\"\n" % [fname, dl] - ext = dl.splitFile().ext - - if ext == ".dll": - winlib &= lib - elif ext == ".so": - linlib &= lib - elif ext == ".dylib": - osxlib &= lib - - if winlib != "": - outlib &= win & winlib & "\n" - if linlib != "": - outlib &= lin & linlib & "\n" - if osxlib != "": - outlib &= osx & osxlib & "\n" - - if outlib != "": - extflags &= " --dynlib:dynlib$#" % fname - else: - if file.isAbsolute(): - passC &= "const header$# = \"$#\"\n" % [fname, file] - else: - passC &= "const header$# = currentSourcePath().splitPath().head & \"$#\"\n" % - [fname, file.relativePath()] - extflags = "--header:header$#" % fname - - # Run c2nim on generated file - var cmd = "c2nim $# $# --out:$# $#" % [c2nimConfig.flags, extflags, outfile, cfile] - when defined(windows): - cmd = "cmd /c " & cmd - discard execProc(cmd) - - if c2nimConfig.preprocess or c2nimConfig.ctags: - try: - removeFile(cfile) - except: - discard - - # Import nim modules - if c2nimConfig.recurse: - prepend(outfile, incout) - - # Nim doesn't like {.cdecl.} for type proc() - freplace(outfile, re"(?m)(.*? = proc.*?)\{.cdecl.\}", "$#") - freplace(outfile, " {.cdecl.})", ")") - - # Include {.compile.} directives - for cpl in c2nimConfig.compile: - let fcpl = search(cpl) - if getFileInfo(fcpl).kind == pcFile: - prepend(outfile, compile(file=fcpl)) - else: - prepend(outfile, compile(dir=fcpl)) - - # Add any pragmas - if outpragma != "": - prepend(outfile, outpragma) - - # Add header file and include paths - if passC != "": - prepend(outfile, passC) - - # Add dynamic library - if outlib != "": - prepend(outfile, outlib) - -# ### -# Processor - -proc runFile(file: string, cfgin: OrderedTableRef) = - var - cfg = cfgin - sfile = search(file) - - for pattern in gWildcards.keys(): - var match: RegexMatch - let pat = pattern.replace(".", "\\.").replace("*", ".*").replace("?", ".?") - if file.find(toPattern(pat), match): - echo "Appending " & file & " " & pattern - for key in gWildcards[pattern].keys(): - cfg[key & "." & pattern] = gWildcards[pattern][key] - - var - srch = "" - - c2nimConfig = c2nimConfigObj( - flags: "--stdcall", ppflags: "", - recurse: false, inline: false, preprocess: false, ctags: false, defines: false, - dynlib: @[], compile: @[], pragma: @[] - ) - - for act in cfg.keys(): - let (action, val) = getKey(act) - if val == true: - if action == "create": - createDir(file.splitPath().head) - writeFile(file, cfg[act]) - elif action in @["prepend", "append", "replace", "comment", - "rename", "compile", "dynlib", "pragma", - "pipe"] and sfile != "": - if action == "prepend": - if srch != "": - prepend(sfile, cfg[act], cfg[srch]) - else: - prepend(sfile, cfg[act]) - elif action == "append": - if srch != "": - append(sfile, cfg[act], cfg[srch]) - else: - append(sfile, cfg[act]) - elif action == "replace": - if srch != "": - freplace(sfile, cfg[srch], cfg[act]) - elif action == "comment": - if srch != "": - comment(sfile, cfg[srch], cfg[act]) - elif action == "rename": - rename(sfile, cfg[act]) - elif action == "compile": - c2nimConfig.compile.add(cfg[act]) - elif action == "dynlib": - c2nimConfig.dynlib.add(cfg[act]) - elif action == "pragma": - c2nimConfig.pragma.add(cfg[act]) - elif action == "pipe": - pipe(sfile, cfg[act]) - srch = "" - elif action == "search": - srch = act - - if file.splitFile().ext != ".nim": - var noprocess = false - - for act in cfg.keys(): - let (action, val) = getKey(act) - if val == true: - if cfg[act] == "true": - if action == "recurse": - c2nimConfig.recurse = true - elif action == "inline": - c2nimConfig.inline = true - elif action == "preprocess": - c2nimConfig.preprocess = true - elif action == "ctags": - c2nimConfig.ctags = true - elif action == "defines": - c2nimConfig.defines = true - elif action == "noprocess": - noprocess = true - elif action == "flags": - c2nimConfig.flags = cfg[act] - elif action == "ppflags": - c2nimConfig.ppflags = cfg[act] - - if c2nimConfig.recurse and c2nimConfig.inline: - echo "Cannot use recurse and inline simultaneously" - quit(1) - - # Remove static inline function bodies - removeStatic(sfile) - - if not noprocess: - c2nim(file, getNimout(sfile), c2nimConfig) - - # Add them back for compilation - reAddStatic(sfile) - -proc runCfg(cfg: string) = - if not fileExists(cfg): - echo "Config doesn't exist: " & cfg - quit(1) - - gProjectDir = parentDir(cfg.expandFilename()) - - gConfig = loadConfig(cfg) - - if gConfig.hasKey("n.global"): - if gConfig["n.global"].hasKey("output"): - gOutput = gConfig["n.global"]["output"] - if dirExists(gOutput): - if "-f" in commandLineParams(): - try: - removeDir(gOutput) - except OSError: - echo "Directory in use: " & gOutput - quit(1) - else: - for f in walkFiles(gOutput/"*.nim"): - try: - removeFile(f) - except OSError: - echo "Unable to delete: " & f - quit(1) - createDir(gOutput) - - if gConfig["n.global"].hasKey("cpp_compiler"): - gCppCompiler = gConfig["n.global"]["cpp_compiler"] - else: - # Reset on a per project basis - gCppCompiler = getEnv(cppCompilerEnv, defaultCppCompiler) - - if gConfig["n.global"].hasKey("c_compiler"): - gCCompiler = gConfig["n.global"]["c_compiler"] - else: - # Reset on a per project basis - gCCompiler = getEnv(cCompilerEnv, defaultCCompiler) - - if gConfig["n.global"].hasKey("filter"): - gFilter = gConfig["n.global"]["filter"] - if gConfig["n.global"].hasKey("quotes"): - if gConfig["n.global"]["quotes"] == "false": - gQuotes = false - - if gConfig.hasKey("n.include"): - for inc in gConfig["n.include"].keys(): - gIncludes.add(inc.addEnv()) - - if gConfig.hasKey("n.exclude"): - for excl in gConfig["n.exclude"].keys(): - gExcludes.add(excl.addEnv()) - - if gConfig.hasKey("n.prepare"): - for prep in gConfig["n.prepare"].keys(): - let (key, val) = getKey(prep) - if val == true: - let prepVal = gConfig["n.prepare"][prep] - if key == "download": - downloadUrl(prepVal) - elif key == "extract": - extractZip(prepVal) - elif key == "git": - gitRemotePull(prepVal) - elif key == "gitremote": - gitRemotePull(prepVal, false) - elif key == "gitsparse": - gitSparseCheckout(prepVal) - elif key == "execute": - discard execProc(prepVal) - elif key == "copy": - doCopy(prepVal) - - if gConfig.hasKey("n.wildcard"): - var wildcard = "" - for wild in gConfig["n.wildcard"].keys(): - let (key, val) = getKey(wild) - if val == true: - if key == "wildcard": - wildcard = gConfig["n.wildcard"][wild] - else: - gWildcards.setSectionKey(wildcard, wild, - gConfig["n.wildcard"][wild]) - - for file in gConfig.keys(): - if file in @["n.global", "n.include", "n.exclude", - "n.prepare", "n.wildcard", "n.post"]: - continue - - runFile(file, gConfig[file]) - - if gConfig.hasKey("n.post"): - for post in gConfig["n.post"].keys(): - let (key, val) = getKey(post) - if val == true: - let postVal = gConfig["n.post"][post] - if key == "reset": - gitReset() - elif key == "execute": - discard execProc(postVal) - -# ### -# Main loop - -for i in commandLineParams(): - if i != "-f": - runCfg(i) diff --git a/nimgen.nimble b/nimgen.nimble index 03dfb78..b1590df 100644 --- a/nimgen.nimble +++ b/nimgen.nimble @@ -5,13 +5,13 @@ author = "genotrance" description = "c2nim helper to simplify and automate the wrapping of C libraries" license = "MIT" +bin = @["nimgen"] +srcDir = "src" skipDirs = @["tests"] # Dependencies requires "nim >= 0.17.0", "c2nim >= 0.9.13", "regex >= 0.7.1" -bin = @["nimgen"] - task test, "Test nimgen": exec "nim e tests/nimgentest.nims" diff --git a/src/nimgen.nim b/src/nimgen.nim new file mode 100644 index 0000000..44ee88b --- /dev/null +++ b/src/nimgen.nim @@ -0,0 +1,7 @@ +import os + +import nimgen/config + +for i in commandLineParams(): + if i != "-f": + runCfg(i) diff --git a/src/nimgen/config.nim b/src/nimgen/config.nim new file mode 100644 index 0000000..3a0f3b0 --- /dev/null +++ b/src/nimgen/config.nim @@ -0,0 +1,229 @@ +import os, parsecfg, regex, strutils, tables + +import file, fileops, gencore, globals, prepare + +proc `[]`*(table: OrderedTableRef[string, string], key: string): string = + ## Gets table values with env vars inserted + tables.`[]`(table, key).addEnv + +proc getKey(ukey: string): tuple[key: string, val: bool] = + var kv = ukey.replace(re"\..*", "").split("-", 1) + if kv.len() == 1: + kv.add("") + + if (kv[1] == "") or + (kv[1] == "win" and defined(Windows)) or + (kv[1] == "lin" and defined(Linux)) or + (kv[1] == "osx" and defined(MacOSX)): + return (kv[0], true) + + return (kv[0], false) + +proc runFile*(file: string, cfgin: OrderedTableRef) = + var + cfg = cfgin + sfile = search(file) + + for pattern in gWildcards.keys(): + var match: RegexMatch + let pat = pattern.replace(".", "\\.").replace("*", ".*").replace("?", ".?") + if file.find(toPattern(pat), match): + echo "Appending " & file & " " & pattern + for key in gWildcards[pattern].keys(): + cfg[key & "." & pattern] = gWildcards[pattern][key] + + var + srch = "" + + c2nimConfig = c2nimConfigObj( + flags: "--stdcall", ppflags: "", + recurse: false, inline: false, preprocess: false, ctags: false, defines: false, + dynlib: @[], compile: @[], pragma: @[] + ) + + for act in cfg.keys(): + let (action, val) = getKey(act) + if val == true: + if action == "create": + createDir(file.splitPath().head) + writeFile(file, cfg[act]) + elif action in @["prepend", "append", "replace", "comment", + "rename", "compile", "dynlib", "pragma", + "pipe"] and sfile != "": + if action == "prepend": + if srch != "": + prepend(sfile, cfg[act], cfg[srch]) + else: + prepend(sfile, cfg[act]) + elif action == "append": + if srch != "": + append(sfile, cfg[act], cfg[srch]) + else: + append(sfile, cfg[act]) + elif action == "replace": + if srch != "": + freplace(sfile, cfg[srch], cfg[act]) + elif action == "comment": + if srch != "": + comment(sfile, cfg[srch], cfg[act]) + elif action == "rename": + rename(sfile, cfg[act]) + elif action == "compile": + c2nimConfig.compile.add(cfg[act]) + elif action == "dynlib": + c2nimConfig.dynlib.add(cfg[act]) + elif action == "pragma": + c2nimConfig.pragma.add(cfg[act]) + elif action == "pipe": + pipe(sfile, cfg[act]) + srch = "" + elif action == "search": + srch = act + + if file.splitFile().ext != ".nim": + var noprocess = false + + for act in cfg.keys(): + let (action, val) = getKey(act) + if val == true: + if cfg[act] == "true": + if action == "recurse": + c2nimConfig.recurse = true + elif action == "inline": + c2nimConfig.inline = true + elif action == "preprocess": + c2nimConfig.preprocess = true + elif action == "ctags": + c2nimConfig.ctags = true + elif action == "defines": + c2nimConfig.defines = true + elif action == "noprocess": + noprocess = true + elif action == "flags": + c2nimConfig.flags = cfg[act] + elif action == "ppflags": + c2nimConfig.ppflags = cfg[act] + + if c2nimConfig.recurse and c2nimConfig.inline: + echo "Cannot use recurse and inline simultaneously" + quit(1) + + if not noprocess: + var incls = c2nim(file, getNimout(sfile), c2nimConfig) + if c2nimConfig.recurse and incls.len() != 0: + var + cfg = newOrderedTable[string, string]() + + for name, value in c2nimConfig.fieldPairs: + when value is string: + cfg[name] = value + when value is bool: + cfg[name] = $value + + for i in c2nimConfig.dynlib: + cfg["dynlib." & i] = i + + for inc in incls: + runFile(inc, cfg) + +proc runCfg*(cfg: string) = + if not fileExists(cfg): + echo "Config doesn't exist: " & cfg + quit(1) + + gProjectDir = parentDir(cfg.expandFilename()) + + gConfig = loadConfig(cfg) + + if gConfig.hasKey("n.global"): + if gConfig["n.global"].hasKey("output"): + gOutput = gConfig["n.global"]["output"] + if dirExists(gOutput): + if "-f" in commandLineParams(): + try: + removeDir(gOutput) + except OSError: + echo "Directory in use: " & gOutput + quit(1) + else: + for f in walkFiles(gOutput/"*.nim"): + try: + removeFile(f) + except OSError: + echo "Unable to delete: " & f + quit(1) + createDir(gOutput) + + if gConfig["n.global"].hasKey("cpp_compiler"): + gCppCompiler = gConfig["n.global"]["cpp_compiler"] + else: + # Reset on a per project basis + gCppCompiler = getEnv(cppCompilerEnv, defaultCppCompiler) + + if gConfig["n.global"].hasKey("c_compiler"): + gCCompiler = gConfig["n.global"]["c_compiler"] + else: + # Reset on a per project basis + gCCompiler = getEnv(cCompilerEnv, defaultCCompiler) + + if gConfig["n.global"].hasKey("filter"): + gFilter = gConfig["n.global"]["filter"] + if gConfig["n.global"].hasKey("quotes"): + if gConfig["n.global"]["quotes"] == "false": + gQuotes = false + + if gConfig.hasKey("n.include"): + for inc in gConfig["n.include"].keys(): + gIncludes.add(inc.addEnv()) + + if gConfig.hasKey("n.exclude"): + for excl in gConfig["n.exclude"].keys(): + gExcludes.add(excl.addEnv()) + + if gConfig.hasKey("n.prepare"): + for prep in gConfig["n.prepare"].keys(): + let (key, val) = getKey(prep) + if val == true: + let prepVal = gConfig["n.prepare"][prep] + if key == "download": + downloadUrl(prepVal) + elif key == "extract": + extractZip(prepVal) + elif key == "git": + gitRemotePull(prepVal) + elif key == "gitremote": + gitRemotePull(prepVal, false) + elif key == "gitsparse": + gitSparseCheckout(prepVal) + elif key == "execute": + discard execProc(prepVal) + elif key == "copy": + doCopy(prepVal) + + if gConfig.hasKey("n.wildcard"): + var wildcard = "" + for wild in gConfig["n.wildcard"].keys(): + let (key, val) = getKey(wild) + if val == true: + if key == "wildcard": + wildcard = gConfig["n.wildcard"][wild] + else: + gWildcards.setSectionKey(wildcard, wild, + gConfig["n.wildcard"][wild]) + + for file in gConfig.keys(): + if file in @["n.global", "n.include", "n.exclude", + "n.prepare", "n.wildcard", "n.post"]: + continue + + runFile(file, gConfig[file]) + + if gConfig.hasKey("n.post"): + for post in gConfig["n.post"].keys(): + let (key, val) = getKey(post) + if val == true: + let postVal = gConfig["n.post"][post] + if key == "reset": + gitReset() + elif key == "execute": + discard execProc(postVal) diff --git a/src/nimgen/file.nim b/src/nimgen/file.nim new file mode 100644 index 0000000..27296d2 --- /dev/null +++ b/src/nimgen/file.nim @@ -0,0 +1,100 @@ +import os, ospaths, pegs, regex, strutils, tables + +import globals + +# ### +# File loction + +proc getNimout*(file: string, rename=true): string = + result = file.splitFile().name.replace(re"[\-\.]", "_") & ".nim" + if gOutput != "": + result = gOutput/result + + if not rename: + return + + if gRenames.hasKey(file): + result = gRenames[file] + + if not dirExists(parentDir(result)): + createDir(parentDir(result)) + +proc exclude*(file: string): bool = + for excl in gExcludes: + if excl in file: + return true + return false + +proc search*(file: string): string = + if exclude(file): + return "" + + result = file + if file.splitFile().ext == ".nim": + result = getNimout(file) + elif not fileExists(result) and not dirExists(result): + var found = false + for inc in gIncludes: + result = inc/file + if fileExists(result) or dirExists(result): + found = true + break + if not found: + echo "File doesn't exist: " & file + quit(1) + + # Only keep relative directory + return result.multiReplace([("\\", $DirSep), ("//", $DirSep), (gProjectDir & $DirSep, "")]) + +# ### +# Loading / unloading + +proc openRetry*(file: string, mode: FileMode = fmRead): File = + while true: + try: + result = open(file, mode) + break + except IOError: + sleep(100) + +template withFile*(file: string, body: untyped): untyped = + if fileExists(file): + var f = openRetry(file) + + var contentOrig = f.readAll() + f.close() + var content {.inject.} = contentOrig + + body + + if content != contentOrig: + f = openRetry(file, fmWrite) + write(f, content) + f.close() + else: + echo "Missing file " & file + +proc rename*(file: string, renfile: string) = + if file.splitFile().ext == ".nim": + return + + var + nimout = getNimout(file, false) + newname = renfile.replace("$nimout", extractFilename(nimout)) + + if newname =~ peg"(!\$.)*{'$replace'\s*'('\s*{(!\)\S)+}')'}": + var final = nimout.extractFilename() + for entry in matches[1].split(","): + let spl = entry.split("=") + if spl.len() != 2: + echo "Bad replace syntax: " & renfile + quit(1) + + let + srch = spl[0].strip() + repl = spl[1].strip() + + final = final.replace(srch, repl) + newname = newname.replace(matches[0], final) + + gRenames[file] = gOutput/newname diff --git a/src/nimgen/fileops.nim b/src/nimgen/fileops.nim new file mode 100644 index 0000000..d7ecd62 --- /dev/null +++ b/src/nimgen/fileops.nim @@ -0,0 +1,110 @@ +import os, regex, strutils + +import file, prepare + +# ### +# Manipulating content + +proc prepend*(file: string, data: string, search="") = + withFile(file): + if search == "": + content = data & content + else: + let idx = content.find(search) + if idx != -1: + content = content[0..<idx] & data & content[idx..<content.len()] + +proc pipe*(file: string, command: string) = + let cmd = command % ["file", file] + let commandResult = execProc(cmd).strip() + if commandResult != "": + withFile(file): + content = commandResult + +proc append*(file: string, data: string, search="") = + withFile(file): + if search == "": + content &= data + else: + let idx = content.find(search) + let idy = idx + search.len() + if idx != -1: + content = content[0..<idy] & data & content[idy..<content.len()] + +proc freplace*(file: string, pattern: string, repl="") = + withFile(file): + if pattern in content: + content = content.replace(pattern, repl) + +proc freplace*(file: string, pattern: Regex, repl="") = + withFile(file): + var m: RegexMatch + if content.find(pattern, m): + if "$#" in repl: + content = content.replace(pattern, + proc (m: RegexMatch, s: string): string = repl % s[m.group(0)[0]]) + else: + content = content.replace(pattern, repl) + +proc comment*(file: string, pattern: string, numlines: string) = + let + ext = file.splitFile().ext.toLowerAscii() + cmtchar = if ext == ".nim": "#" else: "//" + + withFile(file): + var + idx = content.find(pattern) + num = 0 + + try: + num = numlines.parseInt() + except ValueError: + echo "Bad comment value, should be integer: " & numlines + if idx != -1: + for i in 0 .. num-1: + if idx >= content.len(): + break + content = content[0..<idx] & cmtchar & content[idx..<content.len()] + while idx < content.len(): + idx += 1 + if content[idx] == '\L': + idx += 1 + break + +proc removeStatic*(filename: string) = + ## Replace static function bodies with a semicolon and commented + ## out body + withFile(filename): + content = content.replace( + re"(?m)(static inline.*?\))(\s*\{(\s*?.*?$)*[\n\r]\})", + proc (m: RegexMatch, s: string): string = + let funcDecl = s[m.group(0)[0]] + let body = s[m.group(1)[0]].strip() + result = "" + + result.add("$#;" % [funcDecl]) + result.add(body.replace(re"(?m)^", "//")) + ) + +proc reAddStatic*(filename: string) = + ## Uncomment out the body and remove the semicolon. Undoes + ## removeStatic + withFile(filename): + content = content.replace( + re"(?m)(static inline.*?\));(\/\/\s*\{(\s*?.*?$)*[\n\r]\/\/\})", + proc (m: RegexMatch, s: string): string = + let funcDecl = s[m.group(0)[0]] + let body = s[m.group(1)[0]].strip() + result = "" + + result.add("$# " % [funcDecl]) + result.add(body.replace(re"(?m)^\/\/", "")) + ) + +proc fixFuncProtos*(file: string) = + withFile(file): + content = content.replace(re"(?m)(^.*?)[ ]*\(\*(.*?)\((.*?)\)\)[ \r\n]*\((.*?[\r\n]*.*?)\);", + proc (m: RegexMatch, s: string): string = + (("typedef $# (*type_$#)($#);\n" % [s[m.group(0)[0]], s[m.group(1)[0]], s[m.group(3)[0]]]) & + ("type_$# $#($#);" % [s[m.group(1)[0]], s[m.group(1)[0]], s[m.group(2)[0]]])) + ) diff --git a/src/nimgen/gencore.nim b/src/nimgen/gencore.nim new file mode 100644 index 0000000..59e86fe --- /dev/null +++ b/src/nimgen/gencore.nim @@ -0,0 +1,295 @@ +import os, regex, ropes, sequtils, strutils, tables + +import file, fileops, globals, prepare + +proc addEnv*(str: string): string = + var newStr = str + for pair in envPairs(): + try: + newStr = newStr % [pair.key, pair.value.string] + except ValueError: + # Ignore if there are no values to replace. We + # want to continue anyway + discard + + try: + newStr = newStr % ["output", gOutput] + except ValueError: + # Ignore if there are no values to replace. We + # want to continue anyway + discard + + # if there are still format args, print a warning + if newStr.contains("${"): + echo "WARNING: \"", newStr, "\" still contains an uninterpolated value!" + + return newStr + +proc compile*(dir="", file=""): string = + proc fcompile(file: string): string = + return "{.compile: \"$#\".}" % file.replace("\\", "/") + + var data = "" + if dir != "" and dirExists(dir): + for f in walkFiles(dir / "*.c"): + data &= fcompile(f) & "\n" + + if file != "" and fileExists(file): + data &= fcompile(file) & "\n" + + return data + +proc getIncls*(file: string, inline=false): seq[string] = + result = @[] + if inline and file in gDoneInline: + return + + let curPath = splitFile(expandFileName(file)).dir + withFile(file): + for f in content.findAll(re"(?m)^\s*#\s*include\s+(.*?)$"): + var inc = content[f.group(0)[0]].strip() + if ((gQuotes and inc.contains("\"")) or (gFilter != "" and gFilter in inc)) and (not exclude(inc)): + let addInc = inc.replace(re"""[<>"]""", "").replace(re"\/[\*\/].*$", "").strip() + try: + # Try searching for a local library. expandFilename will throw + # OSError if the file does not exist + let + finc = expandFileName(curPath / addInc) + fname = finc.replace(curPath & $DirSep, "") + + if fname.len() > 0: + # only add if the file is non-empty + result.add(fname.search()) + except OSError: + # If it's a system library + result.add(addInc) + + result = result.deduplicate() + + gDoneInline.add(file) + + if inline: + var sres = newSeq[string]() + for incl in result: + let sincl = search(incl) + if sincl == "": + continue + + sres.add(getIncls(sincl, inline)) + result.add(sres) + + result = result.deduplicate() + +proc getDefines*(file: string, inline=false): string = + result = "" + if inline: + var incls = getIncls(file, inline) + for incl in incls: + let sincl = search(incl) + if sincl != "": + echo "Inlining " & sincl + result &= getDefines(sincl) + withFile(file): + for def in content.findAll(re"(?m)^(\s*#\s*define\s+[\w\d_]+\s+[\d\-.xf]+)(?:\r|//|/*).*?$"): + result &= content[def.group(0)[0]] & "\n" + +proc runPreprocess*(file, ppflags, flags: string, inline: bool): string = + var + pproc = if flags.contains("cpp"): gCppCompiler else: gCCompiler + cmd = "$# -E $# $#" % [pproc, ppflags, file] + + for inc in gIncludes: + cmd &= " -I " & inc + + # Run preprocessor + var data = execProc(cmd) + + # Include content only from file + var + rdata: Rope + start = false + sfile = file.replace("\\", "/") + + if inline: + sfile = sfile.parentDir() + for line in data.splitLines(): + if line.strip() != "": + if line[0] == '#' and not line.contains("#pragma"): + start = false + if sfile in line.replace("\\", "/").replace("//", "/"): + start = true + if not ("\\" in line) and not ("/" in line) and extractFilename(sfile) in line: + start = true + else: + if start: + rdata.add( + line.replace("_Noreturn", "") + .replace("(())", "") + .replace("WINAPI", "") + .replace("__attribute__", "") + .replace("extern \"C\"", "") + .replace(re"\(\([_a-z]+?\)\)", "") + .replace(re"\(\(__format__[\s]*\(__[gnu_]*printf__, [\d]+, [\d]+\)\)\);", ";") & "\n" + ) + return $rdata + +proc runCtags*(file: string): string = + var + cmd = "ctags -o - --fields=+S+K --c-kinds=+p --file-scope=no " & file + fps = execProc(cmd) + fdata = "" + + for line in fps.splitLines(): + var spl = line.split(re"\t") + if spl.len() > 4: + if spl[0] != "main" and spl[3] != "member": + var fn = "" + var match: RegexMatch + if spl[2].find(re"/\^(.*?)\(", match): + fn = spl[2][match.group(0)[0]] + fn &= spl[4].replace("signature:", "") & ";" + fdata &= fn & "\n" + + return fdata + +template relativePath(path: untyped): untyped = + path.multiReplace([(gOutput, ""), ("\\", "/"), ("//", "/")]) + +proc c2nim*(fl, outfile: string, c2nimConfig: c2nimConfigObj): seq[string] = + var + incls: seq[string] = @[] + incout = "" + file = search(fl) + + if file == "": + return + + if file in gDoneRecursive: + return + + echo "Processing $# => $#" % [file, outfile] + gDoneRecursive.add(file) + + # Remove static inline function bodies + removeStatic(file) + + fixFuncProtos(file) + + if c2nimConfig.recurse: + incls = getIncls(file) + for inc in incls: + incout &= "import $#\n" % inc.search().getNimout()[0 .. ^5] + + var cfile = file + if c2nimConfig.preprocess: + cfile = "temp-$#.c" % [outfile.extractFilename()] + writeFile(cfile, runPreprocess(file, c2nimConfig.ppflags, c2nimConfig.flags, c2nimConfig.inline)) + elif c2nimConfig.ctags: + cfile = "temp-$#.c" % [outfile.extractFilename()] + writeFile(cfile, runCtags(file)) + + if c2nimConfig.defines and (c2nimConfig.preprocess or c2nimConfig.ctags): + prepend(cfile, getDefines(file, c2nimConfig.inline)) + + var + extflags = "" + passC = "" + outlib = "" + outpragma = "" + + passC = "import ospaths, strutils\n" + + for inc in gIncludes: + if inc.isAbsolute(): + passC &= ("""{.passC: "-I\"$#\"".}""" % [inc]) & "\n" + else: + passC &= ( + """{.passC: "-I\"" & currentSourcePath().splitPath().head & "$#\"".}""" % + inc.relativePath() + ) & "\n" + + for prag in c2nimConfig.pragma: + outpragma &= "{." & prag & ".}\n" + + let fname = file.splitFile().name.replace(re"[\.\-]", "_") + + if c2nimConfig.dynlib.len() != 0: + let + win = "when defined(Windows):\n" + lin = "when defined(Linux):\n" + osx = "when defined(MacOSX):\n" + + var winlib, linlib, osxlib: string = "" + for dl in c2nimConfig.dynlib: + let + lib = " const dynlib$# = \"$#\"\n" % [fname, dl] + ext = dl.splitFile().ext + + if ext == ".dll": + winlib &= lib + elif ext == ".so": + linlib &= lib + elif ext == ".dylib": + osxlib &= lib + + if winlib != "": + outlib &= win & winlib & "\n" + if linlib != "": + outlib &= lin & linlib & "\n" + if osxlib != "": + outlib &= osx & osxlib & "\n" + + if outlib != "": + extflags &= " --dynlib:dynlib$#" % fname + else: + if file.isAbsolute(): + passC &= "const header$# = \"$#\"\n" % [fname, file] + else: + passC &= "const header$# = currentSourcePath().splitPath().head & \"$#\"\n" % + [fname, file.relativePath()] + extflags = "--header:header$#" % fname + + # Run c2nim on generated file + var cmd = "c2nim $# $# --out:$# $#" % [c2nimConfig.flags, extflags, outfile, cfile] + when defined(windows): + cmd = "cmd /c " & cmd + discard execProc(cmd) + + if c2nimConfig.preprocess or c2nimConfig.ctags: + try: + removeFile(cfile) + except: + discard + + # Import nim modules + if c2nimConfig.recurse: + prepend(outfile, incout) + + # Nim doesn't like {.cdecl.} for type proc() + freplace(outfile, re"(?m)(.*? = proc.*?)\{.cdecl.\}", "$#") + freplace(outfile, " {.cdecl.})", ")") + + # Include {.compile.} directives + for cpl in c2nimConfig.compile: + let fcpl = search(cpl) + if getFileInfo(fcpl).kind == pcFile: + prepend(outfile, compile(file=fcpl)) + else: + prepend(outfile, compile(dir=fcpl)) + + # Add any pragmas + if outpragma != "": + prepend(outfile, outpragma) + + # Add header file and include paths + if passC != "": + prepend(outfile, passC) + + # Add dynamic library + if outlib != "": + prepend(outfile, outlib) + + # Add back static functions for compilation + reAddStatic(file) + + return incls diff --git a/src/nimgen/globals.nim b/src/nimgen/globals.nim new file mode 100644 index 0000000..c371fe2 --- /dev/null +++ b/src/nimgen/globals.nim @@ -0,0 +1,40 @@ +import os, parsecfg, tables + +const + cCompilerEnv* = "CC" + cppCompilerEnv* = "CPP" + defaultCCompiler* = "gcc" + defaultCppCompiler* = "g++" + +var + gDoneRecursive*: seq[string] = @[] + gDoneInline*: seq[string] = @[] + + gProjectDir* = "" + gConfig*: Config + gFilter* = "" + gQuotes* = true + gCppCompiler* = getEnv(cppCompilerEnv, defaultCCompiler) + gCCompiler* = getEnv(cCompilerEnv, defaultCppCompiler) + gOutput* = "" + gIncludes*: seq[string] = @[] + gExcludes*: seq[string] = @[] + gRenames* = initTable[string, string]() + gWildcards* = newConfig() + +type + c2nimConfigObj* = object + flags*, ppflags*: string + recurse*, inline*, preprocess*, ctags*, defines*: bool + dynlib*, compile*, pragma*: seq[string] + +const gDoc* = """ +Nimgen is a helper for c2nim to simpilfy and automate the wrapping of C libraries + +Usage: + nimgen [options] <file.cfg>... + +Options: + -f delete all artifacts and regenerate +""" + diff --git a/src/nimgen/prepare.nim b/src/nimgen/prepare.nim new file mode 100644 index 0000000..a1857a5 --- /dev/null +++ b/src/nimgen/prepare.nim @@ -0,0 +1,117 @@ +import os, osproc, streams, strutils + +import globals + +proc execProc*(cmd: string): string = + result = "" + var + p = startProcess(cmd, options = {poStdErrToStdOut, poUsePath, poEvalCommand}) + + outp = outputStream(p) + line = newStringOfCap(120).TaintedString + + while true: + if outp.readLine(line): + result.add(line) + result.add("\n") + elif not running(p): break + + var x = p.peekExitCode() + if x != 0: + echo "Command failed: " & $x + echo cmd + echo result + quit(1) + +proc extractZip*(zipfile: string) = + var cmd = "unzip -o $#" + if defined(Windows): + cmd = "powershell -nologo -noprofile -command \"& { Add-Type -A " & + "'System.IO.Compression.FileSystem'; " & + "[IO.Compression.ZipFile]::ExtractToDirectory('$#', '.'); }\"" + + setCurrentDir(gOutput) + defer: setCurrentDir(gProjectDir) + + echo "Extracting " & zipfile + discard execProc(cmd % zipfile) + +proc downloadUrl*(url: string) = + let + file = url.extractFilename() + ext = file.splitFile().ext.toLowerAscii() + + var cmd = "curl $# -o $#" + if defined(Windows): + cmd = "powershell wget $# -OutFile $#" + + if not (ext == ".zip" and fileExists(gOutput/file)): + echo "Downloading " & file + discard execProc(cmd % [url, gOutput/file]) + + if ext == ".zip": + extractZip(file) + +proc gitReset*() = + echo "Resetting Git repo" + + setCurrentDir(gOutput) + defer: setCurrentDir(gProjectDir) + + discard execProc("git reset --hard HEAD") + +proc gitCheckout*(filename: string) {.used.} = + echo "Resetting file: $#" % [filename] + + setCurrentDir(gOutput) + defer: setCurrentDir(gProjectDir) + + let adjustedFile = filename.replace(gOutput & $DirSep, "") + + discard execProc("git checkout $#" % [adjustedFile]) + +proc gitRemotePull*(url: string, pull=true) = + if dirExists(gOutput/".git"): + if pull: + gitReset() + return + + setCurrentDir(gOutput) + defer: setCurrentDir(gProjectDir) + + echo "Setting up Git repo" + discard execProc("git init .") + discard execProc("git remote add origin " & url) + + if pull: + echo "Checking out artifacts" + discard execProc("git pull --depth=1 origin master") + +proc gitSparseCheckout*(plist: string) = + let sparsefile = ".git/info/sparse-checkout" + if fileExists(gOutput/sparsefile): + gitReset() + return + + setCurrentDir(gOutput) + defer: setCurrentDir(gProjectDir) + + discard execProc("git config core.sparsecheckout true") + writeFile(sparsefile, plist) + + echo "Checking out artifacts" + discard execProc("git pull --depth=1 origin master") + +proc doCopy*(flist: string) = + for pair in flist.split(","): + let spl = pair.split("=") + if spl.len() != 2: + echo "Bad copy syntax: " & flist + quit(1) + + let + lfile = spl[0].strip() + rfile = spl[1].strip() + + copyFile(lfile, rfile) + echo "Copied $# to $#" % [lfile, rfile] |
