diff --git a/.travis.yml b/.travis.yml index 955ad8115..d1938d3cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,11 +2,11 @@ language: scala os: - linux script: + - sbt ++$TRAVIS_SCALA_VERSION "test" - sbt ++$TRAVIS_SCALA_VERSION "scripted rpm/* debian/* universal/*" scala: - 2.10.3 jdk: - - openjdk6 - openjdk7 - oraclejdk8 notifications: diff --git a/build.sbt b/build.sbt index 2ef79ca2b..65dcf86c7 100644 --- a/build.sbt +++ b/build.sbt @@ -12,7 +12,8 @@ scalacOptions in Compile ++= Seq("-deprecation", "-target:jvm-1.6") libraryDependencies ++= Seq( "org.apache.commons" % "commons-compress" % "1.4.1", - "org.vafer" % "jdeb" % "1.3" artifacts (Artifact("jdeb", "jar", "jar")) + "org.vafer" % "jdeb" % "1.3" artifacts (Artifact("jdeb", "jar", "jar")), + "org.scalatest" %% "scalatest" % "2.2.4" % "test" ) site.settings diff --git a/src/main/scala/com/typesafe/sbt/packager/FileUtil.scala b/src/main/scala/com/typesafe/sbt/packager/FileUtil.scala index d942a39b9..e3e8bfdb0 100644 --- a/src/main/scala/com/typesafe/sbt/packager/FileUtil.scala +++ b/src/main/scala/com/typesafe/sbt/packager/FileUtil.scala @@ -1,18 +1,61 @@ package com.typesafe.sbt package packager -import java.io.File -import sbt.Process +import java.io.{ File, IOException } +import java.nio.file.{ Paths, Files } +import java.nio.file.attribute.{ PosixFilePermission, PosixFilePermissions } +/** + * Setting the file permissions + */ object chmod { + + /** + * Using java 7 nio API to set the permissions. + * + * @param file + * @param perms in octal format + */ def apply(file: File, perms: String): Unit = - Process(Seq("chmod", perms, file.getAbsolutePath)).! match { - case 0 => () - case n => sys.error("Error running chmod " + perms + " " + file) - } - def safe(file: File, perms: String): Unit = - try apply(file, perms) - catch { - case e: RuntimeException => () + try { + Files.setPosixFilePermissions(file.toPath, permissions(perms)) + } catch { + case e: IOException => sys.error("Error setting permissions " + perms + " on " + file.getAbsolutePath + ": " + e.getMessage) } + +} + +/** + * Converts a octal unix permission representation into + * a java `PosiFilePermissions` compatible string. + */ +object permissions { + + /** + * @param perms in octal format + * @return java 7 posix file permissions + */ + def apply(perms: String): java.util.Set[PosixFilePermission] = PosixFilePermissions fromString convert(perms) + + def convert(perms: String): String = { + require(perms.length == 4 || perms.length == 3, s"Permissions must have 3 or 4 digits, got [$perms]") + // ignore setuid/setguid/sticky bit + val i = if (perms.length == 3) 0 else 1 + val user = Character getNumericValue (perms charAt i) + val group = Character getNumericValue (perms charAt i + 1) + val other = Character getNumericValue (perms charAt i + 2) + + asString(user) + asString(group) + asString(other) + } + + private def asString(perm: Int): String = perm match { + case 0 => "---" + case 1 => "--x" + case 2 => "-w-" + case 3 => "-wx" + case 4 => "r--" + case 5 => "r-x" + case 6 => "rw-" + case 7 => "rwx" + } } \ No newline at end of file diff --git a/src/main/scala/com/typesafe/sbt/packager/universal/Archives.scala b/src/main/scala/com/typesafe/sbt/packager/universal/Archives.scala index f397657cf..13d3096ed 100644 --- a/src/main/scala/com/typesafe/sbt/packager/universal/Archives.scala +++ b/src/main/scala/com/typesafe/sbt/packager/universal/Archives.scala @@ -7,20 +7,44 @@ import sbt._ /** Helper methods to package up files into compressed archives. */ object Archives { - /** Makes a zip file in the given target directory using the given name. */ - def makeZip(target: File, name: String, mappings: Seq[(File, String)]): File = { + /** + * Makes a zip file in the given target directory using the given name. + * + * @param target folder to build package in + * @param name of output (without extension) + * @param mappings included in the output + * @param top level directory + * @return zip file + */ + def makeZip(target: File, name: String, mappings: Seq[(File, String)], top: Option[String]): File = { val zip = target / (name + ".zip") - // TODO - If mappings already start with the given name, don't add it? - val m2 = mappings map { case (f, p) => f -> (name + "/" + p) } + + // add top level directory if defined + val m2 = top map { dir => + mappings map { case (f, p) => f -> (dir + "/" + p) } + } getOrElse (mappings) + ZipHelper.zip(m2, zip) zip } - /** Makes a zip file in the given target directory using the given name. */ - def makeNativeZip(target: File, name: String, mappings: Seq[(File, String)]): File = { + /** + * Makes a zip file in the given target directory using the given name. + * + * @param target folder to build package in + * @param name of output (without extension) + * @param mappings included in the output + * @param top level directory + * @return zip file + */ + def makeNativeZip(target: File, name: String, mappings: Seq[(File, String)], top: Option[String]): File = { val zip = target / (name + ".zip") - // TODO - If mappings already start with the given name, don't add it? - val m2 = mappings map { case (f, p) => f -> (name + "/" + p) } + + // add top level directory if defined + val m2 = top map { dir => + mappings map { case (f, p) => f -> (dir + "/" + p) } + } getOrElse (mappings) + ZipHelper.zipNative(m2, zip) zip } @@ -29,8 +53,14 @@ object Archives { * Makes a dmg file in the given target directory using the given name. * * Note: Only works on OSX + * + * @param target folder to build package in + * @param name of output (without extension) + * @param mappings included in the output + * @param top level directory : NOT USED + * @return dmg file */ - def makeDmg(target: File, name: String, mappings: Seq[(File, String)]): File = { + def makeDmg(target: File, name: String, mappings: Seq[(File, String)], top: Option[String]): File = { val t = target / "dmg" val dmg = target / (name + ".dmg") if (!t.isDirectory) IO.createDirectory(t) @@ -113,25 +143,43 @@ object Archives { val makeTgz = makeTarball(gzip, ".tgz") _ val makeTar = makeTarball(identity, ".tar") _ - /** Helper method used to construct tar-related compression functions. */ - def makeTarball(compressor: File => File, ext: String)(target: File, name: String, mappings: Seq[(File, String)]): File = { + /** + * Helper method used to construct tar-related compression functions. + * @param target folder to build package in + * @param name of output (without extension) + * @param mappings included in the output + * @param top level directory + * @return tar file + * + */ + def makeTarball(compressor: File => File, ext: String)(target: File, name: String, mappings: Seq[(File, String)], top: Option[String]): File = { val relname = name val tarball = target / (name + ext) IO.withTemporaryDirectory { f => val rdir = f / relname - val m2 = mappings map { case (f, p) => f -> (rdir / name / p) } + val m2 = top map { dir => + mappings map { case (f, p) => f -> (rdir / dir / p) } + } getOrElse { + mappings map { case (f, p) => f -> (rdir / p) } + } + IO.copy(m2) // TODO - Is this enough? for (f <- (m2 map { case (_, f) => f }); if f.getAbsolutePath contains "/bin/") { println("Making " + f.getAbsolutePath + " executable") f.setExecutable(true, false) } + IO.createDirectory(tarball.getParentFile) - val distdir = IO.listFiles(rdir).headOption.getOrElse { - sys.error("Unable to find tarball in directory: " + rdir.getAbsolutePath + ".\n This could be an issue with the temporary filesystem used to create tarballs.") + + // all directories that should be zipped + val distdirs = top map (_ :: Nil) getOrElse { + IO.listFiles(rdir).map(_.getName).toList // no top level dir, use all available } + val tmptar = f / (relname + ".tar") - Process(Seq("tar", "-pcvf", tmptar.getAbsolutePath, distdir.getName), Some(rdir)).! match { + + Process(Seq("tar", "-pcvf", tmptar.getAbsolutePath) ++ distdirs, Some(rdir)).! match { case 0 => () case n => sys.error("Error tarballing " + tarball + ". Exit code: " + n) } diff --git a/src/main/scala/com/typesafe/sbt/packager/universal/Keys.scala b/src/main/scala/com/typesafe/sbt/packager/universal/Keys.scala index bba14759c..8e7bfdb64 100644 --- a/src/main/scala/com/typesafe/sbt/packager/universal/Keys.scala +++ b/src/main/scala/com/typesafe/sbt/packager/universal/Keys.scala @@ -11,4 +11,5 @@ trait UniversalKeys { val stage = TaskKey[File]("stage", "Create a local directory with all the files laid out as they would be in the final distribution.") val dist = TaskKey[File]("dist", "Creates the distribution packages.") val stagingDirectory = SettingKey[File]("stagingDirectory", "Directory where we stage distributions/releases.") + val topLevelDirectory = SettingKey[Option[String]]("topLevelDirectory", "Top level dir in compressed output file.") } diff --git a/src/main/scala/com/typesafe/sbt/packager/universal/UniversalPlugin.scala b/src/main/scala/com/typesafe/sbt/packager/universal/UniversalPlugin.scala index 02adc8bf6..84aa154b6 100644 --- a/src/main/scala/com/typesafe/sbt/packager/universal/UniversalPlugin.scala +++ b/src/main/scala/com/typesafe/sbt/packager/universal/UniversalPlugin.scala @@ -65,6 +65,7 @@ object UniversalPlugin extends AutoPlugin { name in UniversalDocs <<= name in Universal, name in UniversalSrc <<= name in Universal, packageName in Universal <<= packageName, + topLevelDirectory := Some((packageName in Universal).value), executableScriptName in Universal <<= executableScriptName ) ++ makePackageSettingsForConfig(Universal) ++ @@ -95,12 +96,12 @@ object UniversalPlugin extends AutoPlugin { dist } - private type Packager = (File, String, Seq[(File, String)]) => File + private type Packager = (File, String, Seq[(File, String)], Option[String]) => File /** Creates packaging settings for a given package key, configuration + archive type. */ private[this] def makePackageSettings(packageKey: TaskKey[File], config: Configuration)(packager: Packager): Seq[Setting[_]] = inConfig(config)(Seq( mappings in packageKey <<= mappings map checkMappings, - packageKey <<= (target, packageName, mappings in packageKey) map packager + packageKey <<= (target, packageName, mappings in packageKey, topLevelDirectory) map packager )) /** check that all mapped files actually exist */ diff --git a/src/main/scala/com/typesafe/sbt/packager/universal/ZipHelper.scala b/src/main/scala/com/typesafe/sbt/packager/universal/ZipHelper.scala index 687dc1408..35b09af70 100644 --- a/src/main/scala/com/typesafe/sbt/packager/universal/ZipHelper.scala +++ b/src/main/scala/com/typesafe/sbt/packager/universal/ZipHelper.scala @@ -10,7 +10,20 @@ import org.apache.commons.compress.compressors.{ } import java.util.zip.Deflater import org.apache.commons.compress.utils.IOUtils +import java.nio.file.{ Paths, Files, FileSystems, FileSystem, StandardCopyOption } +import java.nio.file.attribute.{ PosixFilePermission, PosixFilePermissions } +import java.net.URI +import scala.collection.JavaConverters._ +/** + * + * + * + * @see http://stackoverflow.com/questions/17888365/file-permissions-are-not-being-preserved-while-after-zip + * @see http://stackoverflow.com/questions/3450250/is-it-possible-to-create-a-script-to-save-and-restore-permissions + * @see http://stackoverflow.com/questions/1050560/maintain-file-permissions-when-extracting-from-a-zip-file-using-jdk-5-api + * @see http://docs.oracle.com/javase/7/docs/technotes/guides/io/fsp/zipfilesystemprovider.html + */ object ZipHelper { case class FileMapping(file: File, name: String, unixMode: Option[Int] = None) @@ -41,10 +54,12 @@ object ZipHelper { } /** - * Creates a zip file attempting to give files the appropriate unix permissions using Java 6 APIs. + * Creates a zip file with the apache commons compressor library. + * * Note: This is known to have some odd issues on MacOSX whereby executable permissions * are not actually discovered, even though the Info-Zip headers exist and work on * many variants of linux. Yay Apple. + * * @param sources The files to include in the zip file. * @param outputZip The location of the output file. */ @@ -59,29 +74,32 @@ object ZipHelper { } /** - * Creates a zip file using the given set of filters - * @param sources The files to include in the zip file. A File, Location, Permission pairing. + * Creates a zip file attempting to give files the appropriate unix permissions using Java 7 APIs. + * + * @param sources The files to include in the zip file. * @param outputZip The location of the output file. */ - def zipWithPerms(sources: Traversable[(File, String, Int)], outputZip: File): Unit = { - val mappings = - for { - (file, name, perm) <- sources - } yield FileMapping(file, name, Some(perm)) - archive(mappings.toSeq, outputZip) - } + def zipNIO(sources: Traversable[(File, String)], outputZip: File): Unit = { + require(!outputZip.isDirectory, "Specified output file " + outputZip + " is a directory.") + val mappings = sources.toSeq.map { + case (file, name) => FileMapping(file, name) + } - /** - * Replaces windows backslash file separator with a forward slash, this ensures the zip file entry is correct for - * any system it is extracted on. - * @param path The path of the file in the zip file - */ - private def normalizePath(path: String) = { - val sep = java.io.File.separatorChar - if (sep == '/') - path - else - path.replace(sep, '/') + // make sure everything is available + val outputDir = outputZip.getParentFile + IO createDirectory outputDir + + // zipping the sources into the output zip + withZipFilesystem(outputZip) { system => + mappings foreach { + case FileMapping(dir, name, _) if dir.isDirectory => Files createDirectories (system getPath name) + case FileMapping(file, name, _) => + val dest = system getPath name + // create parent directories if available + Option(dest.getParent) foreach (Files createDirectories _) + Files copy (file.toPath, dest, StandardCopyOption.COPY_ATTRIBUTES) + } + } } private def archive(sources: Seq[FileMapping], outputFile: File): Unit = { @@ -103,6 +121,9 @@ object ZipHelper { } } + /** + * using apache commons compress + */ private def withZipOutput(file: File)(f: ZipArchiveOutputStream => Unit): Unit = { val zipOut = new ZipArchiveOutputStream(file) zipOut setLevel Deflater.BEST_COMPRESSION @@ -111,4 +132,39 @@ object ZipHelper { zipOut.close() } } + + /** + * Replaces windows backslash file separator with a forward slash, this ensures the zip file entry is correct for + * any system it is extracted on. + * @param path The path of the file in the zip file + */ + private def normalizePath(path: String) = { + val sep = java.io.File.separatorChar + if (sep == '/') + path + else + path.replace(sep, '/') + } + + /** + * Opens a zip filesystem and creates the file if necessary. + * + * Note: This will override an existing zipFile if existent! + * + * @param zipFile + * @param f: FileSystem => Unit, logic working in the filesystem + */ + def withZipFilesystem(zipFile: File, overwrite: Boolean = true)(f: FileSystem => Unit) { + if (overwrite) Files deleteIfExists zipFile.toPath + val env = Map("create" -> "true").asJava + val uri = URI.create("jar:file:" + zipFile.getAbsolutePath) + + val system = FileSystems.newFileSystem(uri, env) + try { + f(system) + } finally { + system.close() + } + } + } diff --git a/src/sbt-test/universal/test-zips-no-top-level-dir/build.sbt b/src/sbt-test/universal/test-zips-no-top-level-dir/build.sbt new file mode 100644 index 000000000..ca85bb829 --- /dev/null +++ b/src/sbt-test/universal/test-zips-no-top-level-dir/build.sbt @@ -0,0 +1,7 @@ +enablePlugins(JavaAppPackaging) + +name := "simple-test" + +version := "0.1.0" + +topLevelDirectory := None \ No newline at end of file diff --git a/src/sbt-test/universal/test-zips-no-top-level-dir/project/plugins.sbt b/src/sbt-test/universal/test-zips-no-top-level-dir/project/plugins.sbt new file mode 100644 index 000000000..b53de154c --- /dev/null +++ b/src/sbt-test/universal/test-zips-no-top-level-dir/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % sys.props("project.version")) diff --git a/src/sbt-test/universal/test-zips-no-top-level-dir/src/universal/conf/test b/src/sbt-test/universal/test-zips-no-top-level-dir/src/universal/conf/test new file mode 100644 index 000000000..ab1006ffd --- /dev/null +++ b/src/sbt-test/universal/test-zips-no-top-level-dir/src/universal/conf/test @@ -0,0 +1 @@ +# Test configuration to include in zips. diff --git a/src/sbt-test/universal/test-zips-no-top-level-dir/test b/src/sbt-test/universal/test-zips-no-top-level-dir/test new file mode 100644 index 000000000..72fdb39de --- /dev/null +++ b/src/sbt-test/universal/test-zips-no-top-level-dir/test @@ -0,0 +1,14 @@ +# Run the zip packaging. +> show universal:package-bin +$ exists target/universal/simple-test-0.1.0.zip + +# Run the tgz packaging. +> universal:package-zip-tarball +$ exists target/universal/simple-test-0.1.0.tgz + +# Run the txz packaging. +> universal:package-xz-tarball +$ exists target/universal/simple-test-0.1.0.txz + + +# TODO - Check contents of zips diff --git a/src/sphinx/formats/universal.rst b/src/sphinx/formats/universal.rst index a6ea0987d..21f9a5749 100644 --- a/src/sphinx/formats/universal.rst +++ b/src/sphinx/formats/universal.rst @@ -278,6 +278,32 @@ changelog in addition to the generic packaging by first defining a changelog in Notice how we're *only* modifying the package mappings for Debian linux packages. For more information on the underlying packaging settings, see :ref:`Windows` and :ref:`Linux` documentation. +Change/Remove Top Level Directory in Output +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Your output package (zip, tar, gz) by default contains a single folder +with your application. If you want to change this folder or remove this +top level directory completely use the `topLevelDirectory` setting. + +Removing the top level directory + +.. code-block:: scala + + topLevelDirectory := None + + +Changing it to another value, e.g. the packageName without the version + +.. code-block:: scala + + topLevelDirectory := Some(packageName.value) + +Or just a plain hardcoded string + + +.. code-block:: scala + + topLevelDirectory := Some("awesome-app") MappingsHelper ~~~~~~~~~~~~~~ diff --git a/src/test/scala/com/typesafe/sbt/packager/DeleteDirectoryVisitor.scala b/src/test/scala/com/typesafe/sbt/packager/DeleteDirectoryVisitor.scala new file mode 100644 index 000000000..e0f608fa2 --- /dev/null +++ b/src/test/scala/com/typesafe/sbt/packager/DeleteDirectoryVisitor.scala @@ -0,0 +1,20 @@ +package com.typesafe.sbt.packager + +import java.nio.file._ +import java.nio.file.attribute.BasicFileAttributes +import java.io.IOException + +class DeleteDirectoryVisitor extends SimpleFileVisitor[Path] { + + override def visitFile(file: Path, attrs: BasicFileAttributes) = { + Files delete file + FileVisitResult.CONTINUE + } + + override def postVisitDirectory(dir: Path, exc: IOException) = { + Files delete dir + FileVisitResult.CONTINUE + } + +} + diff --git a/src/test/scala/com/typesafe/sbt/packager/FileUtilSpec.scala b/src/test/scala/com/typesafe/sbt/packager/FileUtilSpec.scala new file mode 100644 index 000000000..c9b2cc033 --- /dev/null +++ b/src/test/scala/com/typesafe/sbt/packager/FileUtilSpec.scala @@ -0,0 +1,28 @@ +package com.typesafe.sbt.packager + +import org.scalatest._ +import java.nio.file.attribute.PosixFilePermission._ + +class FileUtilSpec extends FlatSpec with Matchers { + + "permissions" should "convert octal to symbolic correctly" in { + permissions convert "0000" should be("---------") + permissions convert "0600" should be("rw-------") + permissions convert "0755" should be("rwxr-xr-x") + permissions convert "0777" should be("rwxrwxrwx") + } + + it should "generate valid java PosixFilePermission" in { + permissions("0000") should be(empty) + + val perm1 = permissions("0600") + perm1 should not be (empty) + perm1 should contain only (OWNER_READ, OWNER_WRITE) + + val perm2 = permissions("0755") + perm2 should not be (empty) + perm2 should contain only (OWNER_READ, OWNER_WRITE, OWNER_EXECUTE, GROUP_READ, GROUP_EXECUTE, OTHERS_READ, OTHERS_EXECUTE) + + } + +} \ No newline at end of file diff --git a/src/test/scala/com/typesafe/sbt/packager/universal/ZipHelperSpec.scala b/src/test/scala/com/typesafe/sbt/packager/universal/ZipHelperSpec.scala new file mode 100644 index 000000000..76780910d --- /dev/null +++ b/src/test/scala/com/typesafe/sbt/packager/universal/ZipHelperSpec.scala @@ -0,0 +1,191 @@ +package com.typesafe.sbt.packager.universal + +import com.typesafe.sbt.packager.DeleteDirectoryVisitor +import com.typesafe.sbt.packager.permissions +import org.scalatest._ +import java.io.File +import java.nio.file.{ Path, Paths, Files } +import java.nio.file.attribute.PosixFilePermission._ +import scala.collection.JavaConversions._ + +class ZipHelperSpec extends FlatSpec with Matchers with BeforeAndAfterEach { + + var tmp: Path = _ + + override def beforeEach { + tmp = Files createTempDirectory "_sbt-native-packager" + } + + override def afterEach { + Files.walkFileTree(tmp, new DeleteDirectoryVisitor) + } + + "The ZipHelper.zip" should "create a zip with a single file" in { + zipSingleFile(ZipHelper.zip) + } + + // ignores empty directories + it should "create a zip with nested directories" ignore { + zipNestedFile(ZipHelper.zip) + } + + it should "create a zip with nested directories containing file" in { + zipNestedDirsWithFiles(ZipHelper.zip) + } + + it should "create directories if necessary" in { + createNecessaryDirectories(ZipHelper.zip) + } + + // works only on some systems + it should "preserve the executable bit" ignore { + preserveExecutableBit(ZipHelper.zip) + } + + "The ZipHelper.zipNIO" should "create a zip with a single file" in { + zipSingleFile(ZipHelper.zipNIO) + } + + it should "create a zip with nested directories" in { + zipNestedFile(ZipHelper.zipNIO) + } + + it should "create a zip with nested directories containing file" in { + zipNestedDirsWithFiles(ZipHelper.zipNIO) + } + + it should "create directories if necessary" in { + createNecessaryDirectories(ZipHelper.zipNIO) + } + + // never works + it should "preserve the executable bit" ignore { + preserveExecutableBit(ZipHelper.zipNIO) + } + + "The ZipHelper.zipNative" should "create a zip with a single file" in { + zipSingleFile(ZipHelper.zipNative) + } + + it should "create a zip with nested directories" in { + zipNestedFile(ZipHelper.zipNative) + } + + it should "create a zip with nested directories containing file" in { + zipNestedDirsWithFiles(ZipHelper.zipNative) + } + + it should "create directories if necessary" in { + createNecessaryDirectories(ZipHelper.zipNative) + } + + // never works + it should "preserve the executable bit" ignore { + preserveExecutableBit(ZipHelper.zipNative) + } + + /* ========================================================== */ + /* ========================================================== */ + /* ========================================================== */ + + private type Zipper = (Traversable[(File, String)], File) => Unit + + private def zipSingleFile(zipper: Zipper) { + val out = tmp resolve "single.zip" + val file = tmp resolve "single.txt" + Files createFile file + + zipper(List(file.toFile -> "single.txt"), out.toFile) + + ZipHelper.withZipFilesystem(out.toFile, false) { system => + val zippedFile = system getPath "single.txt" + Files exists zippedFile should be(true) + } + } + + private def zipNestedFile(zipper: Zipper) { + // setup + val out = tmp resolve "nested.zip" + val dir = tmp resolve "dir" + val nested = dir resolve "nested" + Files createDirectories nested + + zipper(List(nested.toFile -> "dir/nested"), out.toFile) + + ZipHelper.withZipFilesystem(out.toFile, false) { system => + + val zDir = system getPath "dir" + Files exists zDir should be(true) + Files isDirectory zDir should be(true) + + val zNested = zDir resolve "nested" + Files exists zNested should be(true) + Files isDirectory zNested should be(true) + } + } + + private def zipNestedDirsWithFiles(zipper: Zipper) { + // setup + val out = tmp resolve "nested-containing.zip" + val dir = tmp resolve "dir" + val file = dir resolve "file.txt" + Files createDirectories dir + Files createFile file + + zipper(List(file.toFile -> "dir/file.txt"), out.toFile) + + ZipHelper.withZipFilesystem(out.toFile, false) { system => + val zDir = system getPath "dir" + Files exists zDir should be(true) + Files isDirectory zDir should be(true) + + val zFile = zDir resolve "file.txt" + Files exists zFile should be(true) + Files isDirectory zFile should be(false) + } + } + + private def createNecessaryDirectories(zipper: Zipper) { + val out = tmp resolve "dir-creation.zip" + val file = tmp resolve "dir-file.txt" + Files createFile file + + zipper(List(file.toFile -> "dir/file.txt"), out.toFile) + + ZipHelper.withZipFilesystem(out.toFile, false) { system => + val zDir = system getPath "dir" + Files exists zDir should be(true) + Files isDirectory zDir should be(true) + + val zFile = zDir resolve "file.txt" + Files exists zFile should be(true) + Files isDirectory zFile should be(false) + } + } + + private def preserveExecutableBit(zipper: Zipper) { + val out = tmp resolve "exec.zip" + val exec = tmp resolve "exec" + Files createFile exec + Files.setPosixFilePermissions(exec, permissions("0755")) + + val perms = Files getPosixFilePermissions exec + perms should contain only (OWNER_READ, OWNER_WRITE, OWNER_EXECUTE, GROUP_READ, GROUP_EXECUTE, OTHERS_READ, OTHERS_EXECUTE) + + zipper(List(exec.toFile -> "exec"), out.toFile) + Files exists out should be(true) + + val unzipped = tmp resolve "unzipped-exec" + ZipHelper.withZipFilesystem(out.toFile, false) { system => + val zippedFile = system getPath "exec" + Files exists zippedFile should be(true) + + Files.copy(zippedFile, unzipped) + } + + // checking permissions + val unzippedPerms = Files getPosixFilePermissions unzipped + unzippedPerms should contain only (OWNER_READ, OWNER_WRITE, OWNER_EXECUTE, GROUP_READ, GROUP_EXECUTE, OTHERS_READ, OTHERS_EXECUTE) + } + +} diff --git a/test-project-simple/project/build.properties b/test-project-simple/project/build.properties index 39d2a9a80..748703f77 100644 --- a/test-project-simple/project/build.properties +++ b/test-project-simple/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.7-M3 +sbt.version=0.13.7