diff --git a/build.sc b/build.sc
index 059df8f1666..b443973f256 100644
--- a/build.sc
+++ b/build.sc
@@ -1247,13 +1247,17 @@ object runner extends MillPublishScalaModule {
}
}
+object idea extends MillPublishScalaModule {
+ def moduleDeps = Seq(scalalib, runner)
+}
+
object dist extends MillPublishJavaModule {
def jar = dev.assembly()
- def moduleDeps = Seq(runner)
+ def moduleDeps = Seq(runner, idea)
}
object dev extends MillPublishScalaModule {
- def moduleDeps = Seq(runner)
+ def moduleDeps = Seq(runner, idea)
def testTransitiveDeps = super.testTransitiveDeps() ++ Seq(
runner.linenumbers.testDep(),
@@ -1437,7 +1441,7 @@ object docs extends Module {
PathRef(workDir / "build" / "site")
}
- def source0: Source = T.source(millSourcePath)
+ def source0 = T.source(millSourcePath)
def source = T {
os.copy(source0().path, T.dest, mergeFolders = true)
diff --git a/docs/modules/ROOT/pages/Installation_IDE_Support.adoc b/docs/modules/ROOT/pages/Installation_IDE_Support.adoc
index c17194efb38..0caf251ce1a 100644
--- a/docs/modules/ROOT/pages/Installation_IDE_Support.adoc
+++ b/docs/modules/ROOT/pages/Installation_IDE_Support.adoc
@@ -291,7 +291,7 @@ To generate IntelliJ IDEA project files into `.idea/`, run:
[source,bash]
----
-mill mill.scalalib.GenIdea/idea
+mill mill.idea.GenIdea/idea
----
== Updating Mill
diff --git a/idea/src/mill/idea/GenIdea.scala b/idea/src/mill/idea/GenIdea.scala
new file mode 100644
index 00000000000..31120c41c3c
--- /dev/null
+++ b/idea/src/mill/idea/GenIdea.scala
@@ -0,0 +1,25 @@
+package mill.idea
+
+import mill.T
+import mill.api.Result
+import mill.define.{Command, Discover, ExternalModule}
+import mill.eval.Evaluator
+
+import scala.util.control.NonFatal
+
+object GenIdea extends ExternalModule {
+
+ def idea(allBootstrapEvaluators: Evaluator.AllBootstrapEvaluators): Command[Unit] = T.command {
+ try {
+ Result.Success(GenIdeaImpl(
+ evaluators = Evaluator.allBootstrapEvaluators.value.value
+ ).run())
+ } catch {
+ case GenIdeaImpl.GenIdeaException(m) => Result.Failure(m)
+ case NonFatal(e) =>
+ Result.Exception(e, new Result.OuterStack(new java.lang.Exception().getStackTrace))
+ }
+ }
+
+ override lazy val millDiscover = Discover[this.type]
+}
diff --git a/idea/src/mill/idea/GenIdeaImpl.scala b/idea/src/mill/idea/GenIdeaImpl.scala
new file mode 100755
index 00000000000..0c5a8cec131
--- /dev/null
+++ b/idea/src/mill/idea/GenIdeaImpl.scala
@@ -0,0 +1,1003 @@
+package mill.idea
+
+import scala.collection.immutable
+import scala.util.Try
+import scala.xml.{Elem, MetaData, Node, NodeSeq, Null, UnprefixedAttribute}
+import coursier.core.compatibility.xmlParseDom
+import coursier.maven.Pom
+import mill.Agg
+import mill.api.Ctx
+import mill.api.{PathRef, Strict}
+import mill.define.{Ctx => _, _}
+import mill.eval.Evaluator
+import mill.main.BuildInfo
+import mill.scalalib.GenIdeaModule.{IdeaConfigFile, JavaFacet}
+import mill.scalalib.internal.JavaModuleUtils
+import mill.util.Classpath
+import mill.{T, scalalib}
+import os.{Path, SubPath}
+import mill.scalalib.{GenIdeaImpl => _, _}
+
+case class GenIdeaImpl(
+ private val evaluators: Seq[Evaluator]
+)(implicit ctx: Ctx) {
+ import GenIdeaImpl._
+
+ val workDir: Path = evaluators.head.rootModule.millSourcePath
+ val ideaDir: Path = workDir / ".idea"
+
+ val ideaConfigVersion = 4
+
+ def run(): Unit = {
+ val pp = new scala.xml.PrettyPrinter(999, 4)
+ val jdkInfo = extractCurrentJdk(ideaDir / "misc.xml")
+ .getOrElse(("JDK_1_8", "1.8 (1)"))
+
+ ctx.log.info("Analyzing modules ...")
+ val layout: Seq[(SubPath, Node)] =
+ xmlFileLayout(evaluators, jdkInfo)
+
+ ctx.log.debug("Cleaning obsolete IDEA project files ...")
+ os.remove.all(ideaDir / "libraries")
+ os.remove.all(ideaDir / "scala_compiler.xml")
+ os.remove.all(ideaDir / "mill_modules")
+
+ ctx.log.info(s"Writing ${layout.size} IDEA project files to ${ideaDir} ...")
+ for ((subPath, xml) <- layout) {
+ ctx.log.debug(s"Writing ${subPath} ...")
+ os.write.over(ideaDir / subPath, pp.format(xml), createFolders = true)
+ }
+ }
+
+ def extractCurrentJdk(ideaPath: os.Path): Option[(String, String)] = {
+ import scala.xml.XML
+ Try {
+ val xml = XML.loadFile(ideaPath.toString)
+ (xml \\ "component")
+ .filter(x => x.attribute("project-jdk-type").map(_.text).contains("JavaSDK"))
+ .map { n =>
+ (n.attribute("languageLevel"), n.attribute("project-jdk-name"))
+ }
+ .collectFirst { case (Some(lang), Some(jdk)) => (lang.text, jdk.text) }
+ }.getOrElse(None)
+ }
+
+ def xmlFileLayout(
+ evaluators: Seq[Evaluator],
+ jdkInfo: (String, String),
+ fetchMillModules: Boolean = true
+ ): Seq[(os.SubPath, scala.xml.Node)] = {
+
+ val rootModules = evaluators.zipWithIndex.map { case (ev, idx) => (ev.rootModule, ev, idx) }
+ val transitive: Seq[(BaseModule, Seq[Module], Evaluator, Int)] = rootModules
+ .map { case (rootModule, ev, idx) =>
+ (rootModule, JavaModuleUtils.transitiveModules(rootModule), ev, idx)
+ }
+
+ val foundModules: Seq[(Segments, Module, Evaluator)] = transitive
+ .flatMap { case (rootMod, transModules, ev, idx) =>
+ transModules.collect {
+ case m: Module =>
+ val rootSegs = rootMod.millSourcePath.relativeTo(workDir).segments
+ val modSegs = m.millModuleSegments.parts
+ val segments: Seq[String] = rootSegs ++ modSegs
+ (Segments(segments.map(Segment.Label)), m, ev)
+ }
+ }
+
+ val modules: Seq[(Segments, JavaModule, Evaluator)] = foundModules
+ .collect { case (s, x: scalalib.JavaModule, ev) => (s, x, ev) }
+ .filterNot(_._2.skipIdea)
+ .distinct
+
+ lazy val modulesByEvaluator: Map[Evaluator, Seq[(Segments, JavaModule)]] = modules
+ .groupMap { case (_, _, ev) => ev } { case (s, m, _) => (s, m) }
+
+// val modules: Seq[(Segments, JavaModule)] =
+// rootModule.millInternal.segmentsToModules.values
+// .collect { case x: scalalib.JavaModule => x }
+// .flatMap(_.transitiveModuleDeps)
+// .filterNot(_.skipIdea)
+// .map(x => (x.millModuleSegments, x))
+// .toSeq
+// .distinct
+
+ val buildLibraryPaths: immutable.Seq[Path] = {
+ if (!fetchMillModules) Nil
+ else {
+ val moduleRepos = modulesByEvaluator.toSeq.flatMap { case (ev, modules) =>
+ ev.evalOrThrow(
+ exceptionFactory = r =>
+ GenIdeaException(
+ s"Failure during resolving repositories: ${Evaluator.formatFailing(r)}"
+ )
+ )(modules.map(_._2.repositoriesTask))
+ }
+ Lib.resolveMillBuildDeps(moduleRepos.flatten, Option(ctx), useSources = true)
+ Lib.resolveMillBuildDeps(moduleRepos.flatten, Option(ctx), useSources = false)
+ }
+ }
+
+ // is head the right one?
+ val buildDepsPaths = Classpath.allJars(evaluators.head.rootModule.getClass.getClassLoader)
+ .map(url => os.Path(java.nio.file.Paths.get(url.toURI)))
+
+ def resolveTasks: Map[Evaluator, Seq[Task[ResolvedModule]]] =
+ modulesByEvaluator.map { case (evaluator, m) =>
+ evaluator -> m.map {
+ case (path, mod) => {
+
+ // same as input of resolvedIvyDeps
+ val allIvyDeps = T.task {
+ mod.transitiveIvyDeps() ++ mod.transitiveCompileIvyDeps()
+ }
+
+ val scalaCompilerClasspath = mod match {
+ case x: ScalaModule => x.scalaCompilerClasspath
+ case _ =>
+ T.task {
+ Agg.empty[PathRef]
+ }
+ }
+
+ val externalLibraryDependencies = T.task {
+ mod.resolveDeps(T.task {
+ val bind = mod.bindDependency()
+ mod.mandatoryIvyDeps().map(bind)
+ })()
+ }
+
+ val externalDependencies = T.task {
+ mod.resolvedIvyDeps() ++
+ T.traverse(mod.transitiveModuleDeps)(_.unmanagedClasspath)().flatten
+ }
+ val extCompileIvyDeps =
+ mod.resolveDeps(T.task {
+ val bind = mod.bindDependency()
+ mod.compileIvyDeps().map(bind)
+ })
+ val extRunIvyDeps = mod.resolvedRunIvyDeps
+
+ val externalSources = T.task {
+ mod.resolveDeps(allIvyDeps, sources = true)()
+ }
+
+ val (scalacPluginsIvyDeps, allScalacOptions) = mod match {
+ case mod: ScalaModule => (
+ T.task(mod.scalacPluginIvyDeps()),
+ T.task(mod.allScalacOptions())
+ )
+ case _ => (
+ T.task {
+ Agg[Dep]()
+ },
+ T.task {
+ Seq()
+ }
+ )
+ }
+
+ val scalacPluginDependencies = T.task {
+ mod.resolveDeps(T.task {
+ val bind = mod.bindDependency()
+ scalacPluginsIvyDeps().map(bind)
+ })()
+ }
+
+ val facets = T.task {
+ mod.ideaJavaModuleFacets(ideaConfigVersion)()
+ }
+
+ val configFileContributions = T.task {
+ mod.ideaConfigFiles(ideaConfigVersion)()
+ }
+
+ val compilerOutput = T.task {
+ mod.ideaCompileOutput()
+ }
+
+ T.task {
+ val resolvedCp: Agg[Scoped[Path]] =
+ externalDependencies().map(_.path).map(Scoped(_, None)) ++
+ extCompileIvyDeps()
+ .map(_.path)
+ .map(Scoped(_, Some("PROVIDED"))) ++
+ extRunIvyDeps().map(_.path).map(Scoped(_, Some("RUNTIME")))
+ // unused, but we want to trigger sources, to have them available (automatically)
+ // TODO: make this a separate eval to handle resolve errors
+ val resolvedSrcs: Agg[PathRef] = externalSources()
+ val resolvedSp: Agg[PathRef] = scalacPluginDependencies()
+ val resolvedCompilerCp: Agg[PathRef] =
+ scalaCompilerClasspath()
+ val resolvedLibraryCp: Agg[PathRef] =
+ externalLibraryDependencies()
+ val scalacOpts: Seq[String] = allScalacOptions()
+ val resolvedFacets: Seq[JavaFacet] = facets()
+ val resolvedConfigFileContributions: Seq[IdeaConfigFile] =
+ configFileContributions()
+ val resolvedCompilerOutput = compilerOutput()
+
+ ResolvedModule(
+ path = path,
+ // FIXME: why do we need to sources in the classpath?
+ // FIXED, was: classpath = resolvedCp.map(_.path).filter(_.ext == "jar") ++ resolvedSrcs.map(_.path),
+ classpath = resolvedCp.filter(_.value.ext == "jar"),
+ module = mod,
+ pluginClasspath = resolvedSp.map(_.path).filter(_.ext == "jar"),
+ scalaOptions = scalacOpts,
+ compilerClasspath = resolvedCompilerCp.map(_.path),
+ libraryClasspath = resolvedLibraryCp.map(_.path),
+ facets = resolvedFacets,
+ configFileContributions = resolvedConfigFileContributions,
+ compilerOutput = resolvedCompilerOutput.path,
+ evaluator = evaluator
+ )
+ }
+ }
+ }
+ }
+
+ val resolvedModules: Seq[ResolvedModule] = {
+ resolveTasks.toSeq.flatMap { case (evaluator, tasks) =>
+ evaluator.evaluate(tasks) match {
+ case r if r.failing.items().nonEmpty =>
+ throw GenIdeaException(
+ s"Failure during resolving modules: ${Evaluator.formatFailing(r)}"
+ )
+ case r => r.values.map(_.value).asInstanceOf[Seq[ResolvedModule]]
+ }
+ }
+ }
+
+ val moduleLabels = modules.map { case (s, m, e) => (m, s) }.toMap
+
+ val allResolved: Seq[Path] =
+ (resolvedModules.flatMap(_.classpath).map(_.value) ++ buildLibraryPaths ++ buildDepsPaths)
+ .distinct
+ .sorted
+
+ val librariesProperties: Map[Path, Agg[Path]] =
+ resolvedModules
+ .flatMap(x => x.libraryClasspath.map(_ -> x.compilerClasspath))
+ .toMap
+
+ val (wholeFileConfigs, configFileContributions) =
+ resolvedModules
+ .flatMap(_.configFileContributions)
+ .partition(_.asWholeFile.isDefined)
+
+ // whole file
+ val ideaWholeConfigFiles: Seq[(SubPath, Elem)] =
+ wholeFileConfigs.flatMap(_.asWholeFile).map { wf =>
+ os.sub / wf._1 -> ideaConfigElementTemplate(wf._2)
+ }
+
+ type FileComponent = (SubPath, Option[String])
+
+ /** Ensure, the additional configs don't collide. */
+ def collisionFreeExtraConfigs(
+ confs: Seq[IdeaConfigFile]
+ ): Map[SubPath, Seq[IdeaConfigFile]] = {
+
+ var seen: Map[FileComponent, Seq[GenIdeaModule.Element]] = Map()
+ var result: Map[SubPath, Seq[IdeaConfigFile]] = Map()
+ confs.foreach { conf =>
+ val key = conf.subPath -> conf.component
+ seen.get(key) match {
+ case None =>
+ seen += key -> conf.config
+ result += conf.subPath -> (result
+ .get(conf.subPath)
+ .getOrElse(Seq()) ++ Seq(conf))
+ case Some(existing) if conf.config == existing =>
+ // identical, ignore
+ case Some(existing) =>
+ def details(elements: Seq[GenIdeaModule.Element]) = {
+ elements.map(
+ ideaConfigElementTemplate(_).toString().replaceAll("\\n", "")
+ )
+ }
+ val msg =
+ s"Config collision in file `${conf.subPath}` and component `${conf.component}`: ${details(
+ conf.config
+ )} vs. ${details(existing)}"
+ ctx.log.error(msg)
+ }
+ }
+ result
+ }
+
+ val fileComponentContributions: Seq[(SubPath, Elem)] =
+ collisionFreeExtraConfigs(configFileContributions).toSeq.map {
+ case (file, configs) =>
+ val map: Map[Option[String], Seq[GenIdeaModule.Element]] =
+ configs
+ .groupBy(_.component)
+ .view
+ .mapValues(_.flatMap(_.config))
+ .toMap
+ file -> ideaConfigFileTemplate(map)
+ }
+
+ val pathShortLibNameDuplicate = allResolved
+ .groupBy(_.last)
+ .filter(_._2.size > 1)
+ .view
+ .mapValues(_.sorted)
+ .mapValues(_.zipWithIndex)
+ .flatMap(y =>
+ y._2.map {
+ case (path, 0) => path -> y._1
+ case (path, idx) => path -> s"${y._1} (${idx})"
+ }
+ )
+ .toMap
+
+ val pathToLibName = allResolved
+ .map(p => p -> pathShortLibNameDuplicate.getOrElse(p, p.last))
+ .toMap
+
+ type ArtifactAndVersion = (String, String)
+
+ def guessJarArtifactNameAndVersionFromPath(
+ path: Path
+ ): Option[ArtifactAndVersion] =
+ Try {
+ // in a local maven repo or a local Coursier repo,
+ // the dir-layout reflects the jar coordinates
+ val fileName = path.last
+ val parentDir = (path / os.up).last
+ val grandParentDir = (path / os.up / os.up).last
+ if (fileName.startsWith(s"${grandParentDir}-${parentDir}")) {
+ // could be a maven or coursier repo
+ Some((grandParentDir, parentDir))
+ } else {
+ None
+ }
+ }.toOption.flatten
+
+ // Tries to group jars with their poms and sources.
+ def toResolvedJar(path: os.Path): Option[ResolvedLibrary] = {
+ val guessedArtifactVersion = guessJarArtifactNameAndVersionFromPath(path)
+ val inCoursierCache = path.startsWith(os.Path(coursier.paths.CoursierPaths.cacheDirectory()))
+ val inIvyLikeLocal = (path / os.up).last == "jars"
+ def inMavenLikeLocal = guessedArtifactVersion.isDefined
+ val isSource = path.last.endsWith("sources.jar")
+ val isPom = path.ext == "pom"
+ val baseName = guessedArtifactVersion
+ .map(av => s"${av._1}-${av._2}")
+ .getOrElse(path.baseName)
+
+ if (inCoursierCache && (isSource || isPom)) {
+ // Remove sources and pom as they'll be recovered from the jar path
+ None
+ } else if (inCoursierCache && path.ext == "jar") {
+ val pom = path / os.up / s"$baseName.pom"
+ val sources = Some(path / os.up / s"$baseName-sources.jar")
+ .filter(_.toIO.exists())
+ Some(CoursierResolved(path, pom, sources))
+ } else if (inIvyLikeLocal && path.ext == "jar") {
+ // assume some jvy-like dir structure
+ val sources =
+ Some(path / os.up / os.up / "srcs" / s"${path.baseName}-sources.jar")
+ .filter(_.toIO.exists())
+ Some(WithSourcesResolved(path, sources))
+ } else if (inMavenLikeLocal) {
+ // assume some maven-like dir structure
+ val sources = Some(path / os.up / s"${baseName}-sources.jar")
+ .filter(_.toIO.exists())
+ Some(WithSourcesResolved(path, sources))
+ } else {
+ Some(OtherResolved(path))
+ }
+ }
+
+ /**
+ * We need to use a very specific library name format.
+ * This is required in order IntelliJ IDEA can recognize `$ivy` imports in `build.sc` files and doesn't show red code.
+ * This is how currently Ammonite integration is done in Scala Plugin for IntelliJ IDEA.
+ *
+ * @see [[https://github.com/JetBrains/intellij-scala/blob/idea223.x/scala/worksheet/src/org/jetbrains/plugins/scala/worksheet/ammonite/AmmoniteUtil.scala#L240]]
+ * @example {{{
+ * //SBT: com.lihaoyi:ammonite-ops_2.13:2.2.0:jar
+ * import $ivy.`com.lihaoyi::ammonite-ops:2.2.0
+ * }}}
+ */
+ def sbtLibraryNameFromPom(pomPath: os.Path): String = {
+ val pom = xmlParseDom(os.read(pomPath)).flatMap(Pom.project)
+ .getOrElse(throw new RuntimeException(s"Could not parse pom file: ${pomPath}"))
+
+ val artifactId = pom.module.name.value
+ val scalaArtifactRegex = ".*_[23]\\.[0-9]{1,2}".r
+ val artifactWithScalaVersion = artifactId.substring(
+ artifactId.length - math.min(5, artifactId.length)
+ ) match {
+ case scalaArtifactRegex(_*) => artifactId
+ case _ =>
+ // Default to the scala binary version used by mill itself
+ s"${artifactId}_${BuildInfo.scalaVersion.split("[.]").take(2).mkString(".")}"
+ }
+ s"SBT: ${pom.module.organization.value}:$artifactWithScalaVersion:${pom.version}:jar"
+ }
+
+ def libraryNames(resolvedJar: ResolvedLibrary): Seq[String] =
+ resolvedJar match {
+ case CoursierResolved(path, pom, _) if buildDepsPaths.contains(path) && pom.toIO.exists() =>
+ Seq(sbtLibraryNameFromPom(pom), pathToLibName(path))
+ case CoursierResolved(path, _, _) =>
+ Seq(pathToLibName(path))
+ case WithSourcesResolved(path, _) =>
+ Seq(pathToLibName(path))
+ case OtherResolved(path) =>
+ Seq(pathToLibName(path))
+ }
+
+ def resolvedLibraries(resolved: Seq[os.Path]): Seq[ResolvedLibrary] =
+ resolved
+ .map(toResolvedJar)
+ .collect { case Some(r) => r }
+
+ val compilerSettings = resolvedModules
+ .foldLeft(Map[(Agg[os.Path], Seq[String]), Vector[JavaModule]]()) {
+ (r, q) =>
+ val key = (q.pluginClasspath, q.scalaOptions)
+ r + (key -> (r.getOrElse(key, Vector()) :+ q.module))
+ }
+
+ val allBuildLibraries: Set[ResolvedLibrary] =
+ resolvedLibraries(buildLibraryPaths ++ buildDepsPaths).toSet
+
+ val fixedFiles: Seq[(SubPath, Elem)] = Seq(
+ Tuple2(os.sub / "misc.xml", miscXmlTemplate(jdkInfo)),
+ Tuple2(os.sub / "scala_settings.xml", scalaSettingsTemplate()),
+ Tuple2(
+ os.sub / "modules.xml",
+ allModulesXmlTemplate(
+ modules.map { case (segments, mod, _) => moduleName(segments) }.sorted
+ )
+ ),
+// Tuple2(
+// os.sub / "mill_modules" / "mill-build.iml",
+// rootXmlTemplate(allBuildLibraries.flatMap(lib => libraryNames(lib)))
+// ),
+ Tuple2(
+ os.sub / "scala_compiler.xml",
+ scalaCompilerTemplate(compilerSettings)
+ )
+ )
+
+ /**
+ * @note `:` in path isn't supported on Windows ~ https://github.com/com-lihaoyi/mill/issues/2243
+ * It comes from [[sbtLibraryNameFromPom]]
+ */
+ def libraryNameToFileSystemPathPart(name: String): String = {
+ name.replaceAll("""[-.:]""", "_")
+ }
+
+ val libraries: Seq[(SubPath, Elem)] =
+ resolvedLibraries(allResolved).flatMap { resolved =>
+ val names = libraryNames(resolved)
+ val sources = resolved match {
+ case CoursierResolved(_, _, s) => s
+ case WithSourcesResolved(_, s) => s
+ case OtherResolved(_) => None
+ }
+ for (name <- names)
+ yield {
+ val compilerCp: Agg[Path] = librariesProperties.getOrElse(resolved.path, Agg.empty)
+ val languageLevel = name match {
+ case _ if compilerCp.iterator.isEmpty => None
+ case _ if name.startsWith("scala3-library_3-3.3.") => Some("Scala_3_3")
+ case _ if name.startsWith("scala3-library_3-3.2.") => Some("Scala_3_2")
+ case _ if name.startsWith("scala3-library_3-3.1.") => Some("Scala_3_1")
+ case _ if name.startsWith("scala3-library_3-3.0.") => Some("Scala_3_0")
+ case _ if name.startsWith("scala-library-2.13.") => Some("Scala_2_13")
+ case _ if name.startsWith("scala-library-2.12.") => Some("Scala_2_12")
+ case _ if name.startsWith("scala-library-2.11.") => Some("Scala_2_11")
+ case _ if name.startsWith("scala-library-2.10.") => Some("Scala_2_10")
+ case _ if name.startsWith("scala-library-2.9.") => Some("Scala_2_9")
+ case _ if name.startsWith("dotty-library-0.27") => Some("Scala_0_27")
+ case _ => None
+ }
+ Tuple2(
+ os.sub / "libraries" / s"${libraryNameToFileSystemPathPart(name)}.xml",
+ libraryXmlTemplate(
+ name = name,
+ path = resolved.path,
+ sources = sources,
+ scalaCompilerClassPath = compilerCp,
+ languageLevel = languageLevel
+ )
+ )
+ }
+ }
+
+ val moduleFiles: Seq[(SubPath, Elem)] = resolvedModules.map {
+ case ResolvedModule(
+ path,
+ resolvedDeps,
+ mod,
+ _,
+ _,
+ _,
+ _,
+ facets,
+ _,
+ compilerOutput,
+ evaluator
+ ) =>
+ val Seq(
+ resourcesPathRefs: Seq[PathRef],
+ generatedSourcePathRefs: Seq[PathRef],
+ allSourcesPathRefs: Seq[PathRef]
+ ) = evaluator.evaluate(
+ Agg(
+ mod.resources,
+ mod.generatedSources,
+ mod.allSources
+ )
+ )
+ .values
+ .map(_.value)
+
+ val generatedSourcePaths = generatedSourcePathRefs.map(_.path)
+ val normalSourcePaths = (allSourcesPathRefs
+ .map(_.path)
+ .toSet -- generatedSourcePaths.toSet).toSeq
+
+ val scalaVersionOpt = mod match {
+ case x: ScalaModule =>
+ Some(
+ evaluator.evalOrThrow(
+ exceptionFactory = r =>
+ GenIdeaException(
+ s"Failure during evaluation of the scalaVersion: ${Evaluator.formatFailing(r)}"
+ )
+ )(x.scalaVersion)
+ )
+ case _ => None
+ }
+
+ val sanizedDeps: Seq[ScopedOrd[String]] = {
+ resolvedDeps
+ .map((s: Scoped[Path]) => pathToLibName(s.value) -> s.scope)
+ .iterator
+ .toSeq
+ .groupBy(_._1)
+ .view
+ .mapValues(_.map(_._2))
+ .map {
+ case (lib, scopes) =>
+ val isCompile = scopes.contains(None)
+ val isProvided = scopes.contains(Some("PROVIDED"))
+ val isRuntime = scopes.contains(Some("RUNTIME"))
+
+ val finalScope = (isCompile, isProvided, isRuntime) match {
+ case (_, true, false) => Some("PROVIDED")
+ case (false, false, true) => Some("RUNTIME")
+ case _ => None
+ }
+
+ ScopedOrd(lib, finalScope)
+ }
+ .toSeq
+ }
+
+ val libNames = Strict.Agg.from(sanizedDeps).iterator.toSeq
+
+ val depNames = Strict.Agg
+ .from(mod.moduleDeps.map((_, None)) ++
+ mod.compileModuleDeps.map((_, Some("PROVIDED"))))
+ .filter(!_._1.skipIdea)
+ .map { case (v, s) => ScopedOrd(moduleName(moduleLabels(v)), s) }
+ .iterator
+ .toSeq
+ .distinct
+
+ val isTest = mod.isInstanceOf[TestModule]
+
+ val elem = moduleXmlTemplate(
+ basePath = mod.intellijModulePath,
+ scalaVersionOpt = scalaVersionOpt,
+ resourcePaths = Strict.Agg.from(resourcesPathRefs.map(_.path)),
+ normalSourcePaths = Strict.Agg.from(normalSourcePaths),
+ generatedSourcePaths = Strict.Agg.from(generatedSourcePaths),
+ compileOutputPath = compilerOutput,
+ libNames = libNames,
+ depNames = depNames,
+ isTest = isTest,
+ facets = facets
+ )
+
+ Tuple2(
+ os.sub / "mill_modules" / s"${moduleName(path)}.iml",
+ elem
+ )
+ }
+
+ {
+ // file collision checks
+ val map =
+ (fixedFiles ++ ideaWholeConfigFiles ++ fileComponentContributions)
+ .groupBy(_._1)
+ .filter(_._2.size > 1)
+ if (map.nonEmpty) {
+ ctx.log.error(
+ s"Config file collisions detected. Check you `ideaConfigFiles` targets. Colliding files: ${map
+ .map(_._1)}. All project files: ${map}"
+ )
+ }
+ }
+
+ fixedFiles ++ ideaWholeConfigFiles ++ fileComponentContributions ++ libraries ++ moduleFiles
+ }
+
+ def relify(p: os.Path) = {
+ val r = p.relativeTo(ideaDir / "mill_modules")
+ (Seq.fill(r.ups)("..") ++ r.segments).mkString("/")
+ }
+
+ def ideaConfigElementTemplate(element: GenIdeaModule.Element): Elem = {
+
+ val example =
+
+ val attribute1: MetaData =
+ if (element.attributes.isEmpty) Null
+ else
+ element.attributes.toSeq.reverse.foldLeft(Null.asInstanceOf[MetaData]) {
+ case (prevAttr, (k, v)) =>
+ new UnprefixedAttribute(k, v, prevAttr)
+ }
+
+ new Elem(
+ prefix = null,
+ label = element.name,
+ attributes1 = attribute1,
+ example.scope,
+ minimizeEmpty = true,
+ child = element.childs.map(ideaConfigElementTemplate): _*
+ )
+ }
+
+ def ideaConfigFileTemplate(
+ components: Map[Option[String], Seq[GenIdeaModule.Element]]
+ ): Elem = {
+
+ {
+ components.toSeq.map { case (name, config) =>
+ {config.map(ideaConfigElementTemplate)}
+ }
+ }
+
+ }
+
+ def scalaSettingsTemplate() = {
+// simpleIdeaConfigFileTemplate(Map("ScalaProjectSettings" -> Map("scFileMode" -> "Ammonite")))
+
+
+
+
+
+ }
+ def miscXmlTemplate(jdkInfo: (String, String)) = {
+
+
+
+
+
+ }
+
+ def allModulesXmlTemplate(selectors: Seq[String]) = {
+
+
+
+ {
+//
+ }
+ {
+ for (selector <- selectors)
+ yield {
+ val filepath = "$PROJECT_DIR$/.idea/mill_modules/" + selector + ".iml"
+ val fileurl = "file://" + filepath
+
+ }
+ }
+
+
+
+ }
+ def rootXmlTemplate(libNames: Strict.Agg[String]): scala.xml.Elem = {
+
+
+
+
+
+
+
+
+
+
+
+ {
+ for (name <- libNames.toSeq.sorted)
+ yield
+ }
+
+
+ }
+
+ /** Try to make the file path a relative JAR URL (to PROJECT_DIR). */
+ def relativeJarUrl(path: os.Path): String = {
+ // When coursier cache dir is on different logical drive than project dir
+ // we can not use a relative path. See issue: https://github.com/lihaoyi/mill/issues/905
+ val relPath = relForwardPath(path, "$PROJECT_DIR$/")
+ if (path.ext == "jar") "jar://" + relPath + "!/" else "file://" + relPath
+ }
+
+ /** Try to make the file path a relative URL (to PROJECT_DIR). */
+ def relativeFileUrl(path: Path): String = {
+ // When coursier cache dir is on different logical drive than project dir
+ // we can not use a relative path. See issue: https://github.com/lihaoyi/mill/issues/905
+ "file://" + relForwardPath(path, "$PROJECT_DIR$/")
+ }
+
+ private def relForwardPath(path: os.Path, prefix: String): String = {
+ def forward(p: os.FilePath): String = p.toString().replace("""\""", "/")
+ Try(prefix + forward(path.relativeTo(workDir))).getOrElse(forward(path))
+ }
+
+ def libraryXmlTemplate(
+ name: String,
+ path: os.Path,
+ sources: Option[os.Path],
+ scalaCompilerClassPath: Agg[Path],
+ languageLevel: Option[String]
+ ): Elem = {
+ val isScalaLibrary = scalaCompilerClassPath.iterator.nonEmpty
+
+
+ {
+ if (isScalaLibrary) {
+
+ {
+ if (languageLevel.isDefined) {languageLevel.get}
+ }
+
+ {
+ scalaCompilerClassPath.iterator.toSeq.sortBy(_.wrapped).map(p =>
+
+ )
+ }
+
+
+ }
+ }
+
+
+
+ {
+ if (sources.isDefined) {
+
+
+
+ }
+ }
+
+
+ }
+ def moduleXmlTemplate(
+ basePath: os.Path,
+ scalaVersionOpt: Option[String],
+ resourcePaths: Strict.Agg[os.Path],
+ normalSourcePaths: Strict.Agg[os.Path],
+ generatedSourcePaths: Strict.Agg[os.Path],
+ compileOutputPath: os.Path,
+ libNames: Seq[ScopedOrd[String]],
+ depNames: Seq[ScopedOrd[String]],
+ isTest: Boolean,
+ facets: Seq[GenIdeaModule.JavaFacet]
+ ): Elem = {
+ val genSources = generatedSourcePaths.toSeq.distinct.sorted.partition(_.startsWith(basePath))
+ val normSources = normalSourcePaths.iterator.toSeq.sorted.partition(_.startsWith(basePath))
+ val resources = resourcePaths.iterator.toSeq.sorted.partition(_.startsWith(basePath))
+
+ def relUrl(path: os.Path): String = "file://$MODULE_DIR$/" + relify(path)
+ def genSourceFolder(path: os.Path): Elem = {
+
+ }
+ def sourceFolder(path: os.Path): Elem = {
+
+ }
+ def resourcesFolder(path: os.Path): Elem = {
+ val resourceType = if (isTest) "java-test-resource" else "java-resource"
+
+ }
+
+
+
+ {
+ val outputUrl = relUrl(compileOutputPath)
+ if (isTest)
+
+ else
+
+ }
+
+ {
+ for (generatedSourcePath <- genSources._2) yield {
+
+ {genSourceFolder(generatedSourcePath)}
+
+ }
+ }
+ {
+ // keep the "real" base path as last content, to ensure, Idea picks it up as "main" module dir
+ for (normalSourcePath <- normSources._2) yield {
+
+ {sourceFolder(normalSourcePath)}
+
+ }
+ }
+ {
+ for (resourcePath <- resources._2) yield {
+
+ {resourcesFolder(resourcePath)}
+
+ }
+ }
+ {
+ // the (potentially empty) content root to denote where a module lives
+ // this is to avoid some strange layout issues
+ // see details at: https://github.com/com-lihaoyi/mill/pull/2638#issuecomment-1685229512
+
+ {
+ for (generatedSourcePath <- genSources._1) yield {
+ genSourceFolder(generatedSourcePath)
+ }
+ }
+ {
+ for (normalSourcePath <- normSources._1) yield {
+ sourceFolder(normalSourcePath)
+ }
+ }
+ {
+ for (resourcePath <- resources._1) yield {
+ resourcesFolder(resourcePath)
+ }
+ }
+
+ }
+
+
+
+ {
+ for (dep <- depNames.sorted)
+ yield dep.scope match {
+ case None =>
+ case Some(scope) =>
+
+ }
+ }
+ {
+ for (name <- libNames.sorted)
+ yield name.scope match {
+ case None =>
+ case Some(scope) =>
+
+ }
+
+ }
+
+ {
+ if (facets.isEmpty) NodeSeq.Empty
+ else {
+
+ {
+ for (facet <- facets) yield {
+
+ {ideaConfigElementTemplate(facet.config)}
+
+ }
+ }
+
+ }
+ }
+
+ }
+
+ def scalaCompilerTemplate(
+ settings: Map[(Agg[os.Path], Seq[String]), Seq[JavaModule]]
+ ) = {
+
+
+
+ {
+ for ((((plugins, params), mods), i) <- settings.toSeq.zip(1 to settings.size))
+ yield moduleName(m.millModuleSegments)).mkString(",")
+ }>
+
+ {
+ for (param <- params)
+ yield
+ }
+
+
+ {
+ for (plugin <- plugins.toSeq)
+ yield
+ }
+
+
+ }
+
+
+ }
+}
+
+object GenIdeaImpl {
+
+ /**
+ * Create the module name (to be used by Idea) for the module based on it segments.
+ * @see [[Module.millModuleSegments]]
+ */
+ def moduleName(p: Segments): String =
+ p.value
+ .foldLeft(new StringBuilder()) {
+ case (sb, Segment.Label(s)) if sb.isEmpty => sb.append(s)
+ case (sb, Segment.Cross(s)) if sb.isEmpty => sb.append(s.mkString("-"))
+ case (sb, Segment.Label(s)) => sb.append(".").append(s)
+ case (sb, Segment.Cross(s)) => sb.append("-").append(s.mkString("-"))
+ }
+ .mkString
+ .toLowerCase()
+
+ sealed trait ResolvedLibrary { def path: os.Path }
+ final case class CoursierResolved(path: os.Path, pom: os.Path, sources: Option[os.Path])
+ extends ResolvedLibrary
+ final case class OtherResolved(path: os.Path) extends ResolvedLibrary
+ final case class WithSourcesResolved(path: os.Path, sources: Option[os.Path])
+ extends ResolvedLibrary
+
+ final case class Scoped[T](value: T, scope: Option[String])
+
+ final case class ScopedOrd[T <: Comparable[T]](value: T, scope: Option[String])
+ extends Ordered[ScopedOrd[T]] {
+ override def compare(that: ScopedOrd[T]): Int =
+ value.compareTo(that.value) match {
+ case 0 =>
+ (scope, that.scope) match {
+ case (None, None) => 0
+ case (Some(l), Some(r)) => l.compare(r)
+ case (None, _) => -1
+ case (_, None) => +1
+ }
+ case x => x
+ }
+ }
+ object ScopedOrd {
+ def apply[T <: Comparable[T]](scoped: Scoped[T]): ScopedOrd[T] =
+ ScopedOrd(scoped.value, scoped.scope)
+ }
+
+ final case class ResolvedModule(
+ path: Segments,
+ classpath: Agg[Scoped[Path]],
+ module: JavaModule,
+ pluginClasspath: Agg[Path],
+ scalaOptions: Seq[String],
+ compilerClasspath: Agg[Path],
+ libraryClasspath: Agg[Path],
+ facets: Seq[JavaFacet],
+ configFileContributions: Seq[IdeaConfigFile],
+ compilerOutput: Path,
+ evaluator: Evaluator
+ )
+
+ case class GenIdeaException(msg: String) extends RuntimeException
+
+}
diff --git a/integration/feature/docannotations/repo/build.sc b/integration/feature/docannotations/repo/build.sc
index 54ea2f3a5e6..959b171777d 100644
--- a/integration/feature/docannotations/repo/build.sc
+++ b/integration/feature/docannotations/repo/build.sc
@@ -4,7 +4,7 @@ import mill.scalalib._
trait JUnitTests extends TestModule.Junit4 {
/**
- * Overriden ivyDeps Docs!!!
+ * Overridden ivyDeps Docs!!!
*/
def ivyDeps = Agg(ivy"com.novocode:junit-interface:0.11")
def task = T {
diff --git a/integration/feature/docannotations/test/src/DocAnnotationsTests.scala b/integration/feature/docannotations/test/src/DocAnnotationsTests.scala
index 83c3107642f..3e1c38c98ff 100644
--- a/integration/feature/docannotations/test/src/DocAnnotationsTests.scala
+++ b/integration/feature/docannotations/test/src/DocAnnotationsTests.scala
@@ -23,7 +23,7 @@ object DocAnnotationsTests extends IntegrationTestSuite {
assert(
globMatches(
"""core.test.ivyDeps(build.sc:...)
- | Overriden ivyDeps Docs!!!
+ | Overridden ivyDeps Docs!!!
|
| Any ivy dependencies you want to add to this Module, in the format
| ivy"org::name:version" for Scala dependencies or ivy"org:name:version"
diff --git a/integration/feature/editing/test/src/MultiLevelBuildTests.scala b/integration/feature/editing/test/src/MultiLevelBuildTests.scala
index 11906a0c98b..8c502b94b04 100644
--- a/integration/feature/editing/test/src/MultiLevelBuildTests.scala
+++ b/integration/feature/editing/test/src/MultiLevelBuildTests.scala
@@ -62,8 +62,9 @@ object MultiLevelBuildTests extends IntegrationTestSuite {
val frameWatched = frame
.evalWatched
.map(_.path)
- .sorted.filter(_.startsWith(wsRoot))
+ .filter(_.startsWith(wsRoot))
.filter(!_.segments.contains("mill-launcher"))
+ .sorted
val expectedWatched = expectedWatched0.sorted
assert(frameWatched == expectedWatched)
diff --git a/integration/feature/gen-idea/repo/extended/build.sc b/integration/feature/gen-idea/repo/extended/build.sc
index e345f8d44cd..1f1e6cd14ae 100644
--- a/integration/feature/gen-idea/repo/extended/build.sc
+++ b/integration/feature/gen-idea/repo/extended/build.sc
@@ -1,4 +1,4 @@
-import $ivy.`org.scalameta::munit:0.7.29`
+import $meta._
import mill._
import mill.api.PathRef
diff --git a/integration/feature/gen-idea/repo/extended/idea/mill_modules/helloworld.iml b/integration/feature/gen-idea/repo/extended/idea/mill_modules/helloworld.iml
index 9265db635f9..5c5918885e0 100644
--- a/integration/feature/gen-idea/repo/extended/idea/mill_modules/helloworld.iml
+++ b/integration/feature/gen-idea/repo/extended/idea/mill_modules/helloworld.iml
@@ -5,10 +5,8 @@
-
+
-
-
diff --git a/integration/feature/gen-idea/repo/extended/idea/mill_modules/helloworld.subscala3.iml b/integration/feature/gen-idea/repo/extended/idea/mill_modules/helloworld.subscala3.iml
index b09e1fb708b..d4e7d4f0ea8 100644
--- a/integration/feature/gen-idea/repo/extended/idea/mill_modules/helloworld.subscala3.iml
+++ b/integration/feature/gen-idea/repo/extended/idea/mill_modules/helloworld.subscala3.iml
@@ -2,10 +2,8 @@
-
+
-
-
diff --git a/integration/feature/gen-idea/repo/extended/idea/mill_modules/helloworld.test.iml b/integration/feature/gen-idea/repo/extended/idea/mill_modules/helloworld.test.iml
index 10d35b52cca..bc388953e1e 100644
--- a/integration/feature/gen-idea/repo/extended/idea/mill_modules/helloworld.test.iml
+++ b/integration/feature/gen-idea/repo/extended/idea/mill_modules/helloworld.test.iml
@@ -2,10 +2,8 @@
-
+
-
-
diff --git a/integration/feature/gen-idea/repo/extended/idea/mill_modules/mill-build.iml b/integration/feature/gen-idea/repo/extended/idea/mill_modules/mill-build.iml
index 7d959abd7c7..089434db76a 100644
--- a/integration/feature/gen-idea/repo/extended/idea/mill_modules/mill-build.iml
+++ b/integration/feature/gen-idea/repo/extended/idea/mill_modules/mill-build.iml
@@ -1,12 +1,13 @@
-
+
+
-
-
-
+
+
+
+
-
diff --git a/integration/feature/gen-idea/repo/extended/idea/mill_modules/mill-build.mill-build.iml b/integration/feature/gen-idea/repo/extended/idea/mill_modules/mill-build.mill-build.iml
new file mode 100644
index 00000000000..937e0beaf01
--- /dev/null
+++ b/integration/feature/gen-idea/repo/extended/idea/mill_modules/mill-build.mill-build.iml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/integration/feature/gen-idea/repo/extended/idea/modules.xml b/integration/feature/gen-idea/repo/extended/idea/modules.xml
index 14fa45333f0..68f06794a08 100644
--- a/integration/feature/gen-idea/repo/extended/idea/modules.xml
+++ b/integration/feature/gen-idea/repo/extended/idea/modules.xml
@@ -1,10 +1,11 @@
-
+
+
diff --git a/integration/feature/gen-idea/repo/extended/mill-build/build.sc b/integration/feature/gen-idea/repo/extended/mill-build/build.sc
new file mode 100644
index 00000000000..5927948a8cd
--- /dev/null
+++ b/integration/feature/gen-idea/repo/extended/mill-build/build.sc
@@ -0,0 +1,6 @@
+import mill._
+import mill.scalalib._
+
+object root extends MillBuildRootModule {
+ def ivyDeps = Agg(ivy"org.scalameta::munit:0.7.29")
+}
\ No newline at end of file
diff --git a/integration/feature/gen-idea/repo/hello-world/idea/mill_modules/helloworld.iml b/integration/feature/gen-idea/repo/hello-world/idea/mill_modules/helloworld.iml
index cd2ce3b271d..f3159dda104 100644
--- a/integration/feature/gen-idea/repo/hello-world/idea/mill_modules/helloworld.iml
+++ b/integration/feature/gen-idea/repo/hello-world/idea/mill_modules/helloworld.iml
@@ -2,10 +2,8 @@
-
+
-
-
diff --git a/integration/feature/gen-idea/repo/hello-world/idea/mill_modules/helloworld.test.iml b/integration/feature/gen-idea/repo/hello-world/idea/mill_modules/helloworld.test.iml
index b7cbb784df6..708183f9e8a 100644
--- a/integration/feature/gen-idea/repo/hello-world/idea/mill_modules/helloworld.test.iml
+++ b/integration/feature/gen-idea/repo/hello-world/idea/mill_modules/helloworld.test.iml
@@ -2,10 +2,8 @@
-
+
-
-
diff --git a/integration/feature/gen-idea/repo/hello-world/idea/mill_modules/mill-build.iml b/integration/feature/gen-idea/repo/hello-world/idea/mill_modules/mill-build.iml
index 7d959abd7c7..781a179b34e 100644
--- a/integration/feature/gen-idea/repo/hello-world/idea/mill_modules/mill-build.iml
+++ b/integration/feature/gen-idea/repo/hello-world/idea/mill_modules/mill-build.iml
@@ -1,12 +1,11 @@
-
+
+
-
-
-
+
+
-
diff --git a/integration/feature/gen-idea/repo/hello-world/idea/modules.xml b/integration/feature/gen-idea/repo/hello-world/idea/modules.xml
index c6a6ad0683c..cab10e5c482 100644
--- a/integration/feature/gen-idea/repo/hello-world/idea/modules.xml
+++ b/integration/feature/gen-idea/repo/hello-world/idea/modules.xml
@@ -1,9 +1,9 @@
-
+
diff --git a/integration/feature/gen-idea/test/src/GenIdeaExtendedTests.scala b/integration/feature/gen-idea/test/src/GenIdeaExtendedTests.scala
index 824d90b18eb..e6b2bcfcf1a 100644
--- a/integration/feature/gen-idea/test/src/GenIdeaExtendedTests.scala
+++ b/integration/feature/gen-idea/test/src/GenIdeaExtendedTests.scala
@@ -1,7 +1,6 @@
-package mill.integration
-package local
+package mill.integration.local
-import os.Path
+import mill.integration.IntegrationTestSuite
import utest._
import scala.util.Try
@@ -15,13 +14,14 @@ object GenIdeaExtendedTests extends IntegrationTestSuite {
def tests: Tests = Tests {
"genIdeaTests" - {
val workspacePath = initWorkspace()
- eval("mill.scalalib.GenIdea/idea")
+ eval("mill.idea.GenIdea/idea")
val checks = Seq(
os.sub / "mill_modules" / "helloworld.iml",
- os.sub / "mill_modules" / "helloworld.test.iml",
os.sub / "mill_modules" / "helloworld.subscala3.iml",
+ os.sub / "mill_modules" / "helloworld.test.iml",
os.sub / "mill_modules" / "mill-build.iml",
+ os.sub / "mill_modules" / "mill-build.mill-build.iml",
os.sub / "libraries" / s"scala_library_${scalaVersionLibPart}_jar.xml",
// NOTE: on IntelliJ Scala Plugin side there is a cosmetic issue: scala suffix is added even for Java libraries (notice `_2_13` suffix)
// In future it might be fixed and `GenIdea` will need to be updated
diff --git a/integration/feature/gen-idea/test/src/GenIdeaTests.scala b/integration/feature/gen-idea/test/src/GenIdeaTests.scala
index 9d019bec006..36c2dbc22ea 100644
--- a/integration/feature/gen-idea/test/src/GenIdeaTests.scala
+++ b/integration/feature/gen-idea/test/src/GenIdeaTests.scala
@@ -1,10 +1,9 @@
-package mill.integration
-package local
+package mill.integration.local
-import os.Path
import utest.{Tests, assert, _}
import scala.util.Try
+import mill.integration.IntegrationTestSuite
import GenIdeaUtils._
object GenIdeaTests extends IntegrationTestSuite {
@@ -54,7 +53,7 @@ object GenIdeaTests extends IntegrationTestSuite {
test("genIdeaTests") {
val workspacePath = initWorkspace()
- eval("mill.scalalib.GenIdea/idea")
+ eval("mill.idea.GenIdea/idea")
val checks = Seq(
os.sub / "mill_modules" / "helloworld.iml",
diff --git a/integration/feature/gen-idea/test/src/GenIdeaUtils.scala b/integration/feature/gen-idea/test/src/GenIdeaUtils.scala
index 89afc9d502a..8aeddba9f46 100644
--- a/integration/feature/gen-idea/test/src/GenIdeaUtils.scala
+++ b/integration/feature/gen-idea/test/src/GenIdeaUtils.scala
@@ -1,7 +1,9 @@
-package mill.integration
+package mill.integration.local
+
import java.util.regex.Pattern
import scala.util.Try
import utest.assert
+
object GenIdeaUtils {
/**
diff --git a/main/eval/src/mill/eval/Evaluator.scala b/main/eval/src/mill/eval/Evaluator.scala
index f20156b3083..2c4cfdca4bd 100644
--- a/main/eval/src/mill/eval/Evaluator.scala
+++ b/main/eval/src/mill/eval/Evaluator.scala
@@ -67,6 +67,10 @@ object Evaluator {
// in directly) we are forced to pass it in via a ThreadLocal
val currentEvaluator = new DynamicVariable[mill.eval.Evaluator](null)
val allBootstrapEvaluators = new DynamicVariable[AllBootstrapEvaluators](null)
+
+ /**
+ * Holds all [[Evaluator]]s needed to evaluate the targets of the project and all it's bootstrap projects.
+ */
case class AllBootstrapEvaluators(value: Seq[Evaluator])
val defaultEnv: Map[String, String] = System.getenv().asScala.toMap
diff --git a/readme.adoc b/readme.adoc
index 6b8154b898d..4b076a0487d 100644
--- a/readme.adoc
+++ b/readme.adoc
@@ -74,7 +74,7 @@ IntelliJ project files via:
[source,bash]
----
-./mill mill.scalalib.GenIdea/idea
+./mill -j 0 mill.idea.GenIdea/idea
----
== Manual & Automated Testing
diff --git a/runner/src/mill/runner/FileImportGraph.scala b/runner/src/mill/runner/FileImportGraph.scala
index d5b041d90ed..17e517b7fa5 100644
--- a/runner/src/mill/runner/FileImportGraph.scala
+++ b/runner/src/mill/runner/FileImportGraph.scala
@@ -4,6 +4,14 @@ import mill.api.internal
import scala.reflect.NameTransformer.encode
import scala.collection.mutable
+/**
+ * @param seenScripts
+ * @param repos
+ * @param ivyDeps
+ * @param importGraphEdges
+ * @param errors
+ * @param millImport If `true`, a meta-build is enabled
+ */
@internal
case class FileImportGraph(
seenScripts: Map[os.Path, String],
diff --git a/runner/src/mill/runner/MillBuildRootModule.scala b/runner/src/mill/runner/MillBuildRootModule.scala
index 2a13161ad50..61d1e2f0f3c 100644
--- a/runner/src/mill/runner/MillBuildRootModule.scala
+++ b/runner/src/mill/runner/MillBuildRootModule.scala
@@ -2,16 +2,14 @@ package mill.runner
import coursier.Repository
import mill._
-import mill.api.{Loose, PathRef, Result, internal}
-import mill.define.{Caller, Discover, Target, Task}
+import mill.api.{PathRef, Result, internal}
+import mill.define.{Discover, Task}
import mill.scalalib.{BoundDep, Dep, DepSyntax, Lib, ScalaModule}
import mill.util.CoursierSupport
import mill.util.Util.millProjectModule
import mill.scalalib.api.Versions
-import os.{Path, rel}
import pprint.Util.literalize
import FileImportGraph.backtickWrap
-import mill.codesig.CodeSig
import mill.main.BuildInfo
import scala.collection.immutable.SortedMap
@@ -41,6 +39,7 @@ class MillBuildRootModule()(implicit
.mkString("/")
override def millSourcePath = millBuildRootModuleInfo.projectRoot / os.up / "mill-build"
+ override def intellijModulePath: os.Path = millSourcePath / os.up
override def resolveDeps(
deps: Task[Agg[BoundDep]],
@@ -61,16 +60,18 @@ class MillBuildRootModule()(implicit
override def scalaVersion: T[String] = "2.13.10"
+ /**
+ * All script files (that will get wrapped later)
+ * @see [[generateScriptSources]]
+ */
def scriptSources = T.sources {
- MillBuildRootModule
- .parseBuildFiles(millBuildRootModuleInfo)
+ MillBuildRootModule.parseBuildFiles(millBuildRootModuleInfo)
.seenScripts
- .keys
- .map(PathRef(_))
+ .keys.map(PathRef(_))
.toSeq
}
- def parseBuildFiles = T {
+ def parseBuildFiles: T[FileImportGraph] = T {
scriptSources()
MillBuildRootModule.parseBuildFiles(millBuildRootModuleInfo)
}
@@ -139,7 +140,7 @@ class MillBuildRootModule()(implicit
}
}
- def scriptImportGraph: T[Map[Path, (Int, Seq[Path])]] = T {
+ def scriptImportGraph: T[Map[os.Path, (Int, Seq[os.Path])]] = T {
parseBuildFiles()
.importGraphEdges
.map { case (path, imports) =>
@@ -211,8 +212,23 @@ class MillBuildRootModule()(implicit
result
}
+ override def sources: T[Seq[PathRef]] = T {
+ scriptSources() ++ {
+ if (parseBuildFiles().millImport) super.sources()
+ else Seq.empty[PathRef]
+ }
+ }
+
+ override def resources: T[Seq[PathRef]] = T {
+ if (parseBuildFiles().millImport) super.resources()
+ else Seq.empty[PathRef]
+ }
+
override def allSourceFiles: T[Seq[PathRef]] = T {
- Lib.findSourceFiles(allSources(), Seq("scala", "java", "sc")).map(PathRef(_))
+ val candidates = Lib.findSourceFiles(allSources(), Seq("scala", "java", "sc"))
+ // We need to unlist those files, which we replaced by generating wrapper scripts
+ val filesToExclude = Lib.findSourceFiles(scriptSources(), Seq("sc"))
+ candidates.filterNot(filesToExclude.contains).map(PathRef(_))
}
def enclosingClasspath = T.sources {
@@ -258,7 +274,7 @@ object MillBuildRootModule {
cliImports: Seq[String]
)(implicit baseModuleInfo: RootModule.Info) extends RootModule {
- implicit private def millBuildRootModuleInfo = MillBuildRootModule.Info(
+ implicit private def millBuildRootModuleInfo: Info = MillBuildRootModule.Info(
enclosingClasspath,
projectRoot,
topLevelProjectRoot0,
@@ -277,7 +293,7 @@ object MillBuildRootModule {
cliImports: Seq[String]
)
- def parseBuildFiles(millBuildRootModuleInfo: MillBuildRootModule.Info) = {
+ def parseBuildFiles(millBuildRootModuleInfo: MillBuildRootModule.Info): FileImportGraph = {
FileImportGraph.parseBuildFiles(
millBuildRootModuleInfo.topLevelProjectRoot,
millBuildRootModuleInfo.projectRoot / os.up
diff --git a/scalalib/src/mill/scalalib/Dep.scala b/scalalib/src/mill/scalalib/Dep.scala
index 119f887f580..553cea8e8b3 100644
--- a/scalalib/src/mill/scalalib/Dep.scala
+++ b/scalalib/src/mill/scalalib/Dep.scala
@@ -1,7 +1,8 @@
package mill.scalalib
import upickle.default.{macroRW, ReadWriter => RW}
-import CrossVersion._
+import mill.scalalib.CrossVersion._
+import coursier.core.Dependency
import mill.scalalib.api.ZincWorkerUtil
case class Dep(dep: coursier.Dependency, cross: CrossVersion, force: Boolean) {
@@ -135,7 +136,7 @@ object Dep {
force
)
}
- private implicit val depFormat = mill.scalalib.JsonFormatters.depFormat
+ private implicit val depFormat: RW[Dependency] = mill.scalalib.JsonFormatters.depFormat
implicit def rw: RW[Dep] = macroRW
}
@@ -207,6 +208,6 @@ case class BoundDep(
}
object BoundDep {
- private implicit val depFormat = mill.scalalib.JsonFormatters.depFormat
+ private implicit val depFormat: RW[Dependency] = mill.scalalib.JsonFormatters.depFormat
implicit val jsonify: upickle.default.ReadWriter[BoundDep] = upickle.default.macroRW
}
diff --git a/scalalib/src/mill/scalalib/GenIdea.scala b/scalalib/src/mill/scalalib/GenIdea.scala
index 3a0fb498d28..34dd5a433c8 100644
--- a/scalalib/src/mill/scalalib/GenIdea.scala
+++ b/scalalib/src/mill/scalalib/GenIdea.scala
@@ -1,27 +1,26 @@
package mill.scalalib
-import scala.util.control.NonFatal
-
import mill.T
import mill.define.{Command, Discover, ExternalModule}
import mill.eval.Evaluator
-import mill.api.Result
+import mill.resolve.SelectMode
+@deprecated("Use mill.idea.GenIdea instead", "Mill 0.11.2")
object GenIdea extends ExternalModule {
+ @deprecated("Use mill.idea.GenIdea/idea instead", "Mill 0.11.2")
def idea(ev: Evaluator): Command[Unit] = T.command {
- try {
- Result.Success(mill.scalalib.GenIdeaImpl(
- evaluator = ev,
- ctx = implicitly,
- rootModule = ev.rootModule,
- discover = ev.rootModule.millDiscover
- ).run())
- } catch {
- case GenIdeaImpl.GenIdeaException(m) => Result.Failure(m)
- case NonFatal(e) =>
- Result.Exception(e, new Result.OuterStack(new java.lang.Exception().getStackTrace))
- }
+ T.log.error(
+ "mill.scalalib.GenIdea/idea is deprecated. Please use mill.idea.GenIdea/idea instead."
+ )
+ mill.main.RunScript.evaluateTasksNamed(
+ ev,
+ Seq(
+ "mill.idea.GenIdea/idea"
+ ),
+ selectMode = SelectMode.Separated
+ )
+ ()
}
override lazy val millDiscover = Discover[this.type]
diff --git a/scalalib/src/mill/scalalib/GenIdeaImpl.scala b/scalalib/src/mill/scalalib/GenIdeaImpl.scala
index 99e2332df55..a7d357b9bc3 100755
--- a/scalalib/src/mill/scalalib/GenIdeaImpl.scala
+++ b/scalalib/src/mill/scalalib/GenIdeaImpl.scala
@@ -8,7 +8,7 @@ import coursier.maven.Pom
import mill.Agg
import mill.api.Ctx.{Home, Log}
-import mill.api.{PathRef, Result, Strict}
+import mill.api.{PathRef, Strict}
import mill.define._
import mill.eval.Evaluator
import mill.main.BuildInfo
@@ -17,6 +17,10 @@ import mill.util.Classpath
import mill.{T, scalalib}
import os.{Path, SubPath}
+/**
+ * This class is outdated and unmaintained. Please use [[mill.idea.GenIdeaImpl]] instead.
+ */
+@deprecated("Use mill.idea.GenIdeaImpl instead", "Mill 0.11.2")
case class GenIdeaImpl(
evaluator: Evaluator,
ctx: Log with Home,
diff --git a/scalalib/src/mill/scalalib/Lib.scala b/scalalib/src/mill/scalalib/Lib.scala
index a2e80e49ce2..9bdd87308b4 100644
--- a/scalalib/src/mill/scalalib/Lib.scala
+++ b/scalalib/src/mill/scalalib/Lib.scala
@@ -145,10 +145,10 @@ object Lib {
Util.millProperty("MILL_BUILD_LIBRARIES") match {
case Some(found) => found.split(',').map(os.Path(_)).distinct.toList
case None =>
- val repos = repos0 ++ Set(
+ val repos = (repos0 ++ Seq(
LocalRepositories.ivy2Local,
Repositories.central
- )
+ )).distinct
val millDeps = BuildInfo.millEmbeddedDeps.split(",").map(d => ivy"$d").map(dep =>
BoundDep(Lib.depToDependency(dep, BuildInfo.scalaVersion, ""), dep.force)
)