How to define a custom build setting? - scala

I want my users to be able to define a value in their project/ definition that will be used as a URL for fetching a remote configuration file, which an sbt plugin will in turn use. I can't figure out how to define such a value though. When I try to add it to build.sbt I get this error:
/Users/2rs2ts/src/my-cool-plugin/build.sbt:74: error: not found: value myConfigUrl
myConfigUrl := "http://mycoolwebsite.com/config.xml"
^
[error] Type error in expression
Probably because it's not part of Keys. But I don't know how I'm supposed to add something like this. Even after that point, I don't know how to access the setting in my plugin's Scala source.

Use settingKey macro to define the myConfigUrl key.
A sample build.sbt could be as follows:
lazy val myConfigUrl = settingKey[String]("URL for fetching a remote configuration file")
myConfigUrl := "http://mycoolwebsite.com/config.xml"
A sample session:
➜ my-cool-plugin xsbt
[info] Loading global plugins from /Users/jacek/.sbt/0.13/plugins
[info] Set current project to my-cool-plugin (in build file:/Users/jacek/sandbox/my-cool-plugin/)
> show myConfigUrl
[info] http://mycoolwebsite.com/config.xml
Given the comment where the OP asked:
How can I refer to this in my project code now? I want to be able to
access the value I assigned to myConfigUrl in one of my .scala files,
not related to the build process.
the key should be defined in build.scala build object as no keys in *.sbt files are visible in project/*.scala files.
Here is a sample project/build.scala build definition with the key:
import sbt._
import Keys._
object build extends Build {
lazy val myConfigUrl = settingKey[String]("URL for fetching a remote configuration file")
lazy val mySettings = inConfig(Compile) { Seq(
myConfigUrl := "http://mycoolwebsite.com/config.xml"
)}
}
With the Scala build, change build.sbt to be as follows:
mySettings
You could do it since every build file is automatically imported in *.sbt files and hence accessing vals becomes simple like that. To have the settings - the single myConfigUrl key - available in the project you need to add the Seq[Setting] val.
Do reload and the key should be available as if it was before:
> show myConfigUrl
[info] http://mycoolwebsite.com/config.xml
Given the comment:
I'm particularly interested in a way to let end users of
my-cool-plugin define their own myConfigUrl which will be used instead
of the default one in my-cool-plugin's build.sbt
it's clear on the intent of the key. This is a key of the plugin so just add sbtPlugin := true to the build, publishLocal the project and use addSbtPlugin to declare a plugin dependency on the plugin in another build.
You may want to read about the new feature of sbt 0.13.5 - auto plugins - that further ease setting up the plugin of yours:
As of sbt 0.13.5, there is a new auto plugins feature that enables
plugins to automatically, and safely, ensure their settings and
dependencies are on a project.

Related

How to import unmanaged dependencies into code

I'm trying to use sbt to pull down an existing github project and then (obviously) use it in my code. My abbreviated build.sbt is shown below:
lazy val myProjInGit = RootProject(
uri("git://github.com/me/sheet-ref.git#master"))
lazy val root = (project in file("."))
.dependsOn(myProjInGit)
.settings(
name := "MainProj",
)
libraryDependencies ++= Seq( /*other stuff... */)
This is probably a basic one, but how should I then be importing this into code? Could I use, say, import myProjInGit.SampleObject.sampleFunction in my Main.scala? I can see a disconcerting lack of info on SO about this topic so I'm not sure that I'm not barking up completely the wrong tree. Thanks in advance for any help.
EDIT: some further details here. The project I am trying to import is just a library, so it only contains a package object. To try to debug things a little, I'm trying to import it as a ProjectRef, rather than a RootProject. I've discovered the projects sbt command, and if I run it on the project I'm trying to import (not the main one), it gives me:
sbt:SheetRef> projects
[info] In file:/data/va-projects/sheet-ref/
[info] * SheetRef
So... it seems that external project is called SheetRef. If I try to import SheetRef using
lazy val sheetRef = ProjectRef(
uri("git://github.com/me/sheet-ref.git#master"), "SheetRef")
then I get an sbt error:
[error] No project 'SheetRef' in 'git://github.com/me/sheet-r
ef.git#master'.
[error] Valid project IDs: root
Which feels unintuitive. I can import it as root, but then I seem to be unable to access it from my code - if it requires an import statement, it's not obvious to me what it should be.
The dependency has to be an SBT project, so SBT knows how to build it. If it is, then yes, you access it just like any other dependency in your code, because it's added to the classpath (after being built) just like any other dependency.
Which feels unintuitive. I can import it as root, but then I seem to be unable to access it from my code - if it requires an import statement, it's not obvious to me what it should be.
The thing is, it doesn't matter how you add the dependency; it could be a normal library dependency, an unmanaged dependency, a source dependency, etc. Those are SBT concepts, which have no meaning to the Scala compiler.
So the import statement can't depend on project names, jar names or anything like that. In fact, it's the same as if you just put those files into your own project.
Is there someone that made such a thing work? I am trying the same, and somehow manage to get the git project imported but I have no idea how to access the imported scala components.

