diff --git a/integration/failure/compile-error/src/CompileErrorTests.scala b/integration/failure/compile-error/src/CompileErrorTests.scala index 4a5ab1247e36..e010f2c6c7ec 100644 --- a/integration/failure/compile-error/src/CompileErrorTests.scala +++ b/integration/failure/compile-error/src/CompileErrorTests.scala @@ -10,25 +10,37 @@ object CompileErrorTests extends UtestIntegrationTestSuite { val res = tester.eval("foo.scalaVersion") assert(!res.isSuccess) + val normalizedError = res.err + .replace(tester.workspacePath.toString, "") + .linesIterator + .filter(s => s.startsWith("[error] ")) + .toVector + .map(_.replace('\\', '/')) - locally { - assert(res.err.contains("""bar.mill:15:9""")) - assert(res.err.contains("""println(doesntExist)""")) - assert(res.err.contains("""Not found: doesntExist""")) - } - - locally { - assert(res.err.contains("""qux.mill:4:34""")) - assert(res.err.contains("""myMsg.substring("0")""")) - assert(res.err.contains("""Found: ("0" : String)""")) - assert(res.err.contains("""Required: Int""")) - } - - locally { - assert(res.err.contains("""build.mill:11:5""")) - assert(res.err.contains("""foo.noSuchMethod""")) - assert(res.err.contains("""value noSuchMethod is not a member""")) - } + // For now some error messages still show generated/mangled code; not ideal, but it'll do + assertGoldenLiteral( + normalizedError, + Vector( + "[error] -- [E007] Type Mismatch Error: /qux.mill:4:33 ", + "[error] 4 |def myOtherMsg = myMsg.substring(\"0\")", + "[error] | ^^^", + "[error] | Found: (\"0\" : String)", + "[error] | Required: Int", + "[error] |", + "[error] | longer explanation available when compiling with `-explain`", + "[error] -- [E006] Not Found Error: /bar.mill:15:8 ", + "[error] 15 |println(doesntExist)", + "[error] | ^^^^^^^^^^^", + "[error] | Not found: doesntExist", + "[error] |", + "[error] | longer explanation available when compiling with `-explain`", + "[error] -- [E008] Not Found Error: /build.mill:11:4 ", + "[error] 11 |foo.noSuchMethod", + "[error] |^^^^^^^^^^^^^^^^", + "[error] |value noSuchMethod is not a member of object package_.this.foo", + "[error] three errors found" + ) + ) } } } diff --git a/integration/failure/root-module-compile-error/src/RootModuleCompileErrorTests.scala b/integration/failure/root-module-compile-error/src/RootModuleCompileErrorTests.scala index 84c4f4200dc3..e9dff403bdea 100644 --- a/integration/failure/root-module-compile-error/src/RootModuleCompileErrorTests.scala +++ b/integration/failure/root-module-compile-error/src/RootModuleCompileErrorTests.scala @@ -12,71 +12,68 @@ object RootModuleCompileErrorTests extends UtestIntegrationTestSuite { assert(!res.isSuccess) - locally { - // For now these error messages still show generated/mangled code; not ideal, but it'll do - assert(res.err.contains("""build.mill:7:67""")) - assert(res.err.contains("""Not found: type UnknownRootModule""")) - assert(res.err.contains( - """abstract class package_ extends _root_.mill.util.MainRootModule, UnknownRootModule {""" - )) - assert( - res.err.contains(""" ^^^^^^^^^^^^^^^^^""") - ) - } - - locally { - // For now these error messages still show generated/mangled code; not ideal, but it'll do - assert(res.err.replace('\\', '/').contains("""foo/package.mill:6:96""")) - assert(res.err.contains("""Not found: type UnknownFooModule""")) - assert(res.err.contains( - """abstract class package_ extends _root_.mill.api.internal.SubfolderModule(build.millDiscover), UnknownFooModule {""" - )) - assert(res.err.contains( - """ ^^^^^^^^^^^^^^^^""" - )) - } - - locally { - assert(res.err.contains("""build.mill:8:22""")) - assert(res.err.contains("""Not found: unknownRootInternalDef""")) - assert(res.err.contains("""def scalaVersion = unknownRootInternalDef""")) - assert(res.err.contains(""" ^^^^^^^^^^^^^^^^^^^^^^""")) - } - - locally { - assert(res.err.contains("""build.mill:5:23""")) - assert(res.err.contains("""Not found: type UnknownBeforeModule""")) - assert(res.err.contains("""object before extends UnknownBeforeModule""")) - assert(res.err.contains(""" ^^^^^^^^^^^^^^^^^^^""")) - } + val normalizedError = res.err + .replace(workspacePath.toString, "") + .linesIterator + .filter(s => s.startsWith("[error] ")) + .toVector + .map(_.replace('\\', '/')) - locally { - assert(res.err.contains("""build.mill:12:22""")) - assert(res.err.contains("""Not found: type UnknownAfterModule""")) - assert(res.err.contains("""object after extends UnknownAfterModule""")) - assert(res.err.contains(""" ^^^^^^^^^^^^^^^^^^""")) - } - - locally { - assert(res.err.replace('\\', '/').contains("""foo/package.mill:7:22""")) - assert(res.err.contains("""Not found: unknownFooInternalDef""")) - assert(res.err.contains("""def scalaVersion = unknownFooInternalDef""")) - assert(res.err.contains(""" ^^^^^^^^^^^^^^^^^^^^^""")) - } - - locally { - assert(res.err.replace('\\', '/').contains("""foo/package.mill:4:23""")) - assert(res.err.contains("""Not found: type UnknownBeforeFooModule""")) - assert(res.err.contains("""object before extends UnknownBeforeFooModule""")) - assert(res.err.contains(""" ^^^^^^^^^^^^^^^^^^^^^^""")) - } - - locally { - assert(res.err.replace('\\', '/').contains("""foo/package.mill:11:22""")) - assert(res.err.contains("""Not found: type UnknownAfterFooModule""")) - assert(res.err.contains("""object after extends UnknownAfterFooModule""")) - assert(res.err.contains(""" ^^^^^^^^^^^^^^^^^^^^^""")) - } + // For now some error messages still show generated/mangled code; not ideal, but it'll do + assertGoldenLiteral( + normalizedError, + Vector( + "[error] -- [E006] Not Found Error: /build.mill:12:75 ", + "[error] 12 |", + "[error] | ^^^^^^^^^^^^^^^^^", + "[error] | Not found: type UnknownRootModule", + "[error] |", + "[error] | longer explanation available when compiling with `-explain`", + "[error] -- [E006] Not Found Error: /foo/package.mill:11:103 ", + "[error] 11 |", + "[error] | ^^^^^^^^^^^^^^^^", + "[error] | Not found: type UnknownFooModule", + "[error] |", + "[error] | longer explanation available when compiling with `-explain`", + "[error] -- [E006] Not Found Error: /build.mill:5:22 ", + "[error] 5 |object before extends UnknownBeforeModule", + "[error] | ^^^^^^^^^^^^^^^^^^^", + "[error] | Not found: type UnknownBeforeModule", + "[error] |", + "[error] | longer explanation available when compiling with `-explain`", + "[error] -- [E006] Not Found Error: /build.mill:8:21 ", + "[error] 8 | def scalaVersion = unknownRootInternalDef", + "[error] | ^^^^^^^^^^^^^^^^^^^^^^", + "[error] | Not found: unknownRootInternalDef", + "[error] |", + "[error] | longer explanation available when compiling with `-explain`", + "[error] -- [E006] Not Found Error: /build.mill:11:21 ", + "[error] 11 |object after extends UnknownAfterModule", + "[error] | ^^^^^^^^^^^^^^^^^^", + "[error] | Not found: type UnknownAfterModule", + "[error] |", + "[error] | longer explanation available when compiling with `-explain`", + "[error] -- [E006] Not Found Error: /foo/package.mill:4:22 ", + "[error] 4 |object before extends UnknownBeforeFooModule", + "[error] | ^^^^^^^^^^^^^^^^^^^^^^", + "[error] | Not found: type UnknownBeforeFooModule", + "[error] |", + "[error] | longer explanation available when compiling with `-explain`", + "[error] -- [E006] Not Found Error: /foo/package.mill:7:21 ", + "[error] 7 | def scalaVersion = unknownFooInternalDef", + "[error] | ^^^^^^^^^^^^^^^^^^^^^", + "[error] | Not found: unknownFooInternalDef", + "[error] |", + "[error] | longer explanation available when compiling with `-explain`", + "[error] -- [E006] Not Found Error: /foo/package.mill:10:21 ", + "[error] 10 |object after extends UnknownAfterFooModule", + "[error] | ^^^^^^^^^^^^^^^^^^^^^", + "[error] | Not found: type UnknownAfterFooModule", + "[error] |", + "[error] | longer explanation available when compiling with `-explain`", + "[error] 8 errors found" + ) + ) } } } diff --git a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala index 5ee87af6d17a..934ffc7b64c4 100644 --- a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala +++ b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala @@ -236,7 +236,7 @@ object SemanticDbJavaModule extends ExternalModule with CoursierModule { ) } - private val userCodeStartMarker = "//SOURCECODE_ORIGINAL_CODE_START_MARKER" + private val userCodeStartMarker = "///SOURCE_CODE_START" private def postProcessed( generatedSourceSemdb: os.Path, @@ -255,7 +255,7 @@ object SemanticDbJavaModule extends ExternalModule with CoursierModule { val generatedSource = sourceroot / generatedSourceSubPath val generatedSourceLines = os.read.lines(generatedSource) val source = generatedSourceLines - .collectFirst { case s"//SOURCECODE_ORIGINAL_FILE_PATH=$rest" => os.Path(rest.trim) } + .collectFirst { case s"///SOURCE_CODE_START:$rest" => os.Path(rest.trim) } .getOrElse { sys.error(s"Cannot get original source from generated source $generatedSource") } diff --git a/libs/javalib/worker/src/mill/javalib/zinc/PositionMapper.scala b/libs/javalib/worker/src/mill/javalib/zinc/PositionMapper.scala deleted file mode 100644 index 7d05e0dd528e..000000000000 --- a/libs/javalib/worker/src/mill/javalib/zinc/PositionMapper.scala +++ /dev/null @@ -1,114 +0,0 @@ -package mill.javalib.zinc - -import mill.constants.CodeGenConstants -import xsbti.VirtualFile - -import java.nio.charset.StandardCharsets -import scala.jdk.CollectionConverters.* - -object PositionMapper { - - import sbt.util.InterfaceUtil - - private val userCodeStartMarker = "//SOURCECODE_ORIGINAL_CODE_START_MARKER" - - /** Transforms positions of problems if coming from a build file. */ - private def lookup(buildSources: Map[String, xsbti.Position => xsbti.Position])( - oldPos: xsbti.Position - ): xsbti.Position = { - val src = oldPos.sourcePath() - if src.isPresent then { - buildSources.get(src.get()) match { - case Some(f) => f(oldPos) - case _ => oldPos - } - } else { - oldPos - } - } - - def create(sources: Array[VirtualFile]) - : (Map[os.Path, os.Path], Option[xsbti.Position => xsbti.Position]) = { - val buildSources0 = { - def isBuild(vf: VirtualFile) = - CodeGenConstants.buildFileExtensions.asScala.exists(ex => - vf.id().endsWith(s".$ex") - ) - - sources.collect({ - case vf if isBuild(vf) => - val str = new String(vf.input().readAllBytes(), StandardCharsets.UTF_8) - - val lines = str.linesWithSeparators.toVector - val adjustedFile = lines - .collectFirst { case s"//SOURCECODE_ORIGINAL_FILE_PATH=$rest" => rest.trim } - .getOrElse(sys.error(vf.id())) - - (vf.id(), adjustedFile, remap(lines, adjustedFile)) - }) - } - - val map = buildSources0 - .map { - case (generated, original, _) => - os.Path(generated) -> os.Path(original) - } - .toMap - val lookupOpt = Option.when(buildSources0.nonEmpty) { - lookup(buildSources0.map { case (generated, _, f) => (generated, f) }.toMap) - } - (map, lookupOpt) - } - - private def remap( - lines: Vector[String], - adjustedFile: String - ): xsbti.Position => xsbti.Position = { - val markerLine = lines.indexWhere(_.startsWith(userCodeStartMarker)) - - val topWrapperLen = lines.take(markerLine + 1).map(_.length).sum - - val originPath = Some(adjustedFile) - val originFile = Some(java.nio.file.Paths.get(adjustedFile).toFile) - - def userCode(offset: java.util.Optional[Integer]): Boolean = - intValue(offset, -1) > topWrapperLen - - def inner(pos0: xsbti.Position): xsbti.Position = { - if userCode(pos0.startOffset()) || userCode(pos0.offset()) then { - val IArray(line, offset, startOffset, endOffset, startLine, endLine) = - IArray( - pos0.line(), - pos0.offset(), - pos0.startOffset(), - pos0.endOffset(), - pos0.startLine(), - pos0.endLine() - ) - .map(intValue(_, 1) - 1) - - val (baseLine, baseOffset) = (markerLine, topWrapperLen) - - InterfaceUtil.position( - line0 = Some(line - baseLine), - content = pos0.lineContent(), - offset0 = Some(offset - baseOffset), - pointer0 = InterfaceUtil.jo2o(pos0.pointer()), - pointerSpace0 = InterfaceUtil.jo2o(pos0.pointerSpace()), - sourcePath0 = originPath, - sourceFile0 = originFile, - startOffset0 = Some(startOffset - baseOffset), - endOffset0 = Some(endOffset - baseOffset), - startLine0 = Some(startLine - baseLine), - startColumn0 = InterfaceUtil.jo2o(pos0.startColumn()), - endLine0 = Some(endLine - baseLine), - endColumn0 = InterfaceUtil.jo2o(pos0.endColumn()) - ) - } else { - pos0 - } - } - - inner - } -} diff --git a/libs/javalib/worker/src/mill/javalib/zinc/TransformingReporter.scala b/libs/javalib/worker/src/mill/javalib/zinc/TransformingReporter.scala deleted file mode 100644 index d7744bbcbd8a..000000000000 --- a/libs/javalib/worker/src/mill/javalib/zinc/TransformingReporter.scala +++ /dev/null @@ -1,211 +0,0 @@ -package mill.javalib.zinc - -private trait TransformingReporter( - color: Boolean, - optPositionMapper: (xsbti.Position => xsbti.Position) | Null -) extends xsbti.Reporter { - - // Overriding this is necessary because for some reason the LoggedReporter doesn't transform positions - // of Actions and DiagnosticRelatedInformation - abstract override def log(problem0: xsbti.Problem): Unit = { - val localMapper = optPositionMapper - val problem = { - if localMapper == null then problem0 - else TransformingReporter.transformProblem(color, problem0, localMapper) - } - super.log(problem) - } -} - -private object TransformingReporter { - - import sbt.util.InterfaceUtil - - import scala.jdk.CollectionConverters.given - - /** implements a transformation that returns the same object if the mapper has no effect. */ - private def transformProblem( - color: Boolean, - problem0: xsbti.Problem, - mapper: xsbti.Position => xsbti.Position - ): xsbti.Problem = { - val pos0 = problem0.position() - val related0 = problem0.diagnosticRelatedInformation() - val actions0 = problem0.actions() - val pos = mapper(pos0) - val related = transformRelateds(related0, mapper) - val actions = transformActions(actions0, mapper) - val posIsNew = pos ne pos0 - if posIsNew || (related ne related0) || (actions ne actions0) then - val rendered = { - // if we transformed the position, then we must re-render the message - if posIsNew then Some(dottyStyleMessage(color, problem0, pos)) - else InterfaceUtil.jo2o(problem0.rendered()) - } - InterfaceUtil.problem( - cat = problem0.category(), - pos = pos, - msg = problem0.message(), - sev = problem0.severity(), - rendered = rendered, - diagnosticCode = InterfaceUtil.jo2o(problem0.diagnosticCode()), - diagnosticRelatedInformation = anyToList(related), - actions = anyToList(actions) - ) - else - problem0 - } - - private type JOrSList[T] = java.util.List[T] | List[T] - - private def anyToList[T](ts: JOrSList[T]): List[T] = ts match { - case ts: List[T] => ts - case ts: java.util.List[T] => ts.asScala.toList - } - - /** Render the message in the style of dotty */ - private def dottyStyleMessage( - color: Boolean, - problem0: xsbti.Problem, - pos: xsbti.Position - ): String = { - val base = problem0.message() - val severity = problem0.severity() - - def shade(msg: String) = - if color then - severity match { - case xsbti.Severity.Error => Console.RED + msg + Console.RESET - case xsbti.Severity.Warn => Console.YELLOW + msg + Console.RESET - case xsbti.Severity.Info => Console.BLUE + msg + Console.RESET - } - else msg - - val normCode = { - problem0.diagnosticCode().filter(_.code() != "-1").map({ inner => - val prefix = s"[E${inner.code()}] " - inner.explanation().map(e => - s"$prefix$e: " - ).orElse(prefix) - }).orElse("") - } - - val optPath = InterfaceUtil.jo2o(pos.sourcePath()).map { path => - val line0 = intValue(pos.line(), -1) - val pointer0 = intValue(pos.pointer(), -1) - if line0 >= 0 && pointer0 >= 0 then - s"$path:$line0:${pointer0 + 1}" - else - path - } - - val normHeader = optPath.map(path => - s"${shade(s"-- $normCode$path")}\n" - ).getOrElse("") - - val optSnippet = { - val snip = pos.lineContent() - val space = pos.pointerSpace().orElse("") - val pointer = intValue(pos.pointer(), -99) - val endCol = intValue(pos.endColumn(), pointer + 1) - if snip.nonEmpty && space.nonEmpty && pointer >= 0 && endCol >= 0 then - val arrowCount = math.max(1, math.min(endCol - pointer, snip.length - space.length)) - Some( - s"""$snip - |$space${"^" * arrowCount}""".stripMargin - ) - else - None - } - - val content = optSnippet.match { - case Some(snippet) => - val initial = { - s"""$snippet - |$base - |""".stripMargin - } - val snippetLine = intValue(pos.line(), -1) - if snippetLine >= 0 then { - // add margin with line number - val lines = initial.linesWithSeparators.toVector - val pre = snippetLine.toString - val rest0 = " " * pre.length - val rest = pre +: Vector.fill(lines.size - 1)(rest0) - rest.lazyZip(lines).map((pre, line) => shade(s"$pre │") + line).mkString - } else { - initial - } - case None => - base - } - - normHeader + content - } - - /** Implements a transformation that returns the same list if the mapper has no effect */ - private def transformActions( - actions0: java.util.List[xsbti.Action], - mapper: xsbti.Position => xsbti.Position - ): JOrSList[xsbti.Action] = { - if actions0.iterator().asScala.exists(a => - a.edit().changes().iterator().asScala.exists(e => - mapper(e.position()) ne e.position() - ) - ) - then { - actions0.iterator().asScala.map(transformAction(_, mapper)).toList - } else { - actions0 - } - } - - /** Implements a transformation that returns the same list if the mapper has no effect */ - private def transformRelateds( - related0: java.util.List[xsbti.DiagnosticRelatedInformation], - mapper: xsbti.Position => xsbti.Position - ): JOrSList[xsbti.DiagnosticRelatedInformation] = { - - if related0.iterator().asScala.exists(r => mapper(r.position()) ne r.position()) then - related0.iterator().asScala.map(transformRelated(_, mapper)).toList - else - related0 - } - - private def transformRelated( - related0: xsbti.DiagnosticRelatedInformation, - mapper: xsbti.Position => xsbti.Position - ): xsbti.DiagnosticRelatedInformation = { - InterfaceUtil.diagnosticRelatedInformation(mapper(related0.position()), related0.message()) - } - - private def transformAction( - action0: xsbti.Action, - mapper: xsbti.Position => xsbti.Position - ): xsbti.Action = { - InterfaceUtil.action( - title = action0.title(), - description = InterfaceUtil.jo2o(action0.description()), - edit = transformEdit(action0.edit(), mapper) - ) - } - - private def transformEdit( - edit0: xsbti.WorkspaceEdit, - mapper: xsbti.Position => xsbti.Position - ): xsbti.WorkspaceEdit = { - InterfaceUtil.workspaceEdit( - edit0.changes().iterator().asScala.map(transformTEdit(_, mapper)).toList - ) - } - - private def transformTEdit( - edit0: xsbti.TextEdit, - mapper: xsbti.Position => xsbti.Position - ): xsbti.TextEdit = { - InterfaceUtil.textEdit( - position = mapper(edit0.position()), - newText = edit0.newText() - ) - } -} diff --git a/libs/javalib/worker/src/mill/javalib/zinc/ZincWorker.scala b/libs/javalib/worker/src/mill/javalib/zinc/ZincWorker.scala index fc73d9ec45cf..68282c02155b 100644 --- a/libs/javalib/worker/src/mill/javalib/zinc/ZincWorker.scala +++ b/libs/javalib/worker/src/mill/javalib/zinc/ZincWorker.scala @@ -382,13 +382,12 @@ class ZincWorker( def mkNewReporter(mapper: (xsbti.Position => xsbti.Position) | Null) = reporter match { case None => - new ManagedLoggedReporter(maxErrors, logger) with RecordingReporter - with TransformingReporter(ctx.logPromptColored, mapper) {} + new ManagedLoggedReporter(maxErrors, logger) with RecordingReporter {} + case Some(forwarder) => new ManagedLoggedReporter(maxErrors, logger) with ForwardingReporter(forwarder) - with RecordingReporter - with TransformingReporter(ctx.logPromptColored, mapper) {} + with RecordingReporter {} } val analysisMap0 = upstreamCompileOutput.map(c => c.classes.path -> c.analysisFile).toMap @@ -443,8 +442,7 @@ class ZincWorker( scalacOptions } - val (originalSourcesMap, posMapperOpt) = PositionMapper.create(virtualSources) - val newReporter = mkNewReporter(posMapperOpt.orNull) + val newReporter = mkNewReporter(null) val inputs = incrementalCompiler.inputs( classpath = classpath, @@ -510,11 +508,7 @@ class ZincWorker( Result.Failure(e.toString) } finally { for (rep <- reporter) { - for (f <- sources) { - rep.fileVisited(f.toNIO) - for (f0 <- originalSourcesMap.get(f)) - rep.fileVisited(f0.toNIO) - } + for (f <- sources) rep.fileVisited(f.toNIO) rep.finish() } previousScalaColor match { diff --git a/runner/meta/src/mill/meta/AsmPositionUpdater.scala b/runner/meta/src/mill/meta/AsmPositionUpdater.scala deleted file mode 100644 index 701d54e344c5..000000000000 --- a/runner/meta/src/mill/meta/AsmPositionUpdater.scala +++ /dev/null @@ -1,43 +0,0 @@ -package mill.meta - -// originally based on https://github.com/VirtusLab/scala-cli/blob/67db91cfbaa74de806fd1d5ed00096affa0125c0/modules/build/src/main/scala/scala/build/postprocessing/AsmPositionUpdater.scala - -import org.objectweb.asm -import java.io.InputStream - -object AsmPositionUpdater { - - private class LineNumberTableMethodVisitor( - lineNumberOffset: Int, - delegate: asm.MethodVisitor - ) extends asm.MethodVisitor(asm.Opcodes.ASM9, delegate) { - override def visitLineNumber(line: Int, start: asm.Label): Unit = - super.visitLineNumber(math.max(0, line + lineNumberOffset), start) - } - - private class LineNumberTableClassVisitor( - lineNumberOffset: Int, - cw: asm.ClassWriter - ) extends asm.ClassVisitor(asm.Opcodes.ASM9, cw) { - - override def visitMethod( - access: Int, - name: String, - descriptor: String, - signature: String, - exceptions: Array[String] - ): asm.MethodVisitor = { - val main = super.visitMethod(access, name, descriptor, signature, exceptions) - new LineNumberTableMethodVisitor(lineNumberOffset, main) - - } - } - - def postProcess(lineNumberOffset: Int, clsInputStream: InputStream): Array[Byte] = { - val reader = new asm.ClassReader(clsInputStream) - val writer = new asm.ClassWriter(reader, 0) - val checker = new LineNumberTableClassVisitor(lineNumberOffset, writer) - reader.accept(checker, 0) - writer.toByteArray - } -} diff --git a/runner/meta/src/mill/meta/CodeGen.scala b/runner/meta/src/mill/meta/CodeGen.scala index 7a85789741cf..f869351fcad1 100644 --- a/runner/meta/src/mill/meta/CodeGen.scala +++ b/runner/meta/src/mill/meta/CodeGen.scala @@ -18,6 +18,7 @@ object CodeGen { def generateWrappedAndSupportSources( projectRoot: os.Path, allScriptCode: Map[os.Path, String], + allPackageStatements: Map[os.Path, String], wrappedDest: os.Path, supportDest: os.Path, millTopLevelProjectRoot: os.Path, @@ -29,7 +30,6 @@ object CodeGen { // Provide `build` as an alias to the root `build_.package_`, since from the user's // perspective it looks like they're writing things that live in `package build`, // but at compile-time we rename things, we so provide an alias to preserve the fiction - val aliasImports = "import build_.{package_ => build}" for (scriptPath <- scriptSources) { val scriptFolderPath = scriptPath / os.up @@ -66,6 +66,32 @@ object CodeGen { } .mkString("\n ") + val aliasImports = Seq( + // Provide `build` as an alias to the root `build_.package_`, since from the user's + // perspective it looks like they're writing things that live in `package build`, + // but at compile-time we rename things, we so provide an alias to preserve the fiction + "import build_.{package_ => build}" + ).mkString("\n") + + val strippedPackageStatementComment = allPackageStatements.get(scriptPath) match { + // Add another comment after the marker comment to substitute any package statement + // that was stripped during codegen and ensure the offsets line up properly + case None => "\n" + case Some("package ") => "\n" + case Some(s) => "\n//" + s.drop(2) + } + + val markerComment = s"///SOURCE_CODE_START:$scriptPath" + strippedPackageStatementComment + + val siblingScripts = scriptSources + .filter(_ != scriptPath) + .filter(p => (p / os.up) == (scriptPath / os.up)) + .map(_.last.split('.').head + "_") + + val importSiblingScripts = siblingScripts + .filter(s => s != "build_" && s != "package_") + .map(s => s"import $pkg.${backtickWrap(s)}.*").mkString("\n") + if (scriptFolderPath == projectRoot) { val buildFileImplCode = generateBuildFileImpl(pkg) os.write.over( @@ -174,19 +200,6 @@ object CodeGen { val scriptCode = allScriptCode(scriptPath) - val markerComment = - s"""//SOURCECODE_ORIGINAL_FILE_PATH=$scriptPath - |//SOURCECODE_ORIGINAL_CODE_START_MARKER""".stripMargin - - val siblingScripts = scriptSources - .filter(_ != scriptPath) - .filter(p => (p / os.up) == (scriptPath / os.up)) - .map(_.last.split('.').head + "_") - - val importSiblingScripts = siblingScripts - .filter(s => s != "build_" && s != "package_") - .map(s => s"import $pkg.${backtickWrap(s)}.*").mkString("\n") - if (isBuildScript) { os.write.over(supportDestDir / "MillMiscInfo.scala", miscInfo, createFolders = true) } @@ -201,8 +214,7 @@ object CodeGen { |$importSiblingScripts | |object $wrapperName { - |$markerComment - |$scriptCode + |$markerComment$scriptCode |} | |export $wrapperName._ @@ -323,17 +335,8 @@ object CodeGen { case None => () } - objectData.finalStat match { - case Some((_, finalStat)) => - val statLines = finalStat.text.linesWithSeparators.toSeq - val fenced = Seq( - "", - if statLines.sizeIs > 1 then statLines.tail.mkString else finalStat.text - ).mkString(System.lineSeparator()) - newScriptCode = finalStat.applyTo(newScriptCode, fenced) - case None => () - } + var generatedStub: String = "" newScriptCode = objectData.parent.applyTo( newScriptCode, if (objectData.parent.text == null) { @@ -353,24 +356,32 @@ object CodeGen { else ", " // no separator found, just use `,` by default } - newParent + sep + objectData.parent.text + val stub = "_MillRootModuleParents" + .take(objectData.parent.text.length) + .padTo(objectData.parent.text.length, ' ') + + generatedStub = s"abstract class $stub extends $newParent$sep${objectData.parent.text}" + + stub } ) newScriptCode = objectData.name.applyTo(newScriptCode, CGConst.wrapperObjectName) - newScriptCode = objectData.obj.applyTo(newScriptCode, "abstract class") + newScriptCode = objectData.obj.applyTo(newScriptCode, "class") s"""$headerCode - |$markerComment - |$newScriptCode - |""".stripMargin + | + |$markerComment$newScriptCode""".stripMargin + + // Not sure why we need to mix System.lineSeparator and \n here, but it seems to + // result in the correct error position reporting for the following code on both + // windows and mac + System.lineSeparator + "\n" + generatedStub case None => s"""$headerCode |abstract class ${CGConst.wrapperObjectName} | extends $newParent { this: ${CGConst.wrapperObjectName}.type => - |$markerComment - |$scriptCode + |$markerComment$scriptCode |}""".stripMargin } diff --git a/runner/meta/src/mill/meta/FileImportGraph.scala b/runner/meta/src/mill/meta/FileImportGraph.scala index 1b0064538f3a..6e066a74a243 100644 --- a/runner/meta/src/mill/meta/FileImportGraph.scala +++ b/runner/meta/src/mill/meta/FileImportGraph.scala @@ -17,7 +17,11 @@ import mill.internal.Util.backtickWrap * @param metaBuild If `true`, a meta-build is enabled */ @internal -case class FileImportGraph(seenScripts: Map[os.Path, String], errors: Seq[String]) +case class FileImportGraph( + seenScripts: Map[os.Path, String], + errors: Seq[String], + seenPkgStatements: Map[os.Path, String] +) /** * Logic around traversing the `import $file` graph, extracting necessary info @@ -42,11 +46,11 @@ object FileImportGraph { walked: Seq[os.Path] ): FileImportGraph = { val seenScripts = mutable.Map.empty[os.Path, String] + val seenPkgStatements = mutable.Map.empty[os.Path, String] val errors = mutable.Buffer.empty[String] def processScript(s: os.Path, useDummy: Boolean = false): Unit = try { - val content = if (useDummy) "" else os.read(s) val fileName = s.relativeTo(topLevelProjectRoot).toString val buildHeaderError = @@ -79,6 +83,7 @@ object FileImportGraph { ) } seenScripts(s) = prefix + stmts.mkString + seenPkgStatements(s) = "package " + importSegments case Left(error) => seenScripts(s) = "" errors.append(error) @@ -95,7 +100,7 @@ object FileImportGraph { walked.foreach(processScript(_)) - new FileImportGraph(seenScripts.toMap, errors.toSeq) + new FileImportGraph(seenScripts.toMap, errors.toSeq, seenPkgStatements.toMap) } def findRootBuildFiles(projectRoot: os.Path) = { diff --git a/runner/meta/src/mill/meta/MillBuildRootModule.scala b/runner/meta/src/mill/meta/MillBuildRootModule.scala index 8a92a7371bf1..69354baf4214 100644 --- a/runner/meta/src/mill/meta/MillBuildRootModule.scala +++ b/runner/meta/src/mill/meta/MillBuildRootModule.scala @@ -39,7 +39,7 @@ trait MillBuildRootModule()(using override def moduleDir: os.Path = rootModuleInfo.projectRoot / os.up / millBuild private[mill] override def intellijModulePathJava: Path = (moduleDir / os.up).toNIO - override def scalaVersion: T[String] = BuildInfo.scalaVersion + override def scalaVersion: T[String] = "3.7.4-RC2" val scriptSourcesPaths = BuildCtx.watchValue { BuildCtx.withFilesystemCheckerDisabled { @@ -120,6 +120,7 @@ trait MillBuildRootModule()(using CodeGen.generateWrappedAndSupportSources( rootModuleInfo.projectRoot / os.up, parsed.seenScripts, + parsed.seenPkgStatements, wrapped, support, rootModuleInfo.topLevelProjectRoot, @@ -131,11 +132,7 @@ trait MillBuildRootModule()(using } def millBuildRootModuleResult = Task { - Tuple3( - runClasspath(), - compile().classes, - codeSignatures() - ) + Tuple3(runClasspath(), compile().classes, codeSignatures()) } def codeSignatures: T[Map[String, Int]] = Task(persistent = true) { @@ -251,7 +248,7 @@ trait MillBuildRootModule()(using ) override def scalacPluginMvnDeps: T[Seq[Dep]] = Seq( - mvn"com.lihaoyi:::scalac-mill-moduledefs-plugin:${Versions.millModuledefsVersion}" + mvn"com.lihaoyi:scalac-mill-moduledefs-plugin_3.7.3:${Versions.millModuledefsVersion}" .exclude("com.lihaoyi" -> "sourcecode_3") ) @@ -259,7 +256,11 @@ trait MillBuildRootModule()(using super.scalacOptions() ++ // This warning comes up for package names with dashes in them like "package build.`foo-bar`", // but Mill generally handles these fine, so no need to warn the user - Seq("-deprecation", "-Wconf:msg=will be encoded on the classpath:silent") + Seq( + "-deprecation", + "-Wconf:msg=will be encoded on the classpath:silent", + "-Ymagic-offset-header:SOURCE_CODE_START" + ) } /** Used in BSP IntelliJ, which can only work with directories */ @@ -309,60 +310,12 @@ trait MillBuildRootModule()(using javaRuntimeOptions = jOpts.runtime, reporter = Task.reporter.apply(hashCode), reportCachedProblems = zincReportCachedProblems() - ).map { - res => - // Perform the line-number updating in a copy of the classfiles, because - // mangling the original class files messes up zinc incremental compilation - val transformedClasses = Task.dest / "transformed-classes" - os.remove.all(transformedClasses) - os.copy(res.classes.path, transformedClasses) - - MillBuildRootModule.updateLineNumbers( - transformedClasses, - generatedScriptSources().wrapped.head.path - ) - - res.copy(classes = PathRef(transformedClasses)) - } + ) } } object MillBuildRootModule { - private def updateLineNumbers(classesDir: os.Path, generatedScriptSourcesPath: os.Path) = { - for (p <- os.walk(classesDir) if p.ext == "class") { - val rel = p.subRelativeTo(classesDir) - // Hack to reverse engineer the `.mill` name from the `.class` file name - val sourceNamePrefixOpt0 = rel.last match { - case s"${pre}_$_.class" => Some(pre) - case s"${pre}$$$_.class" => Some(pre) - case s"${pre}.class" => Some(pre) - case _ => None - } - - val sourceNamePrefixOpt = sourceNamePrefixOpt0 match { - case Some("package") if (rel / os.up) == os.rel / "build_" => Some("build") - case p => p - } - - for (prefix <- sourceNamePrefixOpt) { - val sourceFile = generatedScriptSourcesPath / rel / os.up / s"$prefix.mill" - if (os.exists(sourceFile)) { - - val lineNumberOffset = - os.read.lines(sourceFile).indexOf("//SOURCECODE_ORIGINAL_CODE_START_MARKER") + 1 - os.write.over( - p, - os - .read - .stream(p) - .readBytesThrough(stream => AsmPositionUpdater.postProcess(-lineNumberOffset, stream)) - ) - } - } - } - } - class BootstrapModule()(using rootModuleInfo: RootModule.Info ) extends MainRootModule() with MillBuildRootModule() {