Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scalafix sbt task hangs when invoked as part of compile task #2008

Open
bpholt opened this issue Jun 10, 2024 · 2 comments
Open

Scalafix sbt task hangs when invoked as part of compile task #2008

bpholt opened this issue Jun 10, 2024 · 2 comments

Comments

@bpholt
Copy link

bpholt commented Jun 10, 2024

Prior to Scalafix 0.12.1, we had many repos where we apply Scalafix rules on every compile by chaining the scalafix task as part of the compile task. As of 0.12.1, applying any semantic rules in this way causes sbt to hang in such a way that it can only be killed from the outside (e.g. using kill -9 ${sbt pid}).

As a minimal example, use this build.sbt:

Compile / compile := Def.taskDyn {
  val output = (Compile / compile).value
  Def.task {
    (Compile / scalafix).toTask(" ExplicitResultTypes").value
    output
  }
}.value

project/plugins.sbt:

addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.12.1")

and project/build.properties:

sbt.version=1.10.0

Example output:

[info] welcome to sbt 1.10.0 (Eclipse Adoptium Java 11.0.18)
[info] loading settings for project global-plugins from global.sbt ...
[info] loading global plugins from ~/.sbt/1.0/plugins
[info] loading settings for project sbt-scalafix-issue-build from plugins.sbt ...
[info] loading project definition from ~/sbt-scalafix-issue/project
[info] loading settings for project sbt-scalafix-issue from build.sbt ...
[info] set current project to sbt-scalafix-issue (in build file:~/sbt-scalafix-issue/)
sbt:sbt-scalafix-issue> compile
^C
[warn] Canceling execution...
^Z
[1]+  Stopped                 sbt
~/sbt-scalafix-issue $ kill -9 %1
[1]+  Killed: 9               sbt

If we comment out the (Compile / scalafix).toTask(" ExplicitResultTypes").value line in build.sbt and run the tasks separately, they work fine:

[info] welcome to sbt 1.10.0 (Eclipse Adoptium Java 11.0.18)
[info] loading settings for project global-plugins from global.sbt ...
[info] loading global plugins from ~/.sbt/1.0/plugins
[info] loading settings for project sbt-scalafix-issue-build from plugins.sbt ...
[info] loading project definition from ~/sbt-scalafix-issue/project
[info] loading settings for project sbt-scalafix-issue from build.sbt ...
[info] set current project to sbt-scalafix-issue (in build file:~/sbt-scalafix-issue/)
sbt:sbt-scalafix-issue> compile
[success] Total time: 0 s, completed Jun 10, 2024, 4:56:53 PM
sbt:sbt-scalafix-issue> scalafix ExplicitResultTypes
[success] Total time: 1 s, completed Jun 10, 2024, 4:57:02 PM

Furthermore, using one of the built-in syntactic rules works fine as well.

Replacing Scalafix 0.12.1 with 0.12.0 also works fine.

@bjaglin
Copy link
Collaborator

bjaglin commented Jun 23, 2024

Thanks for the report @bphold!

This is clearly a regression of scalacenter/sbt-scalafix#411. I can't think of an easy way to fix that without reverting that PR...

However, it looks like what you are trying to achieve can be done through a built-in feature.

# build.sbt
scalafixOnCompile := true
# .scalafix.conf
triggered.rules = [ ExplicitResultTypes ]

Would that work for you?

@bpholt
Copy link
Author

bpholt commented Jul 24, 2024

Yes, I think that works! I'm going to try re-updating some of the projects I downgraded from 0.12.1 to 0.12.0 to make sure, but I'm cautiously optimistic.

For background, many of our projects use an AddCatsTaglessInstances scalafix, and are therefore typically structured like this:

lazy val `generated-sources` = (project in file("generated-sources"))
  .settings(
    Compile / compile := Def.taskDyn {
      val compileOutput = (Compile / compile).value

      Def.task {
        (Compile / scalafix).toTask(" AddCatsTaglessInstances").value
        compileOutput
      }
    }.value,
  )

lazy val `main-project` = project.in(file(".")).dependsOn(`generated-sources`)

The generated-sources project uses a third party code generator to generate a bunch of Scala files, which we then modify using scalafix. (Not an ideal structure, obviously, but it's where we are.)

Ideally we'd be able to run main-project/compile and have all of this happen in one step:

  1. generate the sources in the generated-sources project
  2. compile those generated sources so scalafix can make changes
  3. run the AddCatsTaglessInstances scalafix
  4. compile the output of the AddCatsTaglessInstances scalafix
  5. compile the sources in main-project, which depend on the modifications made by the scalafix

As it exists today (both with our previous Compile / compile override and with scalafixOnCompile := true), sbt appears to skip step 4, so when it starts step 5 and tries to compile the main-project sources, it fails because it doesn't recognize the changes made by the scalafix. Explicitly running generated-sources/compile before running main-project/compile works, but it's easy to forget to do. I wonder if the changes made to the sbt task graph by setting scalafixOnCompile := true could also take that into account? 🤔

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants