diff --git a/src/main/scala/com/typesafe/sbt/packager/GenericPackageSettings.scala b/src/main/scala/com/typesafe/sbt/packager/GenericPackageSettings.scala index bafb467d1..455813b10 100644 --- a/src/main/scala/com/typesafe/sbt/packager/GenericPackageSettings.scala +++ b/src/main/scala/com/typesafe/sbt/packager/GenericPackageSettings.scala @@ -7,16 +7,12 @@ 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. @@ -33,6 +29,8 @@ trait GenericPackageSettings * `/conf` directory is given a symlink to `/etc/` * Files in `conf/` or `etc/` directories are automatically marked as configuration. * `../man/...1` files are automatically compressed into .gz files. + * + * TODO - We need to figure out how to handle ownership here. */ def mapGenericMappingsToLinux(mappings: Seq[(File, String)])(rename: String => String): Seq[LinuxPackageMapping] = { val (directories, nondirectories) = mappings.partition(_._1.isDirectory) @@ -62,12 +60,16 @@ trait GenericPackageSettings } def mapGenericFilesToLinux: Seq[Setting[_]] = Seq( + + // Default place to install code. + defaultLinuxInstallLocation := "/usr/share", + // First we look at the src/linux files linuxPackageMappings <++= (sourceDirectory in Linux) map { dir => mapGenericMappingsToLinux((dir.*** --- dir) x relativeTo(dir))(identity) }, // Now we look at the src/universal files. - linuxPackageMappings <++= (normalizedName in Universal, mappings in Universal) map { (pkg, mappings) => + linuxPackageMappings <++= (normalizedName in Universal, mappings in Universal, defaultLinuxInstallLocation) map { (pkg, mappings, installLocation) => // TODO - More windows filters... def isWindowsFile(f: (File, String)): Boolean = f._2 endsWith ".bat" @@ -77,7 +79,7 @@ trait GenericPackageSettings } }, // Now we generate symlinks. - linuxPackageSymlinks <++= (normalizedName in Universal, mappings in Universal) map { (pkg, mappings) => + linuxPackageSymlinks <++= (normalizedName in Universal, mappings in Universal, defaultLinuxInstallLocation) map { (pkg, mappings, installLocation) => for { (file, name) <- mappings if !file.isDirectory @@ -86,7 +88,7 @@ trait GenericPackageSettings } yield LinuxSymlink("/usr/" + name, installLocation+"/"+pkg+"/"+name) }, // Map configuration files - linuxPackageSymlinks <++= (normalizedName in Universal, mappings in Universal) map { (pkg, mappings) => + linuxPackageSymlinks <++= (normalizedName in Universal, mappings in Universal, defaultLinuxInstallLocation) map { (pkg, mappings, installLocation) => val needsConfLink = mappings exists { case (file, name) => (name startsWith "conf/") && !file.isDirectory diff --git a/src/main/scala/com/typesafe/sbt/packager/Keys.scala b/src/main/scala/com/typesafe/sbt/packager/Keys.scala index d85adaeb9..9644686f4 100644 --- a/src/main/scala/com/typesafe/sbt/packager/Keys.scala +++ b/src/main/scala/com/typesafe/sbt/packager/Keys.scala @@ -12,7 +12,7 @@ object Keys extends linux.Keys // TODO - Do these keys belong here? def normalizedName = sbt.Keys.normalizedName - // These keys are used by the JavaApp archetype. + // These keys are used by the JavaApp/JavaServer archetypes. val makeBashScript = TaskKey[Option[File]]("makeBashScript", "Creates or discovers the bash script used by this project.") val bashScriptDefines = TaskKey[Seq[String]]("bashScriptDefines", "A list of definitions that should be written to the bash file template.") val bashScriptExtraDefines = TaskKey[Seq[String]]("bashScriptExtraDefines", "A list of extra definitions that should be written to the bash file template.") @@ -28,4 +28,5 @@ object Keys extends linux.Keys | APP_NAME - the name of the app | APP_DEFINES - the defines to go into the app | """.stripMargin) + val defaultLinuxInstallLocation = SettingKey[String]("defaultLinuxInstallLocation", "The location where we will install generic linux packages.") } \ No newline at end of file diff --git a/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaServerApplication.scala b/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaServerApplication.scala index 6196ad38e..30b0934cc 100644 --- a/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaServerApplication.scala +++ b/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaServerApplication.scala @@ -27,9 +27,9 @@ object JavaServerAppPackaging { def debianUpstartSettings: Seq[Setting[_]] = Seq( - debianUpstartScriptReplacements <<= (maintainer in Debian, packageSummary in Debian, normalizedName, sbt.Keys.version) map { (author, descr, name, version) => + debianUpstartScriptReplacements <<= (maintainer in Debian, packageSummary in Debian, normalizedName, sbt.Keys.version, defaultLinuxInstallLocation) map { (author, descr, name, version, installLocation) => // TODO name-version is copied from UniversalPlugin. This should be consolidated into a setting (install location...) - val chdir = GenericPackageSettings.installLocation + "/" + name + "/bin" + val chdir = installLocation + "/" + name + "/bin" JavaAppUpstartScript.makeReplacements(author = author, descr = descr, execScript = name, chdir = chdir) }, debianMakeUpstartScript <<= (debianUpstartScriptReplacements, normalizedName, target in Universal) map makeDebianUpstartScript, diff --git a/src/main/scala/com/typesafe/sbt/packager/debian/DebianPlugin.scala b/src/main/scala/com/typesafe/sbt/packager/debian/DebianPlugin.scala index f1ccbb59d..a3485ad8b 100644 --- a/src/main/scala/com/typesafe/sbt/packager/debian/DebianPlugin.scala +++ b/src/main/scala/com/typesafe/sbt/packager/debian/DebianPlugin.scala @@ -111,16 +111,6 @@ trait DebianPlugin extends Plugin with linux.LinuxPlugin { chmod(cfile, "0644") cfile }, - /*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, debianControlScriptsReplacements, linuxPackageSymlinks, target) map { (mappings, _, maintScripts, _, replacements, symlinks, t) => @@ -162,7 +152,7 @@ trait DebianPlugin extends Plugin with linux.LinuxPlugin { if file.isFile if !(name startsWith "DEBIAN") if !(name contains "debian-binary") - // TODO - detect symlinks... + // TODO - detect symlinks with Java7 (when we can) rather than hackery... if file.getCanonicalPath == file.getAbsolutePath fixedName = if (name startsWith "/") name drop 1 else name } yield Hashing.md5Sum(file) + " " + fixedName diff --git a/src/main/scala/com/typesafe/sbt/packager/linux/LinuxPlugin.scala b/src/main/scala/com/typesafe/sbt/packager/linux/LinuxPlugin.scala index 0f849316c..c7802870b 100644 --- a/src/main/scala/com/typesafe/sbt/packager/linux/LinuxPlugin.scala +++ b/src/main/scala/com/typesafe/sbt/packager/linux/LinuxPlugin.scala @@ -34,7 +34,7 @@ trait LinuxPlugin extends Plugin { * @param dir - use some directory, e.g. target.value * @param files */ - def packageTemplateMapping(dir: File, files: String*) = LinuxPackageMapping(files map ((dir, _))) + def packageTemplateMapping(files: String*)(dir: File = new File(sys.props("java.io.tmpdir"))) = LinuxPackageMapping(files map ((dir, _))) // TODO - we'd like a set of conventions to take universal mappings and create linux package mappings. diff --git a/src/main/scala/com/typesafe/sbt/packager/rpm/RpmHelper.scala b/src/main/scala/com/typesafe/sbt/packager/rpm/RpmHelper.scala index afedeee98..ab6e979e7 100644 --- a/src/main/scala/com/typesafe/sbt/packager/rpm/RpmHelper.scala +++ b/src/main/scala/com/typesafe/sbt/packager/rpm/RpmHelper.scala @@ -102,6 +102,6 @@ object RpmHelper { } } - def evalMacro(macro: String): String = - Process(Seq("rpm", "--eval", '%' + macro)) !! + def evalMacro(mcro: String): String = + Process(Seq("rpm", "--eval", '%' + mcro)).!! } diff --git a/src/main/scala/com/typesafe/sbt/packager/rpm/RpmMetadata.scala b/src/main/scala/com/typesafe/sbt/packager/rpm/RpmMetadata.scala index c7ff615c6..a140d94b7 100644 --- a/src/main/scala/com/typesafe/sbt/packager/rpm/RpmMetadata.scala +++ b/src/main/scala/com/typesafe/sbt/packager/rpm/RpmMetadata.scala @@ -25,7 +25,6 @@ case class RpmMetadata( case class RpmDescription( license: Option[String] = None, distribution: Option[String] = None, - //vendor: Option[String] = None, url: Option[String] = None, group: Option[String] = None, packager: Option[String] = None, @@ -75,6 +74,34 @@ case class RpmSpec(meta: RpmMetadata, mappings: Seq[LinuxPackageMapping] = Seq.empty, symlinks: Seq[LinuxSymlink] = Seq.empty) { + + // TODO - here we want to validate that all the data we have is ok to place + // in the RPM. e.g. the Description/vendor etc. must meet specific requirements. + // For now we just check existence. + def validate(log: Logger): Unit = { + def ensureOr[T](value: T, msg: String, validator: T => Boolean): Boolean = { + if(validator(value)) true + else { + log.error(msg) + false + } + } + def isNonEmpty(s: String): Boolean = !s.isEmpty + val emptyValidators = + Seq( + ensureOr(meta.name, "`name in Rpm` is empty. Please provide one.", isNonEmpty), + ensureOr(meta.version, "`version in Rpm` is empty. Please provide a vaid version for the rpm SPEC.", isNonEmpty), + ensureOr(meta.release, "`rpmRelease in Rpm` is empty. Please provide a valid release number for the rpm SPEC.", isNonEmpty), + ensureOr(meta.arch, "`packageArchitecture in Rpm` is empty. Please provide a valid archiecture for the rpm SPEC.", isNonEmpty), + ensureOr(meta.vendor, "`rpmVendor in Rpm` is empty. Please provide a valid vendor for the rpm SPEC.", isNonEmpty), + ensureOr(meta.os, "`rpmOs in Rpm` is empty. Please provide a valid os vaue for the rpm SPEC.", isNonEmpty), + ensureOr(meta.summary, "`packageSummary in Rpm` is empty. Please provide a valid summary for the rpm SPEC.", isNonEmpty), + ensureOr(meta.description, "`packageDescription in Rpm` is empty. Please provide a valid description for the rpm SPEC.", isNonEmpty) + ) + // TODO - Continue validating after this point? + if(!emptyValidators.forall(identity)) sys.error("There are issues with the rpm spec data.") + } + private[this] def fixFilename(n: String): String = { val tmp = if(n startsWith "/") n diff --git a/src/main/scala/com/typesafe/sbt/packager/rpm/RpmPlugin.scala b/src/main/scala/com/typesafe/sbt/packager/rpm/RpmPlugin.scala index a2d811ebf..adf733e9e 100644 --- a/src/main/scala/com/typesafe/sbt/packager/rpm/RpmPlugin.scala +++ b/src/main/scala/com/typesafe/sbt/packager/rpm/RpmPlugin.scala @@ -50,6 +50,7 @@ trait RpmPlugin extends Plugin with LinuxPlugin { rpmSpecConfig <<= (rpmMetadata, rpmDescription, rpmDependencies, rpmScripts, linuxPackageMappings, linuxPackageSymlinks) map RpmSpec, packageBin <<= (rpmSpecConfig, target, streams) map { (spec, dir, s) => + spec.validate(s.log) RpmHelper.buildRpm(spec, dir, s.log) }, rpmLint <<= (packageBin, streams) map { (rpm, s) => 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 61f39a910..17e65efcf 100644 --- a/src/main/scala/com/typesafe/sbt/packager/universal/ZipHelper.scala +++ b/src/main/scala/com/typesafe/sbt/packager/universal/ZipHelper.scala @@ -40,6 +40,9 @@ object ZipHelper { } /** Creates a zip file attempting to give files the appropriate unix permissions using Java 6 APIs. + * 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. */ diff --git a/src/main/scala/com/typesafe/sbt/packager/windows/WixHelper.scala b/src/main/scala/com/typesafe/sbt/packager/windows/WixHelper.scala index 6e29950a1..b5c7d3065 100644 --- a/src/main/scala/com/typesafe/sbt/packager/windows/WixHelper.scala +++ b/src/main/scala/com/typesafe/sbt/packager/windows/WixHelper.scala @@ -127,6 +127,11 @@ object WixHelper { ComponentInfo(id, xml) + // TODO - To have shortcuts, you MUST put something in the registry. Here, + // We should have shortcuts actually provide us with what they want in the registry, + // rather than forcing it to be something. + // Also, we need some mechanism to ensure the start menu folder is removed in the event + // that we remove all menu items. case AddShortCuts(targets, workingDir) => val id = cleanStringForId("shortcut_" + makeGUID).takeRight(67) // Room for "_SC" val xml =