How to access sbt managed resource in Scala program - scala

I create a managed resource through the following code in build.sbt:
resourceGenerators in Compile <+=
(resourceManaged in Compile, name, version) map { (dir, n, v) =>
val file = dir / "version"
val contents = Process("git rev-parse HEAD").lines.head
IO.write(file, contents)
Seq(file)
}
I can see it well created under target/scala-2.11/resource_managed/main
I extract its contents in my application as follows:
val version = getClass.getResource("version")
println(Source.fromURL(version).mkString)
I wonder if there's a Scala class for accessing resources, that is more preferable than Java's getClass.getResource

As I wrote in the comment, I think this is the correct approach, and there is no other equivalent method in Scala.
However, I would recommend you to look at the sbt-buildinfo plugin, which in your case would allow you to place the version string directly in a synthetic Scala object.

Related

How to access sbt's ivy resolver (DependencyResolver) to know which artifacts have been published already?

Here's what I got so far:
ivyModule provides me with an IvySbt#Module which in turn can be used to get a list of all artifacts that are published like so:
val module = ivyModule.value
val logger = streams.value.log
val desc = module.moduleDescriptor(logger)
val artifacts = desc.getAllArtifacts.toList
In addition, using publishTo I can obtain the patterns used to construct the URLs for publishing:
val pO: Option[Patterns] = publishTo.value.collect {
case r: URLRepository => r.patterns
}
pO.foreach(println)
which gives this output:
Patterns(ivyPatterns=ArrayBuffer(https://artifactory/repository/local-releases/[organisation]/[module]/[revision]/ivy-[revision].[ext]), artifactPatterns=List(https://artifactory/repository/local-releases/[organisation]/[module](_[scalaVersion])(_[sbtVersion])/[revision]/[artifact]-[revision](-[classifier]).[ext]), isMavenCompatible=true, descriptorOptional=false, skipConsistencyCheck=false)
Instead of using string replace on this, is there a way to access the Ivy resolver(s) used by sbt directly? The class DependencyResolver from Ivy provides both an exists method and a locate method which sound perfect. However, resolver, fullResolvers and projectResolver only returns an sbt flavored Resolver and I don't see how that can be converted to an ivy resolver.
The sbt version in question is 0.13.

How do I create an sbt task to generate code, then include these generated managed sources in my root project?

I would like to have a sbt task that I can run to generate some code. I don't want to generate this with each run, just manually run this task once in awhile. I created a skeleton project to explain (https://github.com/jinyk/sbtmanagedsrc).
build.sbt:
lazy val root = (project in file("."))
.settings(scalaVersion := "2.11.8")
.settings(gensomecode := genSomeCodeTask.value)
/////////////////////////////////////////////////////////////
// fugly way to get managed sources compiled along with main
.settings(unmanagedSourceDirectories in Compile += baseDirectory.value / "target/scala-2.11/src_managed/")
/////////////////////////////////////////////////////////////
lazy val gensomecode = taskKey[Seq[File]]("gen-code")
lazy val genSomeCodeTask = Def.task {
val file = (sourceManaged in Compile).value / "SomeGenCode.scala"
println("file: " + file)
IO.write(file, """object SomeGenCode {
| def doSomething() {
| println("Hi!")
| }
|}""".stripMargin)
Seq(file)
}
So with the build.sbt above I can run sbt gensomecode which creates
target/scala-2.11/src_managed/main/SomeGenCode.scala the default place that sbt puts "managed sources."
I would like to make this SomeGenCode available to the root project.
src/main/scala/Main.scala:
object Main extends App {
SomeGenCode.doSomething()
}
The only thing I can figure out to do is to include the default sourceManaged directory in the root project's unmanagedSourceDirectories (see build.sbt:line 4 aka the line below the fugly way... comment). This is ugly as hell and doesn't seem like it's how managed sources are supposed to be handled.
I'm probably not understanding something basic about sbt's managed sources concept or how to handle the situation of creating an sbt task to generate sources.
What am I missing?
There are three options that I am familiar with:
Generate into the unmanaged source directories.
Generate on every run, by adding sourceGenerators in Compile <+= gensomecode
Similar to (2), but use caching so it doesn't generate the file on every compile. Full example below.
In this example, the cache is based on the content of build.sbt, so whenever that file is changed it will regenerate the file.
lazy val root = (project in file("."))
.settings(scalaVersion := "2.11.8")
.settings(gensomecode <<= genSomeCodeTask)
sourceGenerators in Compile <+= genSomeCodeTask
lazy val gensomecode = taskKey[Seq[File]]("gen-code")
def generateFile(sourceManaged: java.io.File) = {
val file = sourceManaged / "main" / "SomeGenCode.scala"
println("file: " + file)
IO.write(file, """object SomeGenCode {
| def doSomething() {
| println("Hi!")
| }
|}""".stripMargin)
Set(file)
}
def genSomeCodeTask = (sourceManaged in Compile, streams).map {
(sourceManaged, streams) =>
val cachedCompile = FileFunction.cached(
streams.cacheDirectory / "mything",
inStyle = FilesInfo.lastModified,
outStyle = FilesInfo.exists) {
(in: Set[java.io.File]) =>
generateFile(sourceManaged)
}
cachedCompile(Set(file("build.sbt"))).toSeq
}
I hope I wasn't too late for the answer, but let's look at this section about Unmanaged vs Managed files
Classpaths, sources, and resources are separated into two main categories: unmanaged and managed. Unmanaged files are manually created files that are outside of the control of the build. They are the inputs to the build. Managed files are under the control of the build. These include generated sources and resources as well as resolved and retrieved dependencies and compiled classes.
It seems that the key difference between "Unmanaged vs Managed" is "Manually vs Automatically". Now, if we look at documentation for "generating files". We will notice immediately that it means "generating files automatically", since generating files will happen at sbt compile.
Compile / sourceGenerators += <task of type Seq[File]>.taskValue
It makes sense. Since anything that happened during sbt compile should be removed during sbt clean.
Now, from your code below, It seems that you were trying to generate an unmanaged source file (you were not using sourceGenerators, didn't you?), to the managed source file directory. The most obvious problem with this is, your source file will be removed every time you call sbt clean, so you have to run this task again to get this file back (worse, you have to run the task manually, opposed to having the sbt compile do it for you.), thus defeating your purpose of doing it manually once in a while.
val file = (sourceManaged in Compile).value / "SomeGenCode.scala"
To fix this, you have to manually generate files to unmanaged source, which is basically your source code directory (it depends -- mine is "/app"). Yet, you have to annotate it somehow that these files are generated by some means. My solution is something like:
val file = (scalaSource in Compile).value / "generated" / "SomeGenCode.scala"
Hope this help!

How can scala-js integrate with sbt-web?

I would like to use scala-js with sbt-web in such a way that it can be compiled to produce javascript assets that are added to the asset pipeline (e.g. gzip, digest). I am aware of lihaoyi's workbench project but I do not believe this affects the asset pipeline. How can these two projects be integrated as an sbt-web plugin?
Scala-js produces js files from Scala files. The sbt-web documentation calls this a Source file task.
The result would look something like this:
val compileWithScalaJs = taskKey[Seq[File]]("Compiles with Scala js")
compileWithScalaJs := {
// call the correct compilation function / task on the Scala.js project
// copy the resulting javascript files to webTarget.value / "scalajs-plugin"
// return a list of those files
}
sourceGenerators in Assets <+= compileWithScalaJs
Edit
To create a plugin involves a bit more work. Scala.js is not yet an AutoPlugin, so you might have some problems with dependencies.
The first part is that you add the Scala.js library as a dependency to your plugin. You can do that by using code like this:
libraryDependencies += Defaults.sbtPluginExtra(
"org.scala-lang.modules.scalajs" % "scalajs-sbt-plugin" % "0.5.5",
(sbtBinaryVersion in update).value,
(scalaBinaryVersion in update).value
)
Your plugin would look something like this:
object MyScalaJsPlugin extends AutoPlugin {
/*
Other settings like autoImport and requires (for the sbt web dependency),
see the link above for more information
*/
def projectSettings = scalaJSSettings ++ Seq(
// here you add the sourceGenerators in Assets implementation
// these settings are scoped to the project, which allows you access
// to directories in the project as well
)
}
Then in a project that uses this plugin you can do:
lazy val root = project.in( file(".") ).enablePlugins(MyScalaJsPlugin)
Have a look at sbt-play-scalajs. It is an additional sbt plugin that helps integration with Play / sbt-web.
Your build file will look something like this (copied from README):
import sbt.Project.projectToRef
lazy val jsProjects = Seq(js)
lazy val jvm = project.settings(
scalaJSProjects := jsProjects,
pipelineStages := Seq(scalaJSProd)).
enablePlugins(PlayScala).
aggregate(jsProjects.map(projectToRef): _*)
lazy val js = project.enablePlugins(ScalaJSPlugin, ScalaJSPlay)

SBT: Exclude class from Jar

I am converting a legacy jar project to SBT and for strange reasons that are not easily solved, this project comes with "javax/servlet/Servlet.class" inside it. So I need to somehow exclude this class from the jar file generated by package-bin. How do I accomplish this ?. Preferably I would like to exclude using a wildcard (i.e. javax.*).
The SBT assembly plugin does look like it has features that will do this, but I am worried that relying on sbt assembly means that my jar project will not work in a muliti module project (i.e. if I include it as a dependency in a war file then the war projects needs to be told to run assembly on the dependent jar project rather than package-bin - but I may be mistaken here).
Each task declares the other tasks and settings that it uses. You can use inspect to determine these inputs as described on Inspecting Settings and in a recent tutorial-style blog post by John Cheng.
In this case, the relevant task used by packageBin is mappings. The mappings task collects the files to be included in the jar and maps them to the path in the jar. Some background is explained on Mapping Files, but the result is that mappings produces a value of type Seq[(File, String)]. Here, the File is the input file providing the content and the String is the path in the jar.
So, to modify the mappings for the packageBin task, filter out the paths from the default mappings that you don't want to include:
mappings in (Compile,packageBin) ~= { (ms: Seq[(File, String)]) =>
ms filter { case (file, toPath) =>
toPath != "javax/servlet/Servlet.class"
}
}
mappings in (Compile,packageBin) selects the mappings for the main package task (as opposed to test sources or the packageSrc task).
x ~= f means "set x to the result of applying function f to the previous value of x". (See More About Settings for details.)
The filter drops all pairs where the path corresponds to the Servlet class.
I came up with this solution, it defines a new compile task which depends on the previous compile task (thus effectively allowing me to hook in right after the source is compiled and before it's packaged)
def mySettings = {
// add functionality to the standard compile task
inConfig(Compile)(Seq(compile in Compile <<= (target,streams,compile in Compile) map{
(targetDirectory, taskStream, analysis) =>
import taskStream.log
// this runs after compile but before package-bin
recursiveListFiles(targetDirectory, ".*javax.*".r) foreach {
file =>
log.warn("deleting matched resource: " + file.getAbsolutePath())
IO.delete(file)
}
analysis
})) ++
Seq(name := "MyProject", version := "1.0", exportJars := true)
}
def recursiveListFiles(f: File, r: Regex): Array[File] = {
val these = f.listFiles
val good = these.filter(f => r.findFirstIn(f.getName).isDefined)
good ++ these.filter(_.isDirectory).flatMap(recursiveListFiles(_, r))
}
Its a little bit more complicated than what I had hoped but it allows me to do all sorts of modifications prior to packaging (in this case searching the target folder deleting all class files that matches a regular expression). Also it accomplished my second goal of sticking with the default SBT lifecycle.

Making stand-alone jar with Simple Build Tool

Is there a way to tell sbt to package all needed libraries (scala-library.jar) into the main package, so it is stand-alone? (static?)
Edit 2011:
Since then, retronym (which posted an answer in this page back in 2010), made this sbt-plugin "sbt-onejar", now in its new address on GitHub, with docs updated for SBT 0.12.
Packages your project using One-JARâ„¢
onejar-sbt is a simple-build-tool plugin for building a single executable JAR containing all your code and dependencies as nested JARs.
Currently One-JAR version 0.9.7 is used. This is included with the plugin, and need not be separately downloaded.
Original answer:
Directly, this is not possible without extending sbt (a custom action after the model of the "package" sbt action).
GitHub mentions an assembly task, custom made for jetty deployment. You could adapt it for your need though.
The code is pretty generic (from this post, and user Rio):
project / build / AssemblyProject.scala
import sbt._
trait AssemblyProject extends BasicScalaProject
{
def assemblyExclude(base: PathFinder) = base / "META-INF" ** "*"
def assemblyOutputPath = outputPath / assemblyJarName
def assemblyJarName = artifactID + "-assembly-" + version + ".jar"
def assemblyTemporaryPath = outputPath / "assembly-libs"
def assemblyClasspath = runClasspath
def assemblyExtraJars = mainDependencies.scalaJars
def assemblyPaths(tempDir: Path, classpath: PathFinder, extraJars: PathFinder, exclude: PathFinder => PathFinder) =
{
val (libs, directories) = classpath.get.toList.partition(ClasspathUtilities.isArchive)
for(jar <- extraJars.get ++ libs) FileUtilities.unzip(jar, tempDir, log).left.foreach(error)
val base = (Path.lazyPathFinder(tempDir :: directories) ##)
(descendents(base, "*") --- exclude(base)).get
}
lazy val assembly = assemblyTask(assemblyTemporaryPath, assemblyClasspath, assemblyExtraJars, assemblyExclude) dependsOn(compile)
def assemblyTask(tempDir: Path, classpath: PathFinder, extraJars: PathFinder, exclude: PathFinder => PathFinder) =
packageTask(Path.lazyPathFinder(assemblyPaths(tempDir, classpath, extraJars, exclude)), assemblyOutputPath, packageOptions)
}
It takes a bit of work, but you can also use Proguard from within SBT to create a standalone JAR.
I did this recently in the SBT build for Scalala.
Working off of what #retronym offered above, I built a simple example that builds a stand alone jar which includes the Scala libraries (i.e. scala-library.jar) using Proguard with sbt. Thanks, retronym.
The simplest example using sbt-assembly
Create directory project in your home project dir with file assembly.sbt including
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.11.2")
In file build.sbt
import AssemblyKeys._ // put this at the top of the file
assemblySettings
jarName += "Mmyjarnameall.jar"
libraryDependencies ++= Seq( "exmpleofmydependency % "mydep" % "0.1" )
mergeStrategy in assembly <<= (mergeStrategy in assembly) { (old) =>
{
case s if s.endsWith(".class") => MergeStrategy.last
case s if s.endsWith("pom.xml") => MergeStrategy.last
case s if s.endsWith("pom.properties") => MergeStrategy.last
case x => old(x)
}
}
The simplest method is just to create the jar from the command line. If you don't know how to do this I would highly recommend that you do so. Automation is useful, but its much better if you know what the automation is doing.
The easiest way to automate the creation of a runnable jar is to use a bash script or batch script in windows.
The simplest way in sbt is just to add the Scala libraries you need to the resource directories:
unmanagedResourceDirectories in Compile := Seq(file("/sdat/bins/ScalaCurrent/lib/scalaClasses"))
So in my environment ScalaCurrent is a link to the current Scala library. 2.11.4 at the time of writing. The key point is that I extract the Scala library but place it inside a ScalaClassses directory. Each extracted library needs to go into its top level directory.