Coursier: "dep" literal doesn't allow variable in artifactId - scala

I'm trying to build dynamically an artifact name for dependency in Coursier
Fetch()
.addRepositories(...)
.addDependencies(dep"com.foo.company.runner:$runnerArtifactId")
.run()
But Coursier doesn't allow the "dep" literal to contain another literal:
[error] /Users/user/IdeaProjects/pipelines/src/main/scala/com/foo/company/conf/FetchRunner.scala:62:28: Only a single String literal is allowed here
[error] val runnerDependency = dep"com.foo.company.runner:$runnerArtifactId"
How can I build a dependency with dynamic artifactId?

Related

Typesafe Config SBT Multi Module Error When Resolving Placeholder

I'm having a SBT Multi Module project where in some of the sub modules, I'm actually having a place holder to resolve certain configurations. The project structure looks like this:
core
src
main
resources
application.conf
mod1
src
main
resources
application.conf
mod2
src
main
resources
application.conf
In the module2, in my application.conf, I have the following:
# The environment representation of the configurations
# ~~~~~
app {
environment = "test"
name = ${NAME}-split # TODO: Get the default name from application.conf which should also be located here
}
As it can be seen that the NAME is a place holder that I would like to inherit from either the default application.conf that I include or pass it in via a command line argument. As expected, I get to see compiler error like this:
[error] at java.base/java.lang.Thread.run(Thread.java:829)
[error] Caused by: com.typesafe.config.ConfigException$UnresolvedSubstitution: application.test.conf # file:/home/runner/work/housing-price-prediction-data-preparation/housing-price-prediction-data-preparation/split/target/scala-2.12/test-classes/application.test.conf: 7: Could not resolve substitution to a value: ${NAME}
[error] at com.typesafe.config.impl.ConfigReference.resolveSubstitutions(ConfigReference.java:108)
Normally you would put a reference.conf per module and an application.conf at the top application level.
Also note the module level configs are resolved first (in order, first th lower modules than the ones that depend on those). Then the application.conf and finally overrides from the command line.
The order is important, you can't depend on things not yet set, or if they are set to an intem value and then overriden. the dependent config will be based on the one effective when it is resolved.

Bug an error when accessing a class that doesn't have access to type information

