How to reference sourceManaged setting from Build.scala - 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.

Related

Get full classpath without compilation in SBT

I need a piece of code using SBT, possibly internals, which acquires the full classpath of an SBT project without invoking a compilation. Normally I would use "Runtime / fullClasspath", but that needs the project to be compiled first. Is there any way to get the fullClasspath without triggering a compile? I thought the build.sbt alone determined the classpath, and compilation (in theory) isn't necessary.
I asked on the SBT gitter channel, and there it was also mentioned to use dependencyClasspath. dependencyClasspath doesn't require compilation of the root project, but it does require compilation of all the dependents. So that doesn't solve it yet for me. I'm looking for the complete classpath required to running the root project, without compiling its constituents.
I'm interested in any way to work around this, so if there are any farfetched workarounds, those are welcome too.
An example of what I have now:
Global / printMainClasspath := {
val paths = (rootProject / fullClasspath).value
val joinedPaths = paths
.map(_.data)
.mkString(pathSeparator)
println(joinedPaths)
}
This works partially, it creates a task "printMainClasspath" which prints the full classpath. But if I call it in the sbt shell, it compiles the code first. I'd like the classpath to be printed without invoking a full compile of the project. Ideally, only all build.sbt's of all constituent projects are compiled. Is there a way?
val dryClasspath = taskKey[Seq[File]]("dryClasspath")
dryClasspath := {
val data = settingsData.value
val thisProj = thisProjectRef.value
val allProjects = thisProj +: buildDependencies.value.classpathTransitiveRefs(thisProj)
val classDirs = allProjects.flatMap(p => (p / Runtime / classDirectory).get(data))
val externalJars = (Runtime / externalDependencyClasspath).value.map(_.data)
classDirs ++ externalJars
}

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 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)

How to define a custom build setting?

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.

Play Framework project with sub-projects, how to make SBT work correctly

I've started off with a single Play Framework 2.x project (Scala) that I've been working on.
This has now reached a point where I'd like to break down the project into a number of sub and 'sibiling' projects. Specifically - I'm writing a Play REST API service that I wish to also build a Javascript project alongside of like an 'SDK' for the service.
I 'think' this is possible using SBT - but I'm stuck and the documentation seems thin on the ground in this area.
I'd like to have three projects underneath one 'main' project that is nothing but a container for the other three and where the main build files are.
This would be something like:
main
service (play + scala)
common files (scala only)
sdk (javascript)
When I try and run 'play' to build the hierachy I've got - I get variations on the following error:
[error] Not a valid command: play (similar: apply, last, alias)
[error] Not a valid project ID: play
[error] Expected ':' (if selecting a configuration)
[error] Not a valid key: play (similar: playConf, play-conf, playReload)
[error] play
[error] ^
When I have a Build.scala (in the 'main' project) look like this:
object ApplicationBuild extends Build {
val appName = "my service"
val appVersion = "1.0-SNAPSHOT"
// tell the couchbase infrastructure to use the built in logger - which will get redirected to our main logger
System.setProperty("net.spy.log.LoggerImpl", "net.spy.memcached.compat.log.SunLogger")
// project dependencies
val appDependencies = Seq(
...
)
val common = Project("common", file("common"))
val service = play.Project(appName, appVersion, appDependencies, path = file("service")).settings(
scalacOptions ++= Seq("-feature") // turn on normal warnings and feature warnings with scalac
).dependsOn(common)
val sdk = Project("sdk", file("sdk"))
val base = Project("base", file("."))
.dependsOn(service)
.dependsOn(sdk)
.dependsOn(common)
}
The folder hierachy I've got is:
\
\service
\common
\sdk
\project
\target
build.sbt
Am I on the right track and can someone help me out syntax wise, or am I approaching the problem in completely the wrong way and play cannot be used like that? (use sbt directly?).
I think your base project should look like this:
val base = Project("base", file("."))
.aggregate(service,sdk,common)
First, a project is defined by declaring a lazy val of type Project because you may encounter initialization problems.
lazy val base = Project("base", file(".")).aggregate(sdk, service, common)
If you have SBT installed and configured classpath for SBT, try sbt command in the terminal. The current project should be set to "base" automatically.
The reason I suggested you to try sbt command instead of play command is: If the root project is not a PlayProject then the play build script will fail.