Skip to content

Commit 2c39fc4

Browse files
committed
FIX #276 creating directories as necessary and specify top level dir
and sadly realizing that apache commons compress is still the best bet
1 parent bb0e38f commit 2c39fc4

File tree

10 files changed

+181
-41
lines changed

10 files changed

+181
-41
lines changed

src/main/scala/com/typesafe/sbt/packager/universal/Archives.scala

+63-15
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,44 @@ import sbt._
77
/** Helper methods to package up files into compressed archives. */
88
object Archives {
99

10-
/** Makes a zip file in the given target directory using the given name. */
11-
def makeZip(target: File, name: String, mappings: Seq[(File, String)]): File = {
10+
/**
11+
* Makes a zip file in the given target directory using the given name.
12+
*
13+
* @param target folder to build package in
14+
* @param name of output (without extension)
15+
* @param mappings included in the output
16+
* @param top level directory
17+
* @return zip file
18+
*/
19+
def makeZip(target: File, name: String, mappings: Seq[(File, String)], top: Option[String]): File = {
1220
val zip = target / (name + ".zip")
13-
// TODO - If mappings already start with the given name, don't add it?
14-
val m2 = mappings map { case (f, p) => f -> (name + "/" + p) }
21+
22+
// add top level directory if defined
23+
val m2 = top map { dir =>
24+
mappings map { case (f, p) => f -> (dir + "/" + p) }
25+
} getOrElse (mappings)
26+
1527
ZipHelper.zip(m2, zip)
1628
zip
1729
}
1830

19-
/** Makes a zip file in the given target directory using the given name. */
20-
def makeNativeZip(target: File, name: String, mappings: Seq[(File, String)]): File = {
31+
/**
32+
* Makes a zip file in the given target directory using the given name.
33+
*
34+
* @param target folder to build package in
35+
* @param name of output (without extension)
36+
* @param mappings included in the output
37+
* @param top level directory
38+
* @return zip file
39+
*/
40+
def makeNativeZip(target: File, name: String, mappings: Seq[(File, String)], top: Option[String]): File = {
2141
val zip = target / (name + ".zip")
22-
// TODO - If mappings already start with the given name, don't add it?
23-
val m2 = mappings map { case (f, p) => f -> (name + "/" + p) }
42+
43+
// add top level directory if defined
44+
val m2 = top map { dir =>
45+
mappings map { case (f, p) => f -> (dir + "/" + p) }
46+
} getOrElse (mappings)
47+
2448
ZipHelper.zipNative(m2, zip)
2549
zip
2650
}
@@ -29,8 +53,14 @@ object Archives {
2953
* Makes a dmg file in the given target directory using the given name.
3054
*
3155
* Note: Only works on OSX
56+
*
57+
* @param target folder to build package in
58+
* @param name of output (without extension)
59+
* @param mappings included in the output
60+
* @param top level directory : NOT USED
61+
* @return dmg file
3262
*/
33-
def makeDmg(target: File, name: String, mappings: Seq[(File, String)]): File = {
63+
def makeDmg(target: File, name: String, mappings: Seq[(File, String)], top: Option[String]): File = {
3464
val t = target / "dmg"
3565
val dmg = target / (name + ".dmg")
3666
if (!t.isDirectory) IO.createDirectory(t)
@@ -113,25 +143,43 @@ object Archives {
113143
val makeTgz = makeTarball(gzip, ".tgz") _
114144
val makeTar = makeTarball(identity, ".tar") _
115145

116-
/** Helper method used to construct tar-related compression functions. */
117-
def makeTarball(compressor: File => File, ext: String)(target: File, name: String, mappings: Seq[(File, String)]): File = {
146+
/**
147+
* Helper method used to construct tar-related compression functions.
148+
* @param target folder to build package in
149+
* @param name of output (without extension)
150+
* @param mappings included in the output
151+
* @param top level directory
152+
* @return tar file
153+
*
154+
*/
155+
def makeTarball(compressor: File => File, ext: String)(target: File, name: String, mappings: Seq[(File, String)], top: Option[String]): File = {
118156
val relname = name
119157
val tarball = target / (name + ext)
120158
IO.withTemporaryDirectory { f =>
121159
val rdir = f / relname
122-
val m2 = mappings map { case (f, p) => f -> (rdir / name / p) }
160+
val m2 = top map { dir =>
161+
mappings map { case (f, p) => f -> (rdir / dir / p) }
162+
} getOrElse {
163+
mappings map { case (f, p) => f -> (rdir / p) }
164+
}
165+
123166
IO.copy(m2)
124167
// TODO - Is this enough?
125168
for (f <- (m2 map { case (_, f) => f }); if f.getAbsolutePath contains "/bin/") {
126169
println("Making " + f.getAbsolutePath + " executable")
127170
f.setExecutable(true, false)
128171
}
172+
129173
IO.createDirectory(tarball.getParentFile)
130-
val distdir = IO.listFiles(rdir).headOption.getOrElse {
131-
sys.error("Unable to find tarball in directory: " + rdir.getAbsolutePath + ".\n This could be an issue with the temporary filesystem used to create tarballs.")
174+
175+
// all directories that should be zipped
176+
val distdirs = top map (_ :: Nil) getOrElse {
177+
IO.listFiles(rdir).map(_.getName).toList // no top level dir, use all available
132178
}
179+
133180
val tmptar = f / (relname + ".tar")
134-
Process(Seq("tar", "-pcvf", tmptar.getAbsolutePath, distdir.getName), Some(rdir)).! match {
181+
182+
Process(Seq("tar", "-pcvf", tmptar.getAbsolutePath) ++ distdirs, Some(rdir)).! match {
135183
case 0 => ()
136184
case n => sys.error("Error tarballing " + tarball + ". Exit code: " + n)
137185
}

src/main/scala/com/typesafe/sbt/packager/universal/Keys.scala

+1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ trait UniversalKeys {
1111
val stage = TaskKey[File]("stage", "Create a local directory with all the files laid out as they would be in the final distribution.")
1212
val dist = TaskKey[File]("dist", "Creates the distribution packages.")
1313
val stagingDirectory = SettingKey[File]("stagingDirectory", "Directory where we stage distributions/releases.")
14+
val topLevelDirectory = SettingKey[Option[String]]("topLevelDirectory", "Top level dir in compressed output file.")
1415
}

src/main/scala/com/typesafe/sbt/packager/universal/UniversalPlugin.scala

+3-2
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ object UniversalPlugin extends AutoPlugin {
6565
name in UniversalDocs <<= name in Universal,
6666
name in UniversalSrc <<= name in Universal,
6767
packageName in Universal <<= packageName,
68+
topLevelDirectory := Some((packageName in Universal).value),
6869
executableScriptName in Universal <<= executableScriptName
6970
) ++
7071
makePackageSettingsForConfig(Universal) ++
@@ -95,12 +96,12 @@ object UniversalPlugin extends AutoPlugin {
9596
dist
9697
}
9798

98-
private type Packager = (File, String, Seq[(File, String)]) => File
99+
private type Packager = (File, String, Seq[(File, String)], Option[String]) => File
99100
/** Creates packaging settings for a given package key, configuration + archive type. */
100101
private[this] def makePackageSettings(packageKey: TaskKey[File], config: Configuration)(packager: Packager): Seq[Setting[_]] =
101102
inConfig(config)(Seq(
102103
mappings in packageKey <<= mappings map checkMappings,
103-
packageKey <<= (target, packageName, mappings in packageKey) map packager
104+
packageKey <<= (target, packageName, mappings in packageKey, topLevelDirectory) map packager
104105
))
105106

106107
/** check that all mapped files actually exist */

src/main/scala/com/typesafe/sbt/packager/universal/ZipHelper.scala

+70-23
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import scala.collection.JavaConverters._
2525
* @see http://docs.oracle.com/javase/7/docs/technotes/guides/io/fsp/zipfilesystemprovider.html
2626
*/
2727
object ZipHelper {
28-
case class FileMapping(file: File, name: String)
28+
case class FileMapping(file: File, name: String, unixMode: Option[Int] = None)
2929

3030
/**
3131
* Creates a zip file attempting to give files the appropriate unix permissions using Java 6 APIs.
@@ -54,7 +54,7 @@ object ZipHelper {
5454
}
5555

5656
/**
57-
* Creates a zip file attempting to give files the appropriate unix permissions using Java 7 APIs.
57+
* Creates a zip file with the apache commons compressor library.
5858
*
5959
* Note: This is known to have some odd issues on MacOSX whereby executable permissions
6060
* are not actually discovered, even though the Info-Zip headers exist and work on
@@ -64,10 +64,73 @@ object ZipHelper {
6464
* @param outputZip The location of the output file.
6565
*/
6666
def zip(sources: Traversable[(File, String)], outputZip: File): Unit = {
67+
val mappings =
68+
for {
69+
(file, name) <- sources.toSeq
70+
// TODO - Figure out if this is good enough....
71+
perm = if (file.isDirectory || file.canExecute) 0755 else 0644
72+
} yield FileMapping(file, name, Some(perm))
73+
archive(mappings, outputZip)
74+
}
75+
76+
/**
77+
* Creates a zip file attempting to give files the appropriate unix permissions using Java 7 APIs.
78+
*
79+
* @param sources The files to include in the zip file.
80+
* @param outputZip The location of the output file.
81+
*/
82+
def zipNIO(sources: Traversable[(File, String)], outputZip: File): Unit = {
83+
require(!outputZip.isDirectory, "Specified output file " + outputZip + " is a directory.")
6784
val mappings = sources.toSeq.map {
6885
case (file, name) => FileMapping(file, name)
6986
}
70-
archive(mappings, outputZip)
87+
88+
// make sure everything is available
89+
val outputDir = outputZip.getParentFile
90+
IO createDirectory outputDir
91+
92+
// zipping the sources into the output zip
93+
withZipFilesystem(outputZip) { system =>
94+
mappings foreach {
95+
case FileMapping(dir, name, _) if dir.isDirectory => Files createDirectories (system getPath name)
96+
case FileMapping(file, name, _) =>
97+
val dest = system getPath name
98+
// create parent directories if available
99+
Option(dest.getParent) foreach (Files createDirectories _)
100+
Files copy (file.toPath, dest, StandardCopyOption.COPY_ATTRIBUTES)
101+
}
102+
}
103+
}
104+
105+
private def archive(sources: Seq[FileMapping], outputFile: File): Unit = {
106+
if (outputFile.isDirectory) sys.error("Specified output file " + outputFile + " is a directory.")
107+
else {
108+
val outputDir = outputFile.getParentFile
109+
IO createDirectory outputDir
110+
withZipOutput(outputFile) { output =>
111+
for (FileMapping(file, name, mode) <- sources; if !file.isDirectory) {
112+
val entry = new ZipArchiveEntry(file, normalizePath(name))
113+
// Now check to see if we have permissions for this sucker.
114+
mode foreach (entry.setUnixMode)
115+
output putArchiveEntry entry
116+
// TODO - Write file into output?
117+
IOUtils.copy(new java.io.FileInputStream(file), output)
118+
output.closeArchiveEntry()
119+
}
120+
}
121+
}
122+
}
123+
124+
/**
125+
* using apache commons compress
126+
*/
127+
private def withZipOutput(file: File)(f: ZipArchiveOutputStream => Unit): Unit = {
128+
val zipOut = new ZipArchiveOutputStream(file)
129+
zipOut setLevel Deflater.BEST_COMPRESSION
130+
try { f(zipOut) }
131+
finally {
132+
zipOut.close()
133+
}
71134
}
72135

73136
/**
@@ -84,32 +147,15 @@ object ZipHelper {
84147
}
85148

86149
/**
150+
* Opens a zip filesystem and creates the file if necessary.
87151
*
88-
*/
89-
private def archive(sources: Seq[FileMapping], outputFile: File): Unit = {
90-
require(!outputFile.isDirectory, "Specified output file " + outputFile + " is a directory.")
91-
92-
// make sure everything is available
93-
val outputDir = outputFile.getParentFile
94-
IO createDirectory outputDir
95-
96-
// zipping the sources into the output zip
97-
withZipFilesystem(outputFile) { system =>
98-
sources foreach {
99-
case FileMapping(dir, name) if dir.isDirectory => Files createDirectories (system getPath name)
100-
case FileMapping(file, name) => Files copy (file.toPath, system getPath name, StandardCopyOption.COPY_ATTRIBUTES)
101-
}
102-
}
103-
104-
}
105-
106-
/**
107-
* Opens a zip filesystem and creates the file if neccessary
152+
* Note: This will override an existing zipFile if existent!
108153
*
109154
* @param zipFile
110155
* @param f: FileSystem => Unit, logic working in the filesystem
111156
*/
112157
def withZipFilesystem(zipFile: File)(f: FileSystem => Unit) {
158+
Files deleteIfExists zipFile.toPath
113159
val env = Map("create" -> "true").asJava
114160
val uri = URI.create("jar:file:" + zipFile.getAbsolutePath)
115161

@@ -120,4 +166,5 @@ object ZipHelper {
120166
system.close()
121167
}
122168
}
169+
123170
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
enablePlugins(JavaAppPackaging)
2+
3+
name := "simple-test"
4+
5+
version := "0.1.0"
6+
7+
topLevelDirectory := None
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % sys.props("project.version"))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Test configuration to include in zips.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Run the zip packaging.
2+
> show universal:package-bin
3+
$ exists target/universal/simple-test-0.1.0.zip
4+
5+
# Run the tgz packaging.
6+
> universal:package-zip-tarball
7+
$ exists target/universal/simple-test-0.1.0.tgz
8+
9+
# Run the txz packaging.
10+
> universal:package-xz-tarball
11+
$ exists target/universal/simple-test-0.1.0.txz
12+
13+
14+
# TODO - Check contents of zips

src/test/scala/com/typesafe/sbt/packager/universal/ZipHelperSpec.scala

+20
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,26 @@ class ZipHelperSpec extends FlatSpec with Matchers with BeforeAndAfterAll {
7474
}
7575
}
7676

77+
it should "create directories if necessary" in {
78+
// setup
79+
val out = tmp resolve "dir-creation.zip"
80+
val file = tmp resolve "dir-file.txt"
81+
Files createFile file
82+
83+
ZipHelper.zip(List(file.toFile -> "dir/file.txt"), out.toFile)
84+
85+
ZipHelper.withZipFilesystem(out.toFile) { system =>
86+
val zDir = system getPath "dir"
87+
Files exists zDir should be(true)
88+
Files isDirectory zDir should be(true)
89+
90+
val zFile = zDir resolve "file.txt"
91+
Files exists zFile should be(true)
92+
Files isDirectory zFile should be(false)
93+
}
94+
95+
}
96+
7797
/*
7898
* This is currently not possible.
7999
*/
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
sbt.version=0.13.7-M3
1+
sbt.version=0.13.7

0 commit comments

Comments
 (0)