From cebf6073424a50781ecce268eeb8d63f4aa8f0c0 Mon Sep 17 00:00:00 2001 From: Ganesh Viswanathan Date: Tue, 16 Jun 2020 17:41:18 -0500 Subject: libdir support --- nimterop/build.nim | 97 ++++++++++++++++++++++++++++++++++++++++++++++------ nimterop/cimport.nim | 13 ------- nimterop/conan.nim | 2 +- nimterop/jbb.nim | 16 +++------ nimterop/nimconf.nim | 61 ++++++++++++++++----------------- 5 files changed, 120 insertions(+), 69 deletions(-) diff --git a/nimterop/build.nim b/nimterop/build.nim index eda27e7..6cb497d 100644 --- a/nimterop/build.nim +++ b/nimterop/build.nim @@ -158,8 +158,10 @@ proc mkDir*(dir: string) = flag = when not defined(Windows): "-p" else: "" discard execAction(&"mkdir {flag} {dir.sanitizePath}", retry = 2) -proc cpFile*(source, dest: string, move = false) = +proc cpFile*(source, dest: string, psymlink = false, move = false) = ## Copy a file from `source` to `dest` at compile time + ## + ## `psymlink = true` preserves symlinks instead of dereferencing on posix let source = source.replace("/", $DirSep) dest = dest.replace("/", $DirSep) @@ -173,7 +175,10 @@ proc cpFile*(source, dest: string, move = false) = if move: "mv -f" else: - "cp -f" + if psymlink: + "cp -fd" + else: + "cp -f" discard execAction(&"{cmd} {source.sanitizePath} {dest.sanitizePath}", retry = 2) @@ -233,6 +238,20 @@ proc mvTree*(source, dest: string) = ## Move contents of source dir to the destination, not the directory itself cpTree(source, dest, move = true) +proc getFileDate*(fullpath: string): string = + ## Get file date for `fullpath` + var + ret = 0 + cmd = + when defined(Windows): + &"cmd /c for %a in ({fullpath.sanitizePath}) do echo %~ta" + elif defined(Linux): + &"stat -c %y {fullpath.sanitizePath}" + elif defined(OSX) or defined(FreeBSD): + &"stat -f %m {fullpath.sanitizePath}" + + (result, ret) = execAction(cmd) + proc getProjectCacheDir*(name: string, forceClean = true): string = ## Get a cache directory where all nimterop artifacts can be stored ## @@ -1083,7 +1102,8 @@ macro isDefined*(def: untyped): untyped = macro getHeader*( header: static[string], giturl: static[string] = "", dlurl: static[string] = "", - conanuri: static[string] = "", jbburi: static[string] = "", outdir: static[string] = "", + conanuri: static[string] = "", jbburi: static[string] = "", + outdir: static[string] = "", libdir: static[string] = "", conFlags: static[string] = "", cmakeFlags: static[string] = "", makeFlags: static[string] = "", altNames: static[string] = "", buildTypes: static[openArray[BuildType]] = [btCmake, btAutoconf]): untyped = ## Get the path to a header file for wrapping with @@ -1125,7 +1145,14 @@ macro getHeader*( ## ## 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) or with `{.passL.}`. + ## be used for the `dynlib` parameter (within quotes) or with `{.passL.}`. Any dependency + ## libraries downloaded by `Conan` or `JBB` are returned in `const xxxLDeps` as a seq[string]. + ## + ## `libdir` can be used to instruct `getHeader()` to copy shared libraries and their + ## dependencies to that directory. This prevents any runtime failures if `outdir` gets + ## removed or its contents changed. By default, `libdir` is set to the output directory + ## where the program binary will be created. The values of `xxxLPath` and `xxxLDeps` will + ## reflect this new location. ## ## `-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. Note @@ -1214,6 +1241,8 @@ macro getHeader*( "" mode = getCompilerMode(header) + libdir = if libdir.len != 0: libdir else: getOutDir() + # Use alternate library names if specified for regex search if altNames.len != 0: lre = lre % ("(" & altNames.replace(",", "|") & ")") @@ -1272,9 +1301,9 @@ macro getHeader*( static: `preBuild`(`outdir`, prePath) - const - # Library binary path - build if not standard - `lpath`* = + let + # Library binary path - build if not standard / conan / jbb + lpath {.compiletime.} = when useStd: stdLPath elif `nameConan` or `nameJBB`: @@ -1283,7 +1312,7 @@ macro getHeader*( buildLibrary(`lname`, `outdir`, `conFlags`, `cmakeFlags`, `makeFlags`, `buildTypes`) # Library dependecy paths - `ldeps`*: seq[string] = + ldeps {.compiletime.}: seq[string] = when `nameConan`: getConanLDeps(`outdir`) elif `nameJBB`: @@ -1291,6 +1320,7 @@ macro getHeader*( else: @[] + const # Header path - search again in case header is generated in build `path`* = if prePath.len != 0: @@ -1301,12 +1331,57 @@ macro getHeader*( static: doAssert `path`.len != 0, "\nHeader " & `header` & " not found - " & "missing/empty outdir or -d:$1Std -d:$1Git -d:$1DL -d:$1Conan or -d:$1JBB not specified" % `name` - doAssert `lpath`.len != 0, "\nLibrary " & `lname` & " not found" - echo "# Including library " & `lpath` + doAssert lpath.len != 0, "\nLibrary " & `lname` & " not found" + + proc extractFilenameStatic(str: string): string {.compiletime.} = + var + pos = -1 + for i in countdown(str.len - 1, 0): + if str[i] == '/' or str[i] == '\\': + pos = i + 1 + break + result = str[pos .. ^1] + + proc joinPathStatic(str1, str2: string): string {.compiletime.} = + let + sep = when defined(Windows): "\\" else: "/" + result = str1 & sep & str2 - # Automatically link with static library and dependencies when `nameStatic`: + const + `lpath`* = lpath + `ldeps`* = ldeps + + # Automatically link with static library and dependencies {.passL: `lpath`.} if `ldeps`.len != 0: {.passL: `ldeps`.join(" ").} + + static: + echo "# Including library " & lpath + if `ldeps`.len != 0: + echo "# Including dependencies " & `ldeps`.join(" ") + else: + const + `lpath`* = joinPathStatic(`libdir`, lpath.extractFilenameStatic()) + `ldeps`* = block: + var + ldeps = ldeps + for i in 0 ..< ldeps.len: + let + lname = ldeps[i].extractFilenameStatic() + ldeptgt = joinPathStatic(`libdir`, lname) + if not fileExists(ldeptgt) or getFileDate(ldeps[i]) > getFileDate(ldeptgt): + echo "# Copying " & lname & " to " & `libdir` + cpFile(ldeps[i], ldeptgt, psymlink = true) + ldeps[i] = ldeptgt + ldeps + + static: + # Copy shared libraries and dependencies to `libdir` + if not fileExists(`lpath`) or getFileDate(lpath) > getFileDate(`lpath`): + echo "# Copying " & `lpath`.extractFilenameStatic() & " to " & `libdir` + cpFile(lpath, `lpath`) + + echo "# Including library " & `lpath` ) diff --git a/nimterop/cimport.nim b/nimterop/cimport.nim index 0559db7..c81c35b 100644 --- a/nimterop/cimport.nim +++ b/nimterop/cimport.nim @@ -70,19 +70,6 @@ proc walkDirImpl(indir, inext: string, file=true): seq[string] = if ret == 0: result = output.splitLines() -proc getFileDate(fullpath: string): string = - var - ret = 0 - cmd = - when defined(Windows): - &"cmd /c for %a in ({fullpath.sanitizePath}) do echo %~ta" - elif defined(Linux): - &"stat -c %y {fullpath.sanitizePath}" - elif defined(OSX) or defined(FreeBSD): - &"stat -f %m {fullpath.sanitizePath}" - - (result, ret) = execAction(cmd) - proc getCacheValue(fullpath: string): string = if not gStateCT.nocache: result = fullpath.getFileDate() diff --git a/nimterop/conan.nim b/nimterop/conan.nim index bfe2e94..f8740d0 100644 --- a/nimterop/conan.nim +++ b/nimterop/conan.nim @@ -278,7 +278,7 @@ proc parseConanManifest(pkg: ConanPackage, outdir: string) = if line.startsWith("lib/"): if line.endsWith(".a") or line.endsWith(".lib"): pkg.staticLibs.add line - elif line.endsWith(".so"): + elif line.endsWith(".so") or line.endsWith(".dylib"): pkg.sharedLibs.add line elif line.startsWith("bin/") and line.endsWith("dll"): pkg.sharedLibs.add line diff --git a/nimterop/jbb.nim b/nimterop/jbb.nim index 9f0d173..3e7e623 100644 --- a/nimterop/jbb.nim +++ b/nimterop/jbb.nim @@ -94,18 +94,11 @@ proc parseJBBArtifacts(pkg: JBBPackage, outdir: string) = break proc findJBBLibs(pkg: JBBPackage, outdir: string) = - pkg.sharedLibs = findFiles("lib[\\\\/].*\\.(so|dylib)", outdir) - pkg.sharedLibs.add findFiles("bin[\\\\/].*\\.(dll)", outdir) - for i in 0 ..< pkg.sharedLibs.len: - if pkg.sharedLibs[i].isAbsolute: - pkg.sharedLibs[i] = pkg.sharedLibs[i][outdir.len+1 .. ^1] + pkg.sharedLibs = findFiles("(bin|lib)[\\\\/].*\\.(so|dll|dynlib)[0-9.]*", outdir) for lib in findFiles("lib[\\\\/].*\\.(a|lib)$", outdir): if not lib.endsWith(".dll.a"): - if lib.isAbsolute: - pkg.staticLibs.add lib[outdir.len+1 .. ^1] - else: - pkg.staticLibs.add lib + pkg.staticLibs.add lib proc getJBBRepo*(pkg: JBBPackage, outdir: string) = ## Clone JBB package repo and checkout version tag if version is @@ -174,11 +167,10 @@ proc downloadJBB*(pkg: JBBPackage, outdir: string, clean = true) = &" v{pkg.version}" else: "" - path = outdir / "downloads" / pkg.name + path = outdir / pkg.name echo &"# Downloading {pkg.name}{vstr} from BinaryBuilder.org" downloadUrl(pkg.url, path, quiet = true) pkg.findJBBLibs(path) - mvTree(path, outdir) pkg.dlJBBRequires(outdir) @@ -204,7 +196,7 @@ proc getJBBLDeps*(pkg: JBBPackage, outdir: string, shared: bool, main = true): s if not main: for lib in libs: - result.add outdir / lib + result.add lib for cpkg in pkg.requires: result.add cpkg.getJBBLDeps(outdir, shared, main = false) diff --git a/nimterop/nimconf.nim b/nimterop/nimconf.nim index 33215ef..98fc8fe 100644 --- a/nimterop/nimconf.nim +++ b/nimterop/nimconf.nim @@ -16,6 +16,7 @@ type paths*: OrderedSet[string] nimblePaths*: OrderedSet[string] nimcacheDir*: string + outDir*: string proc getJson(projectDir: string): JsonNode = # Get `nim dump` json value for `projectDir` @@ -67,35 +68,6 @@ proc stripName(path, projectName: string): string = else: result = path -proc getNimcacheDir*(projectDir = ""): string = - ## Get nimcache directory for current compilation or specified `projectDir` - when nimvm: - when (NimMajor, NimMinor, NimPatch) >= (1, 2, 0): - # Get value at compile time from `std/compilesettings` - result = stripName( - querySetting(SingleValueSetting.nimcacheDir), - querySetting(SingleValueSetting.projectName) - ) - else: - discard - - # Not Nim v1.2.0+ or runtime - if result.len == 0: - let - # Get project directory for < v1.2.0 at compile time - projectDir = if projectDir.len != 0: projectDir else: getProjectDir() - - # Use `nim dump` to figure out nimcache for `projectDir` - let - dumpJson = getJson(projectDir) - - if dumpJson != nil and dumpJson.hasKey("nimcache"): - result = stripName(dumpJson["nimcache"].getStr(), "dummy") - - # Set to OS defaults if not detectable - if result.len == 0: - result = getOsCacheDir() - proc jsonToSeq(node: JsonNode, key: string): seq[string] = # Convert JsonArray to seq[string] for specified `key` if node.hasKey(key): @@ -126,7 +98,11 @@ proc getNimConfig*(projectDir = ""): Config = libPath = getCurrentCompilerExe().parentDir().parentDir() / "lib" lazyPaths = querySettingSeq(MultipleValueSetting.lazyPaths) searchPaths = querySettingSeq(MultipleValueSetting.searchPaths) - result.nimcacheDir = querySetting(SingleValueSetting.nimcacheDir) + result.nimcacheDir = stripName( + querySetting(SingleValueSetting.nimcacheDir), + querySetting(SingleValueSetting.projectName) + ) + result.outDir = querySetting(SingleValueSetting.outDir) else: discard @@ -150,6 +126,11 @@ proc getNimConfig*(projectDir = ""): Config = # Usually `libPath` is last entry in `searchPaths` libPath = searchPaths[^1] + if dumpJson.hasKey("nimcache"): + result.nimcacheDir = stripName(dumpJson["nimcache"].getStr(), "dummy") + if dumpJson.hasKey("outdir"): + result.outDir = dumpJson["outdir"].getStr() + # Parse version if version.len != 0: let @@ -188,7 +169,11 @@ proc getNimConfig*(projectDir = ""): Config = if not skip: result.paths.incl path - result.nimcacheDir = getNimcacheDir(projectDir) + if result.nimcacheDir.len == 0: + result.nimcacheDir = getOsCacheDir() + + if result.outDir.len == 0: + result.outDir = projectDir proc getNimConfigFlags(cfg: Config): string = # Convert configuration into Nim flags for cfg file or command line @@ -227,4 +212,16 @@ proc writeNimConfig*(cfgFile: string, projectDir = "") = let cfg = getNimConfig(projectDir) cfgOut = getNimConfigFlags(cfg) - writeFile(cfgFile, cfgOut) \ No newline at end of file + writeFile(cfgFile, cfgOut) + +proc getNimcacheDir*(projectDir = ""): string = + ## Get nimcache directory for current compilation or specified `projectDir` + let + cfg = getNimConfig(projectDir) + result = cfg.nimcacheDir + +proc getOutDir*(projectDir = ""): string = + ## Get output directory for current compilation or specified `projectDir` + let + cfg = getNimConfig(projectDir) + result = cfg.outDir -- cgit v1.2.3