com-lihaoyi / mill

Your shiny new Java/Scala build tool!
https://mill-build.com/
MIT License
2k stars 308 forks source link

encountered unrecoverable cycle resolving import #3123

Open james-s-w-clark opened 3 months ago

james-s-w-clark commented 3 months ago

This build.sc works OK:

import mill._
import mill.define.{Module}
import mill.eval.Evaluator

def sayHi(): Command[Unit]  = utils.sayThing("hi")

object utils extends mill.Module {

  def sayThing(thing: String): Command[Unit] =
    T.command {
      println(s"saying $thing")
    }
}

This build.sc hits the titular issue:

import mill._
import mill.define.{Module}
import mill.eval.Evaluator

import utils._
def sayProblem(): Command[Unit]  = sayThing("encountered unrecoverable cycle resolving import")

object utils extends mill.Module {
  def sayThing(thing: String): Command[Unit] =
    T.command {
      println(s"saying $thing")
    }
}

[info] compiling 1 Scala source to /Users/James.Clark/IdeaProjects/mill-cycle-module-import/out/mill-build/compile.dest/classes ... [error] /Users/James.Clark/IdeaProjects/mill-cycle-module-import/build.sc:7:8: encountered unrecoverable cycle resolving import. [error] Note: this is often due in part to a class depending on a definition nested within its companion. [error] If applicable, you may wish to try moving some members into another object. [error] import utils._ [error] ^ [error] /Users/James.Clark/IdeaProjects/mill-cycle-module-import/build.sc:11:27: Modules, Targets and Commands can only be defined within a mill Module [error] object utils extends mill.Module { [error] ^ [error] two errors found 1 targets failed compile Compilation failed

In this short example you're pointed in the right direction (but, at least to me, it's not clear that the solution is to not have this import as in the first example).


If you move the imports around, the error changes too. Just by reordering, you could get e.g.

import utils._
import mill._
import mill.define.{Module}
import mill.eval.Evaluator

def sayProblem(): Command[Unit]  = sayThing("encountered unrecoverable cycle resolving import")

object utils extends mill.Module {
  def sayThing(thing: String): Command[Unit] =
    T.command {
      println(s"saying $thing")
    }
}

[error] import mill. [error] ^ [error] /Users/James.Clark/IdeaProjects/mill-cycle-module-import/build.sc:1:8: encountered unrecoverable cycle resolving import. [error] Note: this is often due in part to a class depending on a definition nested within its companion. [error] If applicable, you may wish to try moving some members into another object. [error] import utils.

That's still not too bad, it still talks about utils at least.


What if you have a bunch more imports?

import mill._
import mill.define.{Module}
import mill.eval.Evaluator
import utils._
import mill.contrib.jmh.JmhModule
import $ivy.`com.lihaoyi::mill-contrib-jmh:`
import mill.scalalib._
import mill.util.Jvm

def sayProblem(): Command[Unit]  = sayThing("encountered unrecoverable cycle resolving import")

object utils extends mill.Module {
  def sayThing(thing: String): Command[Unit] =
    T.command {
      println(s"saying $thing")
    }
}

[build.sc] [49/53] compile [info] compiling 1 Scala source to /Users/James.Clark/IdeaProjects/mill-cycle-module-import/out/mill-build/compile.dest/classes ... [error] /Users/James.Clark/IdeaProjects/mill-cycle-module-import/build.sc:7:13: encountered unrecoverable cycle resolving import. [error] Note: this is often due in part to a class depending on a definition nested within its companion. [error] If applicable, you may wish to try moving some members into another object. [error] import mill.scalalib._ [error] ^ [error] /Users/James.Clark/IdeaProjects/mill-cycle-module-import/build.sc:6:8: encountered unrecoverable cycle resolving import. [error] Note: this is often due in part to a class depending on a definition nested within its companion. [error] If applicable, you may wish to try moving some members into another object. [error] import root._ [error] ^ [error] two errors found 1 targets failed compile Compilation failed

We've lost the guidance towards this bad import.


How did we arrive at this bad import?

In mill 0.10.n, this is OK:

import mill._
import mill.define.{Module}
import mill.eval.Evaluator
import utils._
import mill.contrib.jmh.JmhModule
import $ivy.`com.lihaoyi::mill-contrib-jmh:`
import mill.scalalib._
import mill.util.Jvm

def sayProblem(): Command[Unit]  = sayThing("encountered unrecoverable cycle resolving import")

object utils {
  def sayThing(thing: String): Command[Unit] =
    T.command {
      println(s"saying $thing")
    }
}

But in 0.11.m, you get:

mill-cycle-module-import mill sayProblem No mill version specified. You should provide a version via '.mill-version' file or --mill-version option. Using mill version 0.11.7 [build.sc] [47/53] zincReportCachedProblems Unknown ctx of target sayThing: millbuild.build$utils$@747a0827

I added extends mill.Module as that error lead to this suggestion at https://github.com/com-lihaoyi/mill/issues/2888#issuecomment-1822757675


I couldn't find an issue when searching for encountered unrecoverable cycle resolving import, so at least this should help anyone who comes across the same 👍

lefou commented 2 months ago

I guess re-using public targets and commands defined elsewhere is never a good idea. Instead, you should define a new command with T.command { .. } and call (meaning: depend on) the other command from there. E.g.:

def sayProblem(): Command[Unit]  = T.command {
  sayThing("encountered unrecoverable cycle resolving import")()
}

Even, better, make the sayThing command an annonymous task with T.task { .. }.

Unfortunately, the types are correct, therefore the compiler will not call our implicit conversions here. The proper definition of a command should always contain the T.commnad { .. } on the left hand side, which is not the case in the definition of sayProblem in all of your examples. That means, the commands context isn't correct.