diff --git a/changelog/make-copies-writable.dd b/changelog/make-copies-writable.dd new file mode 100644 index 0000000..ce5edef --- /dev/null +++ b/changelog/make-copies-writable.dd @@ -0,0 +1,8 @@ +When `copyFiles` is used to copy read-only files, it now makes the copy writable. + +Previously, if the target file would already exist due to a prior run of Dub, `copyFiles` would produce an access +denied error because the read-only target could not be overwritten. Note that if you were affected by this behaviour, +you will need to remove those files by hand once to eliminate these errors. + +It is common for version control systems to mark binary files read-only in the working copy, to prevent concurrent +edits of files in unmergeable formats. diff --git a/source/dub/internal/vibecompat/core/file.d b/source/dub/internal/vibecompat/core/file.d index f48176b..6ee345d 100644 --- a/source/dub/internal/vibecompat/core/file.d +++ b/source/dub/internal/vibecompat/core/file.d @@ -156,8 +156,52 @@ } } +private bool isWritable(NativePath name) +{ + version (Windows) + { + import core.sys.windows.windows; + + return (name.toNativeString.getAttributes & FILE_ATTRIBUTE_READONLY) == 0; + } + else version (Posix) + { + import core.sys.posix.sys.stat; + + return (name.toNativeString.getAttributes & S_IWUSR) != 0; + } + else + static assert(false, "Needs implementation."); +} + +private void makeWritable(NativePath name) +{ + makeWritable(name.toNativeString); +} + +private void makeWritable(string name) +{ + version (Windows) + { + import core.sys.windows.windows; + + name.setAttributes(name.getAttributes & ~FILE_ATTRIBUTE_READONLY); + } + else version (Posix) + { + import core.sys.posix.sys.stat; + + name.setAttributes(name.getAttributes | S_IWUSR); + } + else + static assert(false, "Needs implementation."); +} + /** - Creates a hardlink. + Creates a hardlink if possible, a copy otherwise. + + If `from` is read-only and `overwrite` is true, then a copy is made instead + and `to` is made writable; so that repeating the command will not fail. */ void hardLinkFile(NativePath from, NativePath to, bool overwrite = false) { @@ -168,22 +212,27 @@ throw fe; } } - - version (Windows) + const writeAccessChangeRequired = overwrite && !isWritable(from); + if (!writeAccessChangeRequired) { - alias cstr = toUTFz!(const(wchar)*); - if (CreateHardLinkW(cstr(to.toNativeString), cstr(from.toNativeString))) - return; - } - else - { - import core.sys.posix.unistd : link; - alias cstr = toUTFz!(const(char)*); - if (!link(cstr(from.toNativeString), cstr(to.toNativeString))) - return; + version (Windows) + { + alias cstr = toUTFz!(const(wchar)*); + if (CreateHardLinkW(cstr(to.toNativeString), cstr(from.toNativeString))) + return; + } + else + { + import core.sys.posix.unistd : link; + alias cstr = toUTFz!(const(char)*); + if (!link(cstr(from.toNativeString), cstr(to.toNativeString))) + return; + } } // fallback to copy copyFile(from, to, overwrite); + if (writeAccessChangeRequired) + to.makeWritable; } /** diff --git a/test/issue2234-copy-read-only-files.script.d b/test/issue2234-copy-read-only-files.script.d index d785db1..9383e90 100644 --- a/test/issue2234-copy-read-only-files.script.d +++ b/test/issue2234-copy-read-only-files.script.d @@ -99,7 +99,7 @@ { import core.sys.posix.sys.stat; - name.setAttributes(name.getAttributes() | (S_IWUSR | S_IWGRP | S_IWOTH)); + name.setAttributes(name.getAttributes() | S_IWUSR); } else static assert("Needs implementation.");