Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Small improvements to Idea project generation #616

Merged
merged 11 commits into from Jun 28, 2019
142 changes: 65 additions & 77 deletions scalalib/src/GenIdeaImpl.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package mill.scalalib

import ammonite.runtime.SpecialClassLoader
import coursier.Repository
import mill.define._
import mill.eval.{Evaluator, PathRef, Result}
import coursier.core.compatibility.xmlParseDom
import coursier.maven.Pom
import coursier.{LocalRepositories, Repositories, Repository}
import mill.api.Ctx.{Home, Log}
import mill.api.Strict.Agg
import mill.api.{Loose, Strict}
import mill.api.{Loose, Result, Strict}
import mill.define._
import mill.eval.{Evaluator, PathRef}
import mill.{T, scalalib}
import os.Path

Expand All @@ -17,34 +19,35 @@ object GenIdea extends ExternalModule {

def idea(ev: Evaluator) = T.command{
mill.scalalib.GenIdeaImpl(
ev,
implicitly,
ev.rootModule,
ev.rootModule.millDiscover
)
).run()
}

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)
case class GenIdeaImpl(evaluator: Evaluator,
ctx: Log with Home,
rootModule: BaseModule,
discover: Discover[_]) {
val cwd: Path = rootModule.millSourcePath

val jdkInfo = extractCurrentJdk(os.pwd / ".idea" / "misc.xml").getOrElse(("JDK_1_8", "1.8 (1)"))
def run(): Unit = {

os.remove.all(os.pwd/".idea"/"libraries")
os.remove.all(os.pwd/".idea"/"scala_compiler.xml")
os.remove.all(os.pwd/".idea_modules")

val pp = new scala.xml.PrettyPrinter(999, 4)
val jdkInfo = extractCurrentJdk(cwd / ".idea" / "misc.xml").getOrElse(("JDK_1_8", "1.8 (1)"))

val evaluator = new Evaluator(ctx.home, os.pwd / 'out, os.pwd / 'out, rootModule, ctx.log)
os.remove.all(cwd/".idea"/"libraries")
os.remove.all(cwd/".idea"/"scala_compiler.xml")
os.remove.all(cwd/".idea_modules")

for((relPath, xml) <- xmlFileLayout(evaluator, rootModule, jdkInfo, Some(ctx))){
os.write.over(os.pwd/relPath, pp.format(xml), createFolders = true)
os.write.over(cwd/relPath, pp.format(xml), createFolders = true)
}
}

Expand Down Expand Up @@ -77,11 +80,11 @@ object GenIdeaImpl {
else sys.props.get("MILL_BUILD_LIBRARIES") match {
case Some(found) => found.split(',').map(os.Path(_)).distinct.toList
case None =>
val repos = modules.foldLeft(Set.empty[Repository]) { _ ++ _._2.repositories }
val repos = modules.foldLeft(Set.empty[Repository]) { _ ++ _._2.repositories } ++ Set(LocalRepositories.ivy2Local, Repositories.central)
val artifactNames = Seq("main-moduledefs", "main-api", "main-core", "scalalib", "scalajslib")
val Result.Success(res) = scalalib.Lib.resolveDependencies(
repos.toList,
Lib.depToDependency(_, "2.12.4", ""),
Lib.depToDependency(_, "2.12.8", ""),
for(name <- artifactNames)
yield ivy"com.lihaoyi::mill-$name:${sys.props("MILL_VERSION")}",
false,
Expand Down Expand Up @@ -112,7 +115,7 @@ object GenIdeaImpl {
libraryClasspath: Loose.Agg[Path]
)

val resolved = for((path, mod) <- modules) yield {
val resolved = evalOrElse(evaluator, Task.sequence(for((path, mod) <- modules) yield {
val scalaLibraryIvyDeps = mod match{
case x: ScalaModule => x.scalaLibraryIvyDeps
case _ => T.task{Loose.Agg.empty[Dep]}
Expand Down Expand Up @@ -146,55 +149,41 @@ object GenIdeaImpl {
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 resolvedCompilerCp: Loose.Agg[PathRef] = evalOrElse(evaluator, scalaCompilerClasspath, Loose.Agg.empty)
val resolvedLibraryCp: Loose.Agg[PathRef] = evalOrElse(evaluator, externalLibraryDependencies, Loose.Agg.empty)
val scalacOpts: Seq[String] = evalOrElse(evaluator, scalacOptions, Seq())

ResolvedModule(
path,
resolvedCp.map(_.path).filter(_.ext == "jar") ++ resolvedSrcs.map(_.path),
mod,
resolvedSp.map(_.path).filter(_.ext == "jar"),
scalacOpts,
resolvedCompilerCp.map(_.path),
resolvedLibraryCp.map(_.path)
)
}
T.task {
val resolvedCp: Loose.Agg[PathRef] = externalDependencies()
val resolvedSrcs: Loose.Agg[PathRef] = externalSources()
val resolvedSp: Loose.Agg[PathRef] = scalacPluginDependencies()
val resolvedCompilerCp: Loose.Agg[PathRef] = scalaCompilerClasspath()
val resolvedLibraryCp: Loose.Agg[PathRef] = externalLibraryDependencies()
val scalacOpts: Seq[String] = scalacOptions()

ResolvedModule(
path,
resolvedCp.map(_.path).filter(_.ext == "jar") ++ resolvedSrcs.map(_.path),
mod,
resolvedSp.map(_.path).filter(_.ext == "jar"),
scalacOpts,
resolvedCompilerCp.map(_.path),
resolvedLibraryCp.map(_.path)
)
}
}), Seq())

val moduleLabels = modules.map(_.swap).toMap

val allResolved = resolved.flatMap(_.classpath) ++ buildLibraryPaths ++ buildDepsPaths

val librariesProperties = resolved.flatMap(x => x.libraryClasspath.map(_ -> x.compilerClasspath)).toMap

val commonPrefix =
if (allResolved.isEmpty) 0
else {
val minResolvedLength = allResolved.map(_.segmentCount).min
allResolved.map(_.segments.take(minResolvedLength).toList)
.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.last -> p}
.groupBy(_._1)
.groupBy(_.last)
.filter(_._2.size > 1)
.keySet
.mapValues(_.zipWithIndex)
.flatMap(y => y._2.map(x => x._1 -> s"${y._1} (${x._2})"))

val pathToLibName = allResolved
.map{p =>
if (pathShortLibNameDuplicate(p.last))
(p, p.segments.drop(commonPrefix).mkString("_"))
else
(p, p.last)
}
.map(p => p -> pathShortLibNameDuplicate.getOrElse(p, p.last))
.toMap

sealed trait ResolvedLibrary { def path : os.Path }
Expand All @@ -221,24 +210,25 @@ object GenIdeaImpl {

// Hack so that Intellij does not complain about unresolved magic
// imports in build.sc when in fact they are resolved
def sbtLibraryNameFromPom(pom : os.Path) : String = {
val xml = scala.xml.XML.loadFile(pom.toIO)

val groupId = (xml \ "groupId").text
val artifactId = (xml \ "artifactId").text
val version = (xml \ "version").text

// The scala version here is non incidental
s"SBT: $groupId:$artifactId:$version:jar"
def sbtLibraryNameFromPom(pomPath : os.Path) : String = {
val pom = xmlParseDom(os.read(pomPath)).flatMap(Pom.project).right.get

val artifactId = pom.module.name.value
val scalaArtifactRegex = ".*_[23]\\.[0-9]{1,2}".r
val artifactWithScalaVersion = artifactId.substring(artifactId.length - 5) match {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you comment what is and why it is happening here? Shouldn't the val be named artifactWithoutScalaVersion ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forget about the suggested val name, I misread.
My concern is primary the explicit versions. What about 2.13?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I don't like it anymore than you do

The naming that Idea expects is very strange. I will add some examples/tests in order to clarify.

I ended up the writing the code that checks for the Scala version suffix that way because it mimics the way that the Idea Scala Plugin actually does it. I think it was a bad decision.

About appending "_2.12", Idea expects the version to match mill's version of Scala. I wanted to use the BuildInfo plugin for that, but I am not sure if mill itself is using it. I saw the version already hardcoded in the code, so I went with it. I will try to use the BuildInfo plugin instead.

case scalaArtifactRegex(_*) => artifactId
case _ => artifactId + "_2.12"
}
s"SBT: ${pom.module.organization.value}:$artifactWithScalaVersion:${pom.version}:jar"
}

def libraryName(resolvedJar: ResolvedLibrary) : String = resolvedJar match {
def libraryNames(resolvedJar: ResolvedLibrary) : Seq[String] = resolvedJar match {
case CoursierResolved(path, pom, _) if buildDepsPaths.contains(path) =>
sbtLibraryNameFromPom(pom)
Seq(sbtLibraryNameFromPom(pom), pathToLibName(path))
case CoursierResolved(path, _, _) =>
pathToLibName(path)
Seq(pathToLibName(path))
case OtherResolved(path) =>
pathToLibName(path)
Seq(pathToLibName(path))
}

def resolvedLibraries(resolved : Seq[os.Path]) : Seq[ResolvedLibrary] = resolved
Expand Down Expand Up @@ -268,9 +258,7 @@ object GenIdeaImpl {
),
Tuple2(
os.rel/".idea_modules"/"mill-build.iml",
rootXmlTemplate(
for(lib <- allBuildLibraries)
yield libraryName(lib)
rootXmlTemplate(allBuildLibraries.flatMap(lib => libraryNames(lib))
)
),
Tuple2(
Expand All @@ -279,14 +267,14 @@ object GenIdeaImpl {
)
)

val libraries = resolvedLibraries(allResolved).map{ resolved =>
val libraries = resolvedLibraries(allResolved).flatMap{ resolved =>
import resolved.path
val name = libraryName(resolved)
val names = libraryNames(resolved)
val sources = resolved match {
case CoursierResolved(_, _, s) => s.map(p => "jar://" + p + "!/")
case OtherResolved(_) => None
}
Tuple2(os.rel/".idea"/'libraries/s"$name.xml", libraryXmlTemplate(name, path, sources, librariesProperties.getOrElse(path, Loose.Agg.empty)))
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, _, _, _, _) =>
Expand Down Expand Up @@ -341,7 +329,7 @@ object GenIdeaImpl {
}

def relify(p: os.Path) = {
val r = p.relativeTo(os.pwd/".idea_modules")
val r = p.relativeTo(cwd/".idea_modules")
(Seq.fill(r.ups)("..") ++ r.segments).mkString("/")
}

Expand Down
10 changes: 10 additions & 0 deletions scalalib/test/resources/gen-idea-hello-world/build.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import mill.scalalib

trait HelloWorldModule extends scalalib.ScalaModule {
def scalaVersion = "2.12.4"
object test extends super.Tests {
def testFrameworks = Seq("utest.runner.Framework")
}
}

object HelloWorld extends HelloWorldModule
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
<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/.iml" filepath="$PROJECT_DIR$/.idea_modules/.iml"/>
<module fileurl="file://$PROJECT_DIR$/.idea_modules/test.iml" filepath="$PROJECT_DIR$/.idea_modules/test.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>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<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>
</module>
Original file line number Diff line number Diff line change
@@ -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>
17 changes: 0 additions & 17 deletions scalalib/test/resources/gen-idea/idea_modules/iml

This file was deleted.

18 changes: 0 additions & 18 deletions scalalib/test/resources/gen-idea/idea_modules/test.iml

This file was deleted.

Loading