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

avoid full (re)compilation after cold scalafix invocations #411

Merged
merged 2 commits into from
Apr 23, 2024

Conversation

bjaglin
Copy link
Collaborator

@bjaglin bjaglin commented Apr 21, 2024

Follow-up of https://twitter.com/channingwalton/status/1778119590486118908 which puts the finger on a behavior that can be very irritating in large builds

TIL #scala #protip Scalafix.

If you put “scalafix —check” before other steps in your sbt build, your code might get compiled twice.

Put it after compilation steps to avoid the problem.

This revisits #152, leveraging scalafixInvoked (which has the same semantics as the scalafixRunExplicitly mentioned in the old PR, and didn't cause any issue for 3 years) in order to trigger a regular compile from scalafix and thus store zinc analysis to benefit later compilations.

@bjaglin bjaglin force-pushed the store-analysis branch 2 times, most recently from 65df750 to d63114b Compare April 22, 2024 07:59
@bjaglin bjaglin changed the title keep analysis of scalafix-triggered relaxed compilations whenever possible cache analysis of scalafix-triggered relaxed compilations on sbt 1.4+ Apr 22, 2024
Comment on lines -751 to -754
// prevent storage of the analysis with relaxed scalacOptions - despite not depending explicitly on compile,
// it is being triggered for parent configs/projects through evaluation of dependencyClasspath (TrackAlways)
// in the scope where scalafix is invoked
analysis.withHasModified(false)
Copy link
Collaborator Author

@bjaglin bjaglin Apr 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this turned out to be unnecessary, since there is no scenario where a relaxed invocation would override a prior, regular one and invalidate the cache

