summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTobias Roeser <le.petit.fou@web.de>2019-09-05 09:34:32 +0200
committerGitHub <noreply@github.com>2019-09-05 09:34:32 +0200
commited8ea584c67f1ab83c488441779356a36d8fa3bb (patch)
treea4e7e0a900f2ac70906e1097e7542117a591a33a
parent8b91f0caf9a41f9e344328d8e14f5ff2b8149038 (diff)
downloadmill-ed8ea584c67f1ab83c488441779356a36d8fa3bb.tar.gz
mill-ed8ea584c67f1ab83c488441779356a36d8fa3bb.tar.bz2
mill-ed8ea584c67f1ab83c488441779356a36d8fa3bb.zip
Initial module-specific extension support to GenIdea (#684)
* Initial module-specific extension support to GenIdea * Contribute Facets to JavaModule * Generate additional files under .idea directory * Introduced more generel Element result type and unit tests * Moved intellijModulePath into GenIdeaModule * Added unit test for GenIdea extension (Missed that previously)
-rwxr-xr-xscalalib/src/GenIdeaImpl.scala164
-rw-r--r--scalalib/src/GenIdeaModule.scala51
-rw-r--r--scalalib/src/JavaModule.scala9
-rw-r--r--scalalib/test/resources/gen-idea-extended-hello-world/build.sc44
-rw-r--r--scalalib/test/resources/gen-idea-extended-hello-world/idea/compiler.xml8
-rw-r--r--scalalib/test/resources/gen-idea-extended-hello-world/idea/libraries/scala-library-2.12.4.jar.xml18
-rw-r--r--scalalib/test/resources/gen-idea-extended-hello-world/idea/misc.xml5
-rw-r--r--scalalib/test/resources/gen-idea-extended-hello-world/idea/modules.xml9
-rw-r--r--scalalib/test/resources/gen-idea-extended-hello-world/idea_modules/helloworld.iml26
-rw-r--r--scalalib/test/resources/gen-idea-extended-hello-world/idea_modules/helloworld.test.iml18
-rw-r--r--scalalib/test/resources/gen-idea-extended-hello-world/idea_modules/mill-build.iml12
-rw-r--r--scalalib/test/src/GenIdeaExtendedTests.scala41
12 files changed, 365 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 {
diff --git a/scalalib/test/resources/gen-idea-extended-hello-world/build.sc b/scalalib/test/resources/gen-idea-extended-hello-world/build.sc
new file mode 100644
index 00000000..4d1affbb
--- /dev/null
+++ b/scalalib/test/resources/gen-idea-extended-hello-world/build.sc
@@ -0,0 +1,44 @@
+import mill.scalalib
+import mill.define.Command
+import mill.scalalib.GenIdeaModule._
+
+trait HelloWorldModule extends scalalib.ScalaModule {
+ def scalaVersion = "2.12.4"
+ object test extends super.Tests {
+ def testFrameworks = Seq("utest.runner.Framework")
+ }
+
+ def ideaJavaModuleFacets(ideaConfigVersion: Int): Command[Seq[JavaFacet]] = T.command {
+ ideaConfigVersion match {
+ case 4 =>
+ Seq(
+ JavaFacet("AspectJ", "AspectJ",
+ Element("configuration", childs = Seq(
+ Element("projectLibrary", childs = Seq(
+ Element("option", Map("name" -> "name", "value" -> "/tmp"))
+ ))
+ ))
+ )
+ )
+ }
+ }
+
+ override def ideaConfigFiles(ideaConfigVersion: Int): Command[Seq[IdeaConfigFile]] = T.command {
+ ideaConfigVersion match {
+ case 4 =>
+ Seq(
+ IdeaConfigFile(
+ name = "compiler.xml",
+ component = "AjcSettings",
+ config = Seq(Element("option", Map("name" -> "ajcPath", "value" -> "/tmp/aspectjtools.jar")))),
+ IdeaConfigFile(
+ name = "compiler.xml",
+ component = "CompilerConfiguration",
+ config = Seq(Element("option", Map("name" -> "DEFAULT_COMPILER", "value" -> "ajc")))
+ )
+ )
+ }
+ }
+}
+
+object HelloWorld extends HelloWorldModule
diff --git a/scalalib/test/resources/gen-idea-extended-hello-world/idea/compiler.xml b/scalalib/test/resources/gen-idea-extended-hello-world/idea/compiler.xml
new file mode 100644
index 00000000..3622ff42
--- /dev/null
+++ b/scalalib/test/resources/gen-idea-extended-hello-world/idea/compiler.xml
@@ -0,0 +1,8 @@
+<project version="4">
+ <component name="CompilerConfiguration">
+ <option name="DEFAULT_COMPILER" value="ajc"/>
+ </component>
+ <component name="AjcSettings">
+ <option name="ajcPath" value="/tmp/aspectjtools.jar"/>
+ </component>
+</project> \ No newline at end of file
diff --git a/scalalib/test/resources/gen-idea-extended-hello-world/idea/libraries/scala-library-2.12.4.jar.xml b/scalalib/test/resources/gen-idea-extended-hello-world/idea/libraries/scala-library-2.12.4.jar.xml
new file mode 100644
index 00000000..5f7c5056
--- /dev/null
+++ b/scalalib/test/resources/gen-idea-extended-hello-world/idea/libraries/scala-library-2.12.4.jar.xml
@@ -0,0 +1,18 @@
+<component name="libraryTable">
+ <library name="scala-library-2.12.4.jar" type="Scala">
+ <properties>
+ <compiler-classpath>
+ <root url="file://COURSIER_HOME/https/repo1.maven.org/maven2/org/scala-lang/modules/scala-xml_2.12/1.0.6/scala-xml_2.12-1.0.6.jar"/>
+ <root url="file://COURSIER_HOME/https/repo1.maven.org/maven2/org/scala-lang/scala-compiler/2.12.4/scala-compiler-2.12.4.jar"/>
+ <root url="file://COURSIER_HOME/https/repo1.maven.org/maven2/org/scala-lang/scala-library/2.12.4/scala-library-2.12.4.jar"/>
+ <root url="file://COURSIER_HOME/https/repo1.maven.org/maven2/org/scala-lang/scala-reflect/2.12.4/scala-reflect-2.12.4.jar"/>
+ </compiler-classpath>
+ </properties>
+ <CLASSES>
+ <root url="jar://COURSIER_HOME/https/repo1.maven.org/maven2/org/scala-lang/scala-library/2.12.4/scala-library-2.12.4.jar!/"/>
+ </CLASSES>
+ <SOURCES>
+ <root url="jar://COURSIER_HOME/https/repo1.maven.org/maven2/org/scala-lang/scala-library/2.12.4/scala-library-2.12.4-sources.jar!/"/>
+ </SOURCES>
+ </library>
+</component>
diff --git a/scalalib/test/resources/gen-idea-extended-hello-world/idea/misc.xml b/scalalib/test/resources/gen-idea-extended-hello-world/idea/misc.xml
new file mode 100644
index 00000000..f4f144ce
--- /dev/null
+++ b/scalalib/test/resources/gen-idea-extended-hello-world/idea/misc.xml
@@ -0,0 +1,5 @@
+<project version="4">
+ <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8 (1)" project-jdk-type="JavaSDK">
+ <output url="file://$PROJECT_DIR$/target/idea_output"/>
+ </component>
+</project>
diff --git a/scalalib/test/resources/gen-idea-extended-hello-world/idea/modules.xml b/scalalib/test/resources/gen-idea-extended-hello-world/idea/modules.xml
new file mode 100644
index 00000000..193fa62d
--- /dev/null
+++ b/scalalib/test/resources/gen-idea-extended-hello-world/idea/modules.xml
@@ -0,0 +1,9 @@
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/.idea_modules/mill-build.iml" filepath="$PROJECT_DIR$/.idea_modules/mill-build.iml"/>
+ <module fileurl="file://$PROJECT_DIR$/.idea_modules/helloworld.iml" filepath="$PROJECT_DIR$/.idea_modules/helloworld.iml"/>
+ <module fileurl="file://$PROJECT_DIR$/.idea_modules/helloworld.test.iml" filepath="$PROJECT_DIR$/.idea_modules/helloworld.test.iml"/>
+ </modules>
+ </component>
+</project>
diff --git a/scalalib/test/resources/gen-idea-extended-hello-world/idea_modules/helloworld.iml b/scalalib/test/resources/gen-idea-extended-hello-world/idea_modules/helloworld.iml
new file mode 100644
index 00000000..94810f20
--- /dev/null
+++ b/scalalib/test/resources/gen-idea-extended-hello-world/idea_modules/helloworld.iml
@@ -0,0 +1,26 @@
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager">
+ <output url="file://$MODULE_DIR$/../out/HelloWorld/compile/dest/classes"/>
+ <exclude-output/>
+ <content url="file://$MODULE_DIR$/../out/HelloWorld/generatedSources/dest"/>
+ <content url="file://$MODULE_DIR$/../HelloWorld">
+ <sourceFolder url="file://$MODULE_DIR$/../HelloWorld/src" isTestSource="false"/>
+ <sourceFolder url="file://$MODULE_DIR$/../HelloWorld/resources" type="java-resource"/>
+ <excludeFolder url="file://$MODULE_DIR$/../HelloWorld/target"/>
+ </content>
+ <orderEntry type="inheritedJdk"/>
+ <orderEntry type="sourceFolder" forTests="false"/>
+ <orderEntry type="library" name="scala-sdk-2.12.4" level="application"/>
+ <orderEntry type="library" name="scala-library-2.12.4-sources.jar" level="project"/>
+ <orderEntry type="library" name="scala-library-2.12.4.jar" level="project"/>
+ </component>
+ <component name="FacetManager">
+ <facet type="AspectJ" name="AspectJ">
+ <configuration>
+ <projectLibrary>
+ <option name="name" value="/tmp"/>
+ </projectLibrary>
+ </configuration>
+ </facet>
+ </component>
+</module> \ No newline at end of file
diff --git a/scalalib/test/resources/gen-idea-extended-hello-world/idea_modules/helloworld.test.iml b/scalalib/test/resources/gen-idea-extended-hello-world/idea_modules/helloworld.test.iml
new file mode 100644
index 00000000..26fac21d
--- /dev/null
+++ b/scalalib/test/resources/gen-idea-extended-hello-world/idea_modules/helloworld.test.iml
@@ -0,0 +1,18 @@
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager">
+ <output-test url="file://$MODULE_DIR$/../out/HelloWorld/test/compile/dest/classes"/>
+ <exclude-output/>
+ <content url="file://$MODULE_DIR$/../out/HelloWorld/test/generatedSources/dest"/>
+ <content url="file://$MODULE_DIR$/../HelloWorld/test">
+ <sourceFolder url="file://$MODULE_DIR$/../HelloWorld/test/src" isTestSource="true"/>
+ <sourceFolder url="file://$MODULE_DIR$/../HelloWorld/test/resources" type="java-test-resource"/>
+ <excludeFolder url="file://$MODULE_DIR$/../HelloWorld/test/target"/>
+ </content>
+ <orderEntry type="inheritedJdk"/>
+ <orderEntry type="sourceFolder" forTests="false"/>
+ <orderEntry type="library" name="scala-sdk-2.12.4" level="application"/>
+ <orderEntry type="library" name="scala-library-2.12.4-sources.jar" level="project"/>
+ <orderEntry type="library" name="scala-library-2.12.4.jar" level="project"/>
+ <orderEntry type="module" module-name="helloworld" exported=""/>
+ </component>
+</module>
diff --git a/scalalib/test/resources/gen-idea-extended-hello-world/idea_modules/mill-build.iml b/scalalib/test/resources/gen-idea-extended-hello-world/idea_modules/mill-build.iml
new file mode 100644
index 00000000..46fd8c3b
--- /dev/null
+++ b/scalalib/test/resources/gen-idea-extended-hello-world/idea_modules/mill-build.iml
@@ -0,0 +1,12 @@
+<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"/>
+ </component>
+</module> \ No newline at end of file
diff --git a/scalalib/test/src/GenIdeaExtendedTests.scala b/scalalib/test/src/GenIdeaExtendedTests.scala
new file mode 100644
index 00000000..0a4de2e7
--- /dev/null
+++ b/scalalib/test/src/GenIdeaExtendedTests.scala
@@ -0,0 +1,41 @@
+package mill.scalalib
+
+import mill.util.ScriptTestSuite
+import os.Path
+import utest._
+
+object GenIdeaExtendedTests extends ScriptTestSuite(false) {
+
+ override def workspaceSlug: String = "gen-idea-extended-hello-world"
+
+ override def scriptSourcePath: Path = os.pwd / 'scalalib / 'test / 'resources / workspaceSlug
+
+ def tests: Tests = Tests {
+ 'genIdeaTests - {
+ initWorkspace()
+ eval("mill.scalalib.GenIdea/idea")
+
+ Seq(
+ s"$workspaceSlug/idea_modules/helloworld.iml" -> workspacePath / ".idea_modules" /"helloworld.iml",
+ s"$workspaceSlug/idea_modules/helloworld.test.iml" -> workspacePath / ".idea_modules" /"helloworld.test.iml",
+ s"$workspaceSlug/idea/libraries/scala-library-2.12.4.jar.xml" ->
+ workspacePath / ".idea" / "libraries" / "scala-library-2.12.4.jar.xml",
+
+ s"$workspaceSlug/idea/modules.xml" -> workspacePath / ".idea" / "modules.xml",
+ s"$workspaceSlug/idea/misc.xml" -> workspacePath / ".idea" / "misc.xml",
+ s"$workspaceSlug/idea/compiler.xml" -> workspacePath / ".idea" / "compiler.xml"
+
+ ).foreach { case (resource, generated) =>
+ val resourceString = scala.io.Source.fromResource(resource).getLines().mkString("\n")
+ val generatedString = normaliseLibraryPaths(os.read(generated))
+
+ assert(resourceString == generatedString)
+ }
+ }
+ }
+
+ private def normaliseLibraryPaths(in: String): String = {
+ in.replaceAll(coursier.paths.CoursierPaths.cacheDirectory().toString, "COURSIER_HOME")
+ }
+
+}