diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 879d0bcf..68edfea1 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -14,7 +14,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - java-version: [11, 17] + java-version: [17] runs-on: ${{ matrix.os }} @@ -31,7 +31,7 @@ jobs: run: curl -Lo mill.bat "https://raw.githubusercontent.com/lefou/millw/main/millw.bat" if: matrix.os == 'windows-latest' - - run: ./mill -i -k __.test + - run: sudo ./mill -i -k __.test if: matrix.os != 'windows-latest' - run: ./mill.bat -i -k __.jvm.__.test if: matrix.os == 'windows-latest' @@ -46,7 +46,7 @@ jobs: - uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: 11 + java-version: 17 - run: ./mill -i -k __.mimaReportBinaryIssues diff --git a/os/src/ZipOps.scala b/os/src/ZipOps.scala index 317b27a8..357b9e75 100644 --- a/os/src/ZipOps.scala +++ b/os/src/ZipOps.scala @@ -2,7 +2,7 @@ package os import java.net.URI import java.nio.file.{FileSystem, FileSystems, Files} -import java.nio.file.attribute.{BasicFileAttributeView, FileTime, PosixFilePermissions} +import java.nio.file.attribute.{BasicFileAttributeView, FileTime, PosixFilePermission} import java.util.zip.{ZipEntry, ZipFile, ZipInputStream, ZipOutputStream} import scala.collection.JavaConverters._ import scala.util.matching.Regex @@ -85,6 +85,40 @@ object zip { f ) finally f.close() + // preserve permissions + if (!scala.util.Properties.isWin) { + val zipFS = FileSystems.newFileSystem( + new URI("jar", dest.wrapped.toUri.toString, null), + Map( + "create" -> "true", + "enablePosixFileAttributes" -> "true", + "defaultPermissions" -> Set.empty[String].asJava + ).asJava + ) + try { + sources.foreach { source => + if (os.isDir(source.src)) { + for (path <- os.walk(source.src)) { + if ( + os.isFile(path) && shouldInclude(path.toString, excludePatterns, includePatterns) + ) { + val sourcePerms = os.perms(path) + val entry = zipFS.getPath( + (source.dest.getOrElse(os.sub) / path.subRelativeTo(source.src)).toString + ) + Files.setPosixFilePermissions(entry, sourcePerms.toSet()) + } + } + } else if (shouldInclude(source.src.last, excludePatterns, includePatterns)) { + val sourcePerms = os.perms(source.src) + val entry = zipFS.getPath(source.dest.getOrElse(os.sub / source.src.last).toString) + Files.setPosixFilePermissions(entry, sourcePerms.toSet()) + } + } + } finally { + zipFS.close() + } + } } dest } @@ -255,6 +289,33 @@ object unzip { includePatterns: Seq[Regex] = List() ): os.Path = { stream(os.read.stream(source), dest, excludePatterns, includePatterns) + // preserve permissions + if (!scala.util.Properties.isWin) { + val zipFS = FileSystems.newFileSystem( + new URI("jar", source.wrapped.toUri.toString, null), + Map( + "create" -> "true", + "enablePosixFileAttributes" -> "true", + "defaultPermissions" -> Set.empty[String].asJava + ).asJava + ) + try { + os.walk(dest).foreach { path => + val relPath = path.subRelativeTo(dest).toString + if (os.zip.shouldInclude(relPath, excludePatterns, includePatterns)) { + val entry = zipFS.getPath(relPath) + val permissions = + Files.getAttribute(entry, "zip:permissions") + .asInstanceOf[java.util.Set[PosixFilePermission]] + if (permissions != null) { + os.perms.set(path, permissions) + } + } + } + } finally { + zipFS.close() + } + } dest } diff --git a/os/test/src-jvm/ZipOpJvmTests.scala b/os/test/src-jvm/ZipOpJvmTests.scala index dfe4bad2..890ce719 100644 --- a/os/test/src-jvm/ZipOpJvmTests.scala +++ b/os/test/src-jvm/ZipOpJvmTests.scala @@ -133,6 +133,29 @@ object ZipOpJvmTests extends TestSuite { test("append") - prep { wd => zipAndUnzipDontPreserveMtimes(wd, true) } } + test("zipAndUnzipPreservePermissions") - prep { wd => + if (Unix()) { + // Create a file and set its permissions + val testFile = wd / "FileWithPerms" + os.write(testFile, "Test content") + os.perms.set(testFile, "rwxr-xr-x") + // Zip the file with permissions + val zipFile = os.zip( + dest = wd / "zipWithPermsPreservation.zip", + sources = List(testFile) + ) + // Unzip it + val unzippedFolder = wd / "unzippedWithPerms" + os.unzip( + source = zipFile, + dest = unzippedFolder + ) + // Compare the original and actual permissions after unzip + val unzippedFilePerms = os.perms(unzippedFolder / "FileWithPerms") + assert(unzippedFilePerms.toString() == "rwxr-xr-x") + } + } + test("deletePatterns") - prep { wd => val amxFile = "File.amx" os.copy(wd / "File.txt", wd / amxFile)