src/Pure/Admin/build_release.scala
changeset 69401 7a1b7b737c02
parent 69400 c19b7b565998
child 69402 61f4c406d727
equal deleted inserted replaced
69400:c19b7b565998 69401:7a1b7b737c02
    19   {
    19   {
    20     def names: List[String] = main :: fallback.toList
    20     def names: List[String] = main :: fallback.toList
    21   }
    21   }
    22 
    22 
    23   class Release private[Build_Release](
    23   class Release private[Build_Release](
       
    24     progress: Progress,
    24     val date: Date,
    25     val date: Date,
    25     val dist_name: String,
    26     val dist_name: String,
    26     val dist_dir: Path,
    27     val dist_dir: Path,
    27     val dist_version: String,
    28     val dist_version: String,
    28     val ident: String)
    29     val ident: String)
    29   {
    30   {
    30     val isabelle_dir: Path = dist_dir + Path.explode(dist_name)
    31     val isabelle_dir: Path = dist_dir + Path.explode(dist_name)
    31     val isabelle_archive: Path = dist_dir + Path.explode(dist_name + ".tar.gz")
    32     val isabelle_archive: Path = dist_dir + Path.explode(dist_name + ".tar.gz")
    32     val isabelle_library_archive: Path = dist_dir + Path.explode(dist_name + "_library.tar.gz")
    33     val isabelle_library_archive: Path = dist_dir + Path.explode(dist_name + "_library.tar.gz")
    33 
    34 
    34     val other_isabelle_identifier: String = dist_name + "-build"
    35     def other_isabelle(dir: Path): Other_Isabelle =
       
    36       Other_Isabelle(dir + Path.explode(dist_name),
       
    37         isabelle_identifier = dist_name + "-build",
       
    38         progress = progress)
    35 
    39 
    36     def bundle_info(platform_family: String): Bundle_Info =
    40     def bundle_info(platform_family: String): Bundle_Info =
    37       platform_family match {
    41       platform_family match {
    38         case "linux" => Bundle_Info("linux", "Linux", dist_name + "_app.tar.gz", None)
    42         case "linux" => Bundle_Info("linux", "Linux", dist_name + "_app.tar.gz", None)
    39         case "windows" => Bundle_Info("windows", "Windows", dist_name + ".exe", None)
    43         case "windows" => Bundle_Info("windows", "Windows", dist_name + ".exe", None)
   166           line <- split_lines(File.read(path))
   170           line <- split_lines(File.read(path))
   167           if line.nonEmpty && !line.startsWith("#") && !line.startsWith("jedit_build")
   171           if line.nonEmpty && !line.startsWith("#") && !line.startsWith("jedit_build")
   168         } yield bundled(line)).toList))
   172         } yield bundled(line)).toList))
   169   }
   173   }
   170 
   174 
   171   def get_bundled_components(dir: Path, platform: String): List[String] =
   175   def get_bundled_components(dir: Path, platform: String): (List[String], String) =
   172   {
   176   {
   173     val Bundled = new Bundled(platform)
   177     val Bundled = new Bundled(platform)
   174     for (Bundled(name) <- Components.read_components(dir)) yield name
   178     val components =
       
   179       for {
       
   180         Bundled(name) <- Components.read_components(dir)
       
   181         if !name.startsWith("jedit_build")
       
   182       } yield name
       
   183     val jdk_component =
       
   184       components.find(_.startsWith("jdk")) getOrElse error("Missing jdk component")
       
   185     (components, jdk_component)
   175   }
   186   }
   176 
   187 
   177   def activate_bundled_components(dir: Path, platform: String)
   188   def activate_bundled_components(dir: Path, platform: String)
   178   {
   189   {
   179     val Bundled = new Bundled(platform)
   190     val Bundled = new Bundled(platform)
   200 
   211 
   201 
   212 
   202 
   213 
   203   /** build_release **/
   214   /** build_release **/
   204 
   215 
   205   def distribution_classpath(
       
   206     components_base: Path,
       
   207     isabelle_home: Path,
       
   208     isabelle_classpath: String): List[Path] =
       
   209   {
       
   210     val base = isabelle_home.absolute
       
   211     val contrib_base = components_base.absolute
       
   212 
       
   213     Path.split(isabelle_classpath).map(path =>
       
   214     {
       
   215       val abs_path = path.absolute
       
   216       File.relative_path(base, abs_path) match {
       
   217         case Some(rel_path) => rel_path
       
   218         case None =>
       
   219           File.relative_path(contrib_base, abs_path) match {
       
   220             case Some(rel_path) => Components.contrib() + rel_path
       
   221             case None => error("Bad ISABELLE_CLASSPATH element: " + path)
       
   222           }
       
   223       }
       
   224     }) ::: List(Path.explode("src/Tools/jEdit/dist/jedit.jar"))
       
   225   }
       
   226 
       
   227   private def execute(dir: Path, script: String): Unit =
   216   private def execute(dir: Path, script: String): Unit =
   228     Isabelle_System.bash(script, cwd = dir.file).check
   217     Isabelle_System.bash(script, cwd = dir.file).check
   229 
   218 
   230   private def execute_tar(dir: Path, args: String): Unit =
   219   private def execute_tar(dir: Path, args: String): Unit =
   231     Isabelle_System.gnutar(args, cwd = dir.file).check
   220     Isabelle_System.gnutar(args, cwd = dir.file).check
   234     if (Platform.is_macos) "--owner=root --group=staff" else "--owner=root --group=root"
   223     if (Platform.is_macos) "--owner=root --group=staff" else "--owner=root --group=root"
   235 
   224 
   236   private val default_platform_families = List("linux", "windows", "macos")
   225   private val default_platform_families = List("linux", "windows", "macos")
   237 
   226 
   238   def build_release(base_dir: Path,
   227   def build_release(base_dir: Path,
       
   228     options: Options,
   239     components_base: Option[Path] = None,
   229     components_base: Option[Path] = None,
   240     progress: Progress = No_Progress,
   230     progress: Progress = No_Progress,
   241     rev: String = "",
   231     rev: String = "",
   242     afp_rev: String = "",
   232     afp_rev: String = "",
   243     official_release: Boolean = false,
   233     official_release: Boolean = false,
   265         proper_release_name match {
   255         proper_release_name match {
   266           case Some(name) => name + ": " + Date.Format("LLLL uuuu")(date)
   256           case Some(name) => name + ": " + Date.Format("LLLL uuuu")(date)
   267           case None => "Isabelle repository snapshot " + ident + " " + Date.Format.date(date)
   257           case None => "Isabelle repository snapshot " + ident + " " + Date.Format.date(date)
   268         }
   258         }
   269 
   259 
   270       new Release(date, dist_name, dist_dir, dist_version, ident)
   260       new Release(progress, date, dist_name, dist_dir, dist_version, ident)
   271     }
   261     }
   272 
   262 
   273 
   263 
   274     /* make distribution */
   264     /* make distribution */
   275 
   265 
   318 
   308 
   319       make_contrib(release.isabelle_dir)
   309       make_contrib(release.isabelle_dir)
   320 
   310 
   321       execute(release.isabelle_dir, """find . -print | xargs chmod -f u+rw""")
   311       execute(release.isabelle_dir, """find . -print | xargs chmod -f u+rw""")
   322 
   312 
       
   313       record_bundled_components(release.isabelle_dir)
       
   314 
   323 
   315 
   324       /* build tools and documentation */
   316       /* build tools and documentation */
   325 
   317 
   326       val other_isabelle =
   318       val other_isabelle = release.other_isabelle(release.dist_dir)
   327         Other_Isabelle(release.isabelle_dir,
       
   328           isabelle_identifier = release.other_isabelle_identifier,
       
   329           progress = progress)
       
   330 
   319 
   331       other_isabelle.init_settings(
   320       other_isabelle.init_settings(
   332         other_isabelle.init_components(base = components_base, catalogs = List("main")))
   321         other_isabelle.init_components(base = components_base, catalogs = List("main")))
   333       other_isabelle.resolve_components(echo = true)
   322       other_isabelle.resolve_components(echo = true)
   334 
   323 
   404         (if (remote_mac.isEmpty) bundle_info.fallback else None) getOrElse bundle_info.main
   393         (if (remote_mac.isEmpty) bundle_info.fallback else None) getOrElse bundle_info.main
   405       val bundle_archive = release.dist_dir + Path.explode(bundle)
   394       val bundle_archive = release.dist_dir + Path.explode(bundle)
   406       if (bundle_archive.is_file)
   395       if (bundle_archive.is_file)
   407         progress.echo_warning("Application bundle already exists: " + bundle_archive)
   396         progress.echo_warning("Application bundle already exists: " + bundle_archive)
   408       else {
   397       else {
   409         progress.echo(
   398         val isabelle_name = release.dist_name
   410           "\nApplication bundle for " + bundle_info.platform_family + ": " + bundle_archive)
   399         val platform = bundle_info.platform_family
   411         progress.bash(
   400 
   412           "isabelle makedist_bundle " + File.bash_path(release.isabelle_archive) +
   401         progress.echo("\nApplication bundle for " + platform + ": " + bundle_archive)
   413             " " + Bash.string(bundle_info.platform_family) +
   402 
   414             (if (remote_mac == "") "" else " " + Bash.string(remote_mac)),
   403         Isabelle_System.with_tmp_dir("build_release")(tmp_dir =>
   415           echo = true).check
   404         {
       
   405           // release archive
       
   406 
       
   407           execute_tar(tmp_dir, "-xzf " + File.bash_path(release.isabelle_archive))
       
   408           val other_isabelle = release.other_isabelle(tmp_dir)
       
   409           val isabelle_target = other_isabelle.isabelle_home
       
   410 
       
   411 
       
   412           // bundled components
       
   413 
       
   414           progress.echo("Bundled components:")
       
   415 
       
   416           val contrib_dir = Components.contrib(isabelle_target)
       
   417 
       
   418           val (components, jdk_component) = get_bundled_components(isabelle_target, platform)
       
   419 
       
   420           Components.resolve(other_isabelle.components_base(components_base),
       
   421             components, target_dir = Some(contrib_dir), progress = progress)
       
   422 
       
   423           Components.purge(contrib_dir, platform)
       
   424 
       
   425           activate_bundled_components(isabelle_target, platform)
       
   426 
       
   427 
       
   428           // Java parameters
       
   429 
       
   430           val java_options_title = "# Java runtime options"
       
   431           val java_options: List[String] =
       
   432             (for {
       
   433               variable <-
       
   434                 List(
       
   435                   "ISABELLE_JAVA_SYSTEM_OPTIONS",
       
   436                   "JEDIT_JAVA_SYSTEM_OPTIONS",
       
   437                   "JEDIT_JAVA_OPTIONS")
       
   438               opt <- Word.explode(other_isabelle.getenv(variable))
       
   439             } yield opt) ::: List("-Disabelle.jedit_server=" + isabelle_name)
       
   440 
       
   441           val classpath: List[Path] =
       
   442           {
       
   443             val base = isabelle_target.absolute
       
   444             Path.split(other_isabelle.getenv("ISABELLE_CLASSPATH")).map(path =>
       
   445             {
       
   446               val abs_path = path.absolute
       
   447               File.relative_path(base, abs_path) match {
       
   448                 case Some(rel_path) => rel_path
       
   449                 case None => error("Bad ISABELLE_CLASSPATH element: " + abs_path)
       
   450               }
       
   451             }) ::: List(Path.explode("src/Tools/jEdit/dist/jedit.jar"))
       
   452           }
       
   453 
       
   454           val jedit_props = Path.explode("src/Tools/jEdit/dist/properties/jEdit.props")
       
   455 
       
   456 
       
   457           // platform-specific setup (inside archive)
       
   458 
       
   459           if (platform == "linux") {
       
   460             File.write(isabelle_target + Path.explode(isabelle_name + ".options"),
       
   461               terminate_lines(java_options_title :: java_options))
       
   462 
       
   463             val isabelle_run = isabelle_target + Path.explode(isabelle_name + ".run")
       
   464             File.write(isabelle_run,
       
   465               File.read(Path.explode("~~/Admin/Linux/Isabelle.run"))
       
   466                 .replaceAllLiterally("{CLASSPATH}",
       
   467                   classpath.map("$ISABELLE_HOME/" + _).mkString(":"))
       
   468                 .replaceAllLiterally("/jdk/", "/" + jdk_component + "/"))
       
   469             Isabelle_System.bash("chmod +x " + File.bash_path(isabelle_run)).check
       
   470 
       
   471             val linux_app = isabelle_target + Path.explode("contrib/linux_app")
       
   472             File.move(linux_app + Path.explode("Isabelle"),
       
   473               isabelle_target + Path.explode(isabelle_name))
       
   474             Isabelle_System.rm_tree(linux_app)
       
   475           }
       
   476           else if (platform == "macos") {
       
   477             File.move(isabelle_target + Path.explode("contrib/macos_app"), tmp_dir)
       
   478             File.write(isabelle_target + jedit_props,
       
   479               File.read(isabelle_target + jedit_props)
       
   480                 .replaceAll("lookAndFeel=.*", "lookAndFeel=com.apple.laf.AquaLookAndFeel")
       
   481                 .replaceAll("delete-line.shortcut=.*", "delete-line.shortcut=C+d")
       
   482                 .replaceAll("delete.shortcut2=.*", "delete.shortcut2=A+d")
       
   483                 .replaceAll("plugin-blacklist.MacOSX.jar=true", "plugin-blacklist.MacOSX.jar="))
       
   484           }
       
   485           else if (platform == "windows") {
       
   486             val app_template = Path.explode("~~/Admin/Windows/launch4j")
       
   487             val cygwin_template = Path.explode("~~/Admin/Windows/Cygwin")
       
   488 
       
   489             File.move(isabelle_target + Path.explode("contrib/windows_app"), tmp_dir)
       
   490 
       
   491             File.write(isabelle_target + jedit_props,
       
   492               File.read(isabelle_target + jedit_props)
       
   493                 .replaceAll("lookAndFeel=.*",
       
   494                   "lookAndFeel=com.sun.java.swing.plaf.windows.WindowsLookAndFeel")
       
   495                 .replaceAll("foldPainter=.*", "foldPainter=Square"))
       
   496 
       
   497             File.write(isabelle_target + Path.explode(isabelle_name + ".l4j.ini"),
       
   498               (java_options_title :: java_options).map(_ + "\r\n").mkString)
       
   499 
       
   500             val isabelle_xml = Path.explode("isabelle.xml")
       
   501             val isabelle_exe = Path.explode(isabelle_name + ".exe")
       
   502 
       
   503             File.write(tmp_dir + isabelle_xml,
       
   504               File.read(app_template + isabelle_xml)
       
   505                 .replaceAllLiterally("{ISABELLE_NAME}", isabelle_name)
       
   506                 .replaceAllLiterally("{OUTFILE}", File.platform_path(isabelle_target + isabelle_exe))
       
   507                 .replaceAllLiterally("{ICON}",
       
   508                   File.platform_path(app_template + Path.explode("isabelle_transparent.ico")))
       
   509                 .replaceAllLiterally("{SPLASH}",
       
   510                   File.platform_path(app_template + Path.explode("isabelle.bmp")))
       
   511                 .replaceAllLiterally("{CLASSPATH}",
       
   512                   cat_lines(classpath.map(cp =>
       
   513                     "    <cp>%EXEDIR%\\" + File.platform_path(cp).replace('/', '\\') + "</cp>")))
       
   514                 .replaceAllLiterally("\\jdk\\", "\\" + jdk_component + "\\"))
       
   515 
       
   516             Isabelle_System.bash(
       
   517               "\"windows_app/launch4j-${ISABELLE_PLATFORM_FAMILY}/launch4j\" isabelle.xml",
       
   518               cwd = tmp_dir.file).check
       
   519 
       
   520             File.copy(app_template + Path.explode("manifest.xml"),
       
   521               isabelle_target + isabelle_exe.ext("manifest"))
       
   522 
       
   523 
       
   524             File.copy(cygwin_template + Path.explode("Cygwin-Terminal.bat"), isabelle_target)
       
   525 
       
   526             val cygwin_mirror =
       
   527               File.read(isabelle_target + Path.explode("contrib/cygwin/isabelle/cygwin_mirror"))
       
   528 
       
   529             val cygwin_bat = Path.explode("Cygwin-Setup.bat")
       
   530             File.write(isabelle_target + cygwin_bat,
       
   531               File.read(cygwin_template + cygwin_bat)
       
   532                 .replaceAllLiterally("{MIRROR}", cygwin_mirror))
       
   533 
       
   534             Isabelle_System.bash("chmod +x " + File.bash_path(isabelle_target + cygwin_bat)).check
       
   535 
       
   536             for (name <- List("isabelle/postinstall", "isabelle/rebaseall")) {
       
   537               val path = Path.explode(name)
       
   538               File.copy(cygwin_template + path,
       
   539                 isabelle_target + Path.explode("contrib/cygwin") + path)
       
   540             }
       
   541 
       
   542             Isabelle_System.bash("""find . -type f -not -name "*.exe" -not -name "*.dll" """ +
       
   543               (if (Platform.is_macos) "-perm +100" else "-executable") +
       
   544               " -print0 > contrib/cygwin/isabelle/executables",
       
   545               cwd = isabelle_target.file).check
       
   546 
       
   547             Isabelle_System.bash("""find . -type l -exec echo "{}" ";" -exec readlink "{}" ";" """ +
       
   548               """> contrib/cygwin/isabelle/symlinks""",
       
   549               cwd = isabelle_target.file).check
       
   550 
       
   551             Isabelle_System.bash("""find . -type l -exec rm "{}" ";" """,
       
   552               cwd = isabelle_target.file).check
       
   553 
       
   554             File.write(isabelle_target + Path.explode("contrib/cygwin/isabelle/uninitialized"), "")
       
   555           }
       
   556 
       
   557 
       
   558           // archive
       
   559 
       
   560           val archive_name = isabelle_name + "_" + platform + ".tar.gz"
       
   561           progress.echo("Packaging " + archive_name)
       
   562           execute_tar(tmp_dir,
       
   563             "-czf " + File.bash_path(release.dist_dir + Path.explode(archive_name)) + " " +
       
   564             Bash.string(isabelle_name))
       
   565 
       
   566 
       
   567           // platform-specific application (outside archive)
       
   568 
       
   569           progress.echo("Application for " + platform)
       
   570 
       
   571           if (platform == "linux") {
       
   572             Isabelle_System.bash(
       
   573               "ln -s " + Bash.string(isabelle_name + "_linux.tar.gz") + " " +
       
   574               File.bash_path(release.dist_dir + Path.explode(isabelle_name + "_app.tar.gz"))).check
       
   575           }
       
   576           else if (platform == "macos") {
       
   577             val dmg_dir = tmp_dir + Path.explode("macos_app/dmg")
       
   578             val app_dir = dmg_dir + Path.explode(isabelle_name + ".app")
       
   579             File.move(dmg_dir + Path.explode("Isabelle.app"), app_dir)
       
   580 
       
   581             val app_contents = app_dir + Path.explode("Contents")
       
   582             val app_resources = app_contents + Path.explode("Resources")
       
   583             File.move(tmp_dir + Path.explode(isabelle_name), app_resources)
       
   584 
       
   585             File.write(app_contents + Path.explode("Info.plist"),
       
   586               File.read(Path.explode("~~/Admin/MacOS/Info.plist"))
       
   587                 .replaceAllLiterally("{ISABELLE_NAME}", isabelle_name)
       
   588                 .replaceAllLiterally("{JAVA_OPTIONS}",
       
   589                   terminate_lines(java_options.map(opt => "<string>" + opt + "</string>"))))
       
   590 
       
   591             for (cp <- classpath) {
       
   592               Isabelle_System.bash(
       
   593                 "ln -sf " +
       
   594                 Bash.string("../Resources/" + isabelle_name + "/") + File.bash_path(cp) + " " +
       
   595                 File.bash_path(app_contents + Path.explode("Java"))).check
       
   596             }
       
   597 
       
   598             Isabelle_System.bash("ln -sf ../Resources/" + Bash.string(isabelle_name) +
       
   599               "/contrib/" + Bash.string(jdk_component) + "/x86_64-darwin " +
       
   600               File.bash_path(app_contents + Path.explode("PlugIns/bundled.jdk"))).check
       
   601 
       
   602             Isabelle_System.bash("ln -sf ../../Info.plist " +
       
   603               File.bash_path(app_resources + Path.explode(isabelle_name) +
       
   604                 Path.explode(isabelle_name + ".plist"))).check
       
   605             Isabelle_System.bash("ln -sf Contents/Resources/" + Bash.string(isabelle_name) + " " +
       
   606               File.bash_path(app_dir) + "/Isabelle").check
       
   607 
       
   608             val dmg = Path.explode(isabelle_name + ".dmg")
       
   609             (release.dist_dir + dmg).file.delete
       
   610 
       
   611             val dmg_archive = Path.explode(isabelle_name + "_dmg.tar.gz")
       
   612             execute_tar(dmg_dir, "-czf " + File.bash_path(release.dist_dir + dmg_archive) + " .")
       
   613 
       
   614             remote_mac match {
       
   615               case SSH.Target(user, host) =>
       
   616                 progress.echo("Building dmg on " + quote(host) + " ...")
       
   617                 using(SSH.open_session(options, host = host, user = user))(ssh =>
       
   618                 {
       
   619                   ssh.with_tmp_dir(remote_dir =>
       
   620                   {
       
   621                     val cd = "cd " + ssh.bash_path(remote_dir) + "; "
       
   622                     ssh.write_file(remote_dir + dmg_archive, release.dist_dir + dmg_archive)
       
   623                     ssh.execute(
       
   624                       cd + "mkdir root && tar -C root -xzf " + ssh.bash_path(dmg_archive)).check
       
   625                     ssh.execute(
       
   626                       cd + "hdiutil create -srcfolder root -volname Isabelle " +
       
   627                       ssh.bash_path(dmg)).check
       
   628                     ssh.read_file(remote_dir + dmg, release.dist_dir + dmg)
       
   629                   })
       
   630                 })
       
   631               case _ =>
       
   632             }
       
   633           }
       
   634           else if (platform == "windows") {
       
   635             val exe_archive = tmp_dir + Path.explode(isabelle_name + ".7z")
       
   636             exe_archive.file.delete
       
   637 
       
   638             Isabelle_System.bash("7z -y -bd a " + File.bash_path(exe_archive) + " " +
       
   639               Bash.string(isabelle_name), cwd = tmp_dir.file).check
       
   640             if (!exe_archive.is_file) error("Failed to create archive: " + exe_archive)
       
   641 
       
   642             val isabelle_exe = Path.explode(isabelle_name + ".exe")
       
   643             val sfx_exe = tmp_dir + Path.explode("windows_app/7zsd_All_x64.sfx")
       
   644             val sfx_txt =
       
   645               File.read(Path.explode("~~/Admin/Windows/Installer/sfx.txt")).
       
   646                 replaceAllLiterally("{ISABELLE_NAME}", isabelle_name)
       
   647 
       
   648             Bytes.write(release.dist_dir + isabelle_exe,
       
   649               Bytes.read(sfx_exe) + Bytes(sfx_txt) + Bytes.read(exe_archive))
       
   650 
       
   651             Isabelle_System.bash("chmod +x " + (release.dist_dir + isabelle_exe)).check
       
   652           }
       
   653         })
   416       }
   654       }
   417     }
   655     }
   418 
   656 
   419 
   657 
   420     /* minimal website */
   658     /* minimal website */
   450         progress.echo_warning("Library archive already exists: " + release.isabelle_library_archive)
   688         progress.echo_warning("Library archive already exists: " + release.isabelle_library_archive)
   451       }
   689       }
   452       else {
   690       else {
   453         Isabelle_System.with_tmp_dir("build_release")(tmp_dir =>
   691         Isabelle_System.with_tmp_dir("build_release")(tmp_dir =>
   454           {
   692           {
   455             val name = release.dist_name
       
   456             val platform = Isabelle_System.getenv_strict("ISABELLE_PLATFORM_FAMILY")
   693             val platform = Isabelle_System.getenv_strict("ISABELLE_PLATFORM_FAMILY")
   457             val bundle = release.dist_dir + Path.explode(name + "_" + platform + ".tar.gz")
   694             val bundle =
       
   695               release.dist_dir + Path.explode(release.dist_name + "_" + platform + ".tar.gz")
   458             execute_tar(tmp_dir, "-xzf " + File.bash_path(bundle))
   696             execute_tar(tmp_dir, "-xzf " + File.bash_path(bundle))
   459 
   697 
   460             val other_isabelle =
   698             val other_isabelle = release.other_isabelle(tmp_dir)
   461               Other_Isabelle(tmp_dir + Path.explode(name),
       
   462                 isabelle_identifier = release.other_isabelle_identifier, progress = progress)
       
   463 
   699 
   464             Isabelle_System.mkdirs(other_isabelle.etc)
   700             Isabelle_System.mkdirs(other_isabelle.etc)
   465             File.write(other_isabelle.etc_preferences, "ML_system_64 = true\n")
   701             File.write(other_isabelle.etc_preferences, "ML_system_64 = true\n")
   466 
   702 
   467             other_isabelle.bash("bin/isabelle build -j " + parallel_jobs +
   703             other_isabelle.bash("bin/isabelle build -j " + parallel_jobs +
   468                 " -o browser_info -o document=pdf -o document_variants=document:outline=/proof,/ML" +
   704                 " -o browser_info -o document=pdf -o document_variants=document:outline=/proof,/ML" +
   469                 " -s -c -a -d '~~/src/Benchmarks'", echo = true).check
   705                 " -s -c -a -d '~~/src/Benchmarks'", echo = true).check
   470             other_isabelle.isabelle_home_user.file.delete
   706             other_isabelle.isabelle_home_user.file.delete
   471 
   707 
   472             execute(tmp_dir, "chmod -R a+r " + Bash.string(name))
   708             execute(tmp_dir, "chmod -R a+r " + Bash.string(release.dist_name))
   473             execute(tmp_dir, "chmod -R g=o " + Bash.string(name))
   709             execute(tmp_dir, "chmod -R g=o " + Bash.string(release.dist_name))
   474             execute_tar(tmp_dir,
   710             execute_tar(tmp_dir,
   475               tar_options + " -czf " + File.bash_path(release.isabelle_library_archive) +
   711               tar_options + " -czf " + File.bash_path(release.isabelle_library_archive) +
   476               " " + Bash.string(name + "/browser_info"))
   712               " " + Bash.string(release.dist_name + "/browser_info"))
   477           })
   713           })
   478       }
   714       }
   479     }
   715     }
   480 
   716 
   481     release
   717     release
   494       var official_release = false
   730       var official_release = false
   495       var proper_release_name: Option[String] = None
   731       var proper_release_name: Option[String] = None
   496       var website: Option[Path] = None
   732       var website: Option[Path] = None
   497       var parallel_jobs = 1
   733       var parallel_jobs = 1
   498       var build_library = false
   734       var build_library = false
       
   735       var options = Options.init()
   499       var platform_families = default_platform_families
   736       var platform_families = default_platform_families
   500       var rev = ""
   737       var rev = ""
   501 
   738 
   502       val getopts = Getopts("""
   739       val getopts = Getopts("""
   503 Usage: Admin/build_release [OPTIONS] BASE_DIR
   740 Usage: Admin/build_release [OPTIONS] BASE_DIR
   509     -O           official release (not release-candidate)
   746     -O           official release (not release-candidate)
   510     -R RELEASE   proper release with name
   747     -R RELEASE   proper release with name
   511     -W WEBSITE   produce minimal website in given directory
   748     -W WEBSITE   produce minimal website in given directory
   512     -j INT       maximum number of parallel jobs (default 1)
   749     -j INT       maximum number of parallel jobs (default 1)
   513     -l           build library
   750     -l           build library
       
   751     -o OPTION    override Isabelle system OPTION (via NAME=VAL or NAME)
   514     -p NAMES     platform families (default: """ + default_platform_families.mkString(",") + """)
   752     -p NAMES     platform families (default: """ + default_platform_families.mkString(",") + """)
   515     -r REV       Mercurial changeset id (default: RELEASE or tip)
   753     -r REV       Mercurial changeset id (default: RELEASE or tip)
   516 
   754 
   517   Build Isabelle release in base directory, using the local repository clone.
   755   Build Isabelle release in base directory, using the local repository clone.
   518 """,
   756 """,
   522         "O" -> (_ => official_release = true),
   760         "O" -> (_ => official_release = true),
   523         "R:" -> (arg => proper_release_name = Some(arg)),
   761         "R:" -> (arg => proper_release_name = Some(arg)),
   524         "W:" -> (arg => website = Some(Path.explode(arg))),
   762         "W:" -> (arg => website = Some(Path.explode(arg))),
   525         "j:" -> (arg => parallel_jobs = Value.Int.parse(arg)),
   763         "j:" -> (arg => parallel_jobs = Value.Int.parse(arg)),
   526         "l" -> (_ => build_library = true),
   764         "l" -> (_ => build_library = true),
       
   765         "o:" -> (arg => options = options + arg),
   527         "p:" -> (arg => platform_families = space_explode(',', arg)),
   766         "p:" -> (arg => platform_families = space_explode(',', arg)),
   528         "r:" -> (arg => rev = arg))
   767         "r:" -> (arg => rev = arg))
   529 
   768 
   530       val more_args = getopts(args)
   769       val more_args = getopts(args)
   531       val base_dir = more_args match { case List(base_dir) => base_dir case _ => getopts.usage() }
   770       val base_dir = more_args match { case List(base_dir) => base_dir case _ => getopts.usage() }
   532 
   771 
   533       val progress = new Console_Progress()
   772       val progress = new Console_Progress()
   534 
   773 
   535       build_release(Path.explode(base_dir), components_base = components_base, progress = progress,
   774       build_release(Path.explode(base_dir), options, components_base = components_base,
   536         rev = rev, afp_rev = afp_rev, official_release = official_release,
   775         progress = progress, rev = rev, afp_rev = afp_rev, official_release = official_release,
   537         proper_release_name = proper_release_name, website = website,
   776         proper_release_name = proper_release_name, website = website,
   538         platform_families =
   777         platform_families =
   539           if (platform_families.isEmpty) default_platform_families
   778           if (platform_families.isEmpty) default_platform_families
   540           else platform_families,
   779           else platform_families,
   541         build_library = build_library, parallel_jobs = parallel_jobs, remote_mac = remote_mac)
   780         build_library = build_library, parallel_jobs = parallel_jobs, remote_mac = remote_mac)