Re: [sbt] SBT generate code using project defined generator

431 views
Skip to first unread message

Mark Harrah

unread,
Jul 18, 2012, 12:59:24 PM7/18/12
to simple-b...@googlegroups.com
On Tue, 17 Jul 2012 09:43:28 -0700 (PDT)
Alexander Nemish <ane...@gmail.com> wrote:

> Hi,
>
> Duplicating a question on SO:
> http://stackoverflow.com/questions/11509843/sbt-generate-code-using-project-defined-generator
>
> The idea is to:
> 1. Compile scala sources that include Generator.scala
> 2. Run Generator.generate() to generate Java sources
> 3. Compile Java sources
> 4. Pack all the classes into a result jar.
>
> I tried things like:
>
> sourceGenerators in Compile <+= (sourceManaged in Compile, state) map {
> (out, s) =>
> val extracted = Project.extract(s)
> val res = extracted.session.currentEval().eval(
>
> """org.example.Generator.generate("generated")""".getValue(extracted.currentLoader).asInstanceOf[Seq[File]]
> }
>
> with same result:
>
> <setting>:1: error: object example is not a member of package org
>
> Looking at compile task implementation and at AggressiveCompile.scala:
>
> if(order == JavaThenScala) { compileJava(); compileScala() } else {
> compileScala(); compileJava() }
>
> it seems like it's impossible to do this, but maybe I'm missing something.

Generator is in the project sources and so it is not available to task definitions. The Generator class needs to be loaded in a class loader and called via reflection. The build for sbt itself does this. The task implementation is here:

https://github.com/harrah/xsbt/blob/0.13/project/Util.scala#L51

and the task binding (that provides the inputs to the implementation) is here:

https://github.com/harrah/xsbt/blob/0.13/project/Sbt.scala#L216

-Mark

> Any ideas?
>
> Thank you,
> Alexander Nemish
>
> --
> You received this message because you are subscribed to the Google Groups "simple-build-tool" group.
> To view this discussion on the web visit https://groups.google.com/d/msg/simple-build-tool/-/1cuY7OrEZOEJ.
> To post to this group, send email to simple-b...@googlegroups.com.
> To unsubscribe from this group, send email to simple-build-t...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/simple-build-tool?hl=en.
>

Jesse C

unread,
Jul 19, 2012, 2:32:26 PM7/19/12
to simple-b...@googlegroups.com
I haven't done quite this, but I've done two things that are pretty similar which if combined might solve this issue.

1) I've created multiple compile steps in sbt using configurations.
So in my Build.scala I did the following:

lazy val Precompile = config("precompile")

//and then when I created my settings for my project, I appended the following::
inConfig(Precompile)(Defaults.defaultSettings ++ Defaults.configSettings ++ Defaults.configTasks)


then in my build.sbt I did:
sourceDirectories in Precompile <<= sourceDirectory apply ( (src) => {
  Seq[File](src / "precompile/scala")
})

compile in Compile <<= (compile in Compile) dependsOn (compile in RK.Precompile)

// Set the compile classpath to include the output of the Precompile step.
unmanagedClasspath in Compile <<= (unmanagedClasspath in Compile,
                                                        classDirectory in Precompile) map ( (path, f) => {
    path :+  Attributed.blank(f)  
})

obviously that isn't exactly what you want to do.  You'd probably want to make the task that runs
the generator depend on the precompile and then you'd want to add that task as a
sourceGenerator in Compile, so it gets called before compile automatically. 

To handle the second part (when I want to call into code that is being generated as part of a compile),
I  just shell out to java/scala and run a main method.

Pick the classpath that you want to use.  In this case it should be whatever your Precompile classpath was
plus the classDirectory from classpath.  You can probably get away just using the dependencyClasspath.in(Compile).
That's what I'm using.
So I pass that into my task that runs my generator as an argument

def runGenerator(log: Logger, cpath: Seq[Attributed[File]], ....): Seq[File] =
{
  val myClasspath = (cpath map { path => path.data.toString}).mkString(":")
  val cmd = "java -cp %s package.path.to.Generator %s".format(myClasspath, args.mkString(" "))
  val returnVal = <x>{cmd}</x> ! log
  if (returnVal != 0) {
    throw new IllegalStateException("The generator failed.")
  }

  //return whatever was generated.
}

And my task looks something like

  lazy val generatorImpl = generator.in(Compile) <<= (
    streams, dependencyClasspath.in(Compile), ...) map
    { (streams, cpath, ...) =>
      runGenerator(streams.log, cpath, . . . ) }


May not be the 'most correct' way, but it works.  Happy to answer questions if you've got any.
Reply all
Reply to author
Forward
0 new messages