diff options
author | Li Haoyi <haoyi.sg@gmail.com> | 2018-04-07 19:58:57 -0700 |
---|---|---|
committer | Li Haoyi <haoyi.sg@gmail.com> | 2018-04-07 19:58:57 -0700 |
commit | b5350ae72c33d937aaa1953e2dfba3c0d6a88168 (patch) | |
tree | c40b2f0d7f23960045337713043a6040b5ff85ce /scalalib/src/mill/scalalib/GenIdeaImpl.scala | |
parent | 2d72abe64de8dbd98cabc688ce4bb57f733a1d12 (diff) | |
download | mill-b5350ae72c33d937aaa1953e2dfba3c0d6a88168.tar.gz mill-b5350ae72c33d937aaa1953e2dfba3c0d6a88168.tar.bz2 mill-b5350ae72c33d937aaa1953e2dfba3c0d6a88168.zip |
update travis mill version
Diffstat (limited to 'scalalib/src/mill/scalalib/GenIdeaImpl.scala')
-rw-r--r-- | scalalib/src/mill/scalalib/GenIdeaImpl.scala | 389 |
1 files changed, 389 insertions, 0 deletions
diff --git a/scalalib/src/mill/scalalib/GenIdeaImpl.scala b/scalalib/src/mill/scalalib/GenIdeaImpl.scala new file mode 100644 index 00000000..3e90f269 --- /dev/null +++ b/scalalib/src/mill/scalalib/GenIdeaImpl.scala @@ -0,0 +1,389 @@ +package mill.scalalib + +import ammonite.ops._ +import coursier.Repository +import mill.define._ +import mill.eval.{Evaluator, PathRef, Result} +import mill.{T, scalalib} +import mill.util.Ctx.{Home, Log} +import mill.util.{Loose, PrintLogger, Strict} +import mill.util.Strict.Agg + +import scala.util.Try + + +object GenIdea extends ExternalModule { + + def idea(ev: Evaluator[Any]) = T.command{ + mill.scalalib.GenIdeaImpl( + implicitly, + ev.rootModule, + ev.rootModule.millDiscover + ) + } + + implicit def millScoptEvaluatorReads[T] = new mill.main.EvaluatorScopt[T]() + lazy val millDiscover = Discover[this.type] +} +object GenIdeaImpl { + + def apply(ctx: Log with Home, + rootModule: BaseModule, + discover: Discover[_]): Unit = { + val pp = new scala.xml.PrettyPrinter(999, 4) + + val jdkInfo = extractCurrentJdk(pwd / ".idea" / "misc.xml").getOrElse(("JDK_1_8", "1.8 (1)")) + + rm! pwd/".idea" + rm! pwd/".idea_modules" + + + val evaluator = new Evaluator(ctx.home, pwd / 'out, pwd / 'out, rootModule, ctx.log) + + for((relPath, xml) <- xmlFileLayout(evaluator, rootModule, jdkInfo)){ + write.over(pwd/relPath, pp.format(xml)) + } + } + + def extractCurrentJdk(ideaPath: 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[T](evaluator: Evaluator[T], + rootModule: mill.Module, + jdkInfo: (String,String), + fetchMillModules: Boolean = true): Seq[(RelPath, scala.xml.Node)] = { + + val modules = rootModule.millInternal.segmentsToModules.values + .collect{ case x: scalalib.JavaModule => (x.millModuleSegments, x)} + .toSeq + + val buildLibraryPaths = + if (!fetchMillModules) Nil + else sys.props.get("MILL_BUILD_LIBRARIES") match { + case Some(found) => found.split(',').map(Path(_)).distinct.toList + case None => + val repos = modules.foldLeft(Set.empty[Repository]) { _ ++ _._2.repositories } + val artifactNames = Seq("moduledefs", "core", "scalalib", "scalajslib") + val Result.Success(res) = scalalib.Lib.resolveDependencies( + repos.toList, + Lib.depToDependency(_, "2.12.4", ""), + for(name <- artifactNames) + yield ivy"com.lihaoyi::mill-$name:${sys.props("MILL_VERSION")}" + ) + res.items.toList.map(_.path) + } + + val resolved = for((path, mod) <- modules) yield { + val scalaLibraryIvyDeps = mod match{ + case x: ScalaModule => x.scalaLibraryIvyDeps + case _ => T.task{Nil} + } + val allIvyDeps = T.task{mod.transitiveIvyDeps() ++ scalaLibraryIvyDeps() ++ mod.compileIvyDeps()} + val externalDependencies = T.task{ + mod.resolveDeps(allIvyDeps)() ++ + Task.traverse(mod.transitiveModuleDeps)(_.unmanagedClasspath)().flatten + } + + val externalSources = T.task{ + mod.resolveDeps(allIvyDeps, sources = true)() + } + + val (scalacPluginsIvyDeps, scalacOptions) = mod match{ + case mod: ScalaModule => T.task{mod.scalacPluginIvyDeps()} -> T.task{mod.scalacOptions()} + case _ => T.task(Loose.Agg[Dep]()) -> T.task(Seq()) + } + val scalacPluginDependencies = T.task{ + mod.resolveDeps(scalacPluginsIvyDeps)() + } + + val resolvedCp: Loose.Agg[PathRef] = evalOrElse(evaluator, externalDependencies, Loose.Agg.empty) + val resolvedSrcs: Loose.Agg[PathRef] = evalOrElse(evaluator, externalSources, Loose.Agg.empty) + val resolvedSp: Loose.Agg[PathRef] = evalOrElse(evaluator, scalacPluginDependencies, Loose.Agg.empty) + val scalacOpts: Seq[String] = evalOrElse(evaluator, scalacOptions, Seq()) + + ( + path, + resolvedCp.map(_.path).filter(_.ext == "jar") ++ resolvedSrcs.map(_.path), + mod, + resolvedSp.map(_.path).filter(_.ext == "jar"), + scalacOpts + ) + } + val moduleLabels = modules.map(_.swap).toMap + + + val allResolved = resolved.flatMap(_._2) ++ buildLibraryPaths + val commonPrefix = + if (allResolved.isEmpty) 0 + else { + val minResolvedLength = allResolved.map(_.segments.length).min + allResolved.map(_.segments.take(minResolvedLength)) + .transpose + .takeWhile(_.distinct.length == 1) + .length + } + + // only resort to full long path names if the jar name is a duplicate + val pathShortLibNameDuplicate = allResolved + .distinct + .map{p => p.segments.last -> p} + .groupBy(_._1) + .filter(_._2.size > 1) + .keySet + + val pathToLibName = allResolved + .map{p => + if (pathShortLibNameDuplicate(p.segments.last)) + (p, p.segments.drop(commonPrefix).mkString("_")) + else + (p, p.segments.last) + } + .toMap + + val compilerSettings = resolved + .foldLeft(Map[(Loose.Agg[Path], Seq[String]), Vector[JavaModule]]()) { + (r, q) => + val key = (q._4, q._5) + r + (key -> (r.getOrElse(key, Vector()) :+ q._3)) + } + + val fixedFiles = Seq( + Tuple2(".idea"/"misc.xml", miscXmlTemplate(jdkInfo)), + Tuple2(".idea"/"scala_settings.xml", scalaSettingsTemplate()), + Tuple2( + ".idea"/"modules.xml", + allModulesXmlTemplate( + for((path, mod) <- modules) + yield moduleName(path) + ) + ), + Tuple2( + ".idea_modules"/"root.iml", + rootXmlTemplate( + for(path <- buildLibraryPaths) + yield pathToLibName(path) + ) + ), + Tuple2( + ".idea"/"scala_compiler.xml", + scalaCompilerTemplate(compilerSettings) + ) + ) + + val libraries = allResolved.map{path => + val url = "jar://" + path + "!/" + val name = pathToLibName(path) + Tuple2(".idea"/'libraries/s"$name.xml", libraryXmlTemplate(name, url)) + } + + val buildLibraries = buildLibraryPaths.map{path => + val url = "jar://" + path + "!/" + val name = pathToLibName(path) + Tuple2(".idea"/'libraries/s"$name.xml", libraryXmlTemplate(name, url)) + } + + val moduleFiles = resolved.map{ case (path, resolvedDeps, mod, _, _) => + val Seq( + resourcesPathRefs: Seq[PathRef], + sourcesPathRef: Seq[PathRef], + generatedSourcePathRefs: Seq[PathRef], + allSourcesPathRefs: Seq[PathRef] + ) = evaluator.evaluate(Agg(mod.resources, mod.sources, mod.generatedSources, mod.allSources)).values + + val generatedSourcePaths = generatedSourcePathRefs.map(_.path) + val normalSourcePaths = (allSourcesPathRefs.map(_.path).toSet -- generatedSourcePaths.toSet).toSeq + + val paths = Evaluator.resolveDestPaths( + evaluator.outPath, + mod.compile.ctx.segments + ) + val scalaVersionOpt = mod match { + case x: ScalaModule => Some(evaluator.evaluate(Agg(x.scalaVersion)).values.head.asInstanceOf[String]) + case _ => None + } + val generatedSourceOutPath = Evaluator.resolveDestPaths( + evaluator.outPath, + mod.generatedSources.ctx.segments + ) + + val elem = moduleXmlTemplate( + mod.millModuleBasePath.value, + scalaVersionOpt, + Strict.Agg.from(resourcesPathRefs.map(_.path)), + Strict.Agg.from(normalSourcePaths), + Strict.Agg.from(generatedSourcePaths), + paths.out, + generatedSourceOutPath.dest, + Strict.Agg.from(resolvedDeps.map(pathToLibName)), + Strict.Agg.from(mod.moduleDeps.map{ m => moduleName(moduleLabels(m))}.distinct) + ) + Tuple2(".idea_modules"/s"${moduleName(path)}.iml", elem) + } + + fixedFiles ++ libraries ++ moduleFiles ++ buildLibraries + } + + def evalOrElse[T](evaluator: Evaluator[_], e: Task[T], default: => T): T = { + evaluator.evaluate(Agg(e)).values match { + case Seq() => default + case Seq(e: T) => e + } + } + + def relify(p: Path) = { + val r = p.relativeTo(pwd/".idea_modules") + (Seq.fill(r.ups)("..") ++ r.segments).mkString("/") + } + + def moduleName(p: Segments) = p.value.foldLeft(StringBuilder.newBuilder) { + 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() + + def scalaSettingsTemplate() = { + + <project version="4"> + <component name="ScalaProjectSettings"> + <option name="scFileMode" value="Ammonite" /> + </component> + </project> + } + def miscXmlTemplate(jdkInfo: (String,String)) = { + <project version="4"> + <component name="ProjectRootManager" version="2" languageLevel={jdkInfo._1} project-jdk-name={jdkInfo._2} project-jdk-type="JavaSDK"> + <output url="file://$PROJECT_DIR$/target/idea_output"/> + </component> + </project> + } + + def allModulesXmlTemplate(selectors: Seq[String]) = { + <project version="4"> + <component name="ProjectModuleManager"> + <modules> + <module fileurl="file://$PROJECT_DIR$/.idea_modules/root.iml" filepath="$PROJECT_DIR$/.idea_modules/root.iml" /> + { + for(selector <- selectors) + yield { + val filepath = "$PROJECT_DIR$/.idea_modules/" + selector + ".iml" + val fileurl = "file://" + filepath + <module fileurl={fileurl} filepath={filepath} /> + } + } + </modules> + </component> + </project> + } + def rootXmlTemplate(libNames: Strict.Agg[String]) = { + <module type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager"> + <output url="file://$MODULE_DIR$/../out"/> + <content url="file://$MODULE_DIR$/.."> + <excludeFolder url="file://$MODULE_DIR$/../project" /> + <excludeFolder url="file://$MODULE_DIR$/../target" /> + </content> + <exclude-output/> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + { + for(name <- libNames.toSeq.sorted) + yield <orderEntry type="library" name={name} level="project" /> + } + </component> + </module> + } + def libraryXmlTemplate(name: String, url: String) = { + <component name="libraryTable"> + <library name={name} type={if(name.contains("scala-library-")) "Scala" else null}> + <CLASSES> + <root url={url}/> + </CLASSES> + </library> + </component> + } + def moduleXmlTemplate(basePath: Path, + scalaVersionOpt: Option[String], + resourcePaths: Strict.Agg[Path], + normalSourcePaths: Strict.Agg[Path], + generatedSourcePaths: Strict.Agg[Path], + compileOutputPath: Path, + generatedSourceOutputPath: Path, + libNames: Strict.Agg[String], + depNames: Strict.Agg[String]) = { + <module type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager"> + <output url={"file://$MODULE_DIR$/" + relify(compileOutputPath) + "/dest/classes"} /> + <exclude-output /> + <content url={"file://$MODULE_DIR$/" + relify(generatedSourceOutputPath)} /> + <content url={"file://$MODULE_DIR$/" + relify(basePath)}> + { + for (normalSourcePath <- normalSourcePaths.toSeq.sorted) + yield + <sourceFolder url={"file://$MODULE_DIR$/" + relify(normalSourcePath)} isTestSource="false" /> + } + { + for (generatedSourcePath <- generatedSourcePaths.toSeq.sorted) + yield + <sourceFolder url={"file://$MODULE_DIR$/" + relify(generatedSourcePath)} isTestSource="false" generated="true" /> + } + { + for (resourcePath <- resourcePaths.toSeq.sorted) + yield + <sourceFolder url={"file://$MODULE_DIR$/" + relify(resourcePath)} isTestSource="false" type="java-resource" /> + } + <excludeFolder url={"file://$MODULE_DIR$/" + relify(basePath) + "/target"} /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + { + for(scalaVersion <- scalaVersionOpt.toSeq) + yield <orderEntry type="library" name={s"scala-sdk-$scalaVersion"} level="application" /> + } + + { + for(name <- libNames.toSeq.sorted) + yield <orderEntry type="library" name={name} level="project" /> + + } + { + for(depName <- depNames.toSeq.sorted) + yield <orderEntry type="module" module-name={depName} exported="" /> + } + </component> + </module> + } + def scalaCompilerTemplate(settings: Map[(Loose.Agg[Path], Seq[String]), Seq[JavaModule]]) = { + + <project version="4"> + <component name="ScalaCompilerConfiguration"> + { + for((((plugins, params), mods), i) <- settings.toSeq.zip(1 to settings.size)) + yield + <profile name={s"mill $i"} modules={mods.map(m => moduleName(m.millModuleSegments)).mkString(",")}> + <parameters> + { + for(param <- params) + yield <parameter value={param} /> + } + </parameters> + <plugins> + { + for(plugin <- plugins.toSeq) + yield <plugin path={plugin.toString} /> + } + </plugins> + </profile> + } + </component> + </project> + } +} |