token substitution (resource filtering) with sbt - scala

I am looking in SBT for the same functionality Maven has for resource filtering, but does not come out of the box.
After searching quite a bit both here and elsewhere, i found two plugins close in functionality, but neither one really does it. For example, xsbt-filter does not filter tokens such as ${baseDirectory}, while sbt-editsource does not work in conjunction with unit nor integration testing (see issue 9)
So, i tried to code that myself by modifying one of those two plugins, but here is the question i was not able to figure out in SBT, being new to it (and not a Scala pro):
How do you reuse SBT build settings for doing token resolution?
Those settings are in object sbt.Keys. So for example, baseDirectory is:
val baseDirectory = SettingKey[File]("base-directory")
There are dozens of them (perhaps hundreds) that can be used for resolving tokens in a resource file.
In the end, for doing token resolution within the plugin code, you need a map Map[String, String] of all build settings present in Keys, i.e. key is "baseDirectory" and the value is whatever at compile time that value is.
I assume one way would be to use reflection, but before going down that path, i thought i asked if there was a more standard way of doing this from an sbt plugin, which seems fairly basic.
All plugins i have seen so far, are copying and pasting each Keys setting (transforming the variable name into a string for the key) into their plugin code.
For those unfamiliar with resource filtering, it means that the build tool should be able to resolve all tokens present in a resource file and at compile time place the resource file under target after substituting the token key with its value (example: resource file is "/User/me/Documents/myproject/src/test/resources/myfile.txt", in which text has a string ${target}, where "target" is the key and "/User/me/Documents/myproject/target" happens to be its value in that specific build).

In the end, for doing token resolution within the plugin code, you need a map Map[String, String] of all build settings present in Keys, i.e. key is "baseDirectory" and the value is whatever at compile time that value is.
sbt internally holds on to these information, so you can query them.
For example, there's a command in sbt called inspect that tells you the current value for the setting, and all other dependency that it uses. A while back I wrote a plugin that calls it recursively and prints it out in ASCII art tree, called sbt-inspectr: https://github.com/eed3si9n/sbt-inspectr.
There's also Project.runTask(...), which you might need.

Related

How can I change the compiler flags of an sbt project without causing recompilation?

It often comes up during testing and debugging a Scala project built with sbt that I need to pass some extra compiler flags for a particular file. For example -Xlog-implicits to debug implicit resolution problems. However, changing scalacOptions either in build.sbt or the console invalidates the cache and causes the whole project / test suite to be recompiled. In addition to being annoying to wait so long, this also means that a lot of noise from irrelevant files is printed. Instead it would be better if I could compile a specific file with some extra flags from the sbt console, but I did not find a way to do this.
Problem
The reason why changing the scalac options triggers recompilation is because Zinc, Scala's incremental compiler, cannot possibly now which compiler flags affect the semantics of the incremental compilation, so it's pessimistic about it. I believe this can be improved, and some whitelisted flags can be supported, so that next time people like you don't have to ask about it.
Nevertheless, there's a solution to this problem and it's more generic than it looks like at first sight.
Solution
You can create a subproject in your sbt build which is a copy of the project you want to "log implicits" in, but with -Xlog-implicits enabled by default.
// Let's say foo is your project definition
lazy val foo = project.settings(???)
// You define the copy of your project like this
lazy val foo-implicits = foo
.copy(id = "foo-implicits")
.settings(
target := baseDirectory.value./("another-target"),
scalacOptions += "-Xlog-implicits"
)
Note the following properties of this code snippet:
We redefine the project ID because when we reuse a project definition the ID is still the same as the previous one (foo in this case). Sbt fails when there are projects that share the same id.
We redefine the target directory because we want to avoid recompilation. If we keep the same as before, then recompiling foo-implicits will delete the compilation products of the previous compilation (and viceversa). That's exactly what we want to avoid.
We add -Xlog-implicits to the Scalac options as you request in this question. In a generic solution, this piece of code should go away.
When is this useful?
This is useful not only for your use case, but when you want to have different modules of the same project in different Scala versions. The following has two benefits:
You don't use cross-compilation in sbt ++, which is known to have some memory issues.
You can add new library dependencies that only exist for a concrete Scala version.
It has more applications, but I hope this addresses your question.

Turn off Scala lint warnings at a less-than-global scope?

I'd like to be able to turn off a particular compiler warning, but only for a single file in my project. Is this possible?
The context is that I have a single source file that makes calls to an external macro library that produces adapted-arg warnings. I found that I can eliminate these warnings by changing my build.sbt file:
scalacOptions ++= Seq("-Xlint:-adapted-args,_" /*, ... */)
However, this turns off the warning globally, and I only want it off for the single file that raises them.
I haven't had any luck searching for any of the following possible solutions I thought might exist:
Specifying separate compilation options for different files in my project in my build.sbt file
Providing some pragma-like comment in my source file to change the warnings generated by the compiler, similar to the special scalastyle:on/off comments recognized by Scalastyle
Some annotation for smaller regions of code, like #unchecked
So, is there any way to have different linting options in effect for different files, or even for limited regions of code?
The expectation was that folks would write a custom Reporter that would filter out undesirable warnings.
It's easy to write one that filters by file name, perhaps given a whitelist.
The error/warn API supplies the textual Position. It would also be easy to parse the position.lineContent for a magic comment token like IGNORE or SUPPRESS, which is not as convenient as a SuppressWarnings annotation but is easy to implement.
The compiler asks the reporter if there were errors.
The custom reporter is specified with -Xreporter myclass, or it wouldn't surprise me if someone has written an sbt plugin.