@bjaglin bjaglin changed the title cache analysis of scalafix-triggered relaxed compilations on sbt 1.4+ cache analysis of scalafix-triggered compilations on sbt 1.4+ Apr 22, 2024
@bjaglin bjaglin changed the title cache analysis of scalafix-triggered compilations on sbt 1.4+ prevent recompilation after cold scalafix invocations on sbt 1.4+ Apr 22, 2024
@@ -583,7 +585,7 @@ object ScalafixPlugin extends AutoPlugin {
case "--stdout" =>
// --stdout cannot be cached as we don't capture the output to replay it
throw StampingImpossible
case "--tool-classpath" =>
case tcp if tcp.startsWith("--tool-classpath") =>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this PR exposed that a scripted was passing for the wrong reason: a scalafix invocation was failing because of a cache miss recompilation while it should have failed in scalafix itself

# files should be re-checked if a custom tool classpath is used (even if the rule is the same)
> set scalafixConfig := None
$ mkdir src/main/scala
$ copy-file files/Valid.scala src/main/scala/Valid.scala
> scalafix --tool-classpath=target/scala-2.12/classes RemoveUnused
$ exec chmod 000 src/main/scala/Valid.scala
-> scalafix --tool-classpath=target/scala-2.12/classes RemoveUnused
$ delete src/main/scala

@bjaglin
Copy link
Collaborator Author

bjaglin commented Apr 22, 2024

transient failure in inconfig scripted

[info] [success] Total time: 2 s, completed Apr 22, 2024, 8:11:51 PM
Error: [info] [error] ## Exception when compiling 1 sources to /tmp/sbt_b75cce04/inconfig/example/target/scala-2.12/it-classes
Error: [info] [error] java.lang.IllegalArgumentException: requirement failed: Source file '/tmp/sbt_b75cce04/inconfig/example/target/scala-2.12/classes.bak/sbt8052693458003257266.class' does not exist.
Error: [info] [error] scala.Predef$.require(Predef.scala:281)
Error: [info] [error] sbt.io.IO$.copyFile(IO.scala:860)
Error: [info] [error] sbt.io.IO$.move(IO.scala:1083)
Error: [info] [error] sbt.internal.inc.ClassFileManager$TransactionalClassFileManager.$anonfun$complete$5(ClassFileManager.scala:169)
Error: [info] [error] sbt.internal.inc.ClassFileManager$TransactionalClassFileManager.$anonfun$complete$5$adapted(ClassFileManager.scala:169)
Error: [info] [error] scala.collection.TraversableLike$WithFilter.$anonfun$foreach$1(TraversableLike.scala:877)
Error: [info] [error] scala.collection.mutable.HashMap.$anonfun$foreach$1(HashMap.scala:149)
Error: [info] [error] scala.collection.mutable.HashTable.foreachEntry(HashTable.scala:237)
Error: [info] [error] scala.collection.mutable.HashTable.foreachEntry$(HashTable.scala:230)
Error: [info] [error] scala.collection.mutable.HashMap.foreachEntry(HashMap.scala:44)
Error: [info] [error] scala.collection.mutable.HashMap.foreach(HashMap.scala:149)
Error: [info] [error] scala.collection.TraversableLike$WithFilter.foreach(TraversableLike.scala:876)
Error: [info] [error] sbt.internal.inc.ClassFileManager$TransactionalClassFileManager.complete(ClassFileManager.scala:169)
Error: [info] [error] xsbti.compile.WrappedClassFileManager.complete(WrappedClassFileManager.java:53)
Error: [info] [error] sbt.internal.inc.Incremental$.manageClassfiles(Incremental.scala:158)
Error: [info] [error] sbt.internal.inc.Incremental$.compile(Incremental.scala:92)
Error: [info] [error] sbt.internal.inc.IncrementalCompile$.apply(Compile.scala:75)
Error: [info] [error] sbt.internal.inc.IncrementalCompilerImpl.compileInternal(IncrementalCompilerImpl.scala:348)
Error: [info] [error] sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileIncrementally$1(IncrementalCompilerImpl.scala:301)
Error: [info] [error] sbt.internal.inc.IncrementalCompilerImpl.handleCompilationError(IncrementalCompilerImpl.scala:168)
Error: [info] [error] sbt.internal.inc.IncrementalCompilerImpl.compileIncrementally(IncrementalCompilerImpl.scala:248)
Error: [info] [error] sbt.internal.inc.IncrementalCompilerImpl.compile(IncrementalCompilerImpl.scala:74)
Error: [info] [error] sbt.Defaults$.compileIncrementalTaskImpl(Defaults.scala:1757)
Error: [info] [error] sbt.Defaults$.$anonfun$compileIncrementalTask$1(Defaults.scala:1730)
Error: [info] [error] scala.Function1.$anonfun$compose$1(Function1.scala:49)
Error: [info] [error] sbt.internal.util.$tilde$greater.$anonfun$$u2219$1(TypeFunctions.scala:62)
Error: [info] [error] sbt.std.Transform$$anon$4.work(Transform.scala:67)
Error: [info] [error] sbt.Execute.$anonfun$submit$2(Execute.scala:280)
Error: [info] [error] sbt.internal.util.ErrorHandling$.wideConvert(ErrorHandling.scala:19)
Error: [info] [error] sbt.Execute.work(Execute.scala:289)
Error: [info] [error] sbt.Execute.$anonfun$submit$1(Execute.scala:280)
Error: [info] [error] sbt.ConcurrentRestrictions$$anon$4.$anonfun$submitValid$1(ConcurrentRestrictions.scala:178)
Error: [info] [error] sbt.CompletionService$$anon$2.call(CompletionService.scala:37)
Error: [info] [error] java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
Error: [info] [error] java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
Error: [info] [error] java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
Error: [info] [error] java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
Error: [info] [error] java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
Error: [info] [error] java.base/java.lang.Thread.run(Thread.java:829)
Error: [info] [error]            
[info] [info] Running scalafix on 1 Scala sources
Error: [info] [error] java.lang.IllegalArgumentException: requirement failed: Source file '/tmp/sbt_b75cce04/inconfig/example/target/scala-2.12/classes.bak/sbt8052693458003257266.class' does not exist.
Error: [info] [error] 	at scala.Predef$.require(Predef.scala:281)
Error: [info] [error] 	at sbt.io.IO$.copyFile(IO.scala:860)
Error: [info] [error] 	at sbt.io.IO$.move(IO.scala:1083)
Error: [info] [error] 	at sbt.internal.inc.ClassFileManager$TransactionalClassFileManager.$anonfun$complete$5(ClassFileManager.scala:169)
Error: [info] [error] 	at sbt.internal.inc.ClassFileManager$TransactionalClassFileManager.$anonfun$complete$5$adapted(ClassFileManager.scala:169)
Error: [info] [error] 	at scala.collection.TraversableLike$WithFilter.$anonfun$foreach$1(TraversableLike.scala:877)
Error: [info] [error] 	at scala.collection.mutable.HashMap.$anonfun$foreach$1(HashMap.scala:149)
Error: [info] [error] 	at scala.collection.mutable.HashTable.foreachEntry(HashTable.scala:237)
Error: [info] [error] 	at scala.collection.mutable.HashTable.foreachEntry$(HashTable.scala:230)
Error: [info] [error] 	at scala.collection.mutable.HashMap.foreachEntry(HashMap.scala:44)
Error: [info] [error] 	at scala.collection.mutable.HashMap.foreach(HashMap.scala:149)
Error: [info] [error] 	at scala.collection.TraversableLike$WithFilter.foreach(TraversableLike.scala:876)
Error: [info] [error] 	at sbt.internal.inc.ClassFileManager$TransactionalClassFileManager.complete(ClassFileManager.scala:169)
Error: [info] [error] 	at xsbti.compile.WrappedClassFileManager.complete(WrappedClassFileManager.java:53)
Error: [info] [error] 	at sbt.internal.inc.Incremental$.manageClassfiles(Incremental.scala:158)
Error: [info] [error] 	at sbt.internal.inc.Incremental$.compile(Incremental.scala:92)
Error: [info] [error] 	at sbt.internal.inc.IncrementalCompile$.apply(Compile.scala:75)
Error: [info] [error] 	at sbt.internal.inc.IncrementalCompilerImpl.compileInternal(IncrementalCompilerImpl.scala:348)
Error: [info] [error] 	at sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileIncrementally$1(IncrementalCompilerImpl.scala:301)
Error: [info] [error] 	at sbt.internal.inc.IncrementalCompilerImpl.handleCompilationError(IncrementalCompilerImpl.scala:168)
Error: [info] [error] 	at sbt.internal.inc.IncrementalCompilerImpl.compileIncrementally(IncrementalCompilerImpl.scala:248)
Error: [info] [error] 	at sbt.internal.inc.IncrementalCompilerImpl.compile(IncrementalCompilerImpl.scala:74)
Error: [info] [error] 	at sbt.Defaults$.compileIncrementalTaskImpl(Defaults.scala:1757)
Error: [info] [error] 	at sbt.Defaults$.$anonfun$compileIncrementalTask$1(Defaults.scala:1730)
Error: [info] [error] 	at scala.Function1.$anonfun$compose$1(Function1.scala:49)
Error: [info] [error] 	at sbt.internal.util.$tilde$greater.$anonfun$$u2219$1(TypeFunctions.scala:62)
Error: [info] [error] 	at sbt.std.Transform$$anon$4.work(Transform.scala:67)
Error: [info] [error] 	at sbt.Execute.$anonfun$submit$2(Execute.scala:280)
Error: [info] [error] 	at sbt.internal.util.ErrorHandling$.wideConvert(ErrorHandling.scala:19)
Error: [info] [error] 	at sbt.Execute.work(Execute.scala:289)
Error: [info] [error] 	at sbt.Execute.$anonfun$submit$1(Execute.scala:280)
Error: [info] [error] 	at sbt.ConcurrentRestrictions$$anon$4.$anonfun$submitValid$1(ConcurrentRestrictions.scala:178)
Error: [info] [error] 	at sbt.CompletionService$$anon$2.call(CompletionService.scala:37)
Error: [info] [error] 	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
Error: [info] [error] 	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
Error: [info] [error] 	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
Error: [info] [error] 	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
Error: [info] [error] 	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
Error: [info] [error] 	at java.base/java.lang.Thread.run(Thread.java:829)
Error: [info] [error] (example / IntegrationTest / compileIncremental) java.lang.IllegalArgumentException: requirement failed: Source file '/tmp/sbt_b75cce04/inconfig/example/target/scala-2.12/classes.bak/sbt8052693458003257266.class' does not exist.
Error: [info] [error] Total time: 0 s, completed Apr 22, 2024, 8:11:51 PM
Error:  x sbt-scalafix / inconfig 
Error:   Cause of test exception: {line 7}  Command failed: example/scalafixAll failed

@@ -56,7 +56,7 @@ scriptedSbt := {
if (jdk >= 21)
"1.9.0" // first release that supports JDK21
else
(pluginCrossBuild / sbtVersion).value
"1.3.3" // get https://github.com/sbt/sbt/issues/1673 to avoid race conditions
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #411 (comment). I can't tell for sure, but I think that storing analysis files slightly changed the timing of concurrent executions of compile, causing that new transient failure.

@bjaglin bjaglin marked this pull request as ready for review April 22, 2024 22:22
@bjaglin bjaglin force-pushed the store-analysis branch 2 times, most recently from ccdab2b to 67ce03d Compare April 23, 2024 07:04
@bjaglin bjaglin changed the title prevent recompilation after cold scalafix invocations on sbt 1.4+ prevent full recompilation after cold scalafix invocations Apr 23, 2024
@bjaglin bjaglin changed the title prevent full recompilation after cold scalafix invocations avoid full compilation after cold scalafix invocations Apr 23, 2024
@bjaglin bjaglin changed the title avoid full compilation after cold scalafix invocations avoid full (re)compilation after cold scalafix invocations Apr 23, 2024
Comment on lines +5 to +6
> compile
> checkLastCompilationCached
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

users with no fatal warnings calling scalafix before compile no longer have to compile everything twice

Comment on lines +23 to +26
> scalafix --check RemoveUnused
-> checkLastCompilationCached
> scalafix --check RemoveUnused
> checkLastCompilationCached
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

successive scalafix invocations on a cold environment (where no compile was run successfully) now benefit from incremental compilation, no matter which flags are used in the build

Comment on lines -41 to -44
TaskKey[Unit]("checkZincAnalysisPresent") := {
val isPresent = (Compile / previousCompile).value.analysis.isPresent()
assert(isPresent, "zinc analysis not found")
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this helper for whitebox testing since the blackbox one is enough

The Zinc analysis is now saved on compilations triggered by scalafix
invocations. This allows subsequent scalafix invocations not to re-trigger
compilations, and subsequent regular compilations to be either incremental (in
case the build contains `-Xfatal-warnings` or alike) or a full cache hit
otherwise.
@bjaglin bjaglin merged commit 813057e into scalacenter:main Apr 23, 2024
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant