summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--changelog.md12
-rw-r--r--compiler/scriptconfig.nim2
-rw-r--r--lib/pure/os.nim36
-rw-r--r--lib/system/nimscript.nim6
-rw-r--r--tests/newconfig/tfoo.nims11
-rw-r--r--tests/stdlib/tos.nim20
6 files changed, 56 insertions, 31 deletions
diff --git a/changelog.md b/changelog.md
index e7e3aaf0c..d3c85d666 100644
--- a/changelog.md
+++ b/changelog.md
@@ -7,7 +7,7 @@
   on GCC's `__builtin_sadd_overflow` family of functions. (Clang also
   supports these). Some versions of GCC lack this feature and unfortunately
   we cannot detect this case reliably. So if you get compilation errors like
-  "undefined reference to '__builtin_saddll_overflow'" compile your programs
+  "undefined reference to `__builtin_saddll_overflow`" compile your programs
   with `-d:nimEmulateOverflowChecks`.
 
 
@@ -34,8 +34,8 @@
 - `options` now treats `proc` like other pointer types, meaning `nil` proc variables
   are converted to `None`.
 - `relativePath("foo", "foo")` is now `"."`, not `""`, as `""` means invalid path
-  and shouldn't be conflated with `"."`; use -d:nimOldRelativePathBehavior to restore the old
-  behavior
+  and shouldn't be conflated with `"."`; use -d:nimOldRelativePathBehavior to
+  restore the old behavior
 - `joinPath(a,b)` now honors trailing slashes in `b` (or `a` if `b` = "")
 - `times.parse` now only uses input to compute its result, and not `now`:
   `parse("2020", "YYYY", utc())` is now `2020-01-01T00:00:00Z` instead of
@@ -44,6 +44,12 @@
 - `httpcore.==(string, HttpCode)` is now deprecated due to lack of practical
   usage. The `$` operator can be used to obtain the string form of `HttpCode`
   for comparison if desired.
+- `os.walkDir` and `os.walkDirRec` now have new flag, `checkDir` (default: false).
+  If it is set to true, it will throw if input dir is invalid instead of a noop
+  (which is the default behaviour, as it was before this change),
+  `os.walkDirRec` only throws if top-level dir is invalid, but ignores errors for
+  subdirs, otherwise it would be impossible to resume iteration.
+
 
 ### Breaking changes in the compiler
 
diff --git a/compiler/scriptconfig.nim b/compiler/scriptconfig.nim
index d53664912..13ab03426 100644
--- a/compiler/scriptconfig.nim
+++ b/compiler/scriptconfig.nim
@@ -65,7 +65,7 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string;
     if defined(nimsuggest) or graph.config.cmd == cmdCheck:
       discard
     else:
-      os.removeDir getString(a, 0)
+      os.removeDir(getString(a, 0), getBool(a, 1))
   cbos removeFile:
     if defined(nimsuggest) or graph.config.cmd == cmdCheck:
       discard
diff --git a/lib/pure/os.nim b/lib/pure/os.nim
index 1ee71c1fb..e90d3c514 100644
--- a/lib/pure/os.nim
+++ b/lib/pure/os.nim
@@ -2028,8 +2028,8 @@ proc staticWalkDir(dir: string; relative: bool): seq[
                   tuple[kind: PathComponent, path: string]] =
   discard
 
-iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path: string] {.
-  tags: [ReadDirEffect].} =
+iterator walkDir*(dir: string; relative = false, checkDir = false):
+  tuple[kind: PathComponent, path: string] {.tags: [ReadDirEffect].} =
   ## Walks over the directory `dir` and yields for each directory or file in
   ## `dir`. The component type and full path for each item are returned.
   ##
@@ -2038,7 +2038,6 @@ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path:
   ## Example: This directory structure::
   ##   dirA / dirB / fileB1.txt
   ##        / dirC
-  ##        / fileA1.txt
   ##        / fileA2.txt
   ##
   ## and this code:
