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) )