clarified directories;
authorwenzelm
Tue, 08 Mar 2022 17:09:09 +0100
changeset 75247 4a9809ee1a85
parent 75246 f32e5d4cf1a3
child 75248 b57efe7fe1d3
clarified directories;
etc/build.props
src/Pure/Admin/build_vscodium.scala
src/Pure/System/isabelle_tool.scala
src/Tools/VSCode/src/build_vscodium.scala
--- a/etc/build.props	Tue Mar 08 17:02:24 2022 +0100
+++ b/etc/build.props	Tue Mar 08 17:09:09 2022 +0100
@@ -30,7 +30,6 @@
   src/Pure/Admin/build_status.scala \
   src/Pure/Admin/build_vampire.scala \
   src/Pure/Admin/build_verit.scala \
-  src/Pure/Admin/build_vscodium.scala \
   src/Pure/Admin/build_zipperposition.scala \
   src/Pure/Admin/check_sources.scala \
   src/Pure/Admin/ci_profile.scala \
@@ -213,6 +212,7 @@
   src/Tools/Graphview/shapes.scala \
   src/Tools/Graphview/tree_panel.scala \
   src/Tools/VSCode/src/build_vscode.scala \
+  src/Tools/VSCode/src/build_vscodium.scala \
   src/Tools/VSCode/src/channel.scala \
   src/Tools/VSCode/src/dynamic_output.scala \
   src/Tools/VSCode/src/language_server.scala \
