clarified signature: more explicit types;
authorwenzelm
Sun, 20 Nov 2022 23:37:54 +0100
changeset 76518 b30b8e23383c
parent 76517 b67c9ed2c810
child 76519 137cec33346f
clarified signature: more explicit types;
src/Pure/Admin/build_csdp.scala
src/Pure/Admin/build_cvc5.scala
src/Pure/Admin/build_e.scala
src/Pure/Admin/build_easychair.scala
src/Pure/Admin/build_eptcs.scala
src/Pure/Admin/build_foiltex.scala
src/Pure/Admin/build_fonts.scala
src/Pure/Admin/build_jcef.scala
src/Pure/Admin/build_jdk.scala
src/Pure/Admin/build_jedit.scala
src/Pure/Admin/build_lipics.scala
src/Pure/Admin/build_llncs.scala
src/Pure/Admin/build_minisat.scala
src/Pure/Admin/build_pdfjs.scala
src/Pure/Admin/build_polyml.scala
src/Pure/Admin/build_postgresql.scala
src/Pure/Admin/build_prismjs.scala
src/Pure/Admin/build_scala.scala
src/Pure/Admin/build_spass.scala
src/Pure/Admin/build_sqlite.scala
src/Pure/Admin/build_vampire.scala
src/Pure/Admin/build_verit.scala
src/Pure/Admin/build_zipperposition.scala
src/Pure/Admin/build_zstd.scala
src/Pure/System/components.scala
src/Pure/Tools/dotnet_setup.scala
src/Tools/VSCode/src/build_vscode_extension.scala
src/Tools/VSCode/src/build_vscodium.scala
--- a/src/Pure/Admin/build_csdp.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Pure/Admin/build_csdp.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -75,8 +75,8 @@
         }
 
       val component_name = "csdp-" + version
-      val component_dir = Isabelle_System.new_directory(target_dir + Path.basic(component_name))
-      progress.echo("Component " + component_dir)
+      val component_dir =
+        Components.Directory.create(target_dir + Path.basic(component_name), progress = progress)
 
 
       /* platform */
@@ -86,7 +86,8 @@
         proper_string(Isabelle_System.getenv("ISABELLE_PLATFORM64")) getOrElse
         error("No 64bit platform")
 
-      val platform_dir = Isabelle_System.make_directory(component_dir + Path.basic(platform_name))
+      val platform_dir =
+        Isabelle_System.make_directory(component_dir.path + Path.basic(platform_name))
 
 
       /* download source */
@@ -99,7 +100,7 @@
 
       Isabelle_System.bash(
         "tar xzf " + archive_path + " && mv " + Bash.string(source_name) + " src",
-        cwd = component_dir.file).check
+        cwd = component_dir.path.file).check
 
 
       /* build */
@@ -119,7 +120,7 @@
 
       /* install */
 
