I would like to customize the .desktop file created by the javapackager as part of the JDKPackager plugin of sbt-native-packager. It obviously uses a template:
[info] Using default package resource [Menu shortcut descriptor]
(add package/linux/Foo.desktop to the class path to customize)
In particular, I want to add the StartupWMClass entry that will be used by Gnome to unify all the windows opened by my application.
The javapackager refers to the target directory of the plugin, i.e. target/jdkpackager. This is created for example when the javafx-ant build-file is written. So we can piggyback here:
// rewrite the task so that after the ant build is created,
// we add package/linux/MyApp.desktop
writeAntBuild in JDKPackager := {
val res = (writeAntBuild in JDKPackager).value
val main = (mainClass in JDKPackager).value
.getOrElse(sys.error("No main class specified"))
val tgt = (target in JDKPackager).value
val n = (name in JDKPackager).value
val wm = main.replace('.', '-')
val desktop =
s"""[Desktop Entry]
|Name=APPLICATION_NAME
|Comment=APPLICATION_SUMMARY
|Exec=/opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME
|Icon=/opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.png
|Terminal=false
|Type=Application
|Categories=DEPLOY_BUNDLE_CATEGORY
|DESKTOP_MIMES
|StartupWMClass=$wm
|""".stripMargin
IO.write(tgt / "package" / "linux" / s"$n.desktop", desktop)
res
}
As long as there is a corresponding setting in the Ant task XML, there is an alternative solution: just rewriting the XML generated by the plugin, via antBuildDefn, will suffice.
Here's an example of specifying a menu category for the .desktop file, via adding the category attribute :
antBuildDefn in JDKPackager := {
val origTask = (antBuildDefn in JDKPackager).value
val InfoLabel = "info"
val KeyRegex = s"$InfoLabel\\.(.+)".r
import scala.xml._
import scala.xml.transform._
val infoRewrite = new RewriteRule {
override def transform(n: Node) = n match {
case e: Elem if e.prefix == "fx" && e.label == InfoLabel =>
e % Attribute("", "category", "Office", Null)
case other => other
}
}
new RuleTransformer(infoRewrite)(origTask)
}
Related
I have a directory structure like this:
a
/one
hey.class
hey.tasty
you.class
you.tasty
/two
foo.class
foo.tasty
/three
bar.class
bar.tasty
b
/one
/two
/three
I need a way to copy all the .tasty files from their respective places in /a to their corresponding places in /b in a sbt task.
You can create the sbt task and pass the original and destination folders in the following manner:
val copyTasties = inputKey[Unit]("Copy .tasty files")
copyTasties := {
val userInput = Def.spaceDelimited().parsed
if (userInput.size != 2) {
throw new IllegalArgumentException("Original and target directories should be define!")
}
val from = Paths.get(userInput.head)
val to = Paths.get(userInput.last)
Files
.walk(from)
.filter(Files.isRegularFile(_))
.filter(path => path.toString.endsWith("tasty"))
.forEach { original =>
val relative = from.relativize(original)
val destination = to.resolve(relative)
IO.copyFile(original.toFile, destination.toFile)
}
}
Then you can invoke it like this:
copyTasties C:\\Dev\\sandbox\\a C:\\Dev\\sandbox\\b
If the original and destination are stable(for example they are directories inside the project) you can rewrite the task:
import java.nio.file.{Files, Paths}
import sbt._
val copyTastiesHardcoded = taskKey[Unit]("Copy .tasty files")
copyTastiesHardcoded := {
val baseDir = baseDirectory.value.toPath
val from = baseDir.resolve("a")
val to = baseDir.resolve("b")
Files
.walk(from)
.filter(Files.isRegularFile(_))
.filter(path => path.toString.endsWith("tasty"))
.forEach { original =>
val relative = from.relativize(original)
val destination = to.resolve(relative)
IO.copyFile(original.toFile, destination.toFile)
}
}
and invoke it without arguments
copyTastiesHardcoded
There are some useful utility methods in sbt that will help you find files and copy them around. Unfortunately the use a wild mixture of the traditional java.io.File and the more modern java.nio.file.Path, so you need to do conversions between them:
val copyFiles = taskKey[Unit]("copy files")
copyFiles := {
val inputDir = baseDirectory.value.toPath / "a"
val files: Seq[(Path, FileAttributes)] =
FileTreeView.default.list(inputDir.toGlob / RecursiveGlob / "*.tasty")
val outputDir = baseDirectory.value.toPath / "b"
IO.copy {
files.map { case (p, _) =>
p.toFile -> outputDir.resolve(inputDir.relativize(p)).toFile
}
}
}
Note that I'm using a glob here to find the files:
https://www.scala-sbt.org/1.x/docs/Globs.html
I have a file src/main/scala/foo.scala which needs to be inside package bar. Ideally the file should be inside src/main/scala/bar/foo.scala.
// src/main/scala/foo.scala
package bar
// ...
How can I auto-fix this issue throughout my project such that the folder structure matches the package structure?
Is there any SBT plugin etc that can help me fix this issue?
As far as I am aware there are not such tools, though AFAIR IntelliJ can warn about package-directory mismatch.
Best I can think if is custom scalafix (https://scalacenter.github.io/scalafix/) rule - scalafix/scalameta would be used to check file's actual package, translate it to an expected directory and if they differ, move file.
I suggest scalafix/scalameta because there are corner cases like:
you are allowed to write your packages like:
package a
package b
package c
and it almost like package a.b.c except that it automatically imports everything from a and b
you can have package object in your file and then if you have
package a.b
package object c
this file should be in a/b/c directory
so I would prefer to check if file didn't fall under any of those using some existing tooling.
If you are certain that you don't have such cases (I wouldn't without checking) you could:
match the first line with regexp (^package (.*))
translate a.b.c into a/b/c (matched.split('.').map(_.trim).mkString(File.separator))
compare generated location to an actual location ( I suggest resolving absolute file locations)
move file if necessary
If there is a possibility of having more complex case than that, I could replace first step by querying scalafix/scalameta utilities.
Here is an sbt plugin providing packageStructureToDirectoryStructure task that reads package statements from source files, creates corresponding directories, and then moves files to them
import sbt._
import sbt.Keys._
import better.files._
object PackagesToDirectories extends AutoPlugin {
object autoImport {
val packageStructureToDirectoryStructure = taskKey[Unit]("Make directory structure match package structure")
}
import autoImport._
override def trigger = allRequirements
override lazy val projectSettings = Seq(
packageStructureToDirectoryStructure := {
val log = streams.value.log
log.info(s"Refactoring directory structure to match package structure...")
val sourceFiles = (Compile / sources).value
val sourceBase = (Compile / scalaSource).value
def packageStructure(lines: Traversable[String]): String = {
val packageObjectRegex = """package object\s(.+)\s\{""".r
val packageNestingRegex = """package\s(.+)\s\{""".r
val packageRegex = """package\s(.+)""".r
lines
.collect {
case packageObjectRegex(name) => name
case packageNestingRegex(name) => name
case packageRegex(name) => name
}
.flatMap(_.split('.'))
.mkString("/")
}
sourceFiles.foreach { sourceFile =>
val packagePath = packageStructure(sourceFile.toScala.lines)
val destination = file"$sourceBase/$packagePath"
destination.createDirectoryIfNotExists(createParents = true)
val result = sourceFile.toScala.moveToDirectory(destination)
log.info(s"$sourceFile moved to $result")
}
}
)
}
WARNING: Make sure to backup the project before running it.
I am trying to write an SBT task that may be used like this:
> deploy key1=value1 key2=value2 ...
and does the following:
reads a default properties file
adds the parsed keys and values to the properties object
writes the new properties object to resources/config.properties
packages a fat .jar withsbt-assembly
writes the default properties to resources/config.properties
I have been trying to achieve it with Def.sequential, but I cant seem to find a way to use it withinputKey. Mybuild.sbtlooks like this:
val config = inputKey[Unit] (
"Set configuration options before deployment.")
val deploy = inputKey[Unit](
"assemble fat .jar with configuration options")
val defaultProperties = settingKey[Properties](
"default application properties.")
val propertiesPath = settingKey[File]("path to config.properties")
val writeDefaultProperties = taskKey[Unit]("write default properties file.")
val parser = (((' ' ~> StringBasic) <~ '=') ~ StringBasic).+
lazy val root = (project in file("."))
.settings(
propertiesPath := {
val base = (resourceDirectory in Compile).value
base / "config.properties"
},
defaultProperties := {
val path = propertiesPath.value
val defaultConfig = new Properties
IO.load(defaultConfig, path)
defaultConfig
},
config := {
val path = propertiesPath.value
val defaultConfig = defaultProperties.value
val options = parser.parsed
val deployConfig = new Properties
deployConfig.putAll(defaultConfig)
options.foreach(option =>
deployConfig
.setProperty(option._1, option._2))
IO.write(deployConfig, "", path)
},
writeDefaultProperties := {
val default = defaultProperties.value
val path = propertiesPath.value
IO.write(default, "", path)
},
deploy := Def.sequential(
config.parsed, // does not compile
assembly,
writeDefaultProperties),
...)
Can I makeDef.sequential work with input keys, or do I need to do something more involved?
See Defining a sequential task with Def.sequential. It uses scalastyle as an example:
(scalastyle in Compile).toTask("")
How do I get SBT staging directory at build time?
I want to do a tricky clone of a remote repo, and the stagingDirectory of SBT seems to be a nice fit.
How do I get the directory inside "Build.scala" ?
SBT source code:
http://www.scala-sbt.org/0.13.1/sxr/sbt/BuildPaths.scala.html#sbt.BuildPaths.stagingDirectory
=======
Underlying problem NOT directly relevant to the question. I wanted to use a subdirectory of a git dependency in SBT. SBT doesn't provide this out of the box so I wrote a simple wrapper:
object Git {
def clone(cloneFrom: String, branch: String, subdirectory: String) = {
val uniqueHash = Hash.halfHashString(cloneFrom + branch)
val cloneTo = file(sys.props("user.home")) / ".sbt" / "staging" / uniqueHash
val clonedDir = creates(cloneTo) {
Resolvers.run("git", "clone", cloneFrom, cloneTo.absolutePath)
Resolvers.run(Some(cloneTo), "git", "checkout", "-q", branch)
}
clonedDir / subdirectory
}
}
usage:
lazy val myDependency = Git.clone(cloneFrom = "git://...someproject.git", branch = "v2.4", subdirectory = "someModule")
Looking at the API from your link, there are two methods you can use getGlobalBase and getStagingDirectory, both take state.
import sbt._
import Keys._
import sbt.BuildPaths._
object MyBuild extends Build {
val outputStaging = taskKey[Unit]("Outputs staging")
lazy val root = project.in(file(".")).settings(
outputStaging := {
val s = state.value
println(getStagingDirectory(s, getGlobalBase(s)))
}
)
}
Edit
After your last comment, I think you're looking for a custom resolver. The custom resolver has an access to a ResolveInfo object, which has a property called staging.
For example this is how you could achieve what you're looking for (actually without accessing staging directly):
object MyBuild extends Build {
lazy val root = project.in(file(".")).dependsOn(RootProject(uri("dir+git://github.com/lpiepiora/test-repo.git#branch=master&dir=subdir")))
override def buildLoaders = BuildLoader.resolve(myCustomGitResolver) +: super.buildLoaders
def myCustomGitResolver(info: BuildLoader.ResolveInfo): Option[() => File] =
if(info.uri.getScheme != "dir+git") None
else {
import RichURI.fromURI
val uri = info.uri
val (branch, directory) = parseOutBranchNameAndDir(uri.getFragment)
val gitResolveInfo = new BuildLoader.ResolveInfo(
uri.copy(scheme = "git", fragment = branch), info.staging, info.config, info.state
)
println(uri.copy(scheme = "git", fragment = branch))
Resolvers.git(gitResolveInfo).map(fn => () => fn() / directory)
}
// just an ugly way to get the branch and the folder
// you may want something more sophisticated
private def parseOutBranchNameAndDir(fragment: String): (String, String) = {
val Array(branch, dir) = fragment.split('&')
(branch.split('=')(1), dir.split('=')(1))
}
}
The idea is that we delegate to the predefined git resolver, and we let it do it's work, after it's done, we'll return a subdirectory: fn() / directory.
This is an example and of course you could stick to your logic of getting the repository. The staging directory will be available to you in the resolver method.
I am trying to port my java code to pure scala, so would appreciate any help in this regard.
The code below works by first translating my business logic into java code. Here I'm using freemarker template to do template based code generation. After file creation I then use the java comiler to compile the code and create a jar file, which is persisted in a temporary directory
I am currently using the javax.tools.* package that provides runtime compilation. What is the Scala equiavalent to that approach? I'd like to generate pure Scala code using freemarker templates and then run Scala compilation to create a jar file.
Below is sample Java code I am using to make this happen.
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(javaFileObjects);
StringBuilder builder = new StringBuilder();
builder.append(service.getConfig().getProp("coreLib"));
builder.append(";" +result.getCodeContext().getOmClasspath());
builder.append(";" +jarBuilder.toString());
builder.append(";" +service.getConfig().getProp("tempCodeGen"));
String[] compileOptions = new String[]{"-d", result.getCodeContext().getOmClasspath(),"-cp",builder.toString()} ;
Iterable<String> compilationOptionss = Arrays.asList(compileOptions);
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
CompilationTask compilerTask = compiler.getTask(null, stdFileManager, diagnostics, compilationOptionss, null, compilationUnits) ;
boolean status = compilerTask.call();
Here's some methods from my own code to compile a project and package it into a jar. Its very far from polished, or properly commented, but hopefully it will indicate where you need to start. I don't think you need to use String Builder as this is not performance critical:
def buildAll(name: String, projDir: String, mainClass: String = ""): Unit =
{
import scala.tools.nsc.{Settings,Global}
val relSrc : List[String] = List()
val maniVersion = "None"
def targDir: String = projDir + "/targ"
def srcDir: String = projDir + "/src"
def srcDirs: List[String] = srcDir :: relSrc
import java.io._
val sings = new scala.tools.nsc.Settings
new File(targDir).mkdir
sings.outputDirs.setSingleOutput(targDir.toString)
val comp = new Global(sings)
val crun: comp.Run = new comp.Run
def getList(fName: String): List[String] =
{
println("starting getList " + fName)
val file = new File(fName)
if (file.isDirectory) file.listFiles.toList.flatMap(i => getList(fName + "/" + i.getName))
else List(fName)
}
crun.compile(srcDirs.flatMap(i => getList(i)))
import sys.process._
("cp -r /sdat/projects/ScalaLibs/scala " + targDir + "/scala").!
import java.util.jar._
val manif = new Manifest
val mf = manif.getMainAttributes
mf.put(Attributes.Name.MANIFEST_VERSION, maniVersion)
if (mainClass != "") mf.put(Attributes.Name.MAIN_CLASS, mainClass)
val jarName = name + ".jar"
val jarOut: JarOutputStream = new JarOutputStream(new FileOutputStream(projDir + "/" + jarName), manif)
AddAllToJar(targDir, jarOut)
jarOut.close
}
def addToJar(jarOut: JarOutputStream, file: File, reldir: String): Unit =
{
val fName = reldir + file.getName
val fNameMod = if (file.isDirectory) fName + "/" else fName
val entry = new JarEntry(fNameMod)
entry.setTime(file.lastModified)
jarOut.putNextEntry(entry)
if (file.isDirectory)
{
jarOut.closeEntry
file.listFiles.foreach(i => addToJar(jarOut, i, fName + "/"))
}
else
{
var buf = new Array[Byte](1024)
val in = new FileInputStream(file)
Stream.continually(in.read(buf)).takeWhile(_ != -1).foreach(jarOut.write(buf, 0, _))
in.close
jarOut.closeEntry()
}
}
def AddAllToJar(targDir: String, jarOut: JarOutputStream): Unit =
new java.io.File(targDir).listFiles.foreach(i => addToJar(jarOut, i, ""))
You need to add the Scala Compiler to the build path. The Scala compiler takes a list of sourcefiles and produces the compiled class files in the directory set in the output directory. Getting to grips with the full capabilities of the compiler is a major task though.
And when using scala you don't need to use any free-marker similar tools. Since scala version 2.10 there is string interpolation feature.
val traitName = "MyTrait"
val packageName = "my.pack"
val typeParams = List("A", "B", "C")
s"""
|package ${packageName}
|
|trait ${traitName}[${typeParams.mkString(",")}] {
| ${typeParams.map(t => s"val ${t.toLowerCase()}: ${t}")}
|}
|
""".stripMargin
will yield:
package my.pack
trait MyTrait[A,B,C] {
List(val a: A, val b: B, val c: C)
}
No dependencies needed :)
If you would like to compile generated code in runtime, the simplest solution is twitter eval utility
See the suggestion here - Generating a class from string and instantiating it in Scala 2.10
Or the Eval class from twitter's util library -
https://github.com/twitter/util/blob/master/util-eval/src/main/scala/com/twitter/util/Eval.scala