diff --git a/src/Pure/Tools/scala_project.scala b/src/Pure/Tools/scala_project.scala --- a/src/Pure/Tools/scala_project.scala +++ b/src/Pure/Tools/scala_project.scala @@ -1,299 +1,298 @@ /* Title: Pure/Tools/scala_project.scala Author: Makarius Manage Isabelle/Scala/Java project sources, with output to Gradle or Maven for IntelliJ IDEA. */ package isabelle object Scala_Project { /** build tools **/ val java_version: String = "17" val scala_version: String = "3.1.3" abstract class Build_Tool { def project_root: Path def init_project(dir: Path, jars: List[Path]): Unit val java_src_dir: Path = Path.explode("src/main/java") val scala_src_dir: Path = Path.explode("src/main/scala") def detect_project(dir: Path): Boolean = (dir + project_root).is_file && (dir + scala_src_dir).is_dir def package_dir(source_file: Path): Path = { val dir = package_name(source_file) match { case Some(name) => Path.explode(space_explode('.', name).mkString("/")) case None => error("Failed to guess package from " + source_file) } (if (source_file.is_java) java_src_dir else scala_src_dir) + dir } } def build_tools: List[Build_Tool] = List(Gradle, Maven) /* Gradle */ object Gradle extends Build_Tool { override def toString: String = "Gradle" val project_settings: Path = Path.explode("settings.gradle") override val project_root: Path = Path.explode("build.gradle") private def groovy_string(s: String): String = { s.map(c => c match { case '\t' | '\b' | '\n' | '\r' | '\f' | '\\' | '\'' | '"' => "\\" + c case _ => c.toString }).mkString("'", "", "'") } override def init_project(dir: Path, jars: List[Path]): Unit = { File.write(dir + project_settings, "rootProject.name = 'Isabelle'\n") File.write(dir + project_root, """plugins { id 'scala' } repositories { mavenCentral() } dependencies { implementation 'org.scala-lang:scala3-library_3:scala-library:""" + scala_version + """' compileOnly files( """ + jars.map(jar => groovy_string(File.platform_path(jar))).mkString("", ",\n ", ")") + """ } """) } } /* Maven */ object Maven extends Build_Tool { override def toString: String = "Maven" override val project_root: Path = Path.explode("pom.xml") override def init_project(dir: Path, jars: List[Path]): Unit = { def dependency(jar: Path): String = { val name = jar.expand.drop_ext.base.implode val system_path = File.platform_path(jar.absolute) """ classpath """ + XML.text(name) + """ 0 system """ + XML.text(system_path) + """ """ } val project = """ 4.0.0 isabelle isabelle 0 UTF-8 """ + java_version + """ """ + java_version + """ net.alchim31.maven scala-maven-plugin 4.5.3 """ + scala_version + """ """ + jars.map(dependency).mkString("\n", "\n", "\n") + """ """ File.write(dir + project_root, project) } } /* plugins: modules with dynamic build */ class Plugin(dir: Path) extends Isabelle_System.Service { def context(): Scala_Build.Context = Scala_Build.context(dir) } lazy val plugins: List[Plugin] = Isabelle_System.make_services(classOf[Plugin]) /* file and directories */ lazy val isabelle_files: (List[Path], List[Path]) = { val contexts = Scala_Build.component_contexts() ::: plugins.map(_.context()) val jars1 = Path.split(Isabelle_System.getenv("ISABELLE_CLASSPATH")) val jars2 = contexts.flatMap(_.requirements) val jars = Library.distinct(jars1 ::: jars2).filterNot(path => contexts.exists(_.is_module(path))) val sources = (for { context <- contexts.iterator path <- context.sources.iterator if path.is_scala || path.is_java } yield path).toList (jars, sources) } - lazy val isabelle_scala_files: Map[String, Path] = { - val context = Scala_Build.context(Path.ISABELLE_HOME, component = true) - context.sources.iterator.foldLeft(Map.empty[String, Path]) { - case (map, path) => - if (path.is_scala) { - val base = path.base.implode - map.get(base) match { - case None => map + (base -> path) - case Some(path2) => error("Conflicting base names: " + path + " vs. " + path2) + lazy val isabelle_scala_files: Map[String, Path] = + Scala_Build.context(Path.ISABELLE_HOME, component = true) + .sources.iterator.foldLeft(Map.empty[String, Path]) { + case (map, path) => + if (path.is_scala) { + val base = path.base.implode + map.get(base) match { + case None => map + (base -> path) + case Some(path2) => error("Conflicting base names: " + path + " vs. " + path2) + } } - } - else map - } - } + else map + } /* compile-time position */ def here: Here = { val exn = new Exception exn.getStackTrace.toList match { case _ :: caller :: _ => val name = proper_string(caller.getFileName).getOrElse("") val line = caller.getLineNumber new Here(name, line) case _ => new Here("", 0) } } class Here private[Scala_Project](name: String, line: Int) { override def toString: String = name + ":" + line def position: Position.T = isabelle_scala_files.get(name) match { case Some(path) => Position.Line_File(line, path.implode) case None => Position.none } } /* scala project */ val default_project_dir = Path.explode("$ISABELLE_HOME_USER/scala_project") def package_name(source_file: Path): Option[String] = { val lines = Library.trim_split_lines(File.read(source_file)) val Package = """\s*\bpackage\b\s*(?:object\b\s*)?((?:\w|\.)+)\b.*""".r lines.collectFirst({ case Package(name) => name }) } def scala_project( build_tool: Build_Tool, project_dir: Path = default_project_dir, more_sources: List[Path] = Nil, symlinks: Boolean = false, force: Boolean = false, progress: Progress = new Progress ): Unit = { if (project_dir.file.exists) { val detect = project_dir.is_dir && build_tools.exists(_.detect_project(project_dir)) if (force && detect) { progress.echo("Purging existing project directory: " + project_dir.absolute) Isabelle_System.rm_tree(project_dir) } else error("Project directory already exists: " + project_dir.absolute) } progress.echo("Creating " + build_tool + " project directory: " + project_dir.absolute) Isabelle_System.make_directory(project_dir) val java_src_dir = Isabelle_System.make_directory(project_dir + build_tool.java_src_dir) val scala_src_dir = Isabelle_System.make_directory(project_dir + build_tool.scala_src_dir) val (jars, sources) = isabelle_files isabelle_scala_files build_tool.init_project(project_dir, jars) for (source <- sources ::: more_sources) { val dir = build_tool.package_dir(source) val target_dir = project_dir + dir if (!target_dir.is_dir) { progress.echo(" Creating package directory: " + dir) Isabelle_System.make_directory(target_dir) } if (symlinks) Isabelle_System.symlink(source.absolute, target_dir, native = true) else Isabelle_System.copy_file(source, target_dir) } } /* Isabelle tool wrapper */ val isabelle_tool = Isabelle_Tool("scala_project", "setup IDE project for Isabelle/Java/Scala sources", Scala_Project.here, { args => var build_tool: Option[Build_Tool] = None var project_dir = default_project_dir var symlinks = false var force = false val getopts = Getopts(""" Usage: isabelle scala_project [OPTIONS] [MORE_SOURCES ...] Options are: -D DIR project directory (default: """ + default_project_dir + """) -G use Gradle as build tool -L make symlinks to original source files -M use Maven as build tool -f force update of existing directory Setup project for Isabelle/Scala/jEdit --- to support common IDEs such as IntelliJ IDEA. Either option -G or -M is mandatory to specify the build tool. """, "D:" -> (arg => project_dir = Path.explode(arg)), "G" -> (_ => build_tool = Some(Gradle)), "L" -> (_ => symlinks = true), "M" -> (_ => build_tool = Some(Maven)), "f" -> (_ => force = true)) val more_args = getopts(args) val more_sources = more_args.map(Path.explode) val progress = new Console_Progress if (build_tool.isEmpty) { error("Unspecified build tool: need to provide option -G or -M") } scala_project(build_tool.get, project_dir = project_dir, more_sources = more_sources, symlinks = symlinks, force = force, progress = progress) }) }