summaryrefslogtreecommitdiff
path: root/scalalib/src
diff options
context:
space:
mode:
Diffstat (limited to 'scalalib/src')
-rwxr-xr-xscalalib/src/GenIdeaImpl.scala164
-rw-r--r--scalalib/src/GenIdeaModule.scala51
-rw-r--r--scalalib/src/JavaModule.scala9
3 files changed, 184 insertions, 40 deletions
diff --git a/scalalib/src/GenIdeaImpl.scala b/scalalib/src/GenIdeaImpl.scala
index f737d473..f91eadaf 100755
--- a/scalalib/src/GenIdeaImpl.scala
+++ b/scalalib/src/GenIdeaImpl.scala
@@ -10,9 +10,11 @@ import mill.api.{Loose, Result, Strict}
import mill.define._
import mill.eval.{Evaluator, PathRef}
import mill.{T, scalalib}
-import os.Path
-
+import os.{Path, RelPath}
import scala.util.Try
+import scala.xml.{Elem, MetaData, NodeSeq, Null, UnprefixedAttribute}
+
+import mill.scalalib.GenIdeaModule.{IdeaConfigFile, JavaFacet}
object GenIdea extends ExternalModule {
@@ -34,10 +36,13 @@ case class GenIdeaImpl(evaluator: Evaluator,
ctx: Log with Home,
rootModule: BaseModule,
discover: Discover[_]) {
+ import GenIdeaImpl._
+
val cwd: Path = rootModule.millSourcePath
- def run(): Unit = {
+ val ideaConfigVersion = 4
+ def run(): Unit = {
val pp = new scala.xml.PrettyPrinter(999, 4)
val jdkInfo = extractCurrentJdk(cwd / ".idea" / "misc.xml").getOrElse(("JDK_1_8", "1.8 (1)"))
@@ -68,7 +73,7 @@ case class GenIdeaImpl(evaluator: Evaluator,
ctx: Option[Log],
fetchMillModules: Boolean = true): Seq[(os.RelPath, scala.xml.Node)] = {
- val modules = rootModule.millInternal.segmentsToModules.values
+ val modules: Seq[(Segments, JavaModule)] = rootModule.millInternal.segmentsToModules.values
.collect{ case x: scalalib.JavaModule => x }
.flatMap(_.transitiveModuleDeps)
.map(x => (x.millModuleSegments, x))
@@ -112,7 +117,9 @@ case class GenIdeaImpl(evaluator: Evaluator,
pluginClasspath: Loose.Agg[Path],
scalaOptions: Seq[String],
compilerClasspath: Loose.Agg[Path],
- libraryClasspath: Loose.Agg[Path]
+ libraryClasspath: Loose.Agg[Path],
+ facets: Seq[JavaFacet],
+ configFileContributions: Seq[IdeaConfigFile]
)
val resolved = evalOrElse(evaluator, Task.sequence(for((path, mod) <- modules) yield {
@@ -149,6 +156,14 @@ case class GenIdeaImpl(evaluator: Evaluator,
mod.resolveDeps(scalacPluginsIvyDeps)()
}
+ val facets = T.task{
+ mod.ideaJavaModuleFacets(ideaConfigVersion)()
+ }
+
+ val configFileContributions = T.task{
+ mod.ideaConfigFiles(ideaConfigVersion)()
+ }
+
T.task {
val resolvedCp: Loose.Agg[PathRef] = externalDependencies()
val resolvedSrcs: Loose.Agg[PathRef] = externalSources()
@@ -156,6 +171,8 @@ case class GenIdeaImpl(evaluator: Evaluator,
val resolvedCompilerCp: Loose.Agg[PathRef] = scalaCompilerClasspath()
val resolvedLibraryCp: Loose.Agg[PathRef] = externalLibraryDependencies()
val scalacOpts: Seq[String] = scalacOptions()
+ val resolvedFacets: Seq[JavaFacet] = facets()
+ val resolvedConfigFileContributions: Seq[IdeaConfigFile] = configFileContributions()
ResolvedModule(
path,
@@ -164,7 +181,9 @@ case class GenIdeaImpl(evaluator: Evaluator,
resolvedSp.map(_.path).filter(_.ext == "jar"),
scalacOpts,
resolvedCompilerCp.map(_.path),
- resolvedLibraryCp.map(_.path)
+ resolvedLibraryCp.map(_.path),
+ resolvedFacets,
+ resolvedConfigFileContributions
)
}
}), Seq())
@@ -175,6 +194,38 @@ case class GenIdeaImpl(evaluator: Evaluator,
val librariesProperties = resolved.flatMap(x => x.libraryClasspath.map(_ -> x.compilerClasspath)).toMap
+ val configFileContributions = resolved.flatMap(_.configFileContributions)
+
+ type FileComponent = (String, String)
+ def collisionFree(confs: Seq[IdeaConfigFile]): Map[String, Seq[IdeaConfigFile]] = {
+ var seen: Map[FileComponent, Seq[GenIdeaModule.Element]] = Map()
+ var result: Map[String, Seq[IdeaConfigFile]] = Map()
+ confs.foreach { conf =>
+ val key = conf.name -> conf.component
+ seen.get(key) match {
+ case None =>
+ seen += key -> conf.config
+ result += conf.name -> (result.get(conf.name).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.name}` and component `${conf.component}`: ${details(conf.config)} vs. ${details(existing)}"
+ ctx.map(_.log.error(msg))
+ }
+ }
+ result
+ }
+
+ //TODO: also check against fixed files
+ val fileContributions: Seq[(RelPath, Elem)] = collisionFree(configFileContributions).toSeq.map {
+ case (file, configs) =>
+ val map: Map[String, Seq[GenIdeaModule.Element]] = configs.groupBy(_.component).mapValues(_.flatMap(_.config))
+ (os.rel / ".idea" / file) -> ideaConfigFileTemplate(map)
+ }
+
val pathShortLibNameDuplicate = allResolved
.distinct
.groupBy(_.last)
@@ -245,7 +296,7 @@ case class GenIdeaImpl(evaluator: Evaluator,
val allBuildLibraries : Set[ResolvedLibrary] =
resolvedLibraries(buildLibraryPaths ++ buildDepsPaths).toSet
- val fixedFiles = Seq(
+ val fixedFiles: Seq[(RelPath, Elem)] = Seq(
Tuple2(os.rel/".idea"/"misc.xml", miscXmlTemplate(jdkInfo)),
Tuple2(os.rel/".idea"/"scala_settings.xml", scalaSettingsTemplate()),
Tuple2(
@@ -277,7 +328,7 @@ case class GenIdeaImpl(evaluator: Evaluator,
for(name <- names) yield Tuple2(os.rel/".idea"/'libraries/s"$name.xml", libraryXmlTemplate(name, path, sources, librariesProperties.getOrElse(path, Loose.Agg.empty)))
}
- val moduleFiles = resolved.map{ case ResolvedModule(path, resolvedDeps, mod, _, _, _, _) =>
+ val moduleFiles = resolved.map{ case ResolvedModule(path, resolvedDeps, mod, _, _, _, _, facets, _) =>
val Seq(
resourcesPathRefs: Seq[PathRef],
sourcesPathRef: Seq[PathRef],
@@ -313,19 +364,13 @@ case class GenIdeaImpl(evaluator: Evaluator,
generatedSourceOutPath.dest,
Strict.Agg.from(resolvedDeps.map(pathToLibName)),
Strict.Agg.from(mod.moduleDeps.map{ m => moduleName(moduleLabels(m))}.distinct),
- isTest
+ isTest,
+ facets
)
Tuple2(os.rel/".idea_modules"/s"${moduleName(path)}.iml", elem)
}
- fixedFiles ++ libraries ++ moduleFiles
- }
-
- 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
- }
+ fixedFiles ++ fileContributions ++ libraries ++ moduleFiles
}
def relify(p: os.Path) = {
@@ -333,23 +378,47 @@ case class GenIdeaImpl(evaluator: Evaluator,
(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 ideaConfigElementTemplate(element: GenIdeaModule.Element): Elem = {
- def scalaSettingsTemplate() = {
+ val example = <config/>
+
+ 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[String, Seq[GenIdeaModule.Element]]): Elem = {
+ <project version={ "" + ideaConfigVersion }>
+ {
+ components.toSeq.map { case (name, config) =>
+ <component name={ name }>{config.map(ideaConfigElementTemplate)}</component>
+ }
+ }
+ </project>
+ }
- <project version="4">
+ def scalaSettingsTemplate() = {
+// simpleIdeaConfigFileTemplate(Map("ScalaProjectSettings" -> Map("scFileMode" -> "Ammonite")))
+ <project version={"" + ideaConfigVersion}>
<component name="ScalaProjectSettings">
<option name="scFileMode" value="Ammonite" />
</component>
</project>
}
def miscXmlTemplate(jdkInfo: (String,String)) = {
- <project version="4">
+ <project version={"" + ideaConfigVersion}>
<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>
@@ -357,7 +426,7 @@ case class GenIdeaImpl(evaluator: Evaluator,
}
def allModulesXmlTemplate(selectors: Seq[String]) = {
- <project version="4">
+ <project version={"" + ideaConfigVersion}>
<component name="ProjectModuleManager">
<modules>
<module
@@ -377,7 +446,7 @@ case class GenIdeaImpl(evaluator: Evaluator,
</project>
}
def rootXmlTemplate(libNames: Strict.Agg[String]) = {
- <module type="JAVA_MODULE" version="4">
+ <module type="JAVA_MODULE" version={"" + ideaConfigVersion}>
<component name="NewModuleRootManager">
<output url="file://$MODULE_DIR$/../out"/>
<content url="file://$MODULE_DIR$/..">
@@ -430,9 +499,10 @@ case class GenIdeaImpl(evaluator: Evaluator,
generatedSourceOutputPath: os.Path,
libNames: Strict.Agg[String],
depNames: Strict.Agg[String],
- isTest: Boolean
- ) = {
- <module type="JAVA_MODULE" version="4">
+ isTest: Boolean,
+ facets: Seq[GenIdeaModule.JavaFacet]
+ ): Elem = {
+ <module type="JAVA_MODULE" version={"" + ideaConfigVersion}>
<component name="NewModuleRootManager">
{
val outputUrl = "file://$MODULE_DIR$/" + relify(compileOutputPath) + "/dest/classes"
@@ -479,11 +549,16 @@ case class GenIdeaImpl(evaluator: Evaluator,
yield <orderEntry type="module" module-name={depName} exported="" />
}
</component>
+ { if (facets.isEmpty) NodeSeq.Empty else { <component name="FacetManager">
+ { for (facet <- facets) yield { <facet type={ facet.`type` } name={ facet.name }>
+ { ideaConfigElementTemplate(facet.config) }
+ </facet> } }
+ </component> } }
</module>
}
def scalaCompilerTemplate(settings: Map[(Loose.Agg[os.Path], Seq[String]), Seq[JavaModule]]) = {
- <project version="4">
+ <project version={"" + ideaConfigVersion}>
<component name="ScalaCompilerConfiguration">
{
for((((plugins, params), mods), i) <- settings.toSeq.zip(1 to settings.size))
@@ -507,3 +582,28 @@ case class GenIdeaImpl(evaluator: Evaluator,
</project>
}
}
+
+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(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()
+
+ /**
+ * Evaluate the given task `e`. In case, the task has no successful result(s), return the `default` value instead.
+ */
+ 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
+ }
+ }
+
+}
diff --git a/scalalib/src/GenIdeaModule.scala b/scalalib/src/GenIdeaModule.scala
new file mode 100644
index 00000000..e874da8b
--- /dev/null
+++ b/scalalib/src/GenIdeaModule.scala
@@ -0,0 +1,51 @@
+package mill.scalalib
+
+import mill.define.Command
+import mill.{Module, T}
+
+/**
+ * Module specific configuration of the Idea project file generator.
+ */
+trait GenIdeaModule extends Module {
+ import GenIdeaModule._
+
+ def intellijModulePath: os.Path = millSourcePath
+
+ /**
+ * Skip Idea project file generation.
+ */
+ def skipIdea: Boolean = false
+
+ /**
+ * Contribute facets to the Java module configuration.
+ * @param ideaConfigVersion The IDEA configuration version in use. Probably `4`.
+ * @return
+ */
+ def ideaJavaModuleFacets(ideaConfigVersion: Int): Command[Seq[JavaFacet]] = T.command { Seq[JavaFacet]() }
+
+ /**
+ * Contribute components to idea config files.
+ */
+ def ideaConfigFiles(ideaConfigVersion: Int): Command[Seq[IdeaConfigFile]] = T.command { Seq[IdeaConfigFile]() }
+
+ }
+
+object GenIdeaModule {
+ import upickle.default._
+
+ case class Element(name: String, attributes: Map[String, String] = Map(), childs: Seq[Element] = Seq())
+ object Element {
+ implicit def rw: ReadWriter[Element] = macroRW
+ }
+
+ final case class JavaFacet(`type`: String, name: String, config: Element)
+ object JavaFacet {
+ implicit def rw: ReadWriter[JavaFacet] = macroRW
+ }
+
+ final case class IdeaConfigFile(name: String, component: String, config: Seq[Element])
+ object IdeaConfigFile {
+ implicit def rw: ReadWriter[IdeaConfigFile] = macroRW
+ }
+
+}
diff --git a/scalalib/src/JavaModule.scala b/scalalib/src/JavaModule.scala
index c7066d15..b9987ca1 100644
--- a/scalalib/src/JavaModule.scala
+++ b/scalalib/src/JavaModule.scala
@@ -17,7 +17,7 @@ import mill.api.Loose.Agg
/**
* Core configuration required to compile a single Scala compilation target
*/
-trait JavaModule extends mill.Module with TaskModule { outer =>
+trait JavaModule extends mill.Module with TaskModule with GenIdeaModule { outer =>
def zincWorker: ZincWorkerModule = mill.scalalib.ZincWorkerModule
trait Tests extends TestModule{
@@ -554,14 +554,7 @@ trait JavaModule extends mill.Module with TaskModule { outer =>
*/
def artifactId: T[String] = artifactName()
- def intellijModulePath: os.Path = millSourcePath
-
def forkWorkingDir = T{ ammonite.ops.pwd }
-
- /**
- * Skip Idea project file generation.
- */
- def skipIdea: Boolean = false
}
trait TestModule extends JavaModule with TaskModule {