findFile w/regex, make -j, lib support for getHeader, noexcept / throw

This commit is contained in:
Ganesh Viswanathan 2019-08-23 16:00:43 -05:00
commit 182d473973
3 changed files with 153 additions and 64 deletions

View file

@ -36,12 +36,16 @@ proc testAll() =
execCmd "nim cpp -r tests/tnimterop_cpp.nim"
execTest "tests/tpcre.nim"
## platform specific tests
# platform specific tests
when defined(Windows):
execTest "tests/tmath.nim"
if defined(OSX) or defined(Windows) or not existsEnv("TRAVIS"):
tsoloud() # requires some libraries on linux, need them installed in TRAVIS
# getHeader tests
withDir("tests"):
execCmd("nim e getheader.nims")
const htmldocsDir = "build/htmldocs"
when (NimMajor, NimMinor, NimPatch) >= (0, 19, 9):

View file

@ -1,4 +1,4 @@
import macros, osproc, sequtils, strformat, strutils
import macros, osproc, regex, sequtils, strformat, strutils
import os except findExe
@ -194,6 +194,29 @@ proc gitPull*(url: string, outdir = "", plist = "", checkout = "") =
echo "# Pulling repository"
discard execAction(&"cd {outdirQ} && git pull --depth=1 origin master")
proc findFile*(file: string|Regex, dir: string, recurse = true, first = false): string =
## Find the file in the specified directory
##
## ``file`` can be a string or a regex object
##
## Turn off recursive search with ``recurse`` and stop on first match with
## ``first``. Without it, the shortest match is returned.
when file is Regex:
var
rm: RegexMatch
for f in walkDirRec(dir, followFilter = if recurse: {pcDir} else: {}):
let
fn = f.extractFilename()
when file is string:
if (result.len == 0 or result.len > f.len) and fn == file:
result = f
if first: break
else:
if (result.len == 0 or result.len > f.len) and fn.match(file, rm):
result = f
if first: break
proc configure*(path, check: string, flags = "") =
## Run the GNU `configure` command to generate all Makefiles or other
## build scripts in the specified path
@ -261,7 +284,7 @@ proc cmake*(path, check, flags: string) =
doAssert (path / check).fileExists(), "# cmake failed"
proc make*(path, check: string, flags = "") =
proc make*(path, check: string|Regex, flags = "") =
## Run the `make` command to build all binaries in the specified path
##
## `check` is a file that will be generated by the `make` command.
@ -272,7 +295,7 @@ proc make*(path, check: string, flags = "") =
##
## If make.exe is missing and mingw32-make.exe is available, it will
## be copied over to make.exe in the same location.
if (path / check).fileExists():
if findFile(check, path).len != 0:
return
echo "# Running make " & flags
@ -293,14 +316,7 @@ proc make*(path, check: string, flags = "") =
echo execAction(cmd)
doAssert (path / check).fileExists(), "# make failed"
proc findFile*(file, dir: string): string =
## Find the file in the specified directory
for f in walkDirRec(dir):
if f.extractFilename() == file:
if result.len == 0 or result.len > f.len:
result = f
doAssert findFile(check, path).len != 0, "# make failed"
proc getGccPaths*(mode = "c"): seq[string] =
var
@ -308,7 +324,7 @@ proc getGccPaths*(mode = "c"): seq[string] =
mmode = if mode == "cpp": "c++" else: mode
inc = false
(outp, ret) = gorgeEx(&"""{getEnv("CC", "gcc")} -Wp,-v -x{mmode} {nul}""")
(outp, _) = gorgeEx(&"""{getEnv("CC", "gcc")} -Wp,-v -x{mmode} {nul}""")
for line in outp.splitLines():
if "#include <...> search starts here" in line:
@ -317,11 +333,38 @@ proc getGccPaths*(mode = "c"): seq[string] =
elif "End of search list" in line:
break
if inc:
result.add line.strip()
var
path = line.strip()
path.normalizePath()
if path notin result:
result.add path
proc getGccLibPaths*(mode = "c"): seq[string] =
var
nul = when defined(Windows): "nul" else: "/dev/null"
mmode = if mode == "cpp": "c++" else: mode
(outp, _) = gorgeEx(&"""{getEnv("CC", "gcc")} -v -x{mmode} {nul}""")
for line in outp.splitLines():
if "LIBRARY_PATH=" in line:
for path in line[13 .. ^1].split(PathSep):
var
path = path.strip()
path.normalizePath()
if path notin result:
result.add path
break
proc getStdPath(header: string): string =
for inc in getGccPaths():
result = findFile(header, inc)
result = findFile(header, inc, recurse = false, first = true)
if result.len != 0:
break
proc getStdLibPath(lname: string): string =
for lib in getGccLibPaths():
result = findFile(re(lname), lib, recurse = false, first = true)
if result.len != 0:
break
@ -369,44 +412,49 @@ proc getLocalPath(header, outdir: string): string =
if outdir.len != 0:
result = findFile(header, outdir)
proc buildLibrary(outdir, conFlags, conStaticLib, conDynLib, cmakeFlags, cmakeStaticLib, cmakeDynLib, makeFlags: string) =
proc getNumProcs(): string =
when defined(windows):
getEnv("NUMBER_OF_PROCESSORS").strip()
elif defined(linux):
execAction("nproc").strip()
elif defined(macosx):
execAction("sysctl -n hw.ncpu").strip()
else:
"1"
proc buildLibrary(lname, outdir, conFlags, cmakeFlags, makeFlags: string): string =
var
conDeps = false
conDepStr = ""
cmakeDeps = false
cmakeDepStr = ""
lpath = findFile(re(lname), outdir)
makeFlagsProc = &"-j {getNumProcs()} {makeFlags}"
if lpath.len != 0:
return lpath
if fileExists(outdir / "CMakeLists.txt"):
if findExe("cmake").len != 0:
if cmakeStaticLib.len != 0 or cmakeDynLib.len != 0:
var
gen = ""
when defined(windows):
if findExe("sh").len != 0:
gen = "MSYS Makefiles"
else:
gen = "MinGW Makefiles"
var
gen = ""
when defined(windows):
if findExe("sh").len != 0:
gen = "MSYS Makefiles"
else:
gen = "Unix Makefiles"
cmake(outdir / "build", "Makefile", &".. -G {gen.quoteShell} {cmakeFlags}")
cmakeDeps = true
let
check = if cmakeStaticLib.len != 0: cmakeStaticLib else: cmakeDynLib
make(outdir / "build", check, makeFlags)
gen = "MinGW Makefiles"
else:
cmakeDepStr &= "cmakeStatibLib / cmakeDynLib not specified"
gen = "Unix Makefiles"
cmake(outdir / "build", "Makefile", &".. -G {gen.quoteShell} {cmakeFlags}")
cmakeDeps = true
make(outdir / "build", re(lname), makeFlagsProc)
else:
cmakeDepStr &= "cmake executable missing"
template cfgCommon() {.dirty.} =
if (conStaticLib.len != 0 or conDynLib.len != 0):
configure(outdir, "Makefile", conFlags)
conDeps = true
let
check = if conStaticLib.len != 0: conStaticLib else: conDynLib
make(outdir, check, makeFlags)
else:
conDepStr &= "conStaticLib / conDynLib not specified"
configure(outdir, "Makefile", conFlags)
conDeps = true
make(outdir, re(lname), makeFlagsProc)
if not cmakeDeps:
if not fileExists(outdir / "configure"):
@ -437,53 +485,89 @@ proc buildLibrary(outdir, conFlags, conStaticLib, conDynLib, cmakeFlags, cmakeSt
error = "No build files found in " & outdir
doAssert cmakeDeps or conDeps, &"\n# Build configuration failed - {error}\n"
result = findFile(re(lname), outdir)
proc getDynlibExt(): string =
when defined(windows):
result = ".dll"
elif defined(linux):
result = ".so[0-9.]*"
elif defined(macosx):
result = ".dylib[0-9.]*"
macro getHeader*(header: static[string], giturl: static[string] = "", dlurl: static[string] = "", outdir: static[string] = "",
conFlags: static[string] = "", conStaticLib: static[string] = "", conDynLib: static[string] = "",
cmakeFlags: static[string] = "", cmakeStaticLib: static[string] = "", cmakeDynLib: static[string] = "",
makeFlags: static[string] = ""): untyped =
conFlags: static[string] = "", cmakeFlags: static[string] = "", makeFlags: static[string] = ""): untyped =
## Get the path to a header file for wrapping with
## `cImport() <cimport.html#cImport.m%2C%2Cstring%2Cstring%2Cstring>`_ or
## `c2nImport() <cimport.html#c2nImport.m%2C%2Cstring%2Cstring%2Cstring>`_.
##
## Checks defines based on the header name (e.g. lzma from lzma.h), to use different
## ways to obtain the source.
## This proc checks -d:xxx defines based on the header name (e.g. lzma from lzma.h),
## and accordingly employs different ways to obtain the source.
##
## ``-d:xxxStd`` - search standard system paths. E.g. ``/usr/include`` and ``/usr/lib`` on Linux
## ``-d:xxxGit`` - clone source from a git repo specified in ``giturl``
## ``-d:xxxDL`` - download source from ``dlurl`` and extract if required
##
## This allows a single wrapper to be used in different ways depending on the user's needs.
## If no defines are specified, ``outdir`` will be searched for the header.
## If no -d:xxx defines are specified, ``outdir`` will be searched for the header.
##
## The library is then configured (either with cmake or autotools if possible) and then built
## using make.
## The library is then configured (with cmake or autotools if possible) and built
## using make, unless using ``-d:xxxStd`` which presumes that the system package
## manager was used to install prebuilt headers and binaries.
##
## The header path is stored in ``const xxxPath`` and can be used in a ``cImport()`` call
## in the calling wrapper. The dynamic library path is stored in ``const xxxLPath`` and can
## be used for the ``dynlib`` parameter (within quotes).
##
## ``-d:xxxStatic`` can be specified to statically link with the library instead. This
## will automatically add a ``{.passL.}`` call to the static library for convenience.
var
name = header.split(".")[0]
stdName = newIdentNode(name & "Std")
gitName = newIdentNode(name & "Git")
dlName = newIdentNode(name & "DL")
nameStd = newIdentNode(name & "Std")
nameGit = newIdentNode(name & "Git")
nameDL = newIdentNode(name & "DL")
nameStatic = newIdentNode(name & "Static")
path = newIdentNode(name & "Path")
lpath = newIdentNode(name & "LPath")
version = newIdentNode(name & "Version")
lname = newIdentNode(name & "LName")
lre = "(lib)?$1[0-9.\\-]*\\" % name
result = newNimNode(nnkStmtList)
result.add(quote do:
const `version`* {.strdefine.} = ""
when defined(`stdName`):
const `path`* = getStdPath(`header`)
else:
const `path`* =
when defined(`gitName`):
getGitPath(`header`, `giturl`, `outdir`, `version`)
elif defined(`dlName`):
getDlPath(`header`, `dlurl`, `outdir`, `version`)
const
`version`* {.strdefine.} = ""
`lname` =
when defined(`nameStatic`):
`lre` & ".a"
else:
getLocalPath(`header`, `outdir`)
`lre` & getDynlibExt()
static:
doAssert `path`.len != 0, "\nHeader " & `header` & " not found - " & "missing/empty outdir or -d:$1Std -d:$1Git or -d:$1DL not specified" % `name`
when defined(`nameStd`):
const
`path`* = getStdPath(`header`)
`lpath`* = getStdLibPath(`lname`)
else:
const
`path`* =
when defined(`nameGit`):
getGitPath(`header`, `giturl`, `outdir`, `version`)
elif defined(`nameDL`):
getDlPath(`header`, `dlurl`, `outdir`, `version`)
else:
getLocalPath(`header`, `outdir`)
buildLibrary(`outdir`, `conFlags`, `conStaticLib`, `conDynLib`, `cmakeFlags`, `cmakeStaticLib`, `cmakeDynLib`, `makeFlags`)
`lpath`* = buildLibrary(`lname`, `outdir`, `conFlags`, `cmakeFlags`, `makeFlags`)
static:
doAssert `path`.len != 0, "\nHeader " & `header` & " not found - " & "missing/empty outdir or -d:$1Std -d:$1Git or -d:$1DL not specified" % `name`
doAssert `lpath`.len != 0, "\nLibrary " & `lname` & " not found"
echo "# Including library " & `lpath`
when defined(`nameStatic`):
{.passL: `lpath`.}
)

View file

@ -73,6 +73,7 @@ proc initGrammar(): Grammar =
(type_identifier)
)
{paramListGrammar}
(noexcept|throw_specifier?)
)
"""