1/*  Title:      Pure/Admin/build_jdk.scala
2    Author:     Makarius
3
4Build Isabelle jdk component from original platform installations.
5*/
6
7package isabelle
8
9
10import java.nio.file.Files
11import java.nio.file.attribute.PosixFilePermission
12
13import scala.util.matching.Regex
14
15
16object Build_JDK
17{
18  /* version */
19
20  sealed case class Version(short: String, full: String)
21
22  def detect_version(s: String): Version =
23  {
24    val Version_Dir_Entry = """^jdk1\.(\d+)\.0_(\d+)(?:\.jdk)?$""".r
25    s match {
26      case Version_Dir_Entry(a, b) => Version(a + "u" + b, "1." + a + ".0_" + b)
27      case _ => error("Cannot detect JDK version from " + quote(s))
28    }
29  }
30
31
32  /* platform */
33
34  sealed case class JDK_Platform(name: String, exe: String, regex: Regex)
35  {
36    override def toString: String = name
37
38    def detect(jdk_dir: Path): Boolean =
39    {
40      val path = jdk_dir + Path.explode(exe)
41      if (path.is_file) {
42        val file_descr = Isabelle_System.bash("file -b " + File.bash_path(path)).check.out
43        regex.pattern.matcher(file_descr).matches
44      }
45      else false
46    }
47  }
48  val jdk_platforms =
49    List(
50      JDK_Platform("x86_64-linux", "bin/java", """.*ELF 64-bit.*x86[-_]64.*""".r),
51      JDK_Platform("x86_64-windows", "bin/java.exe", """.*PE32\+ executable.*x86[-_]64.*""".r),
52      JDK_Platform("x86_64-darwin", "Contents/Home/bin/java", """.*Mach-O 64-bit.*x86[-_]64.*""".r))
53
54
55  /* README */
56
57  def readme(version: Version): String =
58"""This is JDK/JRE """ + version.full + """ as required for Isabelle.
59
60See https://www.oracle.com/technetwork/java/javase/downloads/index.html
61for the original downloads, which are covered by the Oracle Binary
62Code License Agreement for Java SE.
63
64Linux, Windows, Mac OS X all work uniformly, depending on certain
65platform-specific subdirectories.
66"""
67
68
69  /* settings */
70
71  val settings =
72"""# -*- shell-script -*- :mode=shellscript:
73
74case "$ISABELLE_PLATFORM_FAMILY" in
75  linux)
76    ISABELLE_JAVA_PLATFORM="$ISABELLE_PLATFORM64"
77    ISABELLE_JDK_HOME="$COMPONENT/$ISABELLE_JAVA_PLATFORM"
78    ;;
79  windows)
80    ISABELLE_JAVA_PLATFORM="$ISABELLE_WINDOWS_PLATFORM64"
81    ISABELLE_JDK_HOME="$COMPONENT/$ISABELLE_JAVA_PLATFORM"
82    ;;
83  macos)
84    ISABELLE_JAVA_PLATFORM="$ISABELLE_PLATFORM64"
85    ISABELLE_JDK_HOME="$COMPONENT/$ISABELLE_JAVA_PLATFORM/Contents/Home"
86    ;;
87esac
88"""
89
90
91  /* extract archive */
92
93  def extract_archive(dir: Path, archive: Path): (Version, JDK_Platform) =
94  {
95    try {
96      val tmp_dir = dir + Path.explode("tmp")
97      Isabelle_System.mkdirs(tmp_dir)
98      Isabelle_System.gnutar(
99        "-C " + File.bash_path(tmp_dir) + " -xzf " + File.bash_path(archive)).check
100      val dir_entry =
101        File.read_dir(tmp_dir) match {
102          case List(s) => s
103          case _ => error("Archive contains multiple directories")
104        }
105      val version = detect_version(dir_entry)
106
107      val jdk_dir = tmp_dir + Path.explode(dir_entry)
108      val platform =
109        jdk_platforms.find(_.detect(jdk_dir)) getOrElse error("Failed to detect JDK platform")
110
111      val platform_dir = dir + Path.explode(platform.name)
112      if (platform_dir.is_dir) error("Directory already exists: " + platform_dir)
113      File.move(jdk_dir, platform_dir)
114
115      (version, platform)
116    }
117    catch { case ERROR(msg) => cat_error(msg, "The error(s) above occurred for " + archive) }
118  }
119
120
121  /* build jdk */
122
123  def build_jdk(
124    archives: List[Path],
125    progress: Progress = No_Progress,
126    target_dir: Path = Path.current)
127  {
128    if (Platform.is_windows) error("Cannot build jdk on Windows")
129
130    Isabelle_System.with_tmp_dir("jdk")(dir =>
131      {
132        progress.echo("Extracting ...")
133        val extracted = archives.map(extract_archive(dir, _))
134
135        val version =
136          extracted.map(_._1).toSet.toList match {
137            case List(version) => version
138            case Nil => error("No archives")
139            case versions =>
140              error("Archives contain multiple JDK versions: " +
141                commas_quote(versions.map(_.short)))
142          }
143
144        val missing_platforms =
145          jdk_platforms.filterNot(p1 => extracted.exists({ case (_, p2) => p1.name == p2.name }))
146        if (missing_platforms.nonEmpty)
147          error("Missing platforms: " + commas_quote(missing_platforms.map(_.name)))
148
149        val jdk_name = "jdk-" + version.short
150        val jdk_path = Path.explode(jdk_name)
151        val component_dir = dir + jdk_path
152
153        Isabelle_System.mkdirs(component_dir + Path.explode("etc"))
154        File.write(component_dir + Path.explode("etc/settings"), settings)
155        File.write(component_dir + Path.explode("README"), readme(version))
156
157        for ((_, platform) <- extracted)
158          File.move(dir + Path.explode(platform.name), component_dir)
159
160        for (file <- File.find_files(component_dir.file, include_dirs = true)) {
161          val path = file.toPath
162          val perms = Files.getPosixFilePermissions(path)
163          perms.add(PosixFilePermission.OWNER_READ)
164          perms.add(PosixFilePermission.GROUP_READ)
165          perms.add(PosixFilePermission.OTHERS_READ)
166          perms.add(PosixFilePermission.OWNER_WRITE)
167          if (file.isDirectory) {
168            perms.add(PosixFilePermission.OWNER_WRITE)
169            perms.add(PosixFilePermission.OWNER_EXECUTE)
170            perms.add(PosixFilePermission.GROUP_EXECUTE)
171            perms.add(PosixFilePermission.OTHERS_EXECUTE)
172          }
173          Files.setPosixFilePermissions(path, perms)
174        }
175
176        File.find_files((component_dir + Path.explode("x86_64-darwin")).file,
177          file => file.getName.startsWith("._")).foreach(_.delete)
178
179        progress.echo("Sharing ...")
180        val main_dir :: other_dirs =
181          jdk_platforms.map(platform => (component_dir + Path.explode(platform.name)).file.toPath)
182        for {
183          file1 <- File.find_files(main_dir.toFile).iterator
184          path1 = file1.toPath
185          dir2 <- other_dirs.iterator
186        } {
187          val path2 = dir2.resolve(main_dir.relativize(path1))
188          val file2 = path2.toFile
189          if (!Files.isSymbolicLink(path2) && file2.isFile && File.eq_content(file1, file2)) {
190            file2.delete
191            Files.createLink(path2, path1)
192          }
193        }
194
195        progress.echo("Archiving ...")
196        Isabelle_System.gnutar("--owner=root --group=root -C " + File.bash_path(dir) +
197          " -czf " + File.bash_path(target_dir + jdk_path.ext("tar.gz")) + " " + jdk_name).check
198      })
199  }
200
201
202  /* Isabelle tool wrapper */
203
204  val isabelle_tool =
205    Isabelle_Tool("build_jdk", "build Isabelle jdk component from original platform installations",
206    args =>
207    {
208      var target_dir = Path.current
209
210      val getopts = Getopts("""
211Usage: isabelle build_jdk [OPTIONS] ARCHIVES...
212
213  Options are:
214    -D DIR       target directory (default ".")
215
216  Build jdk component from tar.gz archives, with original jdk installations
217  for x86_64 Linux, Windows, Mac OS X.
218""",
219        "D:" -> (arg => target_dir = Path.explode(arg)))
220
221      val more_args = getopts(args)
222      if (more_args.isEmpty) getopts.usage()
223
224      val archives = more_args.map(Path.explode(_))
225      val progress = new Console_Progress()
226
227      build_jdk(archives = archives, progress = progress, target_dir = target_dir)
228    }, admin = true)
229}
230