src/Pure/Tools/scala_project.scala
changeset 74960 f03ece7155d6
parent 74830 40bb5f41e06c
child 75082 ea4fa50dbb74
equal deleted inserted replaced
74959:340c5f3506a8 74960:f03ece7155d6
     1 /*  Title:      Pure/Tools/scala_project.scala
     1 /*  Title:      Pure/Tools/scala_project.scala
     2     Author:     Makarius
     2     Author:     Makarius
     3 
     3 
     4 Manage Isabelle/Scala/Java project sources, with output to Maven for
     4 Manage Isabelle/Scala/Java project sources, with output to Gradle or
     5 IntelliJ IDEA.
     5 Maven for IntelliJ IDEA.
     6 */
     6 */
     7 
     7 
     8 package isabelle
     8 package isabelle
     9 
     9 
    10 
    10 
    11 object Scala_Project
    11 object Scala_Project
    12 {
    12 {
    13   /* Maven project */
    13   /** build tools **/
    14 
    14 
    15   def java_version: String = "15"
    15   def java_version: String = "15"
    16   def scala_version: String = scala.util.Properties.versionNumberString
    16   def scala_version: String = scala.util.Properties.versionNumberString
    17 
    17 
    18   def maven_project(jars: List[Path]): String =
    18   abstract class Build_Tool
    19   {
    19   {
    20     def dependency(jar: Path): String =
    20     def project_root: Path
    21     {
    21     def init_project(dir: Path, jars: List[Path]): Unit
    22       val name = jar.expand.drop_ext.base.implode
    22 
    23       val system_path = File.platform_path(jar.absolute)
    23     val java_src_dir: Path = Path.explode("src/main/java")
       
    24     val scala_src_dir: Path = Path.explode("src/main/scala")
       
    25 
       
    26     def detect_project(dir: Path): Boolean =
       
    27       (dir + project_root).is_file &&
       
    28       (dir + scala_src_dir).is_dir
       
    29 
       
    30     def package_dir(source_file: Path): Path =
       
    31     {
       
    32       val is_java = source_file.is_java
       
    33       val dir =
       
    34         package_name(source_file) match {
       
    35           case Some(name) =>
       
    36             if (is_java) Path.explode(space_explode('.', name).mkString("/"))
       
    37             else Path.basic(name)
       
    38           case None => error("Failed to guess package from " + source_file)
       
    39         }
       
    40       (if (is_java) java_src_dir else scala_src_dir) + dir
       
    41     }
       
    42   }
       
    43 
       
    44   def build_tools: List[Build_Tool] = List(Gradle, Maven)
       
    45 
       
    46 
       
    47   /* Gradle */
       
    48 
       
    49   object Gradle extends Build_Tool
       
    50   {
       
    51     override def toString: String = "Gradle"
       
    52 
       
    53     val project_settings: Path = Path.explode("settings.gradle")
       
    54     override val project_root: Path = Path.explode("build.gradle")
       
    55 
       
    56     private def groovy_string(s: String): String =
       
    57     {
       
    58       s.map(c =>
       
    59         c match {
       
    60           case '\t' | '\b' | '\n' | '\r' | '\f' | '\\' | '\'' | '"' => "\\" + c
       
    61           case _ => c.toString
       
    62         }).mkString("'", "", "'")
       
    63     }
       
    64 
       
    65     override def init_project(dir: Path, jars: List[Path]): Unit =
       
    66     {
       
    67       File.write(dir + project_settings, "rootProject.name = 'Isabelle'\n")
       
    68       File.write(dir + project_root,
       
    69 """plugins {
       
    70   id 'scala'
       
    71 }
       
    72 
       
    73 repositories {
       
    74   mavenCentral()
       
    75 }
       
    76 
       
    77 dependencies {
       
    78   implementation 'org.scala-lang:scala-library:""" + scala_version + """'
       
    79   compileOnly files(
       
    80     """ + jars.map(jar => groovy_string(File.platform_path(jar))).mkString("", ",\n    ", ")") +
       
    81 """
       
    82 }
       
    83 """)
       
    84     }
       
    85   }
       
    86 
       
    87 
       
    88   /* Maven */
       
    89 
       
    90   object Maven extends Build_Tool
       
    91   {
       
    92     override def toString: String = "Maven"
       
    93 
       
    94     override val project_root: Path = Path.explode("pom.xml")
       
    95 
       
    96     override def init_project(dir: Path, jars: List[Path]): Unit =
       
    97     {
       
    98       def dependency(jar: Path): String =
       
    99       {
       
   100         val name = jar.expand.drop_ext.base.implode
       
   101         val system_path = File.platform_path(jar.absolute)
    24       """  <dependency>
   102       """  <dependency>
    25     <groupId>classpath</groupId>
   103     <groupId>classpath</groupId>
    26     <artifactId>""" + XML.text(name) + """</artifactId>
   104     <artifactId>""" + XML.text(name) + """</artifactId>
    27     <version>0</version>
   105     <version>0</version>
    28     <scope>system</scope>
   106     <scope>system</scope>
    29     <systemPath>""" + XML.text(system_path) + """</systemPath>
   107     <systemPath>""" + XML.text(system_path) + """</systemPath>
    30   </dependency>"""
   108   </dependency>"""
    31     }
   109       }
    32 
   110 
    33     """<?xml version="1.0" encoding="UTF-8"?>
   111       val project = """<?xml version="1.0" encoding="UTF-8"?>
    34 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   112 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    35   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   113   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    36   <modelVersion>4.0.0</modelVersion>
   114   <modelVersion>4.0.0</modelVersion>
    37 
   115 
    38   <groupId>isabelle</groupId>
   116   <groupId>isabelle</groupId>
    58     </plugins>
   136     </plugins>
    59   </build>
   137   </build>
    60 
   138 
    61   <dependencies>""" + jars.map(dependency).mkString("\n", "\n", "\n") + """</dependencies>
   139   <dependencies>""" + jars.map(dependency).mkString("\n", "\n", "\n") + """</dependencies>
    62 </project>"""
   140 </project>"""
       
   141 
       
   142       File.write(dir + project_root, project)
       
   143     }
    63   }
   144   }
    64 
   145 
    65 
   146 
    66   /* plugins: modules with dynamic build */
   147   /* plugins: modules with dynamic build */
    67 
   148 
   146     val lines = Library.trim_split_lines(File.read(source_file))
   227     val lines = Library.trim_split_lines(File.read(source_file))
   147     val Package = """\s*\bpackage\b\s*(?:object\b\s*)?((?:\w|\.)+)\b.*""".r
   228     val Package = """\s*\bpackage\b\s*(?:object\b\s*)?((?:\w|\.)+)\b.*""".r
   148     lines.collectFirst({ case Package(name) => name })
   229     lines.collectFirst({ case Package(name) => name })
   149   }
   230   }
   150 
   231 
   151   def the_package_dir(source_file: Path): Path =
       
   152   {
       
   153     package_name(source_file) match {
       
   154       case Some(name) =>
       
   155         if (source_file.is_java) Path.explode(space_explode('.', name).mkString("/"))
       
   156         else Path.basic(name)
       
   157       case None => error("Failed to guess package from " + source_file)
       
   158     }
       
   159   }
       
   160 
       
   161   def scala_project(
   232   def scala_project(
       
   233     build_tool: Build_Tool,
   162     project_dir: Path = default_project_dir,
   234     project_dir: Path = default_project_dir,
   163     more_sources: List[Path] = Nil,
   235     more_sources: List[Path] = Nil,
   164     symlinks: Boolean = false,
   236     symlinks: Boolean = false,
   165     force: Boolean = false,
   237     force: Boolean = false,
   166     progress: Progress = new Progress): Unit =
   238     progress: Progress = new Progress): Unit =
   167   {
   239   {
   168     if (project_dir.file.exists) {
   240     if (project_dir.file.exists) {
   169       val detect =
   241       val detect = project_dir.is_dir && build_tools.exists(_.detect_project(project_dir))
   170         project_dir.is_dir &&
       
   171         (project_dir + Path.explode("pom.xml")).is_file &&
       
   172         (project_dir + Path.explode("src/main/scala")).is_dir
       
   173 
   242 
   174       if (force && detect) {
   243       if (force && detect) {
   175         progress.echo("Purging existing project directory: " + project_dir.absolute)
   244         progress.echo("Purging existing project directory: " + project_dir.absolute)
   176         Isabelle_System.rm_tree(project_dir)
   245         Isabelle_System.rm_tree(project_dir)
   177       }
   246       }
   178       else error("Project directory already exists: " + project_dir.absolute)
   247       else error("Project directory already exists: " + project_dir.absolute)
   179     }
   248     }
   180 
   249 
   181     progress.echo("Creating project directory: " + project_dir.absolute)
   250     progress.echo("Creating " + build_tool + " project directory: " + project_dir.absolute)
   182     Isabelle_System.make_directory(project_dir)
   251     Isabelle_System.make_directory(project_dir)
   183 
   252 
   184     val java_src_dir = Isabelle_System.make_directory(Path.explode("src/main/java"))
   253     val java_src_dir = Isabelle_System.make_directory(project_dir + build_tool.java_src_dir)
   185     val scala_src_dir = Isabelle_System.make_directory(Path.explode("src/main/scala"))
   254     val scala_src_dir = Isabelle_System.make_directory(project_dir + build_tool.scala_src_dir)
   186 
   255 
   187     val (jars, sources) = isabelle_files
   256     val (jars, sources) = isabelle_files
   188     isabelle_scala_files
   257     isabelle_scala_files
   189 
   258 
   190     File.write(project_dir + Path.explode("pom.xml"), maven_project(jars))
   259     build_tool.init_project(project_dir, jars)
   191 
   260 
   192     for (source <- sources ::: more_sources) {
   261     for (source <- sources ::: more_sources) {
   193       val dir = (if (source.is_java) java_src_dir else scala_src_dir) + the_package_dir(source)
   262       val dir = build_tool.package_dir(source)
   194       val target_dir = project_dir + dir
   263       val target_dir = project_dir + dir
   195       if (!target_dir.is_dir) {
   264       if (!target_dir.is_dir) {
   196         progress.echo("  Creating package directory: " + dir)
   265         progress.echo("  Creating package directory: " + dir)
   197         Isabelle_System.make_directory(target_dir)
   266         Isabelle_System.make_directory(target_dir)
   198       }
   267       }
   203 
   272 
   204 
   273 
   205   /* Isabelle tool wrapper */
   274   /* Isabelle tool wrapper */
   206 
   275 
   207   val isabelle_tool =
   276   val isabelle_tool =
   208     Isabelle_Tool("scala_project", "setup Maven project for Isabelle/Scala/jEdit",
   277     Isabelle_Tool("scala_project", "setup IDE project for Isabelle/Java/Scala sources",
   209       Scala_Project.here, args =>
   278       Scala_Project.here, args =>
   210     {
   279     {
       
   280       var build_tool: Option[Build_Tool] = None
   211       var project_dir = default_project_dir
   281       var project_dir = default_project_dir
   212       var symlinks = false
   282       var symlinks = false
   213       var force = false
   283       var force = false
   214 
   284 
   215       val getopts = Getopts("""
   285       val getopts = Getopts("""
   216 Usage: isabelle scala_project [OPTIONS] [MORE_SOURCES ...]
   286 Usage: isabelle scala_project [OPTIONS] [MORE_SOURCES ...]
   217 
   287 
   218   Options are:
   288   Options are:
   219     -D DIR       project directory (default: """ + default_project_dir + """)
   289     -D DIR       project directory (default: """ + default_project_dir + """)
       
   290     -G           use Gradle as build tool
   220     -L           make symlinks to original source files
   291     -L           make symlinks to original source files
       
   292     -M           use Maven as build tool
   221     -f           force update of existing directory
   293     -f           force update of existing directory
   222 
   294 
   223   Setup Maven project for Isabelle/Scala/jEdit --- to support common IDEs
   295   Setup project for Isabelle/Scala/jEdit --- to support common IDEs such
   224   such as IntelliJ IDEA.
   296   as IntelliJ IDEA. Either option -G or -M is mandatory to specify the
       
   297   build tool.
   225 """,
   298 """,
   226         "D:" -> (arg => project_dir = Path.explode(arg)),
   299         "D:" -> (arg => project_dir = Path.explode(arg)),
       
   300         "G" -> (_ => build_tool = Some(Gradle)),
   227         "L" -> (_ => symlinks = true),
   301         "L" -> (_ => symlinks = true),
       
   302         "M" -> (_ => build_tool = Some(Maven)),
   228         "f" -> (_ => force = true))
   303         "f" -> (_ => force = true))
   229 
   304 
   230       val more_args = getopts(args)
   305       val more_args = getopts(args)
   231 
   306 
   232       val more_sources = more_args.map(Path.explode)
   307       val more_sources = more_args.map(Path.explode)
   233       val progress = new Console_Progress
   308       val progress = new Console_Progress
   234 
   309 
   235       scala_project(project_dir = project_dir, more_sources = more_sources,
   310       if (build_tool.isEmpty) {
       
   311         error("Unspecified build tool: need to provide option -G or -M")
       
   312       }
       
   313 
       
   314       scala_project(build_tool.get, project_dir = project_dir, more_sources = more_sources,
   236         symlinks = symlinks, force = force, progress = progress)
   315         symlinks = symlinks, force = force, progress = progress)
   237     })
   316     })
   238 }
   317 }