Skip to content

Commit

Permalink
rpm support.
Browse files Browse the repository at this point in the history
* Added RPM building support (very rough).
* Attempted to generalize debian plugin so that settings can be shared between deb + rpm builds.
* Using Config inheritance FOR ALL ITS WORTH.   Someone will hate me later.
  • Loading branch information
jsuereth committed Dec 21, 2011
1 parent 1bdc1a3 commit d0ae3a2
Show file tree
Hide file tree
Showing 7 changed files with 326 additions and 12 deletions.
21 changes: 12 additions & 9 deletions src/main/scala/com/typesafe/packager/debian/DebianPlugin.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package com.typesafe.packager.debian
package com.typesafe.packager
package debian

import Keys._
import sbt._
Expand Down Expand Up @@ -26,28 +27,30 @@ object DebianPlugin extends Plugin {
Process("groff -man -Tascii " + file.getAbsolutePath).!!

def debianSettings: Seq[Setting[_]] = Seq(
// TODO - These settings should move to a common 'linux packaging' plugin location.
linuxPackageMappings := Seq.empty,
packageArchitecture := "all",
sourceDiectory in Debian <<= sourceDiectory apply (_ / "linux"),
target in Debian <<= (target, name in Debian, version in Debian) apply ((t,n,v) => t / (n +"-"+ v))
) ++ inConfig(Debian)(Seq(
name <<= name,
version<<= version,
packageDescription := "",
debianPriority := "optional",
debianArchitecture := "all",
debianSection := "java",
debianPackageDependencies := Seq.empty,
debianPackageRecommends := Seq.empty,
target in Debian <<= (target, name in Debian, version in Debian) apply ((t,n,v) => t / (n +"-"+ v)),
linuxPackageMappings in Debian <<= (linuxPackageMappings).identity
) ++ inConfig(Debian)(Seq(
name <<= name,
version <<= version,
packageDescription := "",
debianPackageMetadata <<=
(name, version, maintainer, packageDescription,
debianPriority, debianArchitecture, debianSection,
debianPriority, packageArchitecture, debianSection,
debianPackageDependencies, debianPackageRecommends) apply PackageMetaData,
debianControlFile <<= (debianPackageMetadata, target) map {
(data, dir) =>
val cfile = dir / "DEBIAN" / "control"
IO.write(cfile, data.makeContent, java.nio.charset.Charset.defaultCharset)
cfile
},
linuxPackageMappings := Seq.empty,
debianExplodedPackage <<= (linuxPackageMappings, debianControlFile, target) map { (mappings, _, t) =>
for {
LinuxPackageMapping(files, perms, zipped) <- mappings
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/com/typesafe/packager/debian/Keys.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ object Keys {
def name = sbt.Keys.name
def version = sbt.Keys.version
def maintainer = linux.Keys.maintainer
val packageDescription = SettingKey[String]("package-description", "The description of the package. Used when searching.")
val debianArchitecture = SettingKey[String]("debian-architecture", "The architecture to package for, defaults to all.")
def packageArchitecture = linux.Keys.packageArchitecture
def packageDescription = linux.Keys.packageDescription
val debianSection = SettingKey[String]("debian-section", "The section category for this deb file.")
val debianPriority = SettingKey[String]("debian-priority")
val debianPackageDependencies = SettingKey[Seq[String]]("debian-package-dependencies", "Packages that this debian package depends on.")
Expand Down
2 changes: 2 additions & 0 deletions src/main/scala/com/typesafe/packager/linux/Keys.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package com.typesafe.packager.linux
import sbt._

object Keys {
val packageArchitecture = SettingKey[String]("package-architecture", "The architecture used for this linux package.")
val packageDescription = SettingKey[String]("package-description", "The description of the package. Used when searching.")
val maintainer = SettingKey[String]("maintainer", "The name/email address of a maintainer for the native package.")
val linuxPackageMappings = TaskKey[Seq[LinuxPackageMapping]]("linux-package-mappings", "File to install location mappings including owner and privileges.")
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ import sbt._
case class LinuxFileMetaData(
user: String = "root",
group: String = "root",
permissions: String = "755") {
permissions: String = "755",
config: String = "false",
docs: Boolean = false) {

def withUser(u: String) = copy(user = u)
def withGroup(g: String) = copy(group = g)
def withPerms(p: String) = copy(permissions = p)
def withConfig(value:String = "true") = copy(config = value)
def asDocs() = copy(docs = true)
}

case class LinuxPackageMapping(
Expand All @@ -20,6 +24,8 @@ case class LinuxPackageMapping(
def withUser(user: String) = copy(fileData = fileData withUser user)
def withGroup(group: String) = copy(fileData = fileData withGroup group)
def withPerms(perms: String) = copy(fileData = fileData withPerms perms)
def withConfig(c: String) = copy(fileData = fileData withConfig c)
def asDocs() = copy(fileData = fileData asDocs ())

/** Modifies the current package mapping to have gzipped data. */
def gzipped = copy(zipped = true)
Expand Down
86 changes: 86 additions & 0 deletions src/main/scala/com/typesafe/packager/rpm/RpmHelper.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.typesafe.packager.rpm

import sbt._

object RpmHelper {

/** Returns the host vendor for an rpm. */
def hostVendor =
Process(Seq("rpm", "-E", "%{_host_vendor}")) !!

def buildRpm(spec: RpmSpec, workArea: File, log: sbt.Logger): File = {
// TODO - check the spec for errors.
buildWorkArea(workArea)
copyFiles(spec,workArea, log)
writeSpecFile(spec, workArea, log)

buildPackage(workArea, spec, log)
// We should probably return the File that was created.
val rpmname = "%s-%s-%s-%s.rpm" format (spec.meta.name, spec.meta.version, spec.meta.release, spec.meta.arch)
workArea / "RPMS" / spec.meta.arch / rpmname
}

private[this] def copyFiles(spec: RpmSpec, workArea: File, log: sbt.Logger): Unit = {
// TODO - special treatment of icon...
val buildroot = workArea / "tmp-buildroot"

def copyWithZip(from: File, to: File, zipped: Boolean): Unit =
if(zipped) IO.gzip(from, to)
else IO.copyFile(from, to, true)
// We don't have to do any permission modifications since that's in the
// the .spec file.
for {
mapping <- spec.mappings
(file, dest) <- mapping.mappings
if file.exists && !file.isDirectory()
target = buildroot / dest
} copyWithZip(file, target, mapping.zipped)
}

private[this] def writeSpecFile(spec: RpmSpec, workArea: File, log: sbt.Logger): File = {
val specdir = workArea / "SPECS"
val rpmBuildroot = workArea / "buildroot"
val tmpBuildRoot = workArea / "tmp-buildroot"
val specfile = specdir / (spec.meta.name + ".spec")
log.debug("Creating SPEC file: " + specfile.getAbsolutePath)
IO.write(specfile, spec.writeSpec(rpmBuildroot, tmpBuildRoot))
specfile
}

private[this] def buildPackage(
workArea: File,
spec: RpmSpec,
log: sbt.Logger): Unit = {
val buildRoot = workArea / "buildroot"
val specsDir = workArea / "SPECS"
val gpg = false
// TODO - Full GPG support (with GPG plugin).
val args: Seq[String] = Seq(
"rpmbuild",
"-bb",
"-buildroot", buildRoot.getAbsolutePath,
"--define", "_topdir " + workArea.getAbsolutePath,
"--target", spec.meta.arch + '-' + spec.meta.vendor + '-' + spec.meta.os
) ++ (
if(gpg) Seq("--define", "_gpg_name " + "<insert keyname>", "--sign")
else Seq.empty
) ++ Seq(spec.meta.name + ".spec")
Process(args, Some(specsDir)) ! log
}

private[this] val topleveldirs = Seq("BUILD","RPMS","SOURCES","SPECS","SRPMS","tmp-buildroot","buildroot")

/** Builds the work area and returns the tmp build root, and rpm build root. */
private[this] def buildWorkArea(workArea: File): Unit = {
if(!workArea.exists) workArea.mkdirs()
// TODO - validate workarea
// Clean out work area
topleveldirs map (workArea / _) foreach { d =>
if(d.exists()) IO.delete(d)
d.mkdir()
}
}

def evalMacro(macro: String): String =
Process(Seq("rpm", "--eval", '%' + macro)) !!
}
137 changes: 137 additions & 0 deletions src/main/scala/com/typesafe/packager/rpm/RpmMetadata.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package com.typesafe.packager
package rpm

import linux.{LinuxPackageMapping,LinuxFileMetaData}
import sbt._

case class RpmMetadata(
name: String,
version: String,
release: String,
arch: String,
vendor: String,
os: String) {
}


case class RpmDescription(
summary: Option[String] = None,
license: Option[String] = None,
distribution: Option[String] = None,
//vendor: Option[String] = None,
url: Option[String] = None,
group: Option[String] = None,
packager: Option[String] = None,
icon: Option[String] = None
)

case class RpmDependencies(
provides: Seq[String] = Seq.empty,
requirements: Seq[String] = Seq.empty,
prereq: Seq[String] = Seq.empty,
obsoletes: Seq[String] = Seq.empty,
conflicts: Seq[String] = Seq.empty) {
def contents: String = {
val sb = new StringBuilder
def appendSetting(prefix: String, values: Seq[String]) =
values foreach (v => sb append (prefix + v + "\n"))
appendSetting("Provides: ", provides)
appendSetting("Requires: ", requirements)
appendSetting("PreReq: ", prereq)
appendSetting("Obsoletes: ", obsoletes)
appendSetting("Conflicts: ", conflicts)
sb.toString
}
}

case class RpmSpec(meta: RpmMetadata,
desc: RpmDescription = RpmDescription(),
deps: RpmDependencies = RpmDependencies(),
mappings: Seq[LinuxPackageMapping] = Seq.empty) {

private[this] def makeFilesLine(target: String, meta: LinuxFileMetaData, isDir: Boolean): String = {
val sb = new StringBuilder
meta.config.toLowerCase match {
case "false" => ()
case "true" => sb append "%config "
case x => sb append ("%config("+x+") ")
}
if(meta.docs) sb append "%doc "
if(isDir) sb append "%dir "
// TODO - map dirs...
sb append "%attr("
sb append meta.permissions
sb append ','
sb append meta.user
sb append ','
sb append meta.group
sb append ") "
sb append target
sb append '\n'
sb.toString
}

private[this] def fileSection: String = {
val sb = new StringBuilder
sb append "\n%files\n"
// TODO - default attribute string.
for {
mapping <- mappings
(file, dest) <- mapping.mappings
} sb append makeFilesLine(dest, mapping.fileData, file.isDirectory)
sb.toString
}

private[this] def installSection(root: File): String = {
val sb = new StringBuilder
sb append "\n"
sb append "%install\n"
sb append "if [ -e $RPM_BUILD_ROOT ]; "
sb append "then\n"
sb append " mv "
sb append root.getAbsolutePath
sb append "/* $RPM_BUILD_ROOT\n"
sb append "else\n"
sb append " mv "
sb append root.getAbsolutePath
sb append " $RPM_BUILD_ROOT\n"
sb append "fi\n"
sb.toString
}

// TODO - This is *very* tied to RPM helper, may belong *in* RpmHelper
def writeSpec(rpmRoot: File, tmpRoot: File): String = {
val sb = new StringBuilder
sb append ("Name: %s\n" format meta.name)
sb append ("Version: %s\n" format meta.version)
sb append ("Release: %s\n" format meta.release)

desc.summary foreach { v => sb append ("Summary: %s\n" format v)}
desc.license foreach { v => sb append ("License: %s\n" format v)}
desc.distribution foreach { v => sb append ("Distribution: %s\n" format v)}
// TODO - Icon

sb append ("Vendor: %s\n" format meta.vendor)
desc.url foreach { v => sb append ("URL: %s\n" format v)}
desc.group foreach { v => sb append ("Group: %s\n" format v)}
desc.packager foreach { v => sb append ("Packager: %s\n" format v)}

sb append deps.contents

// TODO - autoprov + autoreq

sb append ("BuildRoot: %s\n\n" format rpmRoot.getAbsolutePath)

// write build as moving everything into RPM directory.
sb append installSection(tmpRoot)
// TODO - Allow symlinks
// TODO - Allow scriptlets for installation
// "%prep", "%pretrans", "%pre", "%post", "%preun", "%postun", "%posttrans", "%verifyscript", "%clean"
// Write file mappings
sb append fileSection
// TODO - Write triggers...
// TODO - Write changelog...

sb.toString
}
}
Loading

0 comments on commit d0ae3a2

Please sign in to comment.