-      Isabelle_System.copy_file(build_dir + Path.explode("LICENSE"), component_dir)
+      Isabelle_System.copy_file(build_dir + Path.explode("LICENSE"), component_dir.path)
       Isabelle_System.copy_file(build_dir + Path.explode("solver/csdp").platform_exe, platform_dir)
 
       if (Platform.is_windows) {
@@ -132,8 +133,7 @@
 
       /* settings */
 
-      val etc_dir = Isabelle_System.make_directory(component_dir + Path.basic("etc"))
-      File.write(etc_dir + Path.basic("settings"),
+      File.write(component_dir.settings,
         """# -*- shell-script -*- :mode=shellscript:
 
 ISABELLE_CSDP="$COMPONENT/${ISABELLE_WINDOWS_PLATFORM64:-$ISABELLE_PLATFORM64}/csdp"
@@ -142,7 +142,7 @@
 
       /* README */
 
-      File.write(component_dir + Path.basic("README"),
+      File.write(component_dir.README,
 """This is CSDP """ + version + """ from
 """ + download_url + """
 
--- a/src/Pure/Admin/build_cvc5.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Pure/Admin/build_cvc5.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -39,8 +39,8 @@
     /* component name */
 
     val component = "cvc5-" + version
-    val component_dir = Isabelle_System.new_directory(target_dir + Path.basic(component))
-    progress.echo("Component " + component_dir)
+    val component_dir =
+      Components.Directory.create(target_dir + Path.basic(component), progress = progress)
 
 
     /* download executables */
@@ -48,7 +48,7 @@
     for (platform <- platforms) {
       val url = base_url + "/cvc5-" + version + "/" + platform.download_name
 
-      val platform_dir = component_dir + Path.explode(platform.platform_name)
+      val platform_dir = component_dir.path + Path.explode(platform.platform_name)
       val platform_exe = platform_dir + Path.explode("cvc5").exe_if(platform.is_windows)
 
       Isabelle_System.make_directory(platform_dir)
@@ -59,8 +59,7 @@
 
     /* settings */
 
-    val etc_dir = Isabelle_System.make_directory(component_dir + Path.basic("etc"))
-    File.write(etc_dir + Path.basic("settings"),
+    File.write(component_dir.settings,
       """# -*- shell-script -*- :mode=shellscript:
 
 CVC5_HOME="$COMPONENT/${ISABELLE_WINDOWS_PLATFORM64:-${ISABELLE_APPLE_PLATFORM64:-$ISABELLE_PLATFORM64}}"
@@ -77,7 +76,7 @@
 
     /* README */
 
-    File.write(component_dir + Path.basic("README"),
+    File.write(component_dir.README,
       """This distribution of cvc5 was assembled from the official downloads
 from """ + base_url + """ for 64bit macOS,
 Linux, and Windows. There is native support for macOS ARM64, but
@@ -97,7 +96,7 @@
     // download "latest" versions as reasonable approximation
     def raw_download(name: String): Unit =
       Isabelle_System.download_file("https://raw.githubusercontent.com/cvc5/cvc5/main/" + name,
-        component_dir + Path.explode(name))
+        component_dir.path + Path.explode(name))
 
     raw_download("AUTHORS")
     raw_download("COPYING")
--- a/src/Pure/Admin/build_e.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Pure/Admin/build_e.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -24,14 +24,15 @@
       /* component */
 
       val component_name = "e-" + version
-      val component_dir = Isabelle_System.new_directory(target_dir + Path.basic(component_name))
-      progress.echo("Component " + component_dir)
+      val component_dir =
+        Components.Directory.create(target_dir + Path.basic(component_name), progress = progress)
 
       val platform_name =
         proper_string(Isabelle_System.getenv("ISABELLE_PLATFORM64"))
           .getOrElse(error("No 64bit platform"))
 
-      val platform_dir = Isabelle_System.make_directory(component_dir + Path.basic(platform_name))
+      val platform_dir =
+        Isabelle_System.make_directory(component_dir.path + Path.basic(platform_name))
 
 
       /* download source */
@@ -41,7 +42,8 @@
       Isabelle_System.download_file(archive_url, archive_path, progress = progress)
 
       Isabelle_System.bash("tar xzf " + archive_path, cwd = tmp_dir.file).check
-      Isabelle_System.bash("tar xzf " + archive_path + " && mv E src", cwd = component_dir.file).check
+      Isabelle_System.bash("tar xzf " + archive_path + " && mv E src",
+        cwd = component_dir.path.file).check
 
 
       /* build */
@@ -63,8 +65,7 @@
 
       /* install */
 
-      Isabelle_System.copy_file(build_dir + Path.basic("COPYING"),
-        component_dir + Path.basic("LICENSE"))
+      Isabelle_System.copy_file(build_dir + Path.basic("COPYING"), component_dir.LICENSE)
 
       val install_files = List("epclextract", "eprover", "eprover-ho")
       for (name <- install_files ::: install_files.map(_ + ".exe")) {
@@ -77,8 +78,7 @@
 
       /* settings */
 
-      val etc_dir = Isabelle_System.make_directory(component_dir + Path.basic("etc"))
-      File.write(etc_dir + Path.basic("settings"),
+      File.write(component_dir.settings,
         """# -*- shell-script -*- :mode=shellscript:
 
 E_HOME="$COMPONENT/$ISABELLE_PLATFORM64"
@@ -87,7 +87,7 @@
 
       /* README */
 
-      File.write(component_dir + Path.basic("README"),
+      File.write(component_dir.README,
         "This is E prover " + version + " from\n" + archive_url + """
 
 The distribution has been built like this:
--- a/src/Pure/Admin/build_easychair.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Pure/Admin/build_easychair.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -46,17 +46,17 @@
             .getOrElse("Failed to detect version from " + quote(easychair_dir.file_name))
 
         val component = "easychair-" + version
-        val component_dir = Isabelle_System.new_directory(target_dir + Path.basic(component))
-        progress.echo("Component " + component_dir)
+        val component_dir =
+          Components.Directory.create(target_dir + Path.basic(component), progress = progress)
 
-        component_dir.file.delete
-        Isabelle_System.copy_dir(easychair_dir, component_dir)
+        Isabelle_System.rm_tree(component_dir.path)
+        Isabelle_System.copy_dir(easychair_dir, component_dir.path)
+        Isabelle_System.make_directory(component_dir.etc)
 
 
         /* settings */
 
-        val etc_dir = Isabelle_System.make_directory(component_dir + Path.basic("etc"))
-        File.write(etc_dir + Path.basic("settings"),
+        File.write(component_dir.settings,
           """# -*- shell-script -*- :mode=shellscript:
 
 ISABELLE_EASYCHAIR_HOME="$COMPONENT"
@@ -65,7 +65,7 @@
 
         /* README */
 
-        File.write(component_dir + Path.basic("README"),
+        File.write(component_dir.README,
           """This is the Easychair style for authors from
 """ + download_url + """
 
--- a/src/Pure/Admin/build_eptcs.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Pure/Admin/build_eptcs.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -29,8 +29,8 @@
     /* component */
 
     val component = "eptcs-" + version
-    val component_dir = Isabelle_System.new_directory(target_dir + Path.basic(component))
-    progress.echo("Component " + component_dir)
+    val component_dir =
+      Components.Directory.create(target_dir + Path.basic(component), progress = progress)
 
 
     /* download */
@@ -40,14 +40,13 @@
     Isabelle_System.with_tmp_file("download", ext = "zip") { download_file =>
       Isabelle_System.download_file(download_url, download_file, progress = progress)
       Isabelle_System.bash("unzip -x " + File.bash_path(download_file),
-        cwd = component_dir.file).check
+        cwd = component_dir.path.file).check
     }
 
 
     /* settings */
 
-    val etc_dir = Isabelle_System.make_directory(component_dir + Path.basic("etc"))
-    File.write(etc_dir + Path.basic("settings"),
+    File.write(component_dir.settings,
       """# -*- shell-script -*- :mode=shellscript:
 
 ISABELLE_EPTCS_HOME="$COMPONENT"
@@ -56,7 +55,7 @@
 
     /* README */
 
-    File.write(component_dir + Path.basic("README"),
+    File.write(component_dir.README,
       """This is the EPTCS style from
 """ + download_url + """
 
--- a/src/Pure/Admin/build_foiltex.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Pure/Admin/build_foiltex.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -55,17 +55,17 @@
         }
 
         val component = "foiltex-" + version
-        val component_dir = Isabelle_System.new_directory(target_dir + Path.basic(component))
-        progress.echo("Component " + component_dir)
+        val component_dir =
+          Components.Directory.create(target_dir + Path.basic(component), progress = progress)
 
-        component_dir.file.delete
-        Isabelle_System.copy_dir(foiltex_dir, component_dir)
+        Isabelle_System.rm_tree(component_dir.path)
+        Isabelle_System.copy_dir(foiltex_dir, component_dir.path)
+        Isabelle_System.make_directory(component_dir.etc)
 
 
         /* settings */
 
-        val etc_dir = Isabelle_System.make_directory(component_dir + Path.basic("etc"))
-        File.write(etc_dir + Path.basic("settings"),
+        File.write(component_dir.settings,
           """# -*- shell-script -*- :mode=shellscript:
 
 ISABELLE_FOILTEX_HOME="$COMPONENT"
@@ -74,7 +74,7 @@
 
         /* README */
 
-        File.write(component_dir + Path.basic("README"),
+        File.write(component_dir.README,
           """This is FoilTeX from
 """ + download_url + """
 
--- a/src/Pure/Admin/build_fonts.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Pure/Admin/build_fonts.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -307,7 +307,7 @@
 
     // etc/settings
 
-    val settings_path = Components.settings(target_dir)
+    val settings_path = Components.Directory(target_dir).settings
     Isabelle_System.make_directory(settings_path.dir)
 
     def fonts_settings(hinted: Boolean): String =
--- a/src/Pure/Admin/build_jcef.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Pure/Admin/build_jcef.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -52,8 +52,8 @@
     /* component name */
 
     val component = "jcef-" + version
-    val component_dir = Isabelle_System.new_directory(target_dir + Path.basic(component))
-    progress.echo("Component " + component_dir)
+    val component_dir =
+      Components.Directory.create(target_dir + Path.basic(component), progress = progress)
 
 
     /* download and assemble platforms */
@@ -64,7 +64,7 @@
           val url = base_url + "/" + version + "/" + platform.archive
           Isabelle_System.download_file(url, archive_file, progress = progress)
 
-          val platform_dir = component_dir + Path.explode(platform.platform_name)
+          val platform_dir = component_dir.path + Path.explode(platform.platform_name)
           Isabelle_System.make_directory(platform_dir)
           Isabelle_System.gnutar("-xzf " + File.bash_path(archive_file), dir = platform_dir).check
 
@@ -91,8 +91,7 @@
 
     /* settings */
 
-    val etc_dir = Isabelle_System.make_directory(component_dir + Path.basic("etc"))
-    File.write(etc_dir + Path.basic("settings"),
+    File.write(component_dir.settings,
       """# -*- shell-script -*- :mode=shellscript:
 
 ISABELLE_JCEF_PLATFORM="${ISABELLE_WINDOWS_PLATFORM64:-${ISABELLE_APPLE_PLATFORM64:-$ISABELLE_PLATFORM64}}"
@@ -109,7 +108,7 @@
 
     /* README */
 
-    File.write(component_dir + Path.basic("README"),
+    File.write(component_dir.README,
       """This distribution of Java Chromium Embedded Framework (JCEF)
 has been assembled from the binary builds from
 https://github.com/jcefmaven/jcefbuild/releases/tag/""" +version + """
--- a/src/Pure/Admin/build_jdk.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Pure/Admin/build_jdk.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -172,17 +172,16 @@
 
       val jdk_name = "jdk-" + jdk_version
       val jdk_path = Path.explode(jdk_name)
-      val component_dir = dir + jdk_path
+      val component_dir = Components.Directory.create(dir + jdk_path, progress = progress)
 
-      Isabelle_System.make_directory(component_dir + Path.explode("etc"))
-      File.write(Components.settings(component_dir), settings)
-      File.write(component_dir + Path.explode("README"), readme(jdk_version))
+      File.write(component_dir.settings, settings)
+      File.write(component_dir.README, readme(jdk_version))
 
       for (platform <- platforms) {
-        Isabelle_System.move_file(dir + platform.platform_path, component_dir)
+        Isabelle_System.move_file(dir + platform.platform_path, component_dir.path)
       }
 
-      for (file <- File.find_files(component_dir.file, include_dirs = true)) {
+      for (file <- File.find_files(component_dir.path.file, include_dirs = true)) {
         val path = file.toPath
         val perms = Files.getPosixFilePermissions(path)
         perms.add(PosixFilePermission.OWNER_READ)
--- a/src/Pure/Admin/build_jedit.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Pure/Admin/build_jedit.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -103,7 +103,7 @@
     name == "installer"
 
   def build_jedit(
-    component_dir: Path,
+    component_path: Path,
     version: String,
     original: Boolean = false,
     java_home: Path = default_java_home,
@@ -113,9 +113,7 @@
     Isabelle_System.require_command("patch")
     Isabelle_System.require_command("unzip", test = "-h")
 
-    Isabelle_System.new_directory(component_dir)
-
-    val etc_dir = Isabelle_System.make_directory(component_dir + Path.explode("etc"))
+    val component_dir = Components.Directory.create(component_path, progress = progress)
 
 
     /* jEdit directory */
@@ -123,8 +121,8 @@
     val jedit = "jedit" + version
     val jedit_patched = jedit + "-patched"
 
-    val jedit_dir = Isabelle_System.make_directory(component_dir + Path.basic(jedit))
-    val jedit_patched_dir = component_dir + Path.basic(jedit_patched)
+    val jedit_dir = Isabelle_System.make_directory(component_path + Path.basic(jedit))
+    val jedit_patched_dir = component_path + Path.basic(jedit_patched)
 
     def download_jedit(dir: Path, name: String, target_name: String = ""): Path = {
       val jedit_name = jedit + name
@@ -184,9 +182,9 @@
           file <- File.find_files(org_source_dir.file, file => File.is_java(file.getName))
           package_name <- Scala_Project.package_name(File.path(file))
           if !exclude_package(package_name)
-        } yield File.path(component_dir.java_path.relativize(file.toPath).toFile).implode
+        } yield File.path(component_path.java_path.relativize(file.toPath).toFile).implode
 
-      File.write(etc_dir + Path.explode("build.props"),
+      File.write(component_dir.build_props,
         "module = " + jedit_patched + "/jedit.jar\n" +
         "no_build = true\n" +
         "requirements = env:JEDIT_JARS\n" +
@@ -464,15 +462,15 @@
 
     /* make patch */
 
-    File.write(component_dir + Path.basic(jedit).patch,
-      Isabelle_System.make_patch(component_dir, Path.basic(jedit), Path.basic(jedit_patched)))
+    File.write(component_path + Path.basic(jedit).patch,
+      Isabelle_System.make_patch(component_path, Path.basic(jedit), Path.basic(jedit_patched)))
 
     if (!original) Isabelle_System.rm_tree(jedit_dir)
 
 
     /* settings */
 
-    File.write(etc_dir + Path.explode("settings"),
+    File.write(component_dir.settings,
       """# -*- shell-script -*- :mode=shellscript:
 
 JEDIT_HOME="$COMPONENT/""" + jedit_patched + """"
@@ -491,7 +489,7 @@
 
     /* README */
 
-    File.write(component_dir + Path.basic("README"),
+    File.write(component_dir.README,
 """This is a slightly patched version of jEdit """ + version + """ from
 https://sourceforge.net/projects/jedit/files/jedit with some
 additional plugins jars from
--- a/src/Pure/Admin/build_lipics.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Pure/Admin/build_lipics.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -53,16 +53,15 @@
         }
 
         val component = "lipics-" + version
-        val component_dir = Isabelle_System.new_directory(target_dir + Path.basic(component))
-        progress.echo("Component " + component_dir)
+        val component_dir =
+          Components.Directory.create(target_dir + Path.basic(component), progress = progress)
 
-        Isabelle_System.copy_dir(lipics_dir, component_dir)
+        Isabelle_System.copy_dir(lipics_dir, component_dir.path)
 
 
         /* settings */
 
-        val etc_dir = Isabelle_System.make_directory(component_dir + Path.basic("etc"))
-        File.write(etc_dir + Path.basic("settings"),
+        File.write(component_dir.settings,
           """# -*- shell-script -*- :mode=shellscript:
 
 ISABELLE_LIPICS_HOME="$COMPONENT/authors"
@@ -71,7 +70,7 @@
 
         /* README */
 
-        File.write(component_dir + Path.basic("README"),
+        File.write(component_dir.README,
           """This is the Dagstuhl LIPIcs style for authors from
 """ + download_url + """
 
--- a/src/Pure/Admin/build_llncs.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Pure/Admin/build_llncs.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -55,17 +55,17 @@
         }
 
         val component = "llncs-" + version
-        val component_dir = Isabelle_System.new_directory(target_dir + Path.basic(component))
-        progress.echo("Component " + component_dir)
+        val component_dir =
+          Components.Directory.create(target_dir + Path.basic(component), progress = progress)
 
-        component_dir.file.delete
-        Isabelle_System.copy_dir(llncs_dir, component_dir)
+        Isabelle_System.rm_tree(component_dir.path)
+        Isabelle_System.copy_dir(llncs_dir, component_dir.path)
+        Isabelle_System.make_directory(component_dir.etc)
 
 
         /* settings */
 
-        val etc_dir = Isabelle_System.make_directory(component_dir + Path.basic("etc"))
-        File.write(etc_dir + Path.basic("settings"),
+        File.write(component_dir.settings,
           """# -*- shell-script -*- :mode=shellscript:
 
 ISABELLE_LLNCS_HOME="$COMPONENT"
@@ -74,7 +74,7 @@
 
         /* README */
 
-        File.write(component_dir + Path.basic("README"),
+        File.write(component_dir.README,
           """This is the Springer LaTeX LNCS style for authors from
 """ + download_url + """
 
--- a/src/Pure/Admin/build_minisat.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Pure/Admin/build_minisat.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -41,8 +41,8 @@
         }
 
       val component = proper_string(component_name) getOrElse make_component_name(version)
-      val component_dir = Isabelle_System.new_directory(target_dir + Path.basic(component))
-      progress.echo("Component " + component_dir)
+      val component_dir =
+        Components.Directory.create(target_dir + Path.basic(component), progress = progress)
 
 
       /* platform */
@@ -51,7 +51,8 @@
         proper_string(Isabelle_System.getenv("ISABELLE_PLATFORM64")) getOrElse
           error("No 64bit platform")
 
-      val platform_dir = Isabelle_System.make_directory(component_dir + Path.basic(platform_name))
+      val platform_dir =
+        Isabelle_System.make_directory(component_dir.path + Path.basic(platform_name))
 
 
       /* download source */
@@ -64,7 +65,7 @@
 
       Isabelle_System.bash(
         "tar xzf " + archive_path + " && mv " + Bash.string(source_name) + " src",
-        cwd = component_dir.file).check
+        cwd = component_dir.path.file).check
 
 
       /* build */
@@ -72,7 +73,7 @@
       progress.echo("Building Minisat for " + platform_name + " ...")
 
       val build_dir = tmp_dir + Path.basic(source_name)
-      Isabelle_System.copy_file(build_dir + Path.explode("LICENSE"), component_dir)
+      Isabelle_System.copy_file(build_dir + Path.explode("LICENSE"), component_dir.path)
 
       if (Platform.is_macos) {
         File.change(build_dir + Path.explode("Makefile")) {
@@ -91,8 +92,7 @@
 
       /* settings */
 
-      val etc_dir = Isabelle_System.make_directory(component_dir + Path.basic("etc"))
-      File.write(etc_dir + Path.basic("settings"),
+      File.write(component_dir.settings,
         """# -*- shell-script -*- :mode=shellscript:
 
 MINISAT_HOME="$COMPONENT/$ISABELLE_PLATFORM64"
@@ -103,7 +103,7 @@
 
       /* README */
 
-      File.write(component_dir + Path.basic("README"),
+      File.write(component_dir.README,
         "This Isabelle component provides Minisat " + version + """ using the
 sources from """.stripMargin + download_url + """
 
--- a/src/Pure/Admin/build_pdfjs.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Pure/Admin/build_pdfjs.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -31,8 +31,8 @@
     /* component name */
 
     val component = "pdfjs-" + version
-    val component_dir = Isabelle_System.new_directory(target_dir + Path.basic(component))
-    progress.echo("Component " + component_dir)
+    val component_dir =
+      Components.Directory.create(target_dir + Path.basic(component), progress = progress)
 
 
     /* download */
@@ -42,14 +42,13 @@
       Isabelle_System.download_file(download_url + "/pdfjs-" + version + "-legacy-dist.zip",
         archive_file, progress = progress)
       Isabelle_System.bash("unzip -x " + File.bash_path(archive_file),
-        cwd = component_dir.file).check
+        cwd = component_dir.path.file).check
     }
 
 
     /* settings */
 
-    val etc_dir = Isabelle_System.make_directory(component_dir + Path.basic("etc"))
-    File.write(etc_dir + Path.basic("settings"),
+    File.write(component_dir.settings,
       """# -*- shell-script -*- :mode=shellscript:
 
 ISABELLE_PDFJS_HOME="$COMPONENT"
@@ -58,7 +57,7 @@
 
     /* README */
 
-    File.write(component_dir + Path.basic("README"),
+    File.write(component_dir.README,
       """This is PDF.js from
 """ + download_url + """
 
--- a/src/Pure/Admin/build_polyml.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Pure/Admin/build_polyml.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -178,20 +178,19 @@
 
   def build_polyml_component(
     source_archive: Path,
-    component_dir: Path,
+    component_path: Path,
     sha1_root: Option[Path] = None
   ): Unit = {
-    Isabelle_System.new_directory(component_dir)
-    extract_sources(source_archive, component_dir)
+    val component_dir = Components.Directory.create(component_path)
+    extract_sources(source_archive, component_path)
 
-    Isabelle_System.copy_file(Path.explode("~~/Admin/polyml/README"), component_dir)
-
-    val etc_dir = Isabelle_System.make_directory(component_dir + Path.explode("etc"))
-    Isabelle_System.copy_file(Path.explode("~~/Admin/polyml/settings"), etc_dir)
+    Isabelle_System.copy_file(Path.explode("~~/Admin/polyml/settings"), component_dir.etc)
+    Isabelle_System.copy_file(Path.explode("~~/Admin/polyml/README"), component_dir.path)
 
     sha1_root match {
       case Some(dir) =>
-        Mercurial.repository(dir).archive(File.standard_path(component_dir + Path.explode("sha1")))
+        Mercurial.repository(dir)
+          .archive(File.standard_path(component_dir.path + Path.explode("sha1")))
       case None =>
     }
   }
--- a/src/Pure/Admin/build_postgresql.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Pure/Admin/build_postgresql.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -36,13 +36,13 @@
 
     /* component */
 
-    val component_dir = Isabelle_System.new_directory(target_dir + Path.basic(download_name))
-    progress.echo("Component " + component_dir)
+    val component_dir =
+      Components.Directory.create(target_dir + Path.basic(download_name), progress = progress)
 
 
     /* LICENSE */
 
-    File.write(component_dir + Path.basic("LICENSE"),
+    File.write(component_dir.LICENSE,
 """Copyright (c) 1997, PostgreSQL Global Development Group
 All rights reserved.
 
@@ -71,7 +71,7 @@
 
     /* README */
 
-    File.write(component_dir + Path.basic("README"),
+    File.write(component_dir.README,
 """This is PostgreSQL JDBC """ + download_version + """ from
 """ + notable_urls.mkString(" and ") + """
 
@@ -81,9 +81,7 @@
 
     /* settings */
 
-    val etc_dir = Isabelle_System.make_directory(component_dir + Path.basic("etc"))
-
-    File.write(etc_dir + Path.basic("settings"),
+    File.write(component_dir.settings,
 """# -*- shell-script -*- :mode=shellscript:
 
 classpath "$COMPONENT/""" + download_name + """.jar"
@@ -92,7 +90,7 @@
 
     /* jar */
 
-    val jar = component_dir + Path.basic(download_name).ext("jar")
+    val jar = component_dir.path + Path.basic(download_name).ext("jar")
     Isabelle_System.download_file(download_url, jar, progress = progress)
   }
 
--- a/src/Pure/Admin/build_prismjs.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Pure/Admin/build_prismjs.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -27,8 +27,8 @@
     /* component name */
 
     val component = "prismjs-" + version
-    val component_dir = Isabelle_System.new_directory(target_dir + Path.basic(component))
-    progress.echo("Component " + component_dir)
+    val component_dir =
+      Components.Directory.create(target_dir + Path.basic(component), progress = progress)
 
 
     /* download */
@@ -37,16 +37,16 @@
       Isabelle_System.bash("npm init -y && npm install prismjs@" + Bash.string(version),
         cwd = tmp_dir.file).check
 
-      component_dir.file.delete()
+      Isabelle_System.rm_tree(component_dir.path)
       Isabelle_System.copy_dir(tmp_dir + Path.explode("node_modules/prismjs"),
-        component_dir)
+        component_dir.path)
+      Isabelle_System.make_directory(component_dir.etc)
     }
 
 
     /* settings */
 
-    val etc_dir = Isabelle_System.make_directory(component_dir + Path.basic("etc"))
-    File.write(etc_dir + Path.basic("settings"),
+    File.write(component_dir.settings,
       """# -*- shell-script -*- :mode=shellscript:
 
 ISABELLE_PRISMJS_HOME="$COMPONENT"
@@ -55,7 +55,7 @@
 
     /* README */
 
-    File.write(component_dir + Path.basic("README"),
+    File.write(component_dir.README,
       """This is Prism.js """ + version + """ from https://www.npmjs.com/package/prismjs
 
 
--- a/src/Pure/Admin/build_scala.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Pure/Admin/build_scala.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -71,19 +71,18 @@
     /* component */
 
     val component_name = main_download.name + "-" + main_download.version
-    val component_dir = Isabelle_System.new_directory(target_dir + Path.basic(component_name))
-    progress.echo("Component " + component_dir)
+    val component_dir =
+      Components.Directory.create(target_dir + Path.basic(component_name), progress = progress)
 
 
     /* download */
 
-    main_download.get_unpacked(component_dir, strip = 1, progress = progress)
+    main_download.get_unpacked(component_dir.path, strip = 1, progress = progress)
 
-    val lib_dir = component_dir + Path.explode("lib")
     lib_downloads.foreach(download =>
-      download.get(lib_dir + Path.basic(download.artifact), progress = progress))
+      download.get(component_dir.lib + Path.basic(download.artifact), progress = progress))
 
-    File.write(component_dir + Path.basic("LICENSE"),
+    File.write(component_dir.LICENSE,
       Url.read(Url("https://www.apache.org/licenses/LICENSE-2.0.txt")))
 
 
@@ -95,8 +94,8 @@
         cat_lines(List(
           no_function("stty"),
           no_function("tput"),
-          "PROG_HOME=" + File.bash_path(component_dir),
-          File.read(component_dir + Path.explode("bin/common"))
+          "PROG_HOME=" + File.bash_path(component_dir.path),
+          File.read(component_dir.path + Path.explode("bin/common"))
             .replace("scala_exit_status=127", "scala_exit_status=0"),
           "compilerJavaClasspathArgs",
           "echo \"$jvm_cp_args\""))
@@ -114,8 +113,7 @@
 
     /* settings */
 
-    val etc_dir = Isabelle_System.make_directory(component_dir + Path.basic("etc"))
-    File.write(etc_dir + Path.basic("settings"),
+    File.write(component_dir.settings,
       """# -*- shell-script -*- :mode=shellscript:
 
 SCALA_HOME="$COMPONENT"
@@ -127,7 +125,7 @@
 
     val patched_scripts = List("bin/scala", "bin/scalac")
     for (name <- patched_scripts) {
-      File.change(component_dir + Path.explode(name)) {
+      File.change(component_dir.path + Path.explode(name)) {
         _.replace(""""-Dscala.home=$PROG_HOME"""", """"-Dscala.home=\"$PROG_HOME\""""")
       }
     }
@@ -135,7 +133,7 @@
 
     /* README */
 
-    File.write(component_dir + Path.basic("README"),
+    File.write(component_dir.README,
       "This distribution of Scala integrates the following parts:\n\n" +
       (main_download :: lib_downloads).map(_.print).mkString("\n\n") + """
 
--- a/src/Pure/Admin/build_spass.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Pure/Admin/build_spass.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -51,14 +51,15 @@
         progress.echo_warning("Odd SPASS version " + version + " (expected " + standard_version + ")")
       }
 
-      val component_dir = Isabelle_System.new_directory(target_dir + Path.basic(component_name))
-      progress.echo("Component " + component_dir)
+      val component_dir =
+        Components.Directory.create(target_dir + Path.basic(component_name), progress = progress)
 
       val platform_name =
         proper_string(Isabelle_System.getenv("ISABELLE_PLATFORM64"))
           .getOrElse(error("No 64bit platform"))
 
-      val platform_dir = Isabelle_System.make_directory(component_dir + Path.basic(platform_name))
+      val platform_dir =
+        Isabelle_System.make_directory(component_dir.path + Path.basic(platform_name))
 
 
       /* download source */
@@ -69,7 +70,7 @@
       Isabelle_System.bash("tar xzf " + archive_path, cwd = tmp_dir.file).check
       Isabelle_System.bash(
         "tar xzf " + archive_path + " && mv " + Bash.string(archive_base_name) + " src",
-        cwd = component_dir.file).check
+        cwd = component_dir.path.file).check
 
 
       /* build */
@@ -93,8 +94,7 @@
 
       /* install */
 
-      Isabelle_System.copy_file(build_dir + Path.basic("LICENCE"),
-        component_dir + Path.basic("LICENSE"))
+      Isabelle_System.copy_file(build_dir + Path.basic("LICENCE"), component_dir.LICENSE)
 
       val install_files = List("SPASS")
       for (name <- install_files ::: install_files.map(_ + ".exe")) {
@@ -105,8 +105,7 @@
 
       /* settings */
 
-      val etc_dir = Isabelle_System.make_directory(component_dir + Path.basic("etc"))
-      File.write(etc_dir + Path.basic("settings"),
+      File.write(component_dir.settings,
         """# -*- shell-script -*- :mode=shellscript:
 
 SPASS_HOME="$COMPONENT/$ISABELLE_PLATFORM64"
@@ -115,7 +114,7 @@
 
       /* README */
 
-      File.write(component_dir + Path.basic("README"),
+      File.write(component_dir.README,
 """This distribution of SPASS 3.8ds, described in Blanchette, Popescu, Wand, and
 Weidenbach's ITP 2012 paper "More SPASS with Isabelle", has been compiled from
 sources available at """ + download_url + """
--- a/src/Pure/Admin/build_sqlite.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Pure/Admin/build_sqlite.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -25,22 +25,20 @@
 
     /* component */
 
-    val component_dir = Isabelle_System.new_directory(target_dir + Path.basic(download_name))
-    progress.echo("Component " + component_dir)
+    val component_dir =
+      Components.Directory.create(target_dir + Path.basic(download_name), progress = progress)
 
 
     /* README */
 
-    File.write(component_dir + Path.basic("README"),
+    File.write(component_dir.README,
       "This is " + download_name + " from\n" + download_url +
         "\n\n        Makarius\n        " + Date.Format.date(Date.now()) + "\n")
 
 
     /* settings */
 
-    val etc_dir = Isabelle_System.make_directory(component_dir + Path.basic("etc"))
-
-    File.write(etc_dir + Path.basic("settings"),
+    File.write(component_dir.settings,
 """# -*- shell-script -*- :mode=shellscript:
 
 ISABELLE_SQLITE_HOME="$COMPONENT"
@@ -51,7 +49,7 @@
 
     /* jar */
 
-    val jar = component_dir + Path.basic(download_name).ext("jar")
+    val jar = component_dir.path + Path.basic(download_name).ext("jar")
     Isabelle_System.download_file(download_url, jar, progress = progress)
 
     Isabelle_System.with_tmp_dir("build") { jar_dir =>
@@ -70,11 +68,11 @@
           "org/sqlite/native/Windows/x86_64/sqlitejdbc.dll" -> "x86_64-windows")
 
       for ((file, dir) <- jar_files) {
-        val target = Isabelle_System.make_directory(component_dir + Path.explode(dir))
+        val target = Isabelle_System.make_directory(component_dir.path + Path.explode(dir))
         Isabelle_System.copy_file(jar_dir + Path.explode(file), target)
       }
 
-      File.set_executable(component_dir + Path.explode("x86_64-windows/sqlitejdbc.dll"), true)
+      File.set_executable(component_dir.path + Path.explode("x86_64-windows/sqlitejdbc.dll"), true)
     }
   }
 
--- a/src/Pure/Admin/build_vampire.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Pure/Admin/build_vampire.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -46,8 +46,8 @@
         }
 
       val component = proper_string(component_name) getOrElse make_component_name(version)
-      val component_dir = Isabelle_System.new_directory(target_dir + Path.basic(component))
-      progress.echo("Component " + component_dir)
+      val component_dir =
+        Components.Directory.create(target_dir + Path.basic(component), progress = progress)
 
 
       /* platform */
@@ -56,7 +56,8 @@
         proper_string(Isabelle_System.getenv("ISABELLE_PLATFORM64")) getOrElse
           error("No 64bit platform")
 
-      val platform_dir = Isabelle_System.make_directory(component_dir + Path.basic(platform_name))
+      val platform_dir =
+        Isabelle_System.make_directory(component_dir.path + Path.basic(platform_name))
 
 
       /* download source */
@@ -69,7 +70,7 @@
 
       Isabelle_System.bash(
         "tar xzf " + archive_path + " && mv " + Bash.string(source_name) + " src",
-        cwd = component_dir.file).check
+        cwd = component_dir.path.file).check
 
 
       /* build */
@@ -77,7 +78,7 @@
       progress.echo("Building Vampire for " + platform_name + " ...")
 
       val build_dir = tmp_dir + Path.basic(source_name)
-      Isabelle_System.copy_file(build_dir + Path.explode("LICENCE"), component_dir)
+      Isabelle_System.copy_file(build_dir + Path.explode("LICENCE"), component_dir.path)
 
       val cmake_opts = if (Platform.is_linux) "-DBUILD_SHARED_LIBS=0 " else ""
       val cmake_out =
@@ -97,8 +98,7 @@
 
       /* settings */
 
-      val etc_dir = Isabelle_System.make_directory(component_dir + Path.basic("etc"))
-      File.write(etc_dir + Path.basic("settings"),
+      File.write(component_dir.settings,
         """# -*- shell-script -*- :mode=shellscript:
 
 VAMPIRE_HOME="$COMPONENT/$ISABELLE_PLATFORM64"
@@ -109,7 +109,7 @@
 
       /* README */
 
-      File.write(component_dir + Path.basic("README"),
+      File.write(component_dir.README,
         "This Isabelle component provides Vampire " + version + """using the
 original sources from """.stripMargin + download_url + """
 
--- a/src/Pure/Admin/build_verit.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Pure/Admin/build_verit.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -41,8 +41,8 @@
         }
 
       val component_name = "verit-" + version
-      val component_dir = Isabelle_System.new_directory(target_dir + Path.basic(component_name))
-      progress.echo("Component " + component_dir)
+      val component_dir =
+        Components.Directory.create(target_dir + Path.basic(component_name), progress = progress)
 
 
       /* platform */
@@ -52,7 +52,8 @@
         proper_string(Isabelle_System.getenv("ISABELLE_PLATFORM64")) getOrElse
         error("No 64bit platform")
 
-      val platform_dir = Isabelle_System.make_directory(component_dir + Path.basic(platform_name))
+      val platform_dir =
+        Isabelle_System.make_directory(component_dir.path + Path.basic(platform_name))
 
 
       /* download source */
@@ -65,7 +66,7 @@
 
       Isabelle_System.bash(
         "tar xzf " + archive_path + " && mv " + Bash.string(source_name) + " src",
-        cwd = component_dir.file).check
+        cwd = component_dir.path.file).check
 
 
       /* build */
@@ -82,7 +83,7 @@
 
       /* install */
 
-      Isabelle_System.copy_file(build_dir + Path.explode("LICENSE"), component_dir)
+      Isabelle_System.copy_file(build_dir + Path.explode("LICENSE"), component_dir.path)
 
       val exe_path = Path.basic("veriT").platform_exe
       Isabelle_System.copy_file(build_dir + exe_path, platform_dir)
@@ -91,8 +92,7 @@
 
       /* settings */
 
-      val etc_dir = Isabelle_System.make_directory(component_dir + Path.basic("etc"))
-      File.write(etc_dir + Path.basic("settings"),
+      File.write(component_dir.settings,
         """# -*- shell-script -*- :mode=shellscript:
 
 ISABELLE_VERIT="$COMPONENT/${ISABELLE_WINDOWS_PLATFORM64:-$ISABELLE_PLATFORM64}/veriT"
@@ -101,7 +101,7 @@
 
       /* README */
 
-      File.write(component_dir + Path.basic("README"),
+      File.write(component_dir.README,
 """This is veriT """ + version + """ from
 """ + download_url + """
 
--- a/src/Pure/Admin/build_zipperposition.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Pure/Admin/build_zipperposition.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -26,8 +26,8 @@
       /* component */
 
       val component_name = "zipperposition-" + version
-      val component_dir = Isabelle_System.new_directory(target_dir + Path.basic(component_name))
-      progress.echo("Component " + component_dir)
+      val component_dir =
+        Components.Directory.create(target_dir + Path.basic(component_name), progress = progress)
 
 
       /* platform */
@@ -36,7 +36,8 @@
         proper_string(Isabelle_System.getenv("ISABELLE_PLATFORM64")) getOrElse
         error("No 64bit platform")
 
-      val platform_dir = Isabelle_System.make_directory(component_dir + Path.basic(platform_name))
+      val platform_dir =
+        Isabelle_System.make_directory(component_dir.path + Path.basic(platform_name))
 
 
       /* build */
@@ -53,7 +54,7 @@
       /* install */
 
       Isabelle_System.copy_file(build_dir + Path.explode("doc/zipperposition/LICENSE"),
-        component_dir)
+        component_dir.path)
 
       val prg_path = Path.basic("zipperposition")
       val exe_path = prg_path.platform_exe
@@ -67,8 +68,7 @@
 
       /* settings */
 
-      val etc_dir = Isabelle_System.make_directory(component_dir + Path.basic("etc"))
-      File.write(etc_dir + Path.basic("settings"),
+      File.write(component_dir.settings,
         """# -*- shell-script -*- :mode=shellscript:
 
 ZIPPERPOSITION_HOME="$COMPONENT/$ISABELLE_PLATFORM64"
@@ -77,7 +77,7 @@
 
       /* README */
 
-      File.write(component_dir + Path.basic("README"),
+      File.write(component_dir.README,
 """This is Zipperposition """ + version + """ from the OCaml/OPAM repository.
 
 
--- a/src/Pure/Admin/build_zstd.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Pure/Admin/build_zstd.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -43,21 +43,20 @@
     /* component */
 
     val component_name = "zstd-jni-" + version
-    val component_dir = Isabelle_System.new_directory(target_dir + Path.basic(component_name))
-    progress.echo("Component " + component_dir)
+    val component_dir =
+      Components.Directory.create(target_dir + Path.basic(component_name), progress = progress)
 
-    File.write(component_dir + Path.basic("README"),
+    File.write(component_dir.README,
       "This is " + component_name + " from\n" + download_url +
         "\n\n        Makarius\n        " + Date.Format.date(Date.now()) + "\n")
 
-    Isabelle_System.download_file(
-      license_url, component_dir + Path.basic("LICENSE"), progress = progress)
+    Isabelle_System.download_file(license_url, component_dir.LICENSE, progress = progress)
 
 
     /* jar */
 
     val jar_name = component_name + ".jar"
-    val jar = component_dir + Path.basic(jar_name)
+    val jar = component_dir.path + Path.basic(jar_name)
     Isabelle_System.download_file(
       download_url + "/" + version + "/" + jar_name, jar, progress = progress)
 
@@ -65,15 +64,13 @@
       progress.echo("Unpacking " + jar)
       Isabelle_System.bash("isabelle_jdk jar xf " + File.bash_path(jar.absolute),
         cwd = jar_dir.file).check
-      for (platform <- platforms) platform.install(jar_dir, component_dir, version)
+      for (platform <- platforms) platform.install(jar_dir, component_dir.path, version)
     }
 
 
     /* settings */
 
-    val etc_dir = Isabelle_System.make_directory(component_dir + Path.basic("etc"))
-
-    File.write(etc_dir + Path.basic("settings"),
+    File.write(component_dir.settings,
 """# -*- shell-script -*- :mode=shellscript:
 
 ISABELLE_ZSTD_HOME="$COMPONENT"
--- a/src/Pure/System/components.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Pure/System/components.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -102,6 +102,31 @@
 
   /* component directory content */
 
+  object Directory {
+    def apply(path: Path): Directory = new Directory(path.absolute)
+
+    def create(path: Path, progress: Progress = new Progress): Directory = {
+      val component_dir = new Directory(path.absolute)
+      progress.echo("Creating component directory " + component_dir.path)
+      Isabelle_System.new_directory(component_dir.path)
+      Isabelle_System.make_directory(component_dir.etc)
+      component_dir
+    }
+  }
+
+  class Directory private(val path: Path) {
+    override def toString: String = path.toString
+
+    def etc: Path = path + Path.basic("etc")
+    def src: Path = path + Path.basic("src")
+    def lib: Path = path + Path.basic("lib")
+    def settings: Path = etc + Path.basic("settings")
+    def components: Path = etc + Path.basic("components")
+    def build_props: Path = etc + Path.basic("build.props")
+    def README: Path = path + Path.basic("README")
+    def LICENSE: Path = path + Path.basic("LICENSE")
+  }
+
   def settings(dir: Path = Path.current): Path = dir + Path.explode("etc/settings")
   def components(dir: Path = Path.current): Path = dir + Path.explode("etc/components")
 
--- a/src/Pure/Tools/dotnet_setup.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Pure/Tools/dotnet_setup.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -76,14 +76,14 @@
       /* component directory */
 
       val component_dir =
-        target_dir + Path.explode(if (version.isEmpty) "dotnet-latest" else "dotnet-" + version)
+        Components.Directory(
+          target_dir + Path.explode(if (version.isEmpty) "dotnet-latest" else "dotnet-" + version))
 
       if (!dry_run) {
-        progress.echo("Component " + component_dir.expand)
+        progress.echo("Component " + component_dir)
+        Isabelle_System.make_directory(component_dir.etc)
 
-        val settings_path = component_dir + Path.explode("etc/settings")
-        Isabelle_System.make_directory(settings_path.dir)
-        File.write(settings_path, """# -*- shell-script -*- :mode=shellscript:
+        File.write(component_dir.settings, """# -*- shell-script -*- :mode=shellscript:
 
 ISABELLE_DOTNET_ROOT="$COMPONENT"
 
@@ -99,7 +99,7 @@
 DOTNET_CLI_HOME="$(platform_path "$ISABELLE_HOME_USER/dotnet")"
 """)
 
-        File.write(component_dir + Path.explode("README"),
+        File.write(component_dir.README,
           """This installation of Dotnet has been produced via "isabelle dotnet_setup".
 
 
@@ -110,7 +110,7 @@
           Components.update_components(false, Path.explode(old))
         }
 
-        Components.update_components(true, component_dir)
+        Components.update_components(true, component_dir.path)
       }
 
 
@@ -119,7 +119,7 @@
       Isabelle_System.with_tmp_file("install", ext = platform.ext) { install =>
         Isabelle_System.download_file(install_url + "." + platform.ext, install)
 
-        val platform_dir = component_dir + Path.explode(platform.name)
+        val platform_dir = component_dir.path + Path.explode(platform.name)
         if (platform_dir.is_dir && !force) {
           progress.echo_warning("Platform " + platform.name + " already installed")
         }
@@ -136,7 +136,7 @@
               (if (dry_run) " -DryRun" else "") +
               " -NoPath"
           progress.bash(script, echo = verbose,
-            cwd = if (dry_run) null else component_dir.file).check
+            cwd = if (dry_run) null else component_dir.path.file).check
           for (exe <- File.find_files(platform_dir.file, pred = _.getName.endsWith(".exe"))) {
             File.set_executable(File.path(exe), true)
           }
--- a/src/Tools/VSCode/src/build_vscode_extension.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Tools/VSCode/src/build_vscode_extension.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -150,8 +150,8 @@
     /* component */
 
     val component_name = "vscode_extension-" + Date.Format.alt_date(Date.now())
-    val component_dir = Isabelle_System.new_directory(target_dir + Path.basic(component_name))
-    progress.echo("Component " + component_dir)
+    val component_dir =
+      Components.Directory.create(target_dir + Path.basic(component_name), progress = progress)
 
 
     /* build */
@@ -183,15 +183,14 @@
           result.out_lines.collectFirst({ case Pattern(name) => name })
             .getOrElse(error("Failed to guess resulting .vsix file name"))
 
-        Isabelle_System.copy_file(build_dir + Path.basic(vsix_name), component_dir)
+        Isabelle_System.copy_file(build_dir + Path.basic(vsix_name), component_dir.path)
         vsix_name
       }
 
 
     /* settings */
 
-    val etc_dir = Isabelle_System.make_directory(component_dir + Path.basic("etc"))
-    File.write(etc_dir + Path.basic("settings"),
+    File.write(component_dir.settings,
       """# -*- shell-script -*- :mode=shellscript:
 
 ISABELLE_VSCODE_VSIX="$COMPONENT/""" + vsix_name + "\"\n")
@@ -199,7 +198,7 @@
 
     /* README */
 
-    File.write(component_dir + Path.basic("README"),
+    File.write(component_dir.README,
       """This the Isabelle/VSCode extension as VSIX package
 
 It has been produced from the sources in $ISABELLE_HOME/src/Tools/extension/.
--- a/src/Tools/VSCode/src/build_vscodium.scala	Sun Nov 13 21:59:19 2022 +0100
+++ b/src/Tools/VSCode/src/build_vscodium.scala	Sun Nov 20 23:37:54 2022 +0100
@@ -326,15 +326,15 @@
     /* component */
 
     val component_name = "vscodium-" + version
-    val component_dir = Isabelle_System.new_directory(target_dir + Path.explode(component_name))
-    progress.echo("Component " + component_dir)
+    val component_dir =
+      Components.Directory.create(target_dir + Path.explode(component_name), progress = progress)
 
 
     /* patches */
 
     progress.echo("\n* Building patches:")
 
-    val patches_dir = Isabelle_System.new_directory(component_dir + Path.explode("patches"))
+    val patches_dir = Isabelle_System.new_directory(component_dir.path + Path.explode("patches"))
 
     def write_patch(name: String, patch: String): Unit =
       File.write(patches_dir + Path.explode(name).patch, patch)
@@ -360,10 +360,10 @@
           cwd = build_dir.file, echo = verbose).check
 
         if (platform_info.is_linux) {
-          Isabelle_System.copy_file(build_dir + Path.explode("LICENSE"), component_dir)
+          Isabelle_System.copy_file(build_dir + Path.explode("LICENSE"), component_dir.path)
         }
 
-        val platform_dir = platform_info.platform_dir(component_dir)
+        val platform_dir = platform_info.platform_dir(component_dir.path)
         Isabelle_System.copy_dir(platform_info.build_dir(build_dir), platform_dir)
         platform_info.setup_node(platform_dir, progress)
         platform_info.setup_electron(platform_dir)
@@ -384,8 +384,7 @@
 
     /* settings */
 
-    val etc_dir = Isabelle_System.make_directory(component_dir + Path.explode("etc"))
-    File.write(etc_dir + Path.explode("settings"),
+    File.write(component_dir.settings,
       """# -*- shell-script -*- :mode=shellscript:
 
 ISABELLE_VSCODIUM_HOME="$COMPONENT/${ISABELLE_WINDOWS_PLATFORM64:-$ISABELLE_PLATFORM64}"
@@ -402,7 +401,7 @@
 
     /* README */
 
-    File.write(component_dir + Path.explode("README"),
+    File.write(component_dir.README,
       "This is VSCodium " + version + " from " + vscodium_repository +
 """