Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix #13222: make relativePath more robust and flexible #13451

Merged
merged 9 commits into from
Apr 21, 2020
Merged
6 changes: 6 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@
- The file descriptors created for internal bookkeeping by `ioselector_kqueue`
and `ioselector_epoll` will no longer be leaked to child processes.

- `relativePath(rel, abs)` and `relativePath(abs, rel)` used to silently give wrong results
(see #13222); instead they now use `getCurrentDir` to resolve those cases,
and this can now throw in edge cases where `getCurrentDir` throws.
`relativePath` also now works for js with `-d:nodejs`.


## Language changes
- In newruntime it is now allowed to assign discriminator field without restrictions as long as case object doesn't have custom destructor. Discriminator value doesn't have to be a constant either. If you have custom destructor for case object and you do want to freely assign discriminator fields, it is recommended to refactor object into 2 objects like this:
```nim
Expand Down
36 changes: 29 additions & 7 deletions lib/pure/os.nim
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ type

include "includes/osseps"

proc absolutePathInternal(path: string): string {.gcsafe.}
timotheecour marked this conversation as resolved.
Show resolved Hide resolved

proc normalizePathEnd(path: var string, trailingSep = false) =
## Ensures ``path`` has exactly 0 or 1 trailing `DirSep`, depending on
## ``trailingSep``, and taking care of edge cases: it preservers whether
Expand Down Expand Up @@ -297,8 +299,12 @@ proc isAbsolute*(path: string): bool {.rtl, noSideEffect, extern: "nos$1", raise
result = path[0] != ':'
elif defined(RISCOS):
result = path[0] == '$'
elif defined(posix):
elif defined(posix) or defined(js):
# `or defined(js)` wouldn't be needed pending https://github.com/nim-lang/Nim/issues/13469
# This works around the problem for posix, but windows is still broken with nim js -d:nodejs
result = path[0] == '/'
else:
doAssert false # if ever hits here, adapt as needed

when FileSystemCaseSensitive:
template `!=?`(a, b: char): bool = a != b
Expand Down Expand Up @@ -360,8 +366,8 @@ when doslikeFileSystem:
else:
return false

proc relativePath*(path, base: string; sep = DirSep): string {.
noSideEffect, rtl, extern: "nos$1", raises: [].} =
timotheecour marked this conversation as resolved.
Show resolved Hide resolved
proc relativePath*(path, base: string, sep = DirSep): string {.
rtl, extern: "nos$1".} =
## Converts `path` to a path relative to `base`.
##
## The `sep` (default: `DirSep <#DirSep>`_) is used for the path normalizations,
Expand Down Expand Up @@ -390,6 +396,12 @@ proc relativePath*(path, base: string; sep = DirSep): string {.
var path = path
path.normalizePathAux
base.normalizePathAux
let a1 = isAbsolute(path)
let a2 = isAbsolute(base)
if a1 and not a2:
base = absolutePathInternal(base)
elif a2 and not a1:
path = absolutePathInternal(path)

when doslikeFileSystem:
if isAbsolute(path) and isAbsolute(base):
Expand Down Expand Up @@ -1293,7 +1305,8 @@ proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1", noNimScript.} =
else:
result = getLastModificationTime(a) > getLastModificationTime(b)

proc getCurrentDir*(): string {.rtl, extern: "nos$1", tags: [], noNimScript.} =
when not defined(nimscript):
proc getCurrentDir*(): string {.rtl, extern: "nos$1", tags: [].} =
## Returns the `current working directory`:idx: i.e. where the built
## binary is run.
##
Expand All @@ -1306,7 +1319,13 @@ proc getCurrentDir*(): string {.rtl, extern: "nos$1", tags: [], noNimScript.} =
## * `setCurrentDir proc <#setCurrentDir,string>`_
## * `currentSourcePath template <system.html#currentSourcePath.t>`_
## * `getProjectPath proc <macros.html#getProjectPath>`_
when defined(windows):
when defined(nodejs):
var ret: cstring
{.emit: "`ret` = process.cwd();".}
return $ret
elif defined(js):
doAssert false, "use -d:nodejs to have `getCurrentDir` defined"
elif defined(windows):
var bufsize = MAX_PATH.int32
when useWinUnicode:
var res = newWideCString("", bufsize)
Expand Down Expand Up @@ -1366,8 +1385,8 @@ proc setCurrentDir*(newDir: string) {.inline, tags: [], noNimScript.} =
else:
if chdir(newDir) != 0'i32: raiseOSError(osLastError(), newDir)

when not weirdTarget:
proc absolutePath*(path: string, root = getCurrentDir()): string {.noNimScript.} =

proc absolutePath*(path: string, root = getCurrentDir()): string =
## Returns the absolute path of `path`, rooted at `root` (which must be absolute;
## default: current directory).
## If `path` is absolute, return it, ignoring `root`.
Expand All @@ -1384,6 +1403,9 @@ when not weirdTarget:
raise newException(ValueError, "The specified root is not absolute: " & root)
Araq marked this conversation as resolved.
Show resolved Hide resolved
joinPath(root, path)

proc absolutePathInternal(path: string): string =
absolutePath(path, getCurrentDir())

proc normalizePath*(path: var string) {.rtl, extern: "nos$1", tags: [].} =
## Normalize a path.
##
Expand Down
14 changes: 14 additions & 0 deletions tests/js/tos.nim
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,17 @@ import os
block:
doAssert "./foo//./bar/".normalizedPath == "foo/bar"
doAssert relativePath(".//foo/bar", "foo") == "bar"
doAssert "/".isAbsolute
doAssert not "".isAbsolute
doAssert not ".".isAbsolute
doAssert not "foo".isAbsolute
doAssert relativePath("", "bar") == ""
doAssert normalizedPath(".///foo//./") == "foo"
let cwd = getCurrentDir()

let isWindows = '\\' in cwd
# defined(windows) doesn't work with -d:nodejs but should
# these actually break because of that (see https://github.com/nim-lang/Nim/issues/13469)
if not isWindows:
doAssert cwd.isAbsolute
doAssert relativePath(getCurrentDir() / "foo", "bar") == "../foo"
3 changes: 3 additions & 0 deletions tests/stdlib/tos.nim
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,9 @@ block ospaths:
doAssert relativePath("", "foo") == ""
doAssert relativePath("././/foo", "foo//./") == "."

doAssert relativePath(getCurrentDir() / "bar", "foo") == "../bar".unixToNativePath
doAssert relativePath("bar", getCurrentDir() / "foo") == "../bar".unixToNativePath

when doslikeFileSystem:
doAssert relativePath(r"c:\foo.nim", r"C:\") == r"foo.nim"
doAssert relativePath(r"c:\foo\bar\baz.nim", r"c:\foo") == r"bar\baz.nim"
Expand Down