How to reference sourceManaged setting from Build.scala

I am trying to extend my build with task that will generate source file.
I am defining my task in project/Build.scala like this (non-relevant pieces omitted):
object ProjectBuild extends Build {
lazy val generateConfiguration = TaskKey[Seq[File]]("generateConfiguration")
lazy val unscopedSettings = Seq(
generateConfiguration <<=
(libraryDependencies, sourceManaged).map { (dependencies, generatedRoot) =>
// here goes implementation
},
sourceGenerators += generateConfiguration.taskValue
)
override lazy val settings = super.settings ++ inConfig(Compile)(unscopedSettings)
}
When I try to import project in sbt I get following error:
[info] Loading project definition from ...web/project
References to undefined settings:
{.}/compile:sourceManaged from {.}/compile:generateConfiguration
(...web/project/Build.scala:19)
Did you mean compile:sourceManaged ?
{.}/compile:sourceGenerators from {.}/compile:sourceGenerators
(...web/project/Build.scala:33)
Did you mean compile:sourceGenerators ?
I understand that my problem is because I probably reference the setting with wrong scope. I suppose, the issue is within 'this build' ({.}) which for some reason is prepended here (as far as I understand, the setting exists in Global scope for this axis).
How should I correctly express dependency to sourceManaged setting in Compile configuration within Scala code (not .sbt)?
P.S.:
sbt 0.13.8
scala 2.11.7
I seem to have found the issue myself.
Possible reason this did not work was the way I put my custom settings into the build - I tried to override lazy val settings of Build.
Since I described my tasks in Build.scala rather than build.sbt which comes before in eventual build definition, it appears that dependent settings are not yet defined! They will be set later by default imports of build.sbt.
Once I moved addition of custom properties in the project to build.sbt while leaving their definition in Build.scala everything worked as expected.
Despite there is information about override order between *.scala and build.sbt and some simple examples of compound build definitions it was not that obvious.

SBT src_managed not availabe for compilation

In my project I have a special SBT plugin to generate some configuration specific settings (like sbt-buildinfo). A special task generates a Scala class and stores it in 'src_managed' folder.
The problem is, that after this file is successfully generated, the following 'compile' can't find this class and I get compilation error.
I have several configurations defined with:
compile in conf <<= (compile in conf).dependsOn(mytask)
I call this plugin like so:
;clean;proj/myconf:compile
You should setup special setting for code generator:
sourceGenerators in Compile <+= (myCodeGeneratorTask in Compile)
SBT generate code using project defined generator

How to make an sbt task run on under multiple scopes

I have written an sbt plugin that generates some sources and resources. It is hard coded to work in the Compile scope.
How can I make it work in the Test scope too, so I can use the plugin when running tests and it will look in and output to the correct folder?
For example, in various points in the code I refer to resourceManaged in Compile which relates to src/main/resourcesbut when test is run, I would like it to be resourceManaged in Test when relates to src/test/resources.
How do I abstract away the scope?
This is a topic discussed in Plugins Best Practices, specifically in the Configuration advices section.
Provide raw settings and configured settings
If your plugin is ObfuscatePlugin, provide baseObfuscateSettings that's not scoped in any configuration:
lazy val baseObfuscateSettings: Seq[Def.Setting[_]] = Seq(
obfuscate := Obfuscate((sources in obfuscate).value),
sources in obfuscate := sources.value
)
As you can see in the above it's accessing sources key, but it's not specified which configuration's source.
inConfig
override lazy val projectSettings = inConfig(Compile)(baseObfuscateSettings)
inConfig scopes the passed in sequence of settings into a particular configuration. If you want to support both Compile and Test out of the box, you can say:
override lazy val projectSettings =
inConfig(Compile)(baseObfuscateSettings) ++
inConfig(Test)(baseObfuscateSettings)

Custom sbt configuration with Intellij auto import

I can't get the embedded sbt plugin (with auto import enabled) in Intellij (13.1) to recognize custom sbt configurations. I have the follow setup in my sbt build file:
lazy val EndToEndTest = config("e2e") extend (Test)
private lazy val e2eSettings =
inConfig(EndToEndTest)(Defaults.testSettings)
lazy val root: Project = Project(
id = "root",
base = file(".")
)
.configs(EndToEndTest)
.settings(e2eSettings)
The code works according to expectations in the sbt console. E.g I can write:
sbt e2e:test (and it will execute tests located in /src/e2e/scala)
The issue is that the directory /src/e2e/scala won't get registered as a source directory in Intellij. This makes it hard to use intellij to manage the tests. I can manually mark the directory as source but it gets reverted every time
I update my sbt files (auto import).
Do a manual update through the sbt tool window
Related:
Using the preconfigured configuration IntegrationTest works as expected but custom once don't.
According to sbt-idea documentation this can be done in your case by adding
ideaExtraTestConfigurations := Seq(EndToEndTest)
to your project settings.