sbt-assembly: prefix extracted files from some jars

In JOGL, there are lots of native jars for different OS x arch combinations. JOGL has several of its own mechanisms to load the right ones if you aren't using java.library.path, and supports a kind of "fat jar" layout.
In a fat jar layout, any native libraries need to be in a subdirectory ./natives/os.and.arch/. However, since the native jars themselves don't have any internal layout, similarly named so/dylib/dll files collide the flat hierarchy in the final jar.
From what I can tell, I don't think I want to de-duplicate with any of the given MergeStrategy because it's only invoked if there is a collision. The layout is mandatory per JOGL's native library loaders - I want to invoke it every time. Is there a mechanism that can allow me to map certain jar -> prefix/with/path in sbt-assembly?
Example
jogl-all-2.1.3-natives-android-armv6.jar is pulled in through a dependency.
$ jar -tf jogl-all-2.1.3-natives-linux-amd64.jar
META-INF/MANIFEST.MF
libjogl_mobile.so
libnewt.so
I'd like this to go here in the final jar:
./natives/
./natives/linux.and.amd64/
./natives/linux.and.amd64/libnewt.so
./natives/linux.and.amd64/libjogl_mobile.so
From what I can tell, I don't think I want to de-duplicate with any of the given MergeStrategy because it's only invoked if there is a collision. The layout is mandatory per JOGL's native library loaders - I want to invoke it every time.
All merge strategies are invoked every time. MergeStrategy.deduplicate, which is the default strategy for most files, just happens to take effect only if there's a collision.
MergeStrategy.rename, applied for README and license files by default for example, will rename the file every time by appending the jar name.
Is there a mechanism that can allow me to map certain jar -> prefix/with/path in sbt-assembly?
There's no strategy out of the box that does exactly that, but you can define a custom strategy similar to MergeStrategy.rename.
Just follow this rule as Xerxes explained here. There is then no longer any risk of collision. The official JogAmp forum is a better place to ask questions about all JogAmp APIs. If you don't follow my advice, GlueGen will be unable to extract and load the correct native libraries. In your case, natives/linux-amd64 is correct whereas natives/linux.and.amd64 isn't.

Buildr: adding a path to the generated eclipse/idea files

I have a legacy java project that we have been moving to buildr/artifactory from ant/jars in svn.
The primary code is in the default (src/main/java) folder, but we have a few external source paths, for various tests that we can't move into the default folder, but we want to have access with it.
Currently, when adding a new library/regenerating IDE fields, it does not pick up these source paths, and I can't find a succinct discussion in the buildr manual for how to actually add them, rather than re-adding everything manually in eclipse (which just gets wiped out on the next regen).
Any idea how to have multiple source paths get picked up explicitly by buildr so that the idea/eclipse targets generate properly?
There are two ways that I know will work with IDEA. The second one might also work with Eclipse, while the first is specific to the idea task.
The IDEA-specific solution:
define 'proj' do
# ...
iml.main_source_directories << _('src/other')
end
iml also has test_source_directories and excluded_directories arrays you can append to.
The possibly eclipse-compatible solution, with more background than you probably want:
The iml object gets its default values for the main and test source directory arrays from project.compile.sources and project.test.compile.sources (slight simplification; resources are considered also). Buildr defines these .sources project attributes from the layout, so instead of explicitly appending to the iml attributes, you could use a custom layout for your project that includes your special source paths. That might work with the eclipse task, but I haven't tried it.

Error with Groovy AST transformations when cleaning project in Eclipse

I'm trying to work through groovy's Implementing Local AST Transformations tutorial, but whenever I clean my project I get this error in each file that has the #WithLogging annotation in it:
Groovy:Could not find class for Transformation Processor AC.LoggingASTTransformation declared by AC.WithLogging
So you have a package named "AC" that contains both "WithLogging.groovy" and "LoggingASTTransformation.groovy" classes? Does it also contain any classes that implement the "WithLogging" interface?
If so, I'd suggest you move the class(es) that use your annotation to a location outside of the annotation defining package (the default will suffice, for diagnostic purposes) - Order of compilation matters with transformations. See this post on the groovy users mailing list for more on that.
Also try changing the annotation from #WithLogging to #AC.WithLogging.
As far as cleaning with Eclipse is concerned, I had a similar issue and found that I had to make a trivial modification after a clean to any file that contained my annotation. IE, add a space somewhere. Then save the file. This should rebuild everything properly.