Adding resource to project-specific SBT autoplugins - scala

SBT lets you define autoplugins specific to your project by putting them in ./project.
I'm trying to add resources to one such autoplugin - by which I mean something that it could access through a call to getClass.getResourceAsStream.
I have, however, not been able to work out how to do that, or even if it was possible. There's no documentation that I could find on the subject, and the obvious (simply putting resources in ./project with the plugin) fails.
Is what I'm trying to achieve possible?

Yes, you need to place your resource in ./project/src/main/resources/
For a quick demonstration that this works, assume the file name is test.txt, put the following in your build.sbt:
lazy val hello = taskKey[Unit]("prints the content of test.txt")
hello := println(IO.readStream(getClass.getResourceAsStream("test.txt")))

Related

sbt how to access base directory of project in scala code

I have been given a code which was created by a vendor and seems like their engineer did a lot of hardcoding in the unit tests.
I have a unit test for a function which outputs the full absolute path of report generated as part of the code as a string.
currently the unit test/assertion that fails looks like
val reportPath = obj.getReportPath()
assert(reportPath.equals("file:/Users/khalid.mahmood/ReportingModule/target/report.csv")
where ReportingModule is the name of the project.
The code logic is fine as for me the value of the reportPath variable comes out to be:
file:/Users/vikas.saxena/coding_dir/ReportingModule/target/report.csv
Since I have the project cloned in a subdirectory called coding_dir in my home directory so the logic looks fine to me.
I want to modify the assertion to ensure that the code pics up the base directory of project by itself and on googling I found that sbt has base as the equivalent of project.baseDir (from maven) from this link
However the following code changes haven't worked out for me
assert(reportPath.equals(s"""$base""" + "/target/report.csv")
Can I get some pointers on how to get this right.
If you're using ScalaTest, you can the ConfigMap to do it.
First you need to tell the ScalaTest Runner to add the path to the ConfigMap. This can be done in your .sbt file like so:
Test / testOptions += Tests.Argument(
TestFrameworks.ScalaTest, s"-DmyParameter=${baseDirectory.value}")
(note that it doesn't have to be baseDirectory.value, many other sbt settings will work. I would suggest target.value for your specific use case).
In the test itself, you then need to access the value from the ConfigMap. The easiest way to do this is to use a Fixture Suite (such as FixtureAnyFunSuite) and mix in the ConfigMapFixture trait:
import org.scalatest.funsuite.FixtureAnyFunSuite
import org.scalatest.fixture.ConfigMapFixture
class ExampleTest extends FixtureAnyFunSuite with ConfigMapFixture {
test("example") { configMap =>
val myParameter = configMap.getRequired[String]("myParameter")
// actual test logic goes here
succeed
}
}
There are of course other ways to solve the problem. For instance, you can also simply get the current working directory (cwd) and work from there. However the downside to that is that in multi-module builds, the cwd will be different depending on whether the Test / fork setting in sbt is true or false. So to make your code robust against these sorts of eventualities, I recommend sticking with the ConfigMap way.

How to avoid recompiling on changes in *.scala.html files

I am using play framework v2.3. The problem I am facing is that any change in html and refreshing browser causes recompilation of the complete code. Can I avoid this?
Twirl templates are compiled, as stated by the docs:
Templates are compiled as standard Scala functions, following a simple naming convention. If you create a views/Application/index.scala.html template file, it will generate a views.html.Application.index class that has an apply() method.
There is no way to disable this behavior because it works this way by design. My suggestion here is use ~ (tilde) before SBT commands so things will happen as you save the file, per instance:
sbt ~run
This will recompile the changed file (and possible others), every time you change and save it. Also, sbt has some options that can possibly help you here: withNameHashing.
See sbt docs to understand how it works. To enable it, add the following line to your build.sbt file:
incOptions := incOptions.value.withNameHashing(nameHashing = true)

SBT: How to define an includeFilter that includes all files in a certain path?

I'm trying to figure out how to write an input filter that matches any file with a particular extension in a specified sub-directory. I cannot seem to get it to work.
More specifically, I want to include modules/*.styl
I'm having a heck of a time getting that pattern to work in my build.sbt file I'm trying to do something like this:
includeFilter in (Assets, StylusKeys.stylus) := "modules" * "*.styl"
I haven't ben able to come up with anything that even will allow SBT to start up. Anyone have a clue?
I'm using SBT 0.13.5.
First the includeFilter key has type FileFilter. Look this link to see how it is defined.
I think there is no easy way to do what you want since implicits from String to FileFilter produce only NameFilter. Those only test the file name, not the path as you want to.
You can define your own FileFilter based on the examples provided. The idea: you create one filter for the parent directory and then combine it with a PatternFilter for the file name.
includeFilter in (Assets, StylusKeys.stylus) := new SimpleFileFilter(file => file.getParent == "modules") && "*.styl"
Of course you will perhaps have to modify the function passed to SimpleFileFilter according to your needs.

How to share code between project and build definition project in SBT

If I have written some source code in my build definition project (in /project/src/main/scala) in SBT. Now I want to use these classes also in the project I am building. Is there a best practice? Currently I have created a custom Task that copies the .scala files over.
Those seem like unnecessarily indirect mechanisms.
unmanagedSourceDirectories in Compile += baseDirectory.value / "project/src/main"
Sharing sourceDirectories as in extempore's answer is the simplest way to go about it, but unfortunately it won't work well with IntelliJ because the project model doesn't allow sharing source roots between multiple modules.
Seth Tisue's approach will work, but requires rebuilding to update sources.
To actually share the sources and have IntelliJ pick up on it directly, you can define a module within the build.
The following approach seems to only work in sbt 1.0+
Create a file project/metabuild.sbt:
val buildShared = project
val buildRoot = (project in file("."))
.dependsOn(buildShared)
and in your build.sbt:
val buildShared = ProjectRef(file("project"), "buildShared")
val root = (project in file("."))
.dependsOn(buildShared)
Then put your shared code in project/buildShared/src/main/scala/ and refresh. Your project will look something like this in IntelliJ:
Full example project: https://github.com/jastice/shared-build-sources
Can you make the following work? Put the source code for the classes in question should be part of your project, not part of your build definition; the “task which serializes a graph of Scala objects using Kryo and writes them as files into the classpath of the project” part sounds like a perfect job for resourceGenerators (see http://www.scala-sbt.org/0.13.2/docs/Howto/generatefiles.html). Then the only remaining problem is how to reference the compiled classes from your resource generator. I'm not familiar with Kryo. In order to use it, do you need to have the compiled classes on the classpath at the time your generator is compiled, or do they just need to be on the classpath on runtime? If the latter is sufficient, that's easier. You can get a classloader from the testLoader in Test key, load the class and instantiate some objects via reflection, and then call Kryo.
If you really need the compiled classes to be on the classpath when your resource generator is compiled, then you have a chicken and egg problem where the build can't be compiled until the project has been compiled, but of course the project can't be compiled before the build definition has been compiled, either. In that case it seems to me you have no choices other than:
1) the workaround you're already doing ("best practice" in this case would consist of using sourceGenerators to copy the sources out of your build definition and into target/src_managed)
2) put the classes in question in a separate project and depend on it from both your build and your project. this is the cleanest solution overall, but you might consider it too heavyweight.
Hope this helps. Interested in seeing others' opinions on this, too.

Define custom test configurations in sbt

I need to define a custom test configuration in sbt which runs test, but with some extra settings. I've been looking around trying to figure out how to do this, but I can't seem to get it right.
What I would like to do is something like this: > test which would run the normal test task and > pipelinetest which would exactly the same as test, only with (javaOptions += "-Dpipeline.run=run".
I've figured out how the set the javaOptions for test, like this:
javaOptions in test += "-Dpipeline.run=run" so what I would like to be able to do is this: javaOptions in pipelinetest += "-Dpipeline.run=run"
How would I define pipelinetest to achieve this goal? Do this need to be a new task? Or does would this be a setting in test. I'm very new to sbt and quite confused over this at the moment, and reading the documentation didn't help, so any help would be greatly appreciated.
I have only a partial answer, but I thought this might be useful info. I was just trying to do something similar for the sbt build in Spark -- I wanted to have a way to run tests with a debugger. Mark Harrah's comment pointed me in the right direction. The change I made was:
lazy val TestDebug = config("testDebug") extend(Test)
...
baseProject
.configs(TestDebug)
.settings(inConfig(TestDebug)(Defaults.testTasks): _*)
.settings(Seq(
javaOptions in TestDebug ++= "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005"
.split(" ").toSeq))
This left my usual invocations of test, testOnly, etc. alone, but now I could also run testDebug:testOnly ..., which would use the extra options defined above. (it probably also created testDebug:test, etc. with those extra options, which aren't useful, but oh well.)
I didn't really understand why, but one important part for me to get this to work was to use inConfig(TestDebug)(Defaults.testTasks), instead of inConfig(TestDebug)(Defaults.testSettings).
In my case, I ran into trouble figuring out how to (a) get it to work for a multi-project build and (b) our build is even weirder b/c its based on a POM file, which makes the project definitions different than every example.
As usual, my issue with sbt is that I find info which seems related, but my build has some unusual aspects which makes me unable to completely cargo-cult the answer; and though it seems like I need trivial modifications, without a thorough understanding, its hard to modify the examples.