--- a/src/Pure/Admin/build_vscodium.scala	Tue Mar 08 17:02:24 2022 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,403 +0,0 @@
-/*  Title:      Pure/Admin/build_vscodium.scala
-    Author:     Makarius
-
-Build component for VSCodium (cross-compiled from sources for all platforms).
-*/
-
-package isabelle
-
-
-import java.util.{Map => JMap}
-import java.security.MessageDigest
-import java.util.Base64
-
-
-object Build_VSCodium
-{
-  /* global parameters */
-
-  lazy val version: String = Isabelle_System.getenv_strict("ISABELLE_VSCODE_VERSION")
-  val vscodium_repository = "https://github.com/VSCodium/vscodium.git"
-  val vscodium_download = "https://github.com/VSCodium/vscodium/releases/download"
-
-  def vscodium_exe(dir: Path): Path = dir + Path.explode("bin/codium")
-
-
-  /* platform info */
-
-  sealed case class Platform_Info(
-    platform: Platform.Family.Value,
-    download_template: String,
-    build_name: String,
-    env: List[String])
-  {
-    def is_linux: Boolean = platform == Platform.Family.linux
-
-    def download_name: String = "VSCodium-" + download_template.replace("{VERSION}", version)
-    def download_zip: Boolean = download_name.endsWith(".zip")
-
-    def download(dir: Path, progress: Progress = new Progress): Unit =
-    {
-      if (download_zip) Isabelle_System.require_command("unzip", test = "-h")
-
-      Isabelle_System.with_tmp_file("download")(download_file =>
-      {
-        Isabelle_System.download_file(vscodium_download + "/" + version + "/" + download_name,
-          download_file, progress = progress)
-
-        progress.echo("Extract ...")
-        if (download_zip) {
-          Isabelle_System.bash("unzip -x " + File.bash_path(download_file), cwd = dir.file).check
-        }
-        else {
-          Isabelle_System.gnutar("-xzf " + File.bash_path(download_file), dir = dir).check
-        }
-      })
-    }
-
-    def get_vscodium_repository(vscodium_dir: Path, progress: Progress = new Progress): Unit =
-    {
-      progress.echo("Getting VSCodium repository ...")
-      Isabelle_System.bash(
-        List(
-          "set -e",
-          "git clone -n " + Bash.string(vscodium_repository) + " .",
-          "git checkout -q " + Bash.string(version)
-        ).mkString("\n"), cwd = vscodium_dir.file).check
-
-      progress.echo("Getting VSCode repository ...")
-      Isabelle_System.bash(environment + "\n" + "./get_repo.sh", cwd = vscodium_dir.file).check
-    }
-
-    def platform_dir(dir: Path): Path =
-    {
-      val platform_name =
-        if (platform == Platform.Family.windows) Platform.Family.native(platform)
-        else Platform.Family.standard(platform)
-      dir + Path.explode(platform_name)
-    }
-
-    def build_dir(dir: Path): Path = dir + Path.explode(build_name)
-
-    def environment: String =
-      (("MS_TAG=" + Bash.string(version)) :: "SHOULD_BUILD=yes" :: "VSCODE_ARCH=x64" :: env)
-        .map(s => "export " + s + "\n").mkString
-
-    def resources_dir(dir: Path): Path =
-    {
-      val resources =
-        if (platform == Platform.Family.macos) "VSCodium.app/Contents/Resources"
-        else "resources"
-      dir + Path.explode(resources)
-    }
-
-    def patch_sources(base_dir: Path): String =
-    {
-      val dir = base_dir + Path.explode("vscode")
-      Isabelle_System.with_copy_dir(dir, dir.orig) {
-        for (name <- Seq("build/lib/electron.js", "build/lib/electron.ts")) {
-          File.change(dir + Path.explode(name), strict = true) {
-            _.replace("""'resources/darwin/' + icon + '.icns'""",
-              """'resources/darwin/' + icon.toLowerCase() + '.icns'""")
-          }
-        }
-        Isabelle_System.make_patch(base_dir, dir.base.orig, dir.base)
-      }
-    }
-
-    def patch_resources(base_dir: Path): String =
-    {
-      val dir = resources_dir(base_dir)
-      Isabelle_System.with_copy_dir(dir, dir.orig) {
-        HTML.init_fonts(dir + Path.explode("app/out/vs/base/browser/ui"))
-
-        val workbench_css = dir + Path.explode("app/out/vs/workbench/workbench.desktop.main.css")
-        val checksum1 = file_checksum(workbench_css)
-        File.append(workbench_css, "\n\n" + HTML.fonts_css_dir(prefix = "../base/browser/ui"))
-        val checksum2 = file_checksum(workbench_css)
-
-        val file_name = workbench_css.file_name
-        File.change_lines(dir + Path.explode("app/product.json")) { _.map(line =>
-          if (line.containsSlice(file_name) && line.contains(checksum1)) {
-            line.replace(checksum1, checksum2)
-          }
-          else line)
-        }
-
-        Isabelle_System.make_patch(dir.dir, dir.orig.base, dir.base)
-      }
-    }
-
-    def node_binaries(dir: Path, progress: Progress): Unit =
-    {
-      Isabelle_System.with_tmp_dir("download")(download_dir =>
-      {
-        download(download_dir, progress = progress)
-        for (name <- Seq("app/node_modules.asar", "app/node_modules.asar.unpacked")) {
-          val rel_path = resources_dir(Path.current) + Path.explode(name)
-          Isabelle_System.rm_tree(dir + rel_path)
-          Isabelle_System.copy_dir(download_dir + rel_path, dir + rel_path)
-        }
-      })
-    }
-
-    def setup_executables(dir: Path): Unit =
-    {
-      val exe = vscodium_exe(dir)
-      Isabelle_System.make_directory(exe.dir)
-
-      platform match {
-        case Platform.Family.macos =>
-          File.write(exe, macos_exe)
-          File.set_executable(exe, true)
-        case Platform.Family.windows =>
-          val files1 = File.find_files(exe.dir.file)
-          val files2 = File.find_files(dir.file, pred = file =>
-            {
-              val name = file.getName
-              name.endsWith(".dll") || name.endsWith(".exe") || name.endsWith(".node")
-            })
-          for (file <- files1 ::: files2) File.set_executable(File.path(file), true)
-          Isabelle_System.bash("chmod -R o-w " + File.bash_path(dir)).check
-        case _ =>
-      }
-    }
-  }
-
-  // see https://github.com/microsoft/vscode/blob/main/build/gulpfile.vscode.js
-  // function computeChecksum(filename)
-  private def file_checksum(path: Path): String =
-  {
-    val digest = MessageDigest.getInstance("MD5")
-    digest.update(Bytes.read(path).array)
-    Bytes(Base64.getEncoder.encode(digest.digest()))
-      .text.replaceAll("=", "")
-  }
-
-  private val platform_infos: Map[Platform.Family.Value, Platform_Info] =
-    Iterator(
-      Platform_Info(Platform.Family.linux, "linux-x64-{VERSION}.tar.gz", "VSCode-linux-x64",
-        List("OS_NAME=linux", "SKIP_LINUX_PACKAGES=True")),
-      Platform_Info(Platform.Family.linux_arm, "linux-arm64-{VERSION}.tar.gz", "VSCode-linux-arm64",
-        List("OS_NAME=linux", "SKIP_LINUX_PACKAGES=True", "VSCODE_ARCH=arm64")),
-      Platform_Info(Platform.Family.macos, "darwin-x64-{VERSION}.zip", "VSCode-darwin-x64",
-        List("OS_NAME=osx")),
-      Platform_Info(Platform.Family.windows, "win32-x64-{VERSION}.zip", "VSCode-win32-x64",
-        List("OS_NAME=windows",
-          "SHOULD_BUILD_ZIP=no",
-          "SHOULD_BUILD_EXE_SYS=no",
-          "SHOULD_BUILD_EXE_USR=no",
-          "SHOULD_BUILD_MSI=no",
-          "SHOULD_BUILD_MSI_NOUP=no")))
-      .map(info => info.platform -> info).toMap
-
-  def the_platform_info(platform: Platform.Family.Value): Platform_Info =
-    platform_infos.getOrElse(platform, error("No platform info for " + quote(platform.toString)))
-
-  def linux_platform_info: Platform_Info =
-    the_platform_info(Platform.Family.linux)
-
-
-  /* check system */
-
-  def check_system(platforms: List[Platform.Family.Value]): Unit =
-  {
-    if (Platform.family != Platform.Family.linux) error("Not a Linux/x86_64 system")
-
-    Isabelle_System.require_command("git")
-    Isabelle_System.require_command("node")
-    Isabelle_System.require_command("yarn")
-    Isabelle_System.require_command("jq")
-
-    if (platforms.contains(Platform.Family.windows)) {
-      Isabelle_System.require_command("wine")
-    }
-
-    if (platforms.exists(platform => the_platform_info(platform).download_zip)) {
-      Isabelle_System.require_command("unzip", test = "-h")
-    }
-  }
-
-
-  /* original repository clones and patches */
-
-  def vscodium_patch(verbose: Boolean = false, progress: Progress = new Progress): String =
-  {
-    val platform_info = linux_platform_info
-    check_system(List(platform_info.platform))
-
-    Isabelle_System.with_tmp_dir("vscodium")(vscodium_dir =>
-    {
-      platform_info.get_vscodium_repository(vscodium_dir, progress = progress)
-      val vscode_dir = vscodium_dir + Path.basic("vscode")
-      progress.echo("Prepare VSCodium ...")
-      Isabelle_System.with_copy_dir(vscode_dir, vscode_dir.orig) {
-        progress.bash(
-          List(
-            "set -e",
-            platform_info.environment,
-            "./prepare_vscode.sh",
-            // enforce binary diff of code.xpm
-            "cp vscode/resources/linux/code.png vscode/resources/linux/rpm/code.xpm"
-          ).mkString("\n"), cwd = vscodium_dir.file, echo = verbose).check
-        Isabelle_System.make_patch(vscodium_dir, vscode_dir.orig.base, vscode_dir.base,
-          diff_options = "--exclude=.git --exclude=node_modules")
-      }
-    })
-  }
-
-
-  /* build vscodium */
-
-  def default_platforms: List[Platform.Family.Value] = Platform.Family.list
-
-  def macos_exe: String =
-"""#!/usr/bin/env bash
-
-unset CDPATH
-VSCODE_PATH="$(cd "$(dirname "$0")"/../VSCodium.app/Contents; pwd)"
-
-ELECTRON="$VSCODE_PATH/MacOS/Electron"
-CLI="$VSCODE_PATH/Resources/app/out/cli.js"
-ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" --ms-enable-electron-run-as-node "$@"
-exit $?
-"""
-
-  def build_vscodium(
-    target_dir: Path = Path.current,
-    platforms: List[Platform.Family.Value] = default_platforms,
-    verbose: Boolean = false,
-    progress: Progress = new Progress): Unit =
-  {
-    check_system(platforms)
-
-
-    /* component */
-
-    val component_name = "vscodium-" + version
-    val component_dir = Isabelle_System.new_directory(target_dir + Path.basic(component_name))
-    progress.echo("Component " + component_dir)
-
-
-    /* patches */
-
-    progress.echo("Building patches:")
-
-    val patches_dir = Isabelle_System.new_directory(component_dir + Path.basic("patches"))
-
-    def write_patch(name: String, patch: String): Unit =
-      File.write(patches_dir + Path.explode(name).patch, patch)
-
-    write_patch("01-vscodium", vscodium_patch(verbose = verbose, progress = progress))
-
-
-    /* build */
-
-    for (platform <- platforms) yield {
-      val platform_info = the_platform_info(platform)
-
-      Isabelle_System.with_tmp_dir("vscodium")(vscodium_dir =>
-      {
-        progress.echo("Building " + platform + ":")
-
-        platform_info.get_vscodium_repository(vscodium_dir, progress = progress)
-
-        val sources_patch = platform_info.patch_sources(vscodium_dir)
-        if (platform_info.is_linux) write_patch("02-isabelle_sources", sources_patch)
-
-        progress.echo("Build VSCodium ...")
-        progress.bash(platform_info.environment + "\n" + "./build.sh",
-          cwd = vscodium_dir.file, echo = verbose).check
-
-        if (platform_info.is_linux) {
-          Isabelle_System.copy_file(vscodium_dir + Path.explode("LICENSE"), component_dir)
-        }
-
-        val platform_dir = platform_info.platform_dir(component_dir)
-        Isabelle_System.copy_dir(platform_info.build_dir(vscodium_dir), platform_dir)
-        platform_info.node_binaries(platform_dir, progress)
-
-        val resources_patch = platform_info.patch_resources(platform_dir)
-        if (platform_info.is_linux) write_patch("03-isabelle_resources", resources_patch)
-
-        platform_info.setup_executables(platform_dir)
-      })
-    }
-
-    Isabelle_System.bash("gzip *.patch", cwd = patches_dir.file).check
-
-
-    /* settings */
-
-    val etc_dir = Isabelle_System.make_directory(component_dir + Path.explode("etc"))
-    File.write(etc_dir + Path.basic("settings"),
-      """# -*- shell-script -*- :mode=shellscript:
-
-ISABELLE_VSCODIUM_HOME="$COMPONENT/${ISABELLE_WINDOWS_PLATFORM64:-$ISABELLE_PLATFORM64}"
-
-case "$ISABELLE_PLATFORM_FAMILY" in
-  linux)
-    ISABELLE_ELECTRON="$ISABELLE_VSCODIUM_HOME/codium"
-    ;;
-  macos)
-    ISABELLE_ELECTRON="$ISABELLE_VSCODIUM_HOME/VSCodium.app/Contents/MacOS/Electron"
-    ;;
-  windows)
-    ISABELLE_ELECTRON="$ISABELLE_VSCODIUM_HOME/VSCodium.exe"
-    ;;
-esac
-""")
-
-
-    /* README */
-
-    File.write(component_dir + Path.basic("README"),
-      "This is VSCodium " + version + " from " + vscodium_repository +
-"""
-
-It has been built from sources using "isabelle build_vscodium". This applies
-a few changes required for Isabelle/VSCode, see "patches" directory for a
-formal record.
-
-
-        Makarius
-        """ + Date.Format.date(Date.now()) + "\n")
-  }
-
-
-  /* Isabelle tool wrapper */
-
-  val isabelle_tool =
-    Isabelle_Tool("build_vscodium", "build component for VSCodium",
-      Scala_Project.here, args =>
-    {
-      var target_dir = Path.current
-      var platforms = default_platforms
-      var verbose = false
-
-      val getopts = Getopts("""
-Usage: vscode_setup [OPTIONS]
-
-  Options are:
-    -D DIR       target directory (default ".")
-    -p NAMES     platform families (default: """ + quote(platforms.mkString(",")) + """)
-    -v           verbose
-
-  Build VSCodium from sources and turn it into an Isabelle component.
-
-  The build platform needs to be Linux with nodejs/yarn, jq, and wine
-  for targeting Windows.
-""",
-        "D:" -> (arg => target_dir = Path.explode(arg)),
-        "p:" -> (arg => platforms = Library.space_explode(',', arg).map(Platform.Family.parse)),
-        "v" -> (_ => verbose = true))
-
-      val more_args = getopts(args)
-      if (more_args.nonEmpty) getopts.usage()
-
-      val progress = new Console_Progress()
-
-      build_vscodium(target_dir = target_dir, platforms = platforms,
-        verbose = verbose, progress = progress)
-    })
-}
--- a/src/Pure/System/isabelle_tool.scala	Tue Mar 08 17:02:24 2022 +0100
+++ b/src/Pure/System/isabelle_tool.scala	Tue Mar 08 17:09:09 2022 +0100
@@ -231,9 +231,9 @@
   Build_Status.isabelle_tool,
   Build_Vampire.isabelle_tool,
   Build_VeriT.isabelle_tool,
