Skip to content

Commit d0ae3a2

Browse files
committed
rpm support.
* 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.
1 parent 1bdc1a3 commit d0ae3a2

File tree

7 files changed

+326
-12
lines changed

7 files changed

+326
-12
lines changed

src/main/scala/com/typesafe/packager/debian/DebianPlugin.scala

+12-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
package com.typesafe.packager.debian
1+
package com.typesafe.packager
2+
package debian
23

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

2829
def debianSettings: Seq[Setting[_]] = Seq(
30+
// TODO - These settings should move to a common 'linux packaging' plugin location.
31+
linuxPackageMappings := Seq.empty,
32+
packageArchitecture := "all",
2933
sourceDiectory in Debian <<= sourceDiectory apply (_ / "linux"),
30-
target in Debian <<= (target, name in Debian, version in Debian) apply ((t,n,v) => t / (n +"-"+ v))
31-
) ++ inConfig(Debian)(Seq(
32-
name <<= name,
33-
version<<= version,
34-
packageDescription := "",
3534
debianPriority := "optional",
36-
debianArchitecture := "all",
3735
debianSection := "java",
3836
debianPackageDependencies := Seq.empty,
3937
debianPackageRecommends := Seq.empty,
38+
target in Debian <<= (target, name in Debian, version in Debian) apply ((t,n,v) => t / (n +"-"+ v)),
39+
linuxPackageMappings in Debian <<= (linuxPackageMappings).identity
40+
) ++ inConfig(Debian)(Seq(
41+
name <<= name,
42+
version <<= version,
43+
packageDescription := "",
4044
debianPackageMetadata <<=
4145
(name, version, maintainer, packageDescription,
42-
debianPriority, debianArchitecture, debianSection,
46+
debianPriority, packageArchitecture, debianSection,
4347
debianPackageDependencies, debianPackageRecommends) apply PackageMetaData,
4448
debianControlFile <<= (debianPackageMetadata, target) map {
4549
(data, dir) =>
4650
val cfile = dir / "DEBIAN" / "control"
4751
IO.write(cfile, data.makeContent, java.nio.charset.Charset.defaultCharset)
4852
cfile
4953
},
50-
linuxPackageMappings := Seq.empty,
5154
debianExplodedPackage <<= (linuxPackageMappings, debianControlFile, target) map { (mappings, _, t) =>
5255
for {
5356
LinuxPackageMapping(files, perms, zipped) <- mappings

src/main/scala/com/typesafe/packager/debian/Keys.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ object Keys {
99
def name = sbt.Keys.name
1010
def version = sbt.Keys.version
1111
def maintainer = linux.Keys.maintainer
12-
val packageDescription = SettingKey[String]("package-description", "The description of the package. Used when searching.")
13-
val debianArchitecture = SettingKey[String]("debian-architecture", "The architecture to package for, defaults to all.")
12+
def packageArchitecture = linux.Keys.packageArchitecture
13+
def packageDescription = linux.Keys.packageDescription
1414
val debianSection = SettingKey[String]("debian-section", "The section category for this deb file.")
1515
val debianPriority = SettingKey[String]("debian-priority")
1616
val debianPackageDependencies = SettingKey[Seq[String]]("debian-package-dependencies", "Packages that this debian package depends on.")

src/main/scala/com/typesafe/packager/linux/Keys.scala

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package com.typesafe.packager.linux
33
import sbt._
44

55
object Keys {
6+
val packageArchitecture = SettingKey[String]("package-architecture", "The architecture used for this linux package.")
7+
val packageDescription = SettingKey[String]("package-description", "The description of the package. Used when searching.")
68
val maintainer = SettingKey[String]("maintainer", "The name/email address of a maintainer for the native package.")
79
val linuxPackageMappings = TaskKey[Seq[LinuxPackageMapping]]("linux-package-mappings", "File to install location mappings including owner and privileges.")
810
}

src/main/scala/com/typesafe/packager/linux/LinuxPackageMapping.scala

+7-1
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@ import sbt._
55
case class LinuxFileMetaData(
66
user: String = "root",
77
group: String = "root",
8-
permissions: String = "755") {
8+
permissions: String = "755",
9+
config: String = "false",
10+
docs: Boolean = false) {
911

1012
def withUser(u: String) = copy(user = u)
1113
def withGroup(g: String) = copy(group = g)
1214
def withPerms(p: String) = copy(permissions = p)
15+
def withConfig(value:String = "true") = copy(config = value)
16+
def asDocs() = copy(docs = true)
1317
}
1418

1519
case class LinuxPackageMapping(
@@ -20,6 +24,8 @@ case class LinuxPackageMapping(
2024
def withUser(user: String) = copy(fileData = fileData withUser user)
2125
def withGroup(group: String) = copy(fileData = fileData withGroup group)
2226
def withPerms(perms: String) = copy(fileData = fileData withPerms perms)
27+
def withConfig(c: String) = copy(fileData = fileData withConfig c)
28+
def asDocs() = copy(fileData = fileData asDocs ())
2329

2430
/** Modifies the current package mapping to have gzipped data. */
2531
def gzipped = copy(zipped = true)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package com.typesafe.packager.rpm
2+
3+
import sbt._
4+
5+
object RpmHelper {
6+
7+
/** Returns the host vendor for an rpm. */
8+
def hostVendor =
9+
Process(Seq("rpm", "-E", "%{_host_vendor}")) !!
10+
11+
def buildRpm(spec: RpmSpec, workArea: File, log: sbt.Logger): File = {
12+
// TODO - check the spec for errors.
13+
buildWorkArea(workArea)
14+
copyFiles(spec,workArea, log)
15+
writeSpecFile(spec, workArea, log)
16+
17+
buildPackage(workArea, spec, log)
18+
// We should probably return the File that was created.
19+
val rpmname = "%s-%s-%s-%s.rpm" format (spec.meta.name, spec.meta.version, spec.meta.release, spec.meta.arch)
20+
workArea / "RPMS" / spec.meta.arch / rpmname
21+
}
22+
23+
private[this] def copyFiles(spec: RpmSpec, workArea: File, log: sbt.Logger): Unit = {
24+
// TODO - special treatment of icon...
25+
val buildroot = workArea / "tmp-buildroot"
26+
27+
def copyWithZip(from: File, to: File, zipped: Boolean): Unit =
28+
if(zipped) IO.gzip(from, to)
29+
else IO.copyFile(from, to, true)
30+
// We don't have to do any permission modifications since that's in the
31+
// the .spec file.
32+
for {
33+
mapping <- spec.mappings
34+
(file, dest) <- mapping.mappings
35+
if file.exists && !file.isDirectory()
36+
target = buildroot / dest
37+
} copyWithZip(file, target, mapping.zipped)
38+
}
39+
40+
private[this] def writeSpecFile(spec: RpmSpec, workArea: File, log: sbt.Logger): File = {
41+
val specdir = workArea / "SPECS"
42+
val rpmBuildroot = workArea / "buildroot"
43+
val tmpBuildRoot = workArea / "tmp-buildroot"
44+
val specfile = specdir / (spec.meta.name + ".spec")
45+
log.debug("Creating SPEC file: " + specfile.getAbsolutePath)
46+
IO.write(specfile, spec.writeSpec(rpmBuildroot, tmpBuildRoot))
47+
specfile
48+
}
49+
50+
private[this] def buildPackage(
51+
workArea: File,
52+
spec: RpmSpec,
53+
log: sbt.Logger): Unit = {
54+
val buildRoot = workArea / "buildroot"
55+
val specsDir = workArea / "SPECS"
56+
val gpg = false
57+
// TODO - Full GPG support (with GPG plugin).
58+
val args: Seq[String] = Seq(
59+
"rpmbuild",
60+
"-bb",
61+
"-buildroot", buildRoot.getAbsolutePath,
62+
"--define", "_topdir " + workArea.getAbsolutePath,
63+
"--target", spec.meta.arch + '-' + spec.meta.vendor + '-' + spec.meta.os
64+
) ++ (
65+
if(gpg) Seq("--define", "_gpg_name " + "<insert keyname>", "--sign")
66+
else Seq.empty
67+
) ++ Seq(spec.meta.name + ".spec")
68+
Process(args, Some(specsDir)) ! log
69+
}
70+
71+
private[this] val topleveldirs = Seq("BUILD","RPMS","SOURCES","SPECS","SRPMS","tmp-buildroot","buildroot")
72+
73+
/** Builds the work area and returns the tmp build root, and rpm build root. */
74+
private[this] def buildWorkArea(workArea: File): Unit = {
75+
if(!workArea.exists) workArea.mkdirs()
76+
// TODO - validate workarea
77+
// Clean out work area
78+
topleveldirs map (workArea / _) foreach { d =>
79+
if(d.exists()) IO.delete(d)
80+
d.mkdir()
81+
}
82+
}
83+
84+
def evalMacro(macro: String): String =
85+
Process(Seq("rpm", "--eval", '%' + macro)) !!
86+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package com.typesafe.packager
2+
package rpm
3+
4+
import linux.{LinuxPackageMapping,LinuxFileMetaData}
5+
import sbt._
6+
7+
case class RpmMetadata(
8+
name: String,
9+
version: String,
10+
release: String,
11+
arch: String,
12+
vendor: String,
13+
os: String) {
14+
}
15+
16+
17+
case class RpmDescription(
18+
summary: Option[String] = None,
19+
license: Option[String] = None,
20+
distribution: Option[String] = None,
21+
//vendor: Option[String] = None,
22+
url: Option[String] = None,
23+
group: Option[String] = None,
24+
packager: Option[String] = None,
25+
icon: Option[String] = None
26+
)
27+
28+
case class RpmDependencies(
29+
provides: Seq[String] = Seq.empty,
30+
requirements: Seq[String] = Seq.empty,
31+
prereq: Seq[String] = Seq.empty,
32+
obsoletes: Seq[String] = Seq.empty,
33+
conflicts: Seq[String] = Seq.empty) {
34+
def contents: String = {
35+
val sb = new StringBuilder
36+
def appendSetting(prefix: String, values: Seq[String]) =
37+
values foreach (v => sb append (prefix + v + "\n"))
38+
appendSetting("Provides: ", provides)
39+
appendSetting("Requires: ", requirements)
40+
appendSetting("PreReq: ", prereq)
41+
appendSetting("Obsoletes: ", obsoletes)
42+
appendSetting("Conflicts: ", conflicts)
43+
sb.toString
44+
}
45+
}
46+
47+
case class RpmSpec(meta: RpmMetadata,
48+
desc: RpmDescription = RpmDescription(),
49+
deps: RpmDependencies = RpmDependencies(),
50+
mappings: Seq[LinuxPackageMapping] = Seq.empty) {
51+
52+
private[this] def makeFilesLine(target: String, meta: LinuxFileMetaData, isDir: Boolean): String = {
53+
val sb = new StringBuilder
54+
meta.config.toLowerCase match {
55+
case "false" => ()
56+
case "true" => sb append "%config "
57+
case x => sb append ("%config("+x+") ")
58+
}
59+
if(meta.docs) sb append "%doc "
60+
if(isDir) sb append "%dir "
61+
// TODO - map dirs...
62+
sb append "%attr("
63+
sb append meta.permissions
64+
sb append ','
65+
sb append meta.user
66+
sb append ','
67+
sb append meta.group
68+
sb append ") "
69+
sb append target
70+
sb append '\n'
71+
sb.toString
72+
}
73+
74+
private[this] def fileSection: String = {
75+
val sb = new StringBuilder
76+
sb append "\n%files\n"
77+
// TODO - default attribute string.
78+
for {
79+
mapping <- mappings
80+
(file, dest) <- mapping.mappings
81+
} sb append makeFilesLine(dest, mapping.fileData, file.isDirectory)
82+
sb.toString
83+
}
84+
85+
private[this] def installSection(root: File): String = {
86+
val sb = new StringBuilder
87+
sb append "\n"
88+
sb append "%install\n"
89+
sb append "if [ -e $RPM_BUILD_ROOT ]; "
90+
sb append "then\n"
91+
sb append " mv "
92+
sb append root.getAbsolutePath
93+
sb append "/* $RPM_BUILD_ROOT\n"
94+
sb append "else\n"
95+
sb append " mv "
96+
sb append root.getAbsolutePath
97+
sb append " $RPM_BUILD_ROOT\n"
98+
sb append "fi\n"
99+
sb.toString
100+
}
101+
102+
// TODO - This is *very* tied to RPM helper, may belong *in* RpmHelper
103+
def writeSpec(rpmRoot: File, tmpRoot: File): String = {
104+
val sb = new StringBuilder
105+
sb append ("Name: %s\n" format meta.name)
106+
sb append ("Version: %s\n" format meta.version)
107+
sb append ("Release: %s\n" format meta.release)
108+
109+
desc.summary foreach { v => sb append ("Summary: %s\n" format v)}
110+
desc.license foreach { v => sb append ("License: %s\n" format v)}
111+
desc.distribution foreach { v => sb append ("Distribution: %s\n" format v)}
112+
// TODO - Icon
113+
114+
sb append ("Vendor: %s\n" format meta.vendor)
115+
desc.url foreach { v => sb append ("URL: %s\n" format v)}
116+
desc.group foreach { v => sb append ("Group: %s\n" format v)}
117+
desc.packager foreach { v => sb append ("Packager: %s\n" format v)}
118+
119+
sb append deps.contents
120+
121+
// TODO - autoprov + autoreq
122+
123+
sb append ("BuildRoot: %s\n\n" format rpmRoot.getAbsolutePath)
124+
125+
// write build as moving everything into RPM directory.
126+
sb append installSection(tmpRoot)
127+
// TODO - Allow symlinks
128+
// TODO - Allow scriptlets for installation
129+
// "%prep", "%pretrans", "%pre", "%post", "%preun", "%postun", "%posttrans", "%verifyscript", "%clean"
130+
// Write file mappings
131+
sb append fileSection
132+
// TODO - Write triggers...
133+
// TODO - Write changelog...
134+
135+
sb.toString
136+
}
137+
}

0 commit comments

Comments
 (0)