There are some classes in the library that have an IMPORT that cannot be resolved.
For example, org.scalatest.tools.Framework in ScalaTest.
If I add scalatest as a dependent library, it will be added to the test classpath, but this import will not be resolved in the normal test classpath.
There is no SBT module in the test classpath.
import sbt.testing.{Event => SbtEvent, Framework => SbtFramework, Runner => SbtRunner, Status => SbtStatus, _}
I need to scan for classes under a specific package arrangement in macro and search for classes with specific features.
def collectXxx(targets: List[c.Symbol]) {
targets.filter { x =>
{
x.isModule || (
x.isClass &&
!x.isAbstract &&
x.asClass.primaryConstructor.isMethod
} && x.typeSignature.baseClasses.contains(XxxTag.typeSymbol)
}
}
This will filter to symbols that are object / class and inherit from Xxx.
This will work in most cases, but if there is a class in targets that cannot be compiled as is, such as scalatest, the compiler error cannot be avoided.
The moment baseClasses is accessed, the macro deployment status is set to global error.
[error] <macro>:1:26: Symbol 'type sbt.testing.Framework' is missing from the classpath.
[error] This symbol is required by 'class org.scalatest.tools.Framework'.
[error] Make sure that type Framework is in your classpath and check for conflicting dependencies with `-Ylog-classpath`.
[error] A full rebuild may help if 'Framework.class' was compiled against an incompatible version of sbt.testing.
[error] type `fresh$macro$612` = org.scalatest.tools.Framework
[error] ^
If you look at the stack trace in debug mode, I was setting global_error when I accessed each property of StubClassSymbol.
java.lang.Throwable
at scala.reflect.internal.Symbols$StubSymbol.fail(Symbols.scala:3552)
at scala.reflect.internal.Symbols$StubSymbol.info(Symbols.scala:3563)
at scala.reflect.internal.Symbols$StubSymbol.info$(Symbols.scala:3563)
at scala.reflect.internal.Symbols$StubClassSymbol.info(Symbols.scala:3567)
| => cat scala.reflect.internal.Symbols$StubClassSymbol.info(Symbols.scala:3567)
at scala.reflect.internal.Types$TypeRef.baseClasses(Types.scala:2593)
at scala.reflect.internal.Types.computeBaseClasses(Types.scala:1703)
at scala.reflect.internal.Types.computeBaseClasses$(Types.scala:1680)
at scala.reflect.internal.SymbolTable.computeBaseClasses(SymbolTable.scala:28)
at scala.reflect.internal.Types.$anonfun$defineBaseClassesOfCompoundType$2(Types.scala:1781)
at scala.reflect.internal.Types$CompoundType.memo(Types.scala:1651)
at scala.reflect.internal.Types.defineBaseClassesOfCompoundType(Types.scala:1781)
at scala.reflect.internal.Types.defineBaseClassesOfCompoundType$(Types.scala:1773)
at scala.reflect.internal.SymbolTable.defineBaseClassesOfCompoundType(SymbolTable.scala:28)
at scala.reflect.internal.Types$CompoundType.baseClasses(Types.scala:1634)
at refuel.internal.AutoDIExtractor.$anonfun$recursivePackageExplore$3(AutoDIExtractor.scala:119)
I thought of a way to get around this.
Perhaps when the import fails to resolve, that TypeSymbol becomes a StubClassSymbol.
So I parsed the structure of the Symbol that went into error and added a condition to filter it if a StubClassSymbol was found. And this one has worked.
!x.typeSignature.asInstanceOf[ClassInfoTypeApi].parents.exists { pr =>
pr.typeSymbol.isClass &&
pr.typeSymbol.asClass.isInstanceOf[scala.reflect.internal.Symbols#StubClassSymbol]
}
But I think this is really pushy. Is there any other way around it? And I wonder if this really covers all cases.

SBT: Invalid project ID: Expected ID character when pointing to module in subdirectory

I a multi-module project structured like this
+- multiModuleProject
+-module1
+-dir1
+-subDirModule1
+-subDirModule2
+-module3
+-build.sbt
I want both subDirModule1 and subDirModule2 to be their own modules outright.
I added something like this to the build.sbt
lazy val subDir1 = Project(id = "dir1/subDirModule1", base = file("dir1/subDirModule1")
lazy val subDir1 = Project(id = "dir1/subDirModule2", base = file("dir1/subDirModule2")
I can't get it to work, I keep getting
[error] java.lang.RuntimeException: Invalid project ID: Expected ID character
[error] dir1/subDirModule1
[error] ^
But I'm sure I've seen a slash being used in another project I've worked on. What going wrong here?
Slash is used as a separator between project ID and config and has been for a long time, so I suspect you are misremembering (if you don't, you'd need to escape it all the time and I at least never remember seeing it). You can of course use it in the path (base argument), just not in the ID:
lazy val subDir1 = Project(id = "subDir1", base = file("dir1/subDirModule1"))
and then use e.g.
sbt> subDir1/compile
You can of course use whatever name you want, but usually the val name and the id will be the same.

Finding Scala libraries location from within Scala program

I'm trying to make one Scala program spawn another Scala program. I managed to obtain java executable from System.getProperty("java.home"), I've obtained some path from System.getProperty("java.class.path") (sbt-launcher.jar location), and with ClassLoader I've got project/target/scala-2.11/classes directory.
However, I am still unable to run it. JVM complain that it is unable to find Scala library's classes:
Exception in thread "main" java.lang.NoClassDefFoundError: scala/concurrent/ExecutionContext
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
at java.lang.Class.privateGetMethodRecursive(Class.java:3048)
at java.lang.Class.getMethod0(Class.java:3018)
at java.lang.Class.getMethod(Class.java:1784)
at sun.launcher.LauncherHelper.validateMainClass(LauncherHelper.java:544)
at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:526)
Caused by: java.lang.ClassNotFoundException: scala.concurrent.ExecutionContext
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 7 more
I am looking for a way to add those files to classpath, but I want it to be portable. I do not look for solutions like hardcoding scala location on local computer nor do I want to use other environment variables and parameters than the one already existing. I also don't want to rely on SBT or Activators presence in user's environment.
Since the parent JVM process can use them their location has to be stored somewhere and I'll be thankful for help with finding out that location.
To successfully spawn one Scala App from another I had to fix several issues with my code:
1. correct main class:
object ChildApp extends App {
println("success")
}
to make sure that ChildApp is runnable by Java it has to be an object. Scala has no concept of static but object methods would (and main will) be compiled into static method.
2. correct class name:
While ChildApp.getClass.getName returns ChildApp$, it refers to an object (so that we could pass otherwise static-method-only class around). Java expects $ in command line - in other works I had to remove tailing $ before passing it into the process builder.
3. complete class path
I haven't found all used JARs within System.getPropertiy("java.class.path"):
val pcp = System getPropertiy "java.class.path" split File.pathSeparator // sbt-launcher.jar only
I haven't found them in SystemClassLoader either:
val scp = ClassLoader.getSystemClassLoader.asInstanceOf[URLClassLoader].getURLs.map(_.toString) // same as above
I did found compiled files from my project using Class' resources:
// format like jar:file:/(your-project/compiled.jar)!some/package/ChildApp.class
lazy val jarClassPathPattern = "jar:(file:)?([^!]+)!.+".r
// format like file:/(your-project/compiled/some/package/ChildApp).class
lazy val fileClassPathPattern = "file:(.+).class".r
val jcp = jarClassPathPattern.findFirstMatchIn(pathToClass) map { matcher =>
val jarDir = Paths get (matcher group 2) getParent()
s"${jarDir}/*"
} toSet
val fcp = fileClassPathPattern.findFirstMatchIn(pathToClass) map { matcher =>
val suffix = "/" + clazz.getName
val fullPath = matcher group 1
fullPath substring (0, fullPath.length - suffix.length)
} toList
Finally I found where all those dependencies where stored:
// use App class' ClassLoader instead of system one
val lcp = ChildApp.getClass.getClassLoader.asInstanceOf[URLClassLoader].getURLs.map(_.toString)
4. bonus - JVM params and java location
val jvmArgs = ManagementFactory.getRuntimeMXBean.getInputArguments.toList
lazy val javaHome = System getProperty "java.home"
lazy val java = Seq(
Paths.get(javaHome, "bin", "java"),
Paths.get(javaHome, "bin", "java.exe")
) filter (Files exists _) head
Then you have everything you need for ProcessBuilder / Process:
val executable = java.toString
val arguments = jvmArgs ++ List("-cp", classPath, mainName) ++ mainClassArguments
PS. I checked several times - those additional JARs aren't passed using neither CLASSPATH environment variable nor with -cp parameter (sbt-launcher.jar's MANIFEST file did't had anything as well). So anyone knowing how they are passed and why my solution actually works, please explain.

UUID Path Bindable - Play Framework

In my build.sbt I have
routesImport += "play.api.mvc.PathBindable.bindableUUID"
And in my routes I have:
GET /groups/:id controllers.GroupController.get(id)
And in my controller I have
class GroupController { ....
def get (id: UUID)
I am getting the following error for the above route
type mismatch;
found : String
required: java.util.UUID
How can used uuid in path in routes file in Play. I am using play 2.4.2 - scala 2.11.7
String is the default type for parameters in the routes file. To change this, you need to explicitly specify a type for the Id:
GET /groups/:id controllers.GroupController.get(id: java.util.UUID)
If you do that, you should find you can also delete the import of bindableUUID in your build file.