-  Build_VSCodium.isabelle_tool,
   Build_Zipperposition.isabelle_tool,
   Check_Sources.isabelle_tool,
   Components.isabelle_tool,
   isabelle.vscode.Build_VSCode.isabelle_tool,
+  isabelle.vscode.Build_VSCodium.isabelle_tool,
   isabelle.vscode.VSCode_Setup.isabelle_tool)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Tools/VSCode/src/build_vscodium.scala	Tue Mar 08 17:09:09 2022 +0100
@@ -0,0 +1,405 @@
+/*  Title:      Tools/VSCode/src/build_vscodium.scala
+    Author:     Makarius
+
+Build component for VSCodium (cross-compiled from sources for all platforms).
+*/
+
+package isabelle.vscode
+
+
+import isabelle._
+
+import java.util.{Map => JMap}
+import java.security.MessageDigest
+import java.util.Base64
+
+
+object Build_VSCodium
+{
+  /* global parameters */
+
+  lazy val version: String = Isabelle_System.getenv_strict("ISABELLE_VSCODE_VERSION")
+  val vscodium_repository = "https://github.com/VSCodium/vscodium.git"
+  val vscodium_download = "https://github.com/VSCodium/vscodium/releases/download"
+
+  def vscodium_exe(dir: Path): Path = dir + Path.explode("bin/codium")
+
+
+  /* platform info */
+
+  sealed case class Platform_Info(
+    platform: Platform.Family.Value,
+    download_template: String,
+    build_name: String,
+    env: List[String])
+  {
+    def is_linux: Boolean = platform == Platform.Family.linux
+
+    def download_name: String = "VSCodium-" + download_template.replace("{VERSION}", version)
+    def download_zip: Boolean = download_name.endsWith(".zip")
+
+    def download(dir: Path, progress: Progress = new Progress): Unit =
+    {
+      if (download_zip) Isabelle_System.require_command("unzip", test = "-h")
+
+      Isabelle_System.with_tmp_file("download")(download_file =>
+      {
+        Isabelle_System.download_file(vscodium_download + "/" + version + "/" + download_name,
+          download_file, progress = progress)
+
+        progress.echo("Extract ...")
+        if (download_zip) {
+          Isabelle_System.bash("unzip -x " + File.bash_path(download_file), cwd = dir.file).check
+        }
+        else {
+          Isabelle_System.gnutar("-xzf " + File.bash_path(download_file), dir = dir).check
+        }
+      })
+    }
+
+    def get_vscodium_repository(vscodium_dir: Path, progress: Progress = new Progress): Unit =
+    {
+      progress.echo("Getting VSCodium repository ...")
+      Isabelle_System.bash(
+        List(
+          "set -e",
+          "git clone -n " + Bash.string(vscodium_repository) + " .",
+          "git checkout -q " + Bash.string(version)
+        ).mkString("\n"), cwd = vscodium_dir.file).check
+
+      progress.echo("Getting VSCode repository ...")
+      Isabelle_System.bash(environment + "\n" + "./get_repo.sh", cwd = vscodium_dir.file).check
+    }
+
+    def platform_dir(dir: Path): Path =
+    {
+      val platform_name =
+        if (platform == Platform.Family.windows) Platform.Family.native(platform)
+        else Platform.Family.standard(platform)
+      dir + Path.explode(platform_name)
+    }
+
+    def build_dir(dir: Path): Path = dir + Path.explode(build_name)
+
+    def environment: String =
+      (("MS_TAG=" + Bash.string(version)) :: "SHOULD_BUILD=yes" :: "VSCODE_ARCH=x64" :: env)
+        .map(s => "export " + s + "\n").mkString
+
+    def resources_dir(dir: Path): Path =
+    {
+      val resources =
+        if (platform == Platform.Family.macos) "VSCodium.app/Contents/Resources"
+        else "resources"
+      dir + Path.explode(resources)
+    }
+
+    def patch_sources(base_dir: Path): String =
+    {
+      val dir = base_dir + Path.explode("vscode")
+      Isabelle_System.with_copy_dir(dir, dir.orig) {
+        for (name <- Seq("build/lib/electron.js", "build/lib/electron.ts")) {
+          File.change(dir + Path.explode(name), strict = true) {
+            _.replace("""'resources/darwin/' + icon + '.icns'""",
+              """'resources/darwin/' + icon.toLowerCase() + '.icns'""")
+          }
+        }
+        Isabelle_System.make_patch(base_dir, dir.base.orig, dir.base)
+      }
+    }
+
+    def patch_resources(base_dir: Path): String =
+    {
+      val dir = resources_dir(base_dir)
+      Isabelle_System.with_copy_dir(dir, dir.orig) {
+        HTML.init_fonts(dir + Path.explode("app/out/vs/base/browser/ui"))
+
+        val workbench_css = dir + Path.explode("app/out/vs/workbench/workbench.desktop.main.css")
+        val checksum1 = file_checksum(workbench_css)
+        File.append(workbench_css, "\n\n" + HTML.fonts_css_dir(prefix = "../base/browser/ui"))
+        val checksum2 = file_checksum(workbench_css)
+
+        val file_name = workbench_css.file_name
+        File.change_lines(dir + Path.explode("app/product.json")) { _.map(line =>
+          if (line.containsSlice(file_name) && line.contains(checksum1)) {
+            line.replace(checksum1, checksum2)
+          }
+          else line)
+        }
+
+        Isabelle_System.make_patch(dir.dir, dir.orig.base, dir.base)
+      }
+    }
+
+    def node_binaries(dir: Path, progress: Progress): Unit =
+    {
+      Isabelle_System.with_tmp_dir("download")(download_dir =>
+      {
+        download(download_dir, progress = progress)
+        for (name <- Seq("app/node_modules.asar", "app/node_modules.asar.unpacked")) {
+          val rel_path = resources_dir(Path.current) + Path.explode(name)
+          Isabelle_System.rm_tree(dir + rel_path)
+          Isabelle_System.copy_dir(download_dir + rel_path, dir + rel_path)
+        }
+      })
+    }
+
+    def setup_executables(dir: Path): Unit =
+    {
+      val exe = vscodium_exe(dir)
+      Isabelle_System.make_directory(exe.dir)
+
+      platform match {
+        case Platform.Family.macos =>
+          File.write(exe, macos_exe)
+          File.set_executable(exe, true)
+        case Platform.Family.windows =>
+          val files1 = File.find_files(exe.dir.file)
+          val files2 = File.find_files(dir.file, pred = file =>
+            {
+              val name = file.getName
+              name.endsWith(".dll") || name.endsWith(".exe") || name.endsWith(".node")
+            })
+          for (file <- files1 ::: files2) File.set_executable(File.path(file), true)
+          Isabelle_System.bash("chmod -R o-w " + File.bash_path(dir)).check
+        case _ =>
+      }
+    }
+  }
+
+  // see https://github.com/microsoft/vscode/blob/main/build/gulpfile.vscode.js
+  // function computeChecksum(filename)
+  private def file_checksum(path: Path): String =
+  {
+    val digest = MessageDigest.getInstance("MD5")
+    digest.update(Bytes.read(path).array)
+    Bytes(Base64.getEncoder.encode(digest.digest()))
+      .text.replaceAll("=", "")
+  }
+
+  private val platform_infos: Map[Platform.Family.Value, Platform_Info] =
+    Iterator(
+      Platform_Info(Platform.Family.linux, "linux-x64-{VERSION}.tar.gz", "VSCode-linux-x64",
+        List("OS_NAME=linux", "SKIP_LINUX_PACKAGES=True")),
+      Platform_Info(Platform.Family.linux_arm, "linux-arm64-{VERSION}.tar.gz", "VSCode-linux-arm64",
+        List("OS_NAME=linux", "SKIP_LINUX_PACKAGES=True", "VSCODE_ARCH=arm64")),
+      Platform_Info(Platform.Family.macos, "darwin-x64-{VERSION}.zip", "VSCode-darwin-x64",
+        List("OS_NAME=osx")),
+      Platform_Info(Platform.Family.windows, "win32-x64-{VERSION}.zip", "VSCode-win32-x64",
+        List("OS_NAME=windows",
+          "SHOULD_BUILD_ZIP=no",
+          "SHOULD_BUILD_EXE_SYS=no",
+          "SHOULD_BUILD_EXE_USR=no",
+          "SHOULD_BUILD_MSI=no",
+          "SHOULD_BUILD_MSI_NOUP=no")))
+      .map(info => info.platform -> info).toMap
+
+  def the_platform_info(platform: Platform.Family.Value): Platform_Info =
+    platform_infos.getOrElse(platform, error("No platform info for " + quote(platform.toString)))
+
+  def linux_platform_info: Platform_Info =
+    the_platform_info(Platform.Family.linux)
+
+
+  /* check system */
+
+  def check_system(platforms: List[Platform.Family.Value]): Unit =
+  {
+    if (Platform.family != Platform.Family.linux) error("Not a Linux/x86_64 system")
+
+    Isabelle_System.require_command("git")
+    Isabelle_System.require_command("node")
+    Isabelle_System.require_command("yarn")
+    Isabelle_System.require_command("jq")
+
+    if (platforms.contains(Platform.Family.windows)) {
+      Isabelle_System.require_command("wine")
+    }
+
+    if (platforms.exists(platform => the_platform_info(platform).download_zip)) {
+      Isabelle_System.require_command("unzip", test = "-h")
+    }
+  }
+
+
+  /* original repository clones and patches */
+
+  def vscodium_patch(verbose: Boolean = false, progress: Progress = new Progress): String =
+  {
+    val platform_info = linux_platform_info
+    check_system(List(platform_info.platform))
+
+    Isabelle_System.with_tmp_dir("vscodium")(vscodium_dir =>
+    {
+      platform_info.get_vscodium_repository(vscodium_dir, progress = progress)
+      val vscode_dir = vscodium_dir + Path.basic("vscode")
+      progress.echo("Prepare VSCodium ...")
+      Isabelle_System.with_copy_dir(vscode_dir, vscode_dir.orig) {
+        progress.bash(
+          List(
+            "set -e",
+            platform_info.environment,
+            "./prepare_vscode.sh",
+            // enforce binary diff of code.xpm
+            "cp vscode/resources/linux/code.png vscode/resources/linux/rpm/code.xpm"
+          ).mkString("\n"), cwd = vscodium_dir.file, echo = verbose).check
+        Isabelle_System.make_patch(vscodium_dir, vscode_dir.orig.base, vscode_dir.base,
+          diff_options = "--exclude=.git --exclude=node_modules")
+      }
+    })
+  }
+
+
+  /* build vscodium */
+
+  def default_platforms: List[Platform.Family.Value] = Platform.Family.list
+
+  def macos_exe: String =
+"""#!/usr/bin/env bash
+
+unset CDPATH
+VSCODE_PATH="$(cd "$(dirname "$0")"/../VSCodium.app/Contents; pwd)"
+
+ELECTRON="$VSCODE_PATH/MacOS/Electron"
+CLI="$VSCODE_PATH/Resources/app/out/cli.js"
+ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" --ms-enable-electron-run-as-node "$@"
+exit $?
+"""
+
+  def build_vscodium(
+    target_dir: Path = Path.current,
+    platforms: List[Platform.Family.Value] = default_platforms,
+    verbose: Boolean = false,
+    progress: Progress = new Progress): Unit =
+  {
+    check_system(platforms)
+
+
+    /* component */
+
+    val component_name = "vscodium-" + version
+    val component_dir = Isabelle_System.new_directory(target_dir + Path.basic(component_name))
+    progress.echo("Component " + component_dir)
+
+
+    /* patches */
+
+    progress.echo("Building patches:")
+
+    val patches_dir = Isabelle_System.new_directory(component_dir + Path.basic("patches"))
+
+    def write_patch(name: String, patch: String): Unit =
+      File.write(patches_dir + Path.explode(name).patch, patch)
+
+    write_patch("01-vscodium", vscodium_patch(verbose = verbose, progress = progress))
+
+
+    /* build */
+
+    for (platform <- platforms) yield {
+      val platform_info = the_platform_info(platform)
+
+      Isabelle_System.with_tmp_dir("vscodium")(vscodium_dir =>
+      {
+        progress.echo("Building " + platform + ":")
+
+        platform_info.get_vscodium_repository(vscodium_dir, progress = progress)
+
+        val sources_patch = platform_info.patch_sources(vscodium_dir)
+        if (platform_info.is_linux) write_patch("02-isabelle_sources", sources_patch)
+
+        progress.echo("Build VSCodium ...")
+        progress.bash(platform_info.environment + "\n" + "./build.sh",
+          cwd = vscodium_dir.file, echo = verbose).check
+
+        if (platform_info.is_linux) {
+          Isabelle_System.copy_file(vscodium_dir + Path.explode("LICENSE"), component_dir)
+        }
+
+        val platform_dir = platform_info.platform_dir(component_dir)
+        Isabelle_System.copy_dir(platform_info.build_dir(vscodium_dir), platform_dir)
+        platform_info.node_binaries(platform_dir, progress)
+
+        val resources_patch = platform_info.patch_resources(platform_dir)
+        if (platform_info.is_linux) write_patch("03-isabelle_resources", resources_patch)
+
+        platform_info.setup_executables(platform_dir)
+      })
+    }
+
+    Isabelle_System.bash("gzip *.patch", cwd = patches_dir.file).check
+
+
+    /* settings */
+
+    val etc_dir = Isabelle_System.make_directory(component_dir + Path.explode("etc"))
+    File.write(etc_dir + Path.basic("settings"),
+      """# -*- shell-script -*- :mode=shellscript:
+
+ISABELLE_VSCODIUM_HOME="$COMPONENT/${ISABELLE_WINDOWS_PLATFORM64:-$ISABELLE_PLATFORM64}"
+
+case "$ISABELLE_PLATFORM_FAMILY" in
+  linux)
+    ISABELLE_ELECTRON="$ISABELLE_VSCODIUM_HOME/codium"
+    ;;
+  macos)
+    ISABELLE_ELECTRON="$ISABELLE_VSCODIUM_HOME/VSCodium.app/Contents/MacOS/Electron"
+    ;;
+  windows)
+    ISABELLE_ELECTRON="$ISABELLE_VSCODIUM_HOME/VSCodium.exe"
+    ;;
+esac
+""")
+
+
+    /* README */
+
+    File.write(component_dir + Path.basic("README"),
+      "This is VSCodium " + version + " from " + vscodium_repository +
+"""
+
+It has been built from sources using "isabelle build_vscodium". This applies
+a few changes required for Isabelle/VSCode, see "patches" directory for a
+formal record.
+
+
+        Makarius
+        """ + Date.Format.date(Date.now()) + "\n")
+  }
+
+
+  /* Isabelle tool wrapper */
+
+  val isabelle_tool =
+    Isabelle_Tool("build_vscodium", "build component for VSCodium",
+      Scala_Project.here, args =>
+    {
+      var target_dir = Path.current
+      var platforms = default_platforms
+      var verbose = false
+
+      val getopts = Getopts("""
+Usage: vscode_setup [OPTIONS]
+
+  Options are:
+    -D DIR       target directory (default ".")
+    -p NAMES     platform families (default: """ + quote(platforms.mkString(",")) + """)
+    -v           verbose
+
+  Build VSCodium from sources and turn it into an Isabelle component.
+
+  The build platform needs to be Linux with nodejs/yarn, jq, and wine
+  for targeting Windows.
+""",
+        "D:" -> (arg => target_dir = Path.explode(arg)),
+        "p:" -> (arg => platforms = Library.space_explode(',', arg).map(Platform.Family.parse)),
+        "v" -> (_ => verbose = true))
+
+      val more_args = getopts(args)
+      if (more_args.nonEmpty) getopts.usage()
+
+      val progress = new Console_Progress()
+
+      build_vscodium(target_dir = target_dir, platforms = platforms,
+        verbose = verbose, progress = progress)
+    })
+}