@@ -2069,7 +2068,10 @@ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path:
     elif defined(windows):
       var f: WIN32_FIND_DATA
       var h = findFirstFile(dir / "*", f)
-      if h != -1:
+      if h == -1:
+        if checkDir:
+          raiseOSError(osLastError(), dir)
+      else:
         defer: findClose(h)
         while true:
           var k = pcFile
@@ -2087,7 +2089,10 @@ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path:
             else: raiseOSError(errCode.OSErrorCode)
     else:
       var d = opendir(dir)
-      if d != nil:
+      if d == nil:
+        if checkDir:
+          raiseOSError(osLastError(), dir)
+      else:
         defer: discard closedir(d)
         while true:
           var x = readdir(d)
@@ -2122,7 +2127,7 @@ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path:
 
 iterator walkDirRec*(dir: string,
                      yieldFilter = {pcFile}, followFilter = {pcDir},
-                     relative = false): string {.tags: [ReadDirEffect].} =
+                     relative = false, checkDir = false): string {.tags: [ReadDirEffect].} =
   ## Recursively walks over the directory `dir` and yields for each file
   ## or directory in `dir`.
   ##
@@ -2159,14 +2164,20 @@ iterator walkDirRec*(dir: string,
   ## * `walkDir iterator <#walkDir.i,string>`_
 
   var stack = @[""]
+  var checkDir = checkDir
   while stack.len > 0:
     let d = stack.pop()
-    for k, p in walkDir(dir / d, relative = true):
+    for k, p in walkDir(dir / d, relative = true, checkDir = checkDir):
       let rel = d / p
       if k in {pcDir, pcLinkToDir} and k in followFilter:
         stack.add rel
       if k in yieldFilter:
         yield if relative: rel else: dir / rel
+    checkDir = false
+      # We only check top-level dir, otherwise if a subdir is invalid (eg. wrong
+      # permissions), it'll abort iteration and there would be no way to
+      # continue iteration.
+      # Future work can provide a way to customize this and do error reporting.
 
 proc rawRemoveDir(dir: string) {.noNimScript.} =
   when defined(windows):
@@ -2181,13 +2192,13 @@ proc rawRemoveDir(dir: string) {.noNimScript.} =
   else:
     if rmdir(dir) != 0'i32 and errno != ENOENT: raiseOSError(osLastError(), dir)
 
-proc removeDir*(dir: string) {.rtl, extern: "nos$1", tags: [
+proc removeDir*(dir: string, checkDir = false) {.rtl, extern: "nos$1", tags: [
   WriteDirEffect, ReadDirEffect], benign, noNimScript.} =
   ## Removes the directory `dir` including all subdirectories and files
   ## in `dir` (recursively).
   ##
   ## If this fails, `OSError` is raised. This does not fail if the directory never
-  ## existed in the first place.
+  ## existed in the first place, unless `checkDir` = true
   ##
   ## See also:
   ## * `tryRemoveFile proc <#tryRemoveFile,string>`_
@@ -2197,10 +2208,13 @@ proc removeDir*(dir: string) {.rtl, extern: "nos$1", tags: [
   ## * `copyDir proc <#copyDir,string,string>`_
   ## * `copyDirWithPermissions proc <#copyDirWithPermissions,string,string>`_
   ## * `moveDir proc <#moveDir,string,string>`_
-  for kind, path in walkDir(dir):
+  for kind, path in walkDir(dir, checkDir = checkDir):
     case kind
     of pcFile, pcLinkToFile, pcLinkToDir: removeFile(path)
-    of pcDir: removeDir(path)
+    of pcDir: removeDir(path, true)
+      # for subdirectories there is no benefit in `checkDir = false`
+      # (unless perhaps for edge case of concurrent processes also deleting
+      # the same files)
   rawRemoveDir(dir)
 
 proc rawCreateDir(dir: string): bool {.noNimScript.} =
diff --git a/lib/system/nimscript.nim b/lib/system/nimscript.nim
index b8abdaa38..f2a843652 100644
--- a/lib/system/nimscript.nim
+++ b/lib/system/nimscript.nim
@@ -30,7 +30,7 @@ proc listDirsImpl(dir: string): seq[string] {.
   tags: [ReadIOEffect], raises: [OSError].} = builtin
 proc listFilesImpl(dir: string): seq[string] {.
   tags: [ReadIOEffect], raises: [OSError].} = builtin
-proc removeDir(dir: string) {.
+proc removeDir(dir: string, checkDir = true) {.
   tags: [ReadIOEffect, WriteIOEffect], raises: [OSError].} = builtin
 proc removeFile(dir: string) {.
   tags: [ReadIOEffect, WriteIOEffect], raises: [OSError].} = builtin
@@ -204,10 +204,10 @@ proc listFiles*(dir: string): seq[string] =
   result = listFilesImpl(dir)
   checkOsError()
 
-proc rmDir*(dir: string) {.raises: [OSError].} =
+proc rmDir*(dir: string, checkDir = false) {.raises: [OSError].} =
   ## Removes the directory `dir`.
   log "rmDir: " & dir:
-    removeDir dir
+    removeDir(dir, checkDir = checkDir)
     checkOsError()
 
 proc rmFile*(file: string) {.raises: [OSError].} =
diff --git a/tests/newconfig/tfoo.nims b/tests/newconfig/tfoo.nims
index 8dcc7d6f1..9e6405a27 100644
--- a/tests/newconfig/tfoo.nims
+++ b/tests/newconfig/tfoo.nims
@@ -24,8 +24,10 @@ doAssert(existsEnv("dummy") == false)
 
 # issue #7283
 putEnv("dummy", "myval")
-doAssert(existsEnv("dummy") == true)
+doAssert(existsEnv("dummy"))
 doAssert(getEnv("dummy") == "myval")
+delEnv("dummy")
+doAssert(existsEnv("dummy") == false)
 
 # issue #7393
 let wd = getCurrentDir()
@@ -64,6 +66,8 @@ else:
   assert toDll("nim") == "libnim.so"
 
 rmDir("tempXYZ")
+doAssertRaises(OSError):
+  rmDir("tempXYZ", checkDir = true)
 assert dirExists("tempXYZ") == false
 mkDir("tempXYZ")
 assert dirExists("tempXYZ") == true
@@ -81,8 +85,3 @@ when false:
 
 rmDir("tempXYZ")
 assert dirExists("tempXYZ") == false
-
-putEnv("dummy", "myval")
-doAssert(existsEnv("dummy") == true)
-delEnv("dummy")
-doAssert(existsEnv("dummy") == false)
diff --git a/tests/stdlib/tos.nim b/tests/stdlib/tos.nim
index d713bfe0c..be5384015 100644
--- a/tests/stdlib/tos.nim
+++ b/tests/stdlib/tos.nim
@@ -163,13 +163,19 @@ block walkDirRec:
 
   removeDir("walkdir_test")
 
-when not defined(windows):
-  block walkDirRelative:
-    createDir("walkdir_test")
-    createSymlink(".", "walkdir_test/c")
-    for k, p in walkDir("walkdir_test", true):
-      doAssert k == pcLinkToDir
-    removeDir("walkdir_test")
+block: # walkDir
+  doAssertRaises(OSError):
+    for a in walkDir("nonexistant", checkDir = true): discard
+  doAssertRaises(OSError):
+    for p in walkDirRec("nonexistant", checkDir = true): discard
+
+  when not defined(windows):
+    block walkDirRelative:
+      createDir("walkdir_test")
+      createSymlink(".", "walkdir_test/c")
+      for k, p in walkDir("walkdir_test", true):
+        doAssert k == pcLinkToDir
+      removeDir("walkdir_test")
 
 block normalizedPath:
   doAssert normalizedPath("") == ""