Skip to content

Commit

Permalink
Merge pull request #14 from sbt/wip/symlinks
Browse files Browse the repository at this point in the history
Add a layer between universal + specific native packages
  • Loading branch information
jsuereth committed Jul 1, 2013
2 parents 9e74bc4 + 2ab07f9 commit c67f2ce
Show file tree
Hide file tree
Showing 24 changed files with 835 additions and 96 deletions.
76 changes: 35 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Add the following to your `project/plugins.sbt` or `~/.sbt/plugins.sbt` file:

resolvers += Resolver.url("scalasbt", new URL("http://scalasbt.artifactoryonline.com/scalasbt/sbt-plugin-releases"))(Resolver.ivyStylePatterns)

addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "0.5.4")
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "0.6.0-symlink-3")

Then, in the project you wish to use the plugin, add the following settings:

Expand All @@ -25,51 +25,26 @@ or
Using the sbt-native-packger plugin requires a bit of understanding of the underlying packaging mechanisms for each operating system it supports. The [generated documentation](http://scala-sbt.org/sbt-native-packager) for the plugin is still a work in progress.


Here's an example excerpt for the debian + rpm package of [sbt-extras](http://github.com/paulp/sbt-extras) project:
Here's an example excerpt for the native packaging of [sbt-launcher-packge](http://github.com/sbt/sbt-launcher-package) project:

// GENERAL LINUX PACKAGING STUFFS
maintainer := "Josh Suereth <[email protected]>",
packageSummary := "Simple Build Tool for Scala-driven builds",
packageDescription := """This script provides a native way to run the Simple Build Tool,
a build tool for Scala software, also called SBT.""",
linuxPackageMappings <+= (baseDirectory) map { bd =>
(packageMapping((bd / "sbt") -> "/usr/bin/sbt")
withUser "root" withGroup "root" withPerms "0755")
a build tool for Scala software, also called SBT.""",
// Here we remove the jar file and launch lib from the symlinks:
linuxPackageSymlinks <<= linuxPackageSymlinks map { links =>
for {
link <- links
if !(link.destination endsWith "sbt-launch-lib.bash")
if !(link.destination endsWith "sbt-launch.jar")
} yield link
},
linuxPackageMappings <+= (sourceDirectory) map { bd =>
(packageMapping(
(bd / "linux" / "usr/share/man/man1/sbt.1") -> "/usr/share/man/man1/sbt.1.gz"
) withPerms "0644" gzipped) asDocs()
},
linuxPackageMappings <+= (sourceDirectory in Linux) map { bd =>
packageMapping(
(bd / "usr/share/doc/sbt/copyright") -> "/usr/share/doc/sbt/copyright"
) withPerms "0644" asDocs()
},
linuxPackageMappings <+= (sourceDirectory in Linux) map { bd =>
packageMapping(
(bd / "usr/share/doc/sbt") -> "/usr/share/doc/sbt"
) asDocs()
},
linuxPackageMappings <+= (sourceDirectory in Linux) map { bd =>
packageMapping(
(bd / "etc/sbt") -> "/etc/sbt"
) withConfig()
},
linuxPackageMappings <+= (sourceDirectory in Linux) map { bd =>
packageMapping(
(bd / "etc/sbt/sbtopts") -> "/etc/sbt/sbtopts"
) withPerms "0644" withConfig("noreplace")
},
linuxPackageMappings <+= (sbtLaunchJar, sourceDirectory in Linux, sbtVersion) map { (jar, dir, v) =>
packageMapping(dir -> "/usr/lib/sbt",
dir -> ("/usr/lib/sbt/" + v),
jar -> ("/usr/lib/sbt/"+v+"/sbt-launch.jar")) withPerms "0755"
},
// DEBIAN SPECIFIC
name in Debian := "sbt",
// DEBIAN SPECIFIC
name in Debian <<= (sbtVersion) apply { (sv) => "sbt" /* + "-" + (sv split "[^\\d]" take 3 mkString ".")*/ },
version in Debian <<= (version, sbtVersion) apply { (v, sv) =>
sv + "-build-" + (v split "\\." map (_.toInt) dropWhile (_ == 0) map ("%02d" format _) mkString "")
val nums = (v split "[^\\d]")
"%s-%s-build-%03d" format (sv, (nums.init mkString "."), nums.last.toInt + 1)
},
debianPackageDependencies in Debian ++= Seq("curl", "java2-runtime", "bash (>= 2.05a-11)"),
debianPackageRecommends in Debian += "git",
Expand All @@ -81,11 +56,30 @@ Here's an example excerpt for the debian + rpm package of [sbt-extras](http://gi

// RPM SPECIFIC
name in Rpm := "sbt",
version in Rpm <<= sbtVersion.identity,
version in Rpm <<= sbtVersion apply { sv => (sv split "[^\\d]" filterNot (_.isEmpty) mkString ".") },
rpmRelease := "1",
rpmVendor := "typesafe",
rpmUrl := Some("http://github.com/paulp/sbt-extras"),
rpmLicense := Some("BSD"),



// WINDOWS SPECIFIC
name in Windows := "sbt",
version in Windows <<= (sbtVersion) apply { sv =>
(sv split "[^\\d]" filterNot (_.isEmpty)) match {
case Array(major,minor,bugfix, _*) => Seq(major,minor,bugfix, "1") mkString "."
case Array(major,minor) => Seq(major,minor,"0","1") mkString "."
case Array(major) => Seq(major,"0","0","1") mkString "."
}
},
maintainer in Windows := "Typesafe, Inc.",
packageSummary in Windows := "Simple Build Tool",
packageDescription in Windows := "THE reactive build tool.",
wixProductId := "ce07be71-510d-414a-92d4-dff47631848a",
wixProductUpgradeId := "4552fb0e-e257-4dbd-9ecb-dba9dbacf424",
javacOptions := Seq("-source", "1.5", "-target", "1.5"),

// Universal ZIP download install.
name in Universal := "sbt"

The full build, including windows MSI generation, can be found [here](https://github.com/sbt/sbt-launcher-package/blob/full-packaging/project/packaging.scala).
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ name := "sbt-native-packager"

organization := "com.typesafe.sbt"

version := "0.5.5"
version := "0.6.0"

scalacOptions in Compile += "-deprecation"

Expand Down
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=0.12.1
sbt.version=0.12.4
4 changes: 3 additions & 1 deletion src/main/scala/com/typesafe/sbt/PackagerPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ object SbtNativePackager extends Plugin
with debian.DebianPlugin
with rpm.RpmPlugin
with windows.WindowsPlugin
with universal.UniversalPlugin {
with universal.UniversalPlugin
with GenericPackageSettings {

def packagerSettings = linuxSettings ++
debianSettings ++
Expand All @@ -38,4 +39,5 @@ object SbtNativePackager extends Plugin
}

// TODO - Add a few targets that detect the current OS and build a package for that OS.

}
149 changes: 149 additions & 0 deletions src/main/scala/com/typesafe/sbt/packager/GenericPackageSettings.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package com.typesafe.sbt
package packager

import Keys._
import sbt._
import sbt.Keys.{name, mappings, sourceDirectory}
import linux.LinuxSymlink
import linux.LinuxPackageMapping

object GenericPackageSettings {
val installLocation = "/usr/share"
}
trait GenericPackageSettings
extends linux.LinuxPlugin
with debian.DebianPlugin
with rpm.RpmPlugin
with windows.WindowsPlugin
with universal.UniversalPlugin {
import GenericPackageSettings._

// This method wires a lot of hand-coded generalities about how to map directories
// into linux, and the conventions we expect.
// It is by no means 100% accurate, but should be ok for the simplest cases.
// For advanced users, use the underlying APIs.
// Right now, it's also pretty focused on command line scripts packages.

/**
* Maps linux file format from the universal from the conventions:
*
* `<project>/src/linux` files are mapped directly into linux packages.
* `<universal>` files are placed under `/usr/share/<package-name>`
* `<universal>/bin` files are given symlinks in `/usr/bin`
* `<universal>/conf` directory is given a symlink to `/etc/<package-name>`
* Files in `conf/` or `etc/` directories are automatically marked as configuration.
* `../man/...1` files are automatically compressed into .gz files.
*/
def mapGenericMappingsToLinux(mappings: Seq[(File, String)])(rename: String => String): Seq[LinuxPackageMapping] = {
val (directories, nondirectories) = mappings.partition(_._1.isDirectory)
val (binaries, nonbinaries) = nondirectories.partition(_._1.canExecute)
val (manPages, nonManPages) = nonbinaries partition {
case (file, name) => (name contains "man/") && (name endsWith ".1")
}
val compressedManPages =
for((file, name) <- manPages)
yield file -> (name + ".gz")
val (configFiles, remaining) = nonManPages partition {
case (file, name) => (name contains "etc/") || (name contains "conf/")
}
def packageMappingWithRename(mappings: (File, String)*): LinuxPackageMapping = {
val renamed =
for((file, name) <- mappings)
yield file -> rename(name)
packageMapping(renamed:_*)
}

Seq(
packageMappingWithRename((binaries ++ directories):_*) withUser "root" withGroup "root" withPerms "0755",
packageMappingWithRename(compressedManPages:_*).gzipped withUser "root" withGroup "root" withPerms "0644",
packageMappingWithRename(configFiles:_*) withConfig() withUser "root" withGroup "root" withPerms "0644",
packageMappingWithRename(remaining:_*) withUser "root" withGroup "root" withPerms "0644"
)
}

def mapGenericFilesToLinux: Seq[Setting[_]] = Seq(
// First we look at the src/linux files
linuxPackageMappings <++= (name in Universal, sourceDirectory in Linux) map { (pkg, dir) =>
mapGenericMappingsToLinux((dir.*** --- dir) x relativeTo(dir))(identity)
},
// Now we look at the src/universal files.
linuxPackageMappings <++= (name in Universal, mappings in Universal) map { (pkg, mappings) =>
// TODO - More windows filters...
def isWindowsFile(f: (File, String)): Boolean =
f._2 endsWith ".bat"

mapGenericMappingsToLinux(mappings filterNot isWindowsFile) { name =>
installLocation + "/" + pkg + "/" + name
}
},
// Now we generate symlinks.
linuxPackageSymlinks <++= (name in Universal, mappings in Universal) map { (pkg, mappings) =>
for {
(file, name) <- mappings
if !file.isDirectory
if name startsWith "bin/"
if !(name endsWith ".bat") // IGNORE windows-y things.
} yield LinuxSymlink("/usr/" + name, installLocation+"/"+pkg+"/"+name)
},
// Map configuration files
linuxPackageSymlinks <++= (name in Universal, mappings in Universal) map { (pkg, mappings) =>
val needsConfLink =
mappings exists { case (file, name) =>
(name startsWith "conf/") && !file.isDirectory
}
if(needsConfLink) Seq(LinuxSymlink(
link="/etc/" + pkg,
destination=installLocation+"/"+pkg+"/conf"))
else Seq.empty
}
)

def mapGenericFilesToWindows: Seq[Setting[_]] = Seq(
mappings in Windows <<= mappings in Universal,
wixFeatures <<= (name in Windows, mappings in Windows) map makeWindowsFeatures
)
// TODO select main script! Filter Config links!
def makeWindowsFeatures(name: String, mappings: Seq[(File, String)]): Seq[windows.WindowsFeature] = {
import windows._

val files =
for {
(file, name) <- mappings
if !file.isDirectory
} yield ComponentFile(name, editable = (name startsWith "conf"))
val corePackage =
WindowsFeature(
id=name+"Core",
title=name,
desc="All core files.",
absent="disallow",
components = files
)
// TODO - Detect bat files to add paths...
val homeEnvVar = name.toUpperCase +"_HOME"
val addBinToPath =
// TODO - we may have issues here...
WindowsFeature(
id="AddBinToPath",
title="Update Enviornment Variables",
desc="Update PATH environment variables (requires restart).",
components = Seq(AddDirectoryToPath("bin"))
)
val configLinks = for {
(file, name) <- mappings
if !file.isDirectory
if name startsWith "conf/"
} yield name
val menuLinks =
WindowsFeature(
id="AddConfigLinks",
title="Configuration start menu links",
desc="Adds start menu shortcuts to edit configuration files.",
components = Seq(AddShortCuts(configLinks))
)
// TODO - Add feature for shortcuts to binary scripts.
Seq(corePackage, addBinToPath, menuLinks)
}


}
48 changes: 41 additions & 7 deletions src/main/scala/com/typesafe/sbt/packager/debian/DebianPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,25 @@ package debian
import Keys._
import sbt._
import linux.LinuxPackageMapping
import linux.LinuxSymlink
import linux.LinuxFileMetaData
import com.typesafe.sbt.packager.Hashing
import com.typesafe.sbt.packager.linux.LinuxSymlink

trait DebianPlugin extends Plugin with linux.LinuxPlugin {
val Debian = config("debian") extend Linux

import com.typesafe.sbt.packager.universal.Archives

private[this] final def copyAndFixPerms(from: File, to: File, perms: LinuxFileMetaData, zipped: Boolean = false): Unit = {
if(zipped) IO.gzip(from, to)
else IO.copyFile(from, to, true)
if(zipped) {
IO.withTemporaryDirectory { dir =>
val tmp = dir / from.getName
IO.copyFile(from, tmp)
val zipped = Archives.gzip(tmp)
IO.copyFile(zipped, to, true)
}
} else IO.copyFile(from, to, true)
// If we have a directory, we need to alter the perms.
chmod(to, perms.permissions)
// TODO - Can we do anything about user/group ownership?
Expand All @@ -32,15 +42,15 @@ trait DebianPlugin extends Plugin with linux.LinuxPlugin {
debianPackageRecommends := Seq.empty,
debianSignRole := "builder",
target in Debian <<= (target, name in Debian, version in Debian) apply ((t,n,v) => t / (n +"-"+ v)),
name in Debian <<= (name in Linux),
version in Debian <<= (version in Linux),
linuxPackageMappings in Debian <<= linuxPackageMappings,
packageDescription in Debian <<= packageDescription in Linux,
packageSummary in Debian <<= packageSummary in Linux,
maintainer in Debian <<= maintainer in Linux,
debianMaintainerScripts := Seq.empty
) ++ inConfig(Debian)(Seq(
name <<= name,
version <<= version,
packageArchitecture := "all",
maintainer := "",
debianPackageInfo <<=
(name, version, maintainer, packageSummary, packageDescription) apply PackageInfo,
debianPackageMetadata <<=
Expand All @@ -59,6 +69,7 @@ trait DebianPlugin extends Plugin with linux.LinuxPlugin {
(data, size, dir) =>
val cfile = dir / "DEBIAN" / "control"
IO.write(cfile, data.makeContent(size), java.nio.charset.Charset.defaultCharset)
chmod(cfile, "0644")
cfile
},
debianConffilesFile <<= (linuxPackageMappings, target) map {
Expand All @@ -71,9 +82,20 @@ trait DebianPlugin extends Plugin with linux.LinuxPlugin {
if file.isFile
} yield name
IO.writeLines(cfile, conffiles)
chmod(cfile, "0644")
cfile
},
debianExplodedPackage <<= (linuxPackageMappings, debianControlFile, debianMaintainerScripts, debianConffilesFile, target) map { (mappings, _, maintScripts, _, t) =>
/*debianLinksfile <<= (name, linuxPackageSymlinks, target) map { (name, symlinks, dir) =>
val lfile = dir / "DEBIAN" / (name + ".links")
val content =
for {
LinuxSymlink(link, destination) <- symlinks
} yield link + " " + destination
IO.writeLines(lfile, content)
chmod(lfile, "0644")
lfile
},*/
debianExplodedPackage <<= (linuxPackageMappings, debianControlFile, debianMaintainerScripts, debianConffilesFile, linuxPackageSymlinks, target) map { (mappings, _, maintScripts, _, symlinks, t) =>
// First Create directories, in case we have any without files in them.
for {
LinuxPackageMapping(files, perms, zipped) <- mappings
Expand All @@ -88,11 +110,20 @@ trait DebianPlugin extends Plugin with linux.LinuxPlugin {
if !file.isDirectory && file.exists
tfile = t / name
} copyAndFixPerms(file, tfile, perms, zipped)

// Now generate relative symlinks
LinuxSymlink.makeSymLinks(symlinks, t)

// TODO: Fix this ugly hack to permission directories correctly!
for(file <- (t.***).get; if file.isDirectory) chmod(file, "0755")
for {
file <- (t.***).get
if file.isDirectory
if file.getCanonicalPath == file.getAbsolutePath // Ignore symlinks.
} chmod(file, "0755")
// Put the maintainer files in `dir / "DEBIAN"` named as specified.
// Valid values for the name are preinst,postinst,prerm,postrm
for ((file, name) <- maintScripts) copyAndFixPerms(file, t / "DEBIAN" / name, LinuxFileMetaData())

t
},
debianMD5sumsFile <<= (debianExplodedPackage, target) map {
Expand All @@ -103,9 +134,12 @@ trait DebianPlugin extends Plugin with linux.LinuxPlugin {
if file.isFile
if !(name startsWith "DEBIAN")
if !(name contains "debian-binary")
// TODO - detect symlinks...
if file.getCanonicalPath == file.getAbsolutePath
fixedName = if(name startsWith "/") name drop 1 else name
} yield Hashing.md5Sum(file) + " " + fixedName
IO.writeLines(md5file, md5sums)
chmod(md5file, "0644")
md5file
},
packageBin <<= (debianExplodedPackage, debianMD5sumsFile, target, streams) map { (pkgdir, _, tdir, s) =>
Expand Down
Loading

0 comments on commit c67f2ce

Please sign in to comment.