diff --git a/lib/logo/isabelle.icns b/lib/logo/isabelle.icns new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..fe5655c7a139a589f8e3a3ba5ceca6ba7631453c GIT binary patch literal 49285 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 };qBz>{+4$yITF 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 C{I9bB diff --git a/src/Pure/Admin/build_release.scala b/src/Pure/Admin/build_release.scala --- a/src/Pure/Admin/build_release.scala +++ b/src/Pure/Admin/build_release.scala @@ -1,922 +1,928 @@ /* Title: Pure/Admin/build_release.scala Author: Makarius Build full Isabelle distribution from repository. */ package isabelle object Build_Release { /** release info **/ sealed case class Bundle_Info( platform: Platform.Family.Value, platform_description: String, name: String) { def path: Path = Path.explode(name) } class Release private[Build_Release]( progress: Progress, val date: Date, val dist_name: String, val dist_dir: Path, val dist_version: String, val ident: String) { val isabelle_dir: Path = dist_dir + Path.explode(dist_name) val isabelle_archive: Path = dist_dir + Path.explode(dist_name + ".tar.gz") val isabelle_library_archive: Path = dist_dir + Path.explode(dist_name + "_library.tar.gz") def other_isabelle(dir: Path): Other_Isabelle = Other_Isabelle(dir + Path.explode(dist_name), isabelle_identifier = dist_name + "-build", progress = progress) def bundle_info(platform: Platform.Family.Value): Bundle_Info = platform match { case Platform.Family.linux => Bundle_Info(platform, "Linux", dist_name + "_linux.tar.gz") case Platform.Family.macos => Bundle_Info(platform, "macOS", dist_name + "_macos.tar.gz") case Platform.Family.windows => Bundle_Info(platform, "Windows", dist_name + ".exe") } } /** generated content **/ /* patch release */ private val getsettings_path = Path.explode("lib/scripts/getsettings") private val ISABELLE_ID = """ISABELLE_ID="(.+)"""".r def patch_release(release: Release, is_official: Boolean) { val dir = release.isabelle_dir for (name <- List("src/Pure/System/distribution.ML", "src/Pure/System/distribution.scala")) { File.change(dir + Path.explode(name), _.replace("val is_identified = false", "val is_identified = true") .replace("val is_official = false", "val is_official = " + is_official)) } File.change(dir + getsettings_path, _.replace("ISABELLE_ID=\"\"", "ISABELLE_ID=" + quote(release.ident)) .replace("ISABELLE_IDENTIFIER=\"\"", "ISABELLE_IDENTIFIER=" + quote(release.dist_name))) File.change(dir + Path.explode("lib/html/library_index_header.template"), _.replace("{ISABELLE}", release.dist_name)) for { name <- List( "src/Pure/System/distribution.ML", "src/Pure/System/distribution.scala", "lib/Tools/version") } { File.change(dir + Path.explode(name), _.replace("repository version", release.dist_version)) } File.change(dir + Path.explode("README"), _.replace("some repository version of Isabelle", release.dist_version)) } /* ANNOUNCE */ def make_announce(release: Release) { File.write(release.isabelle_dir + Path.explode("ANNOUNCE"), """ IMPORTANT NOTE ============== This is a snapshot of Isabelle/""" + release.ident + """ from the repository. """) } /* NEWS */ def make_news(other_isabelle: Other_Isabelle, dist_version: String) { val target = other_isabelle.isabelle_home + Path.explode("doc") val target_fonts = Isabelle_System.make_directory(target + Path.explode("fonts")) other_isabelle.copy_fonts(target_fonts) HTML.write_document(target, "NEWS.html", List(HTML.title("NEWS (" + dist_version + ")")), List( HTML.chapter("NEWS"), HTML.source( Symbol.decode(File.read(other_isabelle.isabelle_home + Path.explode("NEWS")))))) } /* bundled components */ class Bundled(platform: Option[Platform.Family.Value] = None) { def detect(s: String): Boolean = s.startsWith("#bundled") && !s.startsWith("#bundled ") def apply(name: String): String = "#bundled" + (platform match { case None => "" case Some(plat) => "-" + plat }) + ":" + name private val Pattern1 = ("""^#bundled:(.*)$""").r private val Pattern2 = ("""^#bundled-(.*):(.*)$""").r def unapply(s: String): Option[String] = s match { case Pattern1(name) => Some(name) case Pattern2(Platform.Family(plat), name) if platform == Some(plat) => Some(name) case _ => None } } def record_bundled_components(dir: Path) { val catalogs = List("main", "bundled").map((_, new Bundled())) ::: default_platform_families.flatMap(platform => List(platform.toString, "bundled-" + platform.toString). map((_, new Bundled(platform = Some(platform))))) File.append(Components.components(dir), terminate_lines("#bundled components" :: (for { (catalog, bundled) <- catalogs.iterator path = Components.admin(dir) + Path.basic(catalog) if path.is_file line <- split_lines(File.read(path)) if line.nonEmpty && !line.startsWith("#") && !line.startsWith("jedit_build") } yield bundled(line)).toList)) } def get_bundled_components(dir: Path, platform: Platform.Family.Value): (List[String], String) = { val Bundled = new Bundled(platform = Some(platform)) val components = for { Bundled(name) <- Components.read_components(dir) if !name.startsWith("jedit_build") } yield name val jdk_component = components.find(_.startsWith("jdk")) getOrElse error("Missing jdk component") (components, jdk_component) } def activate_components(dir: Path, platform: Platform.Family.Value, more_names: List[String]) { def contrib_name(name: String): String = Components.contrib(name = name).implode val Bundled = new Bundled(platform = Some(platform)) Components.write_components(dir, Components.read_components(dir).flatMap(line => line match { case Bundled(name) => if (Components.check_dir(Components.contrib(dir, name))) Some(contrib_name(name)) else None case _ => if (Bundled.detect(line)) None else Some(line) }) ::: more_names.map(contrib_name)) } def make_contrib(dir: Path) { Isabelle_System.make_directory(Components.contrib(dir)) File.write(Components.contrib(dir, "README"), """This directory contains add-on components that contribute to the main Isabelle distribution. Separate licensing conditions apply, see each directory individually. """) } /** build release **/ private def execute(dir: Path, script: String): Unit = Isabelle_System.bash(script, cwd = dir.file).check private def execute_tar(dir: Path, args: String): Unit = Isabelle_System.gnutar(args, dir = dir).check /* build heaps on remote server */ private def remote_build_heaps( options: Options, platform: Platform.Family.Value, build_sessions: List[String], local_dir: Path) { val server_option = "build_host_" + platform.toString options.string(server_option) match { case SSH.Target(user, host) => using(SSH.open_session(options, host = host, user = user))(ssh => { Isabelle_System.with_tmp_file("tmp", "tar")(local_tmp_tar => { execute_tar(local_dir, "-cf " + File.bash_path(local_tmp_tar) + " .") ssh.with_tmp_dir(remote_dir => { val remote_tmp_tar = remote_dir + Path.basic("tmp.tar") ssh.write_file(remote_tmp_tar, local_tmp_tar) val remote_commands = List( "cd " + File.bash_path(remote_dir), "tar -xf tmp.tar", "./bin/isabelle build -o system_heaps -b -- " + Bash.strings(build_sessions), "tar -cf tmp.tar heaps") ssh.execute(remote_commands.mkString(" && ")).check ssh.read_file(remote_tmp_tar, local_tmp_tar) }) execute_tar(local_dir, "-xf " + File.bash_path(local_tmp_tar)) }) }) case s => error("Bad " + server_option + ": " + quote(s)) } } /* Isabelle application */ def make_isabelle_options(path: Path, options: List[String], line_ending: String = "\n") { val title = "# Java runtime options" File.write(path, (title :: options).map(_ + line_ending).mkString) } def make_isabelle_app( path: Path, isabelle_home_prefix: String, jdk_component: String, classpath: List[Path]) { val script = """#!/usr/bin/env bash # # Author: Makarius # # Main Isabelle application script. # minimal Isabelle environment ISABELLE_HOME="$(cd "$(dirname "$0")"; cd "$(pwd -P)/""" + isabelle_home_prefix + """"; pwd)" source "$ISABELLE_HOME/lib/scripts/isabelle-platform" #paranoia settings -- avoid intrusion of alien options unset "_JAVA_OPTIONS" unset "JAVA_TOOL_OPTIONS" #paranoia settings -- avoid problems of Java/Swing versus XIM/IBus etc. unset XMODIFIERS COMPONENT="$ISABELLE_HOME/contrib/""" + jdk_component + """" source "$COMPONENT/etc/settings" # main declare -a JAVA_OPTIONS=($(perl -p -e 's,#.*$,,g;' "$ISABELLE_HOME/Isabelle.options")) exec "$ISABELLE_JDK_HOME/bin/java" \ "-Disabelle.root=$ISABELLE_HOME" "${JAVA_OPTIONS[@]}" \ -classpath """" + classpath.map(p => "$ISABELLE_HOME/" + p.implode).mkString(":") + """" \ "-splash:$ISABELLE_HOME/lib/logo/isabelle.gif" \ isabelle.Main "$@" """ File.write(path, script) File.set_executable(path, true) } def make_isabelle_plist(path: Path, isabelle_name: String) { File.write(path, """ CFBundleDevelopmentRegion English +CFBundleIconFile +isabelle.icns CFBundleIdentifier de.tum.in.isabelle.""" + isabelle_name + """ CFBundleDisplayName """ + isabelle_name + """ CFBundleInfoDictionaryVersion 6.0 CFBundleName """ + isabelle_name + """ CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 NSHumanReadableCopyright LSMinimumSystemVersion 10.7 LSApplicationCategoryType public.app-category.developer-tools NSHighResolutionCapable true NSSupportsAutomaticGraphicsSwitching true CFBundleDocumentTypes CFBundleTypeExtensions thy CFBundleTypeName Isabelle theory file CFBundleTypeRole Editor LSTypeIsPackage """) } /* main */ private val default_platform_families: List[Platform.Family.Value] = List(Platform.Family.linux, Platform.Family.windows, Platform.Family.macos) def build_release(base_dir: Path, options: Options, components_base: Path = Components.default_components_base, progress: Progress = new Progress, rev: String = "", afp_rev: String = "", official_release: Boolean = false, proper_release_name: Option[String] = None, platform_families: List[Platform.Family.Value] = default_platform_families, more_components: List[Path] = Nil, website: Option[Path] = None, build_sessions: List[String] = Nil, build_library: Boolean = false, parallel_jobs: Int = 1): Release = { val hg = Mercurial.repository(Path.explode("$ISABELLE_HOME")) val release = { val date = Date.now() val dist_name = proper_release_name getOrElse ("Isabelle_" + Date.Format.date(date)) val dist_dir = (base_dir + Path.explode("dist-" + dist_name)).absolute val version = proper_string(rev) orElse proper_release_name getOrElse "tip" val ident = try { hg.id(version) } catch { case ERROR(msg) => cat_error("Bad repository version: " + version, msg) } val dist_version = proper_release_name match { case Some(name) => name + ": " + Date.Format("LLLL uuuu")(date) case None => "Isabelle repository snapshot " + ident + " " + Date.Format.date(date) } new Release(progress, date, dist_name, dist_dir, dist_version, ident) } /* make distribution */ if (release.isabelle_archive.is_file) { progress.echo_warning("Release archive already exists: " + release.isabelle_archive) val archive_ident = Isabelle_System.with_tmp_dir("build_release")(tmp_dir => { val getsettings = Path.explode(release.dist_name) + getsettings_path execute_tar(tmp_dir, "-xzf " + File.bash_path(release.isabelle_archive) + " " + File.bash_path(getsettings)) split_lines(File.read(tmp_dir + getsettings)) .collectFirst({ case ISABELLE_ID(ident) => ident }) .getOrElse(error("Failed to read ISABELLE_ID from " + release.isabelle_archive)) }) if (release.ident != archive_ident) { error("Mismatch of release identification " + release.ident + " vs. archive " + archive_ident) } } else { progress.echo_warning("Producing release archive " + release.isabelle_archive + " ...") Isabelle_System.make_directory(release.dist_dir) if (release.isabelle_dir.is_dir) error("Directory " + release.isabelle_dir + " already exists") progress.echo_warning("Retrieving Mercurial repository version " + release.ident) hg.archive(release.isabelle_dir.expand.implode, rev = release.ident, options = "--type files") for (name <- List(".hg_archival.txt", ".hgtags", ".hgignore", "README_REPOSITORY")) { (release.isabelle_dir + Path.explode(name)).file.delete } progress.echo_warning("Preparing distribution " + quote(release.dist_name)) patch_release(release, proper_release_name.isDefined && official_release) if (proper_release_name.isEmpty) make_announce(release) make_contrib(release.isabelle_dir) execute(release.isabelle_dir, """find . -print | xargs chmod -f u+rw""") record_bundled_components(release.isabelle_dir) /* build tools and documentation */ val other_isabelle = release.other_isabelle(release.dist_dir) other_isabelle.init_settings( other_isabelle.init_components(components_base = components_base, catalogs = List("main"))) other_isabelle.resolve_components(echo = true) try { val export_classpath = "export CLASSPATH=" + Bash.string(other_isabelle.getenv("ISABELLE_CLASSPATH")) + "\n" other_isabelle.bash(export_classpath + "./Admin/build all", echo = true).check other_isabelle.bash(export_classpath + "./bin/isabelle jedit -b", echo = true).check } catch { case ERROR(msg) => cat_error("Failed to build tools:", msg) } try { other_isabelle.bash( "./bin/isabelle build_doc -a -o system_heaps -j " + parallel_jobs, echo = true).check } catch { case ERROR(msg) => cat_error("Failed to build documentation:", msg) } make_news(other_isabelle, release.dist_version) for (name <- List("Admin", "browser_info", "heaps")) { Isabelle_System.rm_tree(other_isabelle.isabelle_home + Path.explode(name)) } other_isabelle.cleanup() progress.echo_warning("Creating distribution archive " + release.isabelle_archive) def execute_dist_name(script: String): Unit = Isabelle_System.bash(script, cwd = release.dist_dir.file, env = Isabelle_System.settings() + ("DIST_NAME" -> release.dist_name)).check execute_dist_name(""" set -e chmod -R a+r "$DIST_NAME" chmod -R u+w "$DIST_NAME" chmod -R g=o "$DIST_NAME" find "$DIST_NAME" -type f "(" -name "*.thy" -o -name "*.ML" -o -name "*.scala" ")" -print | xargs chmod -f u-w """) execute_tar(release.dist_dir, "-czf " + File.bash_path(release.isabelle_archive) + " " + Bash.string(release.dist_name)) execute_dist_name(""" set -e mv "$DIST_NAME" "${DIST_NAME}-old" mkdir "$DIST_NAME" mv "${DIST_NAME}-old/README" "${DIST_NAME}-old/NEWS" "${DIST_NAME}-old/ANNOUNCE" \ "${DIST_NAME}-old/COPYRIGHT" "${DIST_NAME}-old/CONTRIBUTORS" "$DIST_NAME" mkdir "$DIST_NAME/doc" mv "${DIST_NAME}-old/doc/"*.pdf \ "${DIST_NAME}-old/doc/"*.html \ "${DIST_NAME}-old/doc/"*.css \ "${DIST_NAME}-old/doc/fonts" \ "${DIST_NAME}-old/doc/Contents" "$DIST_NAME/doc" rm -f Isabelle && ln -sf "$DIST_NAME" Isabelle rm -rf "${DIST_NAME}-old" """) } /* make application bundles */ val bundle_infos = platform_families.map(release.bundle_info) for (bundle_info <- bundle_infos) { val isabelle_name = release.dist_name val platform = bundle_info.platform progress.echo("\nApplication bundle for " + platform) Isabelle_System.with_tmp_dir("build_release")(tmp_dir => { // release archive execute_tar(tmp_dir, "-xzf " + File.bash_path(release.isabelle_archive)) val other_isabelle = release.other_isabelle(tmp_dir) val isabelle_target = other_isabelle.isabelle_home // bundled components progress.echo("Bundled components:") val contrib_dir = Components.contrib(isabelle_target) val (bundled_components, jdk_component) = get_bundled_components(isabelle_target, platform) Components.resolve(components_base, bundled_components, target_dir = Some(contrib_dir), copy_dir = Some(release.dist_dir + Path.explode("contrib")), progress = progress) val more_components_names = more_components.map(Components.unpack(contrib_dir, _, progress = progress)) Components.purge(contrib_dir, platform) activate_components(isabelle_target, platform, more_components_names) // Java parameters val java_options: List[String] = (for { variable <- List( "ISABELLE_JAVA_SYSTEM_OPTIONS", "JEDIT_JAVA_SYSTEM_OPTIONS", "JEDIT_JAVA_OPTIONS") opt <- Word.explode(other_isabelle.getenv(variable)) } yield { val s = "-Dapple.awt.application.name=" if (opt.startsWith(s)) s + isabelle_name else opt }) ::: List("-Disabelle.jedit_server=" + isabelle_name) val classpath: List[Path] = { val base = isabelle_target.absolute Path.split(other_isabelle.getenv("ISABELLE_CLASSPATH")).map(path => { val abs_path = path.absolute File.relative_path(base, abs_path) match { case Some(rel_path) => rel_path case None => error("Bad ISABELLE_CLASSPATH element: " + abs_path) } }) ::: List(Path.explode("src/Tools/jEdit/dist/jedit.jar")) } val jedit_options = Path.explode("src/Tools/jEdit/etc/options") val jedit_props = Path.explode("src/Tools/jEdit/dist/properties/jEdit.props") // build heaps if (build_sessions.nonEmpty) { progress.echo("Building heaps ...") remote_build_heaps(options, platform, build_sessions, isabelle_target) } // application bundling platform match { case Platform.Family.linux => File.change(isabelle_target + jedit_options, _.replaceAll("jedit_reset_font_size : int =.*", "jedit_reset_font_size : int = 24")) File.change(isabelle_target + jedit_props, _.replaceAll("console.fontsize=.*", "console.fontsize=18") .replaceAll("helpviewer.fontsize=.*", "helpviewer.fontsize=18") .replaceAll("metal.primary.fontsize=.*", "metal.primary.fontsize=18") .replaceAll("metal.secondary.fontsize=.*", "metal.secondary.fontsize=18") .replaceAll("view.fontsize=.*", "view.fontsize=24") .replaceAll("view.gutter.fontsize=.*", "view.gutter.fontsize=16")) make_isabelle_options( isabelle_target + Path.explode("Isabelle.options"), java_options) make_isabelle_app( isabelle_target + Path.explode("lib/scripts/Isabelle_app"), "../..", jdk_component, classpath) val linux_app = isabelle_target + Path.explode("contrib/linux_app") File.move(linux_app + Path.explode("Isabelle"), isabelle_target + Path.explode(isabelle_name)) Isabelle_System.rm_tree(linux_app) val archive_name = isabelle_name + "_linux.tar.gz" progress.echo("Packaging " + archive_name + " ...") execute_tar(tmp_dir, "-czf " + File.bash_path(release.dist_dir + Path.explode(archive_name)) + " " + Bash.string(isabelle_name)) case Platform.Family.macos => File.change(isabelle_target + jedit_props, _.replaceAll("lookAndFeel=.*", "lookAndFeel=com.apple.laf.AquaLookAndFeel") .replaceAll("delete-line.shortcut=.*", "delete-line.shortcut=C+d") .replaceAll("delete.shortcut2=.*", "delete.shortcut2=A+d")) // MacOS application bundle val isabelle_app = Path.explode(isabelle_name + ".app") val app_dir = tmp_dir + isabelle_app val app_contents = app_dir + Path.explode("Contents") + val app_resources = + Isabelle_System.make_directory(app_contents + Path.explode("Resources")) - File.move(tmp_dir + Path.explode(isabelle_name), - Isabelle_System.make_directory(app_contents + Path.explode("Resources"))) + File.move(tmp_dir + Path.explode(isabelle_name), app_resources) val isabelle_home = Path.explode("Contents/Resources/" + isabelle_name) val isabelle_options = Path.explode("Isabelle.options") File.link( isabelle_home, app_dir + Path.explode("Isabelle"), force = true) File.link( isabelle_home + isabelle_options, app_dir + isabelle_options, force = true) + File.copy( + app_dir + isabelle_home + Path.explode("lib/logo/isabelle.icns"), app_resources) + make_isabelle_app( app_dir + Path.explode(isabelle_name), isabelle_home.implode, jdk_component, classpath) make_isabelle_options( app_dir + isabelle_options, java_options ::: List("-Disabelle.app=true")) make_isabelle_plist(app_contents + Path.explode("Info.plist"), isabelle_name) // application archive val archive_name = isabelle_name + "_macos.tar.gz" progress.echo("Packaging " + archive_name + " ...") execute_tar(tmp_dir, "-czf " + File.bash_path(release.dist_dir + Path.explode(archive_name)) + " " + File.bash_path(isabelle_app)) case Platform.Family.windows => File.change(isabelle_target + jedit_props, _.replaceAll("lookAndFeel=.*", "lookAndFeel=com.sun.java.swing.plaf.windows.WindowsLookAndFeel") .replaceAll("foldPainter=.*", "foldPainter=Square")) // application launcher File.move(isabelle_target + Path.explode("contrib/windows_app"), tmp_dir) val app_template = Path.explode("~~/Admin/Windows/launch4j") make_isabelle_options( isabelle_target + Path.explode(isabelle_name + ".l4j.ini"), java_options, line_ending = "\r\n") val isabelle_xml = Path.explode("isabelle.xml") val isabelle_exe = Path.explode(isabelle_name + ".exe") File.write(tmp_dir + isabelle_xml, File.read(app_template + isabelle_xml) .replace("{ISABELLE_NAME}", isabelle_name) .replace("{OUTFILE}", File.platform_path(isabelle_target + isabelle_exe)) .replace("{ICON}", File.platform_path(app_template + Path.explode("isabelle_transparent.ico"))) .replace("{SPLASH}", File.platform_path(app_template + Path.explode("isabelle.bmp"))) .replace("{CLASSPATH}", cat_lines(classpath.map(cp => " %EXEDIR%\\" + File.platform_path(cp).replace('/', '\\') + ""))) .replace("\\jdk\\", "\\" + jdk_component + "\\")) execute(tmp_dir, "\"windows_app/launch4j-${ISABELLE_PLATFORM_FAMILY}/launch4j\" isabelle.xml") File.copy(app_template + Path.explode("manifest.xml"), isabelle_target + isabelle_exe.ext("manifest")) // Cygwin setup val cygwin_template = Path.explode("~~/Admin/Windows/Cygwin") File.copy(cygwin_template + Path.explode("Cygwin-Terminal.bat"), isabelle_target) val cygwin_mirror = File.read(isabelle_target + Path.explode("contrib/cygwin/isabelle/cygwin_mirror")) val cygwin_bat = Path.explode("Cygwin-Setup.bat") File.write(isabelle_target + cygwin_bat, File.read(cygwin_template + cygwin_bat).replace("{MIRROR}", cygwin_mirror)) File.set_executable(isabelle_target + cygwin_bat, true) for (name <- List("isabelle/postinstall", "isabelle/rebaseall")) { val path = Path.explode(name) File.copy(cygwin_template + path, isabelle_target + Path.explode("contrib/cygwin") + path) } execute(isabelle_target, """find . -type f -not -name "*.exe" -not -name "*.dll" """ + (if (Platform.is_macos) "-perm +100" else "-executable") + " -print0 > contrib/cygwin/isabelle/executables") execute(isabelle_target, """find . -type l -exec echo "{}" ";" -exec readlink "{}" ";" """ + """> contrib/cygwin/isabelle/symlinks""") execute(isabelle_target, """find . -type l -exec rm "{}" ";" """) File.write(isabelle_target + Path.explode("contrib/cygwin/isabelle/uninitialized"), "") // executable archive (self-extracting 7z) val archive_name = isabelle_name + ".7z" val exe_archive = tmp_dir + Path.explode(archive_name) exe_archive.file.delete progress.echo("Packaging " + archive_name + " ...") execute(tmp_dir, "7z -y -bd a " + File.bash_path(exe_archive) + " " + Bash.string(isabelle_name)) if (!exe_archive.is_file) error("Failed to create archive: " + exe_archive) val sfx_exe = tmp_dir + Path.explode("windows_app/7zsd_All_x64.sfx") val sfx_txt = File.read(Path.explode("~~/Admin/Windows/Installer/sfx.txt")) .replace("{ISABELLE_NAME}", isabelle_name) Bytes.write(release.dist_dir + isabelle_exe, Bytes.read(sfx_exe) + Bytes(sfx_txt) + Bytes.read(exe_archive)) File.set_executable(release.dist_dir + isabelle_exe, true) } }) progress.echo("DONE") } /* minimal website */ for (dir <- website) { val website_platform_bundles = for { bundle_info <- bundle_infos if (release.dist_dir + bundle_info.path).is_file } yield (bundle_info.name, bundle_info) val isabelle_link = HTML.link(Isabelle_Cronjob.isabelle_repos_source + "/rev/" + release.ident, HTML.text("Isabelle/" + release.ident)) val afp_link = HTML.link(AFP.repos_source + "/rev/" + afp_rev, HTML.text("AFP/" + afp_rev)) HTML.write_document(dir, "index.html", List(HTML.title(release.dist_name)), List( HTML.section(release.dist_name), HTML.subsection("Platforms"), HTML.itemize( website_platform_bundles.map({ case (bundle, bundle_info) => List(HTML.link(bundle, HTML.text(bundle_info.platform_description))) })), HTML.subsection("Repositories"), HTML.itemize( List(List(isabelle_link)) ::: (if (afp_rev == "") Nil else List(List(afp_link)))))) for ((bundle, _) <- website_platform_bundles) File.copy(release.dist_dir + Path.explode(bundle), dir) } /* HTML library */ if (build_library) { if (release.isabelle_library_archive.is_file) { progress.echo_warning("Library archive already exists: " + release.isabelle_library_archive) } else { Isabelle_System.with_tmp_dir("build_release")(tmp_dir => { val bundle = release.dist_dir + Path.explode(release.dist_name + "_" + Platform.family + ".tar.gz") execute_tar(tmp_dir, "-xzf " + File.bash_path(bundle)) val other_isabelle = release.other_isabelle(tmp_dir) Isabelle_System.make_directory(other_isabelle.etc) File.write(other_isabelle.etc_preferences, "ML_system_64 = true\n") other_isabelle.bash("bin/isabelle build -f -j " + parallel_jobs + " -o browser_info -o document=pdf -o document_variants=document:outline=/proof,/ML" + " -o system_heaps -c -a -d '~~/src/Benchmarks'", echo = true).check other_isabelle.isabelle_home_user.file.delete execute(tmp_dir, "chmod -R a+r " + Bash.string(release.dist_name)) execute(tmp_dir, "chmod -R g=o " + Bash.string(release.dist_name)) execute_tar(tmp_dir, "-czf " + File.bash_path(release.isabelle_library_archive) + " " + Bash.string(release.dist_name + "/browser_info")) }) } } release } /** command line entry point **/ def main(args: Array[String]) { Command_Line.tool { var afp_rev = "" var components_base: Path = Components.default_components_base var official_release = false var proper_release_name: Option[String] = None var website: Option[Path] = None var build_sessions: List[String] = Nil var more_components: List[Path] = Nil var parallel_jobs = 1 var build_library = false var options = Options.init() var platform_families = default_platform_families var rev = "" val getopts = Getopts(""" Usage: Admin/build_release [OPTIONS] BASE_DIR Options are: -A REV corresponding AFP changeset id -C DIR base directory for Isabelle components (default: """ + Components.default_components_base + """) -O official release (not release-candidate) -R RELEASE proper release with name -W WEBSITE produce minimal website in given directory -b SESSIONS build platform-specific session images (separated by commas) -c ARCHIVE clean bundling with additional component .tar.gz archive -j INT maximum number of parallel jobs (default 1) -l build library -o OPTION override Isabelle system OPTION (via NAME=VAL or NAME) -p NAMES platform families (default: """ + default_platform_families.mkString(",") + """) -r REV Mercurial changeset id (default: RELEASE or tip) Build Isabelle release in base directory, using the local repository clone. """, "A:" -> (arg => afp_rev = arg), "C:" -> (arg => components_base = Path.explode(arg)), "O" -> (_ => official_release = true), "R:" -> (arg => proper_release_name = Some(arg)), "W:" -> (arg => website = Some(Path.explode(arg))), "b:" -> (arg => build_sessions = space_explode(',', arg)), "c:" -> (arg => { val path = Path.explode(arg) Components.Archive.get_name(path.file_name) more_components = more_components ::: List(path) }), "j:" -> (arg => parallel_jobs = Value.Int.parse(arg)), "l" -> (_ => build_library = true), "o:" -> (arg => options = options + arg), "p:" -> (arg => platform_families = space_explode(',', arg).map(Platform.Family.parse)), "r:" -> (arg => rev = arg)) val more_args = getopts(args) val base_dir = more_args match { case List(base_dir) => base_dir case _ => getopts.usage() } val progress = new Console_Progress() if (platform_families.contains(Platform.Family.windows) && !Isabelle_System.bash("7z i").ok) error("Building for windows requires 7z") build_release(Path.explode(base_dir), options, components_base = components_base, progress = progress, rev = rev, afp_rev = afp_rev, official_release = official_release, proper_release_name = proper_release_name, website = website, platform_families = if (platform_families.isEmpty) default_platform_families else platform_families, more_components = more_components, build_sessions = build_sessions, build_library = build_library, parallel_jobs = parallel_jobs) } } }