Modifying install directory for rpm with sbt native-packager - scala

I am trying to build an rpm package with the sbt-native-packager that installs in a custom directory eg /opt/myapp instead of /usr - due to in-house policy requirements.
I have a build.sbt that will build a standard rpm but I'm stumped when it comes to modifying the directory. My apologies-I'm quite new to scala, sbt and the native pacakger.
I am using mapGenericFilesToLinux and would like to keep its structure - just modifying the first part of the destination directory.
I found this code fragment in a git hub issue https://github.com/sbt/sbt-native-packager/issues/4#issuecomment-6731183
linuxPackageMappings <+= target map { target =>
val src = target / "webapp"
val dest = "/opt/app"
LinuxPackageMapping(
for {
path <- (src ***).get
if !path.isDirectory
} yield path -> path.toString.replaceFirst(src.toString, dest)
)
}
I believe I want to do something similar except to
linuxPackageMappings in Rpm <++= <SOMETHING HERE> {
// for loop that steps through the source and destination and modifies the directory
}
thanks in advance for any help
bye
Pam

So, in sbt 0.12 you need to make sure you specify all the dependent keys you want to use before declaring the value you want. SO, let's pretend two things:
linuxPackageMappings has all your mappings for the packaging.
linuxPackageMappings in Rpm has nothing added to it directly.
We're going to take the value in linuxPackageMappings and alter the directory for linuxPackageMappings in Rpm:
linuxPackageMappings in Rpm <<= (linuxPackageMappings) map { mappings =>
// Let's loop through the mappings and alter their on-disc location....
for(LinuxPackageMapping(filesAndNames, meta, zipped) <- mappings) yield {
val newFilesAndNames = for {
(file, installPath) <- filesAndNames
} yield file -> installPath.replaceFirst("/usr/share/app", "/opt/app")
LinuxPackageMapping(newFilesAndNames, meta, zipped)
}
}
What this does is rip out the linux package mappings (which include whether or not to gzip files, and the user/group owners/permissions) and modify the install path of each file.
Hope that helps! IN sbt-native-packager.NEXT (not released) you can configure the default install location.

Related

How do I copy a zip dependency to the target directory in an SBT build?

I am working on an SBT project that generates an RPM via the sat-native-packager. One of the items that I want to pull into the RPM is a ZIP file that was published from a separate project using the sat-pack plugin. This ZIP file contains a number of JAR files, along with multiple scripts for invoking them.
I have the following in my RPM project's build.sbt:
libraryDependencies += ("com.mycompany" %% "aputils" % "1.0.0-SNAPSHOT").artifacts(Artifact("aputils", "zip", "zip"))
// Task to download and unpack the utils bundle
lazy val unpackUtilsTask = taskKey[Unit]("Download the utils bundle to the target directory")
unpackUtilsTask := {
val log = streams.value.log
val report: UpdateReport = (update in Rpm).value
val filter = artifactFilter(extension = "zip")
val matches: Seq[File] = report.matching(filter)
matches.foreach{ f =>
log.info(s"Filter match: ${f}")
IO.copyFile(f, target.value)
}
}
When I run this task, it does not match any entries in the UpdateReport. Nothing is printed, and no files are copied to target/. If I modify the task to instead print all of the files in the UpdateReport:
report.allFiles.foreach(f => log.info(s"All files: $f))
I see a number of JAR files, but not my ZIP file. The JAR files turn out to be all of the JAR files that are contained in the ZIP file. I am not sure why the ZIP is being unpacked and its contents are being listed as dependencies like this. If I mark the dependency as notTransitive, then the contained JARs are not listed in the report, but the ZIP still isn't included either.
This project is using SBT 0.13.15. I would prefer not to update it to 1.x at this time, but will do so if I must.
I will need to unzip the ZIP file under target/ eventually so I can define one or more packageMapping entries to pull the files into the RPM, but that seems easy enough to do via sbt.IO, if I can first just get a reference to the original ZIP file that is pulled down from our Artifactory server.
This didn't get any responses after a couple days, but I'll post the answer that I was able to come up with after more trial and error.
I was on the right track by examining the UpdateReport, but I wasn't looking at the right data within it. I needed to drill down to find a ModuleReport, which would show me where the .zip file was being downloaded to on the build machine. Once I have that path, it is trivial to unpack it to target/ using IO.unzip(). Here is how my task ended up looking:
libraryDependencies += ("com.mycompany" %% "aputils" % "1.0.0-SNAPSHOT").artifacts(Artifact("aputils", "zip", "zip"))
// Task to unzip the utils ZIP file to the target directory so we can define a package mapping
lazy val unpackUtilsTask = taskKey[Unit]("Download the utils bundle to the target directory")
unpackUtilsTask := {
val log = streams.value.log
val cReport: ConfigurationReport = (update in Compile).value.configuration("compile").get
cReport.modules.foreach{ mReport =>
if (mReport.module.name.startsWith("aputils")) {
mReport.artifacts.foreach{ case (art, f) =>
log.info(s"Unpacking aputils bundle: ${f.getAbsolutePath}")
IO.unzip(f, target.value)
}
}
}
}
packageBin in Rpm := ((packageBin in Rpm).dependsOn(unpackUtilsTask)).value
The last line attaches the task to the task that builds the RPM, so it will be unzipped before the RPM is built, and we can define packageMappings to put the contents of the .zip file into the generated RPM.

How to change the naming structure of the dependent libraries in the zip produced by sbt-native-packager?

I am using the Universal plugin of the sbt-native-packager to create a zip package. I am using the below setting for creating a default structure:
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.1.4")
enablePlugins(JavaAppPackaging)
Currently all my project dependencies in the zip fall under the lib folder e.g.
lib/
ch.qos.logback.logback-classic-1.1.3.jar
dom4j.dom4j-1.6.1.jar
How do I change the name of all the libraries to contain only the artifactId and version of the jar and not the complete name. For example, for the above, I want something like this:
lib/
logback-classic-1.1.3.jar
dom4j-1.6.1.jar
The logic for this is hard coded in the JavaAppPackaging archetype. However you can remap your library dependencies.
mappings in Universal := (mappings in Universal).value.map {
case (file, dest) if dest.startsWith("lib/") =>
file -> changeDestination(dest)
case mapping => mapping
}
def changeDestination(dest: String): String = ???
Next you need to change the scriptClasspathOrdering which is responsible for the app_classpath defined in the BashStartScriptPlugin.
scriptClasspathOrdering := scriptClasspathOrdering.value.map {
case (file, dest) if dest.startsWith("lib/") =>
file -> changeDestination(dest)
case mapping => mapping
}
The destination folder should be lib/ as the bash script assumes this.
Note that I have not tested this as this is a very uncommon use case. However the main idea should be clear :)
cheers,
Muki

sbt native-packager RPM install directory issue

I have tried to modify the install dir of my RPM. This seems to work, however my RPM now is missing the init.d start script. When I look in target/universal/tmp/bin, I don't see systemv-init.
Below is my snippet code from my build.sbt that show how I am overriding the install directory. I have custom start script in src/templates directory for my scala fat jar app. When I remove the below install directory override, the RPM packages fine and install ok in /usr/share. Any help with this issue is greatly appreciated.
linuxPackageMappings in Rpm <<= (linuxPackageMappings) map { mappings =>
for(LinuxPackageMapping(filesAndNames, meta, zipped) <- mappings) yield {
val newFilesAndNames = for {
(file, installPath) <- filesAndNames
} yield file -> installPath.replaceFirst("/usr/share", "/opt")
LinuxPackageMapping(newFilesAndNames, meta, zipped)
}
}
I was able to solve this by removing the above code and just adding one liner to my build.sbt:
defaultLinuxInstallLocation:= "/opt"

Add specific directory and its content to Universal target

I am switching from maven to sbt for a Scala project I am working on. I used to work with the maven assembly plugin where you can map any directory in the workspace to a target directory in the assembly. I didn't find any equivalent in sbt-native-package, it worth provide this feature for the Universe kind.
I understood that everything that is present in the universal subdirectory is copied to the package as such, and it works like a charm, but I lack something like the following snippet.
mappings in Universal += {
directory("my/local/dir") -> "static/dirInPackage"
}
I would like to know if there is already a way to do that, in such case, I would be happy to know how to do it, and I propose my help to commit documentation for that part if you want.
If there is no way to do this kind of customization, I will be happy to propose a patch for that after having discussed specifications.
By the way, great job, your packager is working very well, thanks !
After having discussed with the sbt-native-manager team and a first "rejected" pull request, here is the way to do this directory mapping in the build.sbt file (see pull request https://github.com/sbt/sbt-native-packager/pull/160 which provides mode detailed documentation) :
mappings in Universal <++= (packageBin in Compile, target ) map { (_, target) =>
val dir = target / "scala-2.10" / "api"
(dir.***) pair relativeTo(dir.getParentFile)
}
To reduce verbosity of the above snippet, there is an issue (https://github.com/sbt/sbt-native-packager/issues/161) to propose a more human readable way to express this directory mapping:
mappings in Universal ++= allFilesRelativeTo(file(target / "scala-2.10" / "api"))
From https://github.com/sbt/sbt-native-packager
If you'd like to add additional files to the installation dir, simply add them to the universal mappings:
import com.typesafe.sbt.SbtNativePackager.Universal
mappings in Universal += {
file("my/local/conffile") -> "conf/my.conf"
}
You could use a simple map on top of the directory method result.
==> directory method documentation: MappingsHelper.directory
For example:
// Packaging the content of /src/main/resources under conf add the following:
mappings in Universal ++= (directory("src/main/resources").map(t => (t._1, t._2.replace("resources", "conf"))))
This one seems to be the simplest example that worked for me
Takes all files in res/scripts/ and puts it in the bin/ directory when unzipped.
// In build.sbt
mappings in Universal <++= (packageBin in Compile) map { jar =>
val scriptsDir = new java.io.File("res/scripts/")
scriptsDir.listFiles.toSeq.map { f =>
f -> ("bin/" + f.getName)
}
}
If you choose a file that's not created, it will be created for you, for example assets/ will make a new assets folder with the files. If you want files inside of this one using this approach you'll have to make a new Seq at least that's what I did. Here's my example
assets/
├── scripts
│   └── install_dependencies.sh
└── urbangrizzly.database
and the appropriate build.sbt section:
mappings in Universal <++= (packageBin in Compile) map { jar =>
val assetsDir = new java.io.File("assets/")
val scriptsDir = new java.io.File("assets/scripts")
assetsDir.listFiles.toSeq.map { files =>
files -> ("assets/" + files.getName)
} ++ scriptsDir.listFiles.toSeq.map { files =>
files -> ("assets/scripts/" + files.getName)
}
}
If you need more, just keep using the ++ operator to concatenate the lists

config directory when using sbt-native-packager

I'd like to ask about reasoning behind the fact, that the sbt-native-packager plugin creates a symlink /etc/ -> /usr/share//conf (instead of really putting files there and somehow specifying in the app where to look for them)?
In particular how does it influence update/uninstall+install process? Are the configs somehow preserved (for example for debian with java_server architecture setting)?
I'd like to ask about reasoning behind the fact, that the sbt-native-packager plugin creates a symlink /etc/ -> /usr/share//conf
Keeping everything in one place. You have your application directory which contains everything and then you just link from the OS-specific folders to the according directories in your application folder.
Are the configs somehow preserved
Yes indeed. You can try it out with a simple play application. Add this to your build.sbt
mappings in Universal <+= (packageBin in Compile, baseDirectory ) map { (_, base) =>
val conf = base / "conf" / "application.conf"
conf -> "conf/application.conf"
}
This will map your application.conf in the conf folder. When you build a debian package with
debian:packageBin
you can see in target/-/DEBIAN/conffiles an entry
/usr/share/<app-name>/conf/application.conf
An apt-get remove your-app won't remove this file, only a purge