src/Pure/Admin/build_release.scala
changeset 75393 87ebf5a50283
parent 75291 e4d6b9bd5071
child 75394 42267c650205
equal deleted inserted replaced
75388:b3ca4a6ed74b 75393:87ebf5a50283
     5 */
     5 */
     6 
     6 
     7 package isabelle
     7 package isabelle
     8 
     8 
     9 
     9 
    10 object Build_Release
    10 object Build_Release {
    11 {
       
    12   /** release context **/
    11   /** release context **/
    13 
    12 
    14   private def execute(dir: Path, script: String): Unit =
    13   private def execute(dir: Path, script: String): Unit =
    15     Isabelle_System.bash(script, cwd = dir.file).check
    14     Isabelle_System.bash(script, cwd = dir.file).check
    16 
    15 
    18     Isabelle_System.gnutar(args, dir = dir, strip = strip).check
    17     Isabelle_System.gnutar(args, dir = dir, strip = strip).check
    19 
    18 
    20   private def bash_java_opens(args: String*): String =
    19   private def bash_java_opens(args: String*): String =
    21     Bash.strings(args.toList.flatMap(arg => List("--add-opens", arg + "=ALL-UNNAMED")))
    20     Bash.strings(args.toList.flatMap(arg => List("--add-opens", arg + "=ALL-UNNAMED")))
    22 
    21 
    23   object Release_Context
    22   object Release_Context {
    24   {
       
    25     def apply(
    23     def apply(
    26       target_dir: Path,
    24       target_dir: Path,
    27       release_name: String = "",
    25       release_name: String = "",
    28       components_base: Path = Components.default_components_base,
    26       components_base: Path = Components.default_components_base,
    29       progress: Progress = new Progress): Release_Context =
    27       progress: Progress = new Progress
    30     {
    28     ): Release_Context = {
    31       val date = Date.now()
    29       val date = Date.now()
    32       val dist_name = proper_string(release_name) getOrElse ("Isabelle_" + Date.Format.date(date))
    30       val dist_name = proper_string(release_name) getOrElse ("Isabelle_" + Date.Format.date(date))
    33       val dist_dir = (target_dir + Path.explode("dist-" + dist_name)).absolute
    31       val dist_dir = (target_dir + Path.explode("dist-" + dist_name)).absolute
    34       new Release_Context(release_name, dist_name, dist_dir, components_base, progress)
    32       new Release_Context(release_name, dist_name, dist_dir, components_base, progress)
    35     }
    33     }
    38   class Release_Context private[Build_Release](
    36   class Release_Context private[Build_Release](
    39     val release_name: String,
    37     val release_name: String,
    40     val dist_name: String,
    38     val dist_name: String,
    41     val dist_dir: Path,
    39     val dist_dir: Path,
    42     val components_base: Path,
    40     val components_base: Path,
    43     val progress: Progress)
    41     val progress: Progress
    44   {
    42   ) {
    45     override def toString: String = dist_name
    43     override def toString: String = dist_name
    46 
    44 
    47     val isabelle: Path = Path.explode(dist_name)
    45     val isabelle: Path = Path.explode(dist_name)
    48     val isabelle_dir: Path = dist_dir + isabelle
    46     val isabelle_dir: Path = dist_dir + isabelle
    49     val isabelle_archive: Path = dist_dir + isabelle.tar.gz
    47     val isabelle_archive: Path = dist_dir + isabelle.tar.gz
    52     def other_isabelle(dir: Path): Other_Isabelle =
    50     def other_isabelle(dir: Path): Other_Isabelle =
    53       Other_Isabelle(dir + isabelle,
    51       Other_Isabelle(dir + isabelle,
    54         isabelle_identifier = dist_name + "-build",
    52         isabelle_identifier = dist_name + "-build",
    55         progress = progress)
    53         progress = progress)
    56 
    54 
    57     def make_announce(id: String): Unit =
    55     def make_announce(id: String): Unit = {
    58     {
       
    59       if (release_name.isEmpty) {
    56       if (release_name.isEmpty) {
    60         File.write(isabelle_dir + Path.explode("ANNOUNCE"),
    57         File.write(isabelle_dir + Path.explode("ANNOUNCE"),
    61           """
    58           """
    62 IMPORTANT NOTE
    59 IMPORTANT NOTE
    63 ==============
    60 ==============
    65 This is a snapshot of Isabelle/""" + id + """ from the repository.
    62 This is a snapshot of Isabelle/""" + id + """ from the repository.
    66 """)
    63 """)
    67       }
    64       }
    68     }
    65     }
    69 
    66 
    70     def make_contrib(): Unit =
    67     def make_contrib(): Unit = {
    71     {
       
    72       Isabelle_System.make_directory(Components.contrib(isabelle_dir))
    68       Isabelle_System.make_directory(Components.contrib(isabelle_dir))
    73       File.write(Components.contrib(isabelle_dir, name = "README"),
    69       File.write(Components.contrib(isabelle_dir, name = "README"),
    74         """This directory contains add-on components that contribute to the main
    70         """This directory contains add-on components that contribute to the main
    75 Isabelle distribution.  Separate licensing conditions apply, see each
    71 Isabelle distribution.  Separate licensing conditions apply, see each
    76 directory individually.
    72 directory individually.
    88   }
    84   }
    89 
    85 
    90   sealed case class Bundle_Info(
    86   sealed case class Bundle_Info(
    91     platform: Platform.Family.Value,
    87     platform: Platform.Family.Value,
    92     platform_description: String,
    88     platform_description: String,
    93     name: String)
    89     name: String
    94   {
    90   ) {
    95     def path: Path = Path.explode(name)
    91     def path: Path = Path.explode(name)
    96   }
    92   }
    97 
    93 
    98 
    94 
    99 
    95 
   102   val ISABELLE: Path = Path.basic("Isabelle")
    98   val ISABELLE: Path = Path.basic("Isabelle")
   103   val ISABELLE_ID: Path = Path.explode("etc/ISABELLE_ID")
    99   val ISABELLE_ID: Path = Path.explode("etc/ISABELLE_ID")
   104   val ISABELLE_TAGS: Path = Path.explode("etc/ISABELLE_TAGS")
   100   val ISABELLE_TAGS: Path = Path.explode("etc/ISABELLE_TAGS")
   105   val ISABELLE_IDENTIFIER: Path = Path.explode("etc/ISABELLE_IDENTIFIER")
   101   val ISABELLE_IDENTIFIER: Path = Path.explode("etc/ISABELLE_IDENTIFIER")
   106 
   102 
   107   object Release_Archive
   103   object Release_Archive {
   108   {
   104     def make(bytes: Bytes, rename: String = ""): Release_Archive = {
   109     def make(bytes: Bytes, rename: String = ""): Release_Archive =
       
   110     {
       
   111       Isabelle_System.with_tmp_dir("tmp")(dir =>
   105       Isabelle_System.with_tmp_dir("tmp")(dir =>
   112         Isabelle_System.with_tmp_file("archive", ext = "tar.gz")(archive_path =>
   106         Isabelle_System.with_tmp_file("archive", ext = "tar.gz")(archive_path => {
   113         {
       
   114           val isabelle_dir = Isabelle_System.make_directory(dir + ISABELLE)
   107           val isabelle_dir = Isabelle_System.make_directory(dir + ISABELLE)
   115 
   108 
   116           Bytes.write(archive_path, bytes)
   109           Bytes.write(archive_path, bytes)
   117           execute_tar(isabelle_dir, "-xzf " + File.bash_path(archive_path), strip = 1)
   110           execute_tar(isabelle_dir, "-xzf " + File.bash_path(archive_path), strip = 1)
   118 
   111 
   134     }
   127     }
   135 
   128 
   136     def read(path: Path, rename: String = ""): Release_Archive =
   129     def read(path: Path, rename: String = ""): Release_Archive =
   137       make(Bytes.read(path), rename = rename)
   130       make(Bytes.read(path), rename = rename)
   138 
   131 
   139     def get(url: String, rename: String = "", progress: Progress = new Progress): Release_Archive =
   132     def get(
   140     {
   133       url: String,
       
   134       rename: String = "",
       
   135       progress: Progress = new Progress
       
   136     ): Release_Archive = {
   141       val bytes =
   137       val bytes =
   142         if (Path.is_wellformed(url)) Bytes.read(Path.explode(url))
   138         if (Path.is_wellformed(url)) Bytes.read(Path.explode(url))
   143         else Isabelle_System.download(url, progress = progress).bytes
   139         else Isabelle_System.download(url, progress = progress).bytes
   144       make(bytes, rename = rename)
   140       make(bytes, rename = rename)
   145     }
   141     }
   146   }
   142   }
   147 
   143 
   148   case class Release_Archive private[Build_Release](
   144   case class Release_Archive private[Build_Release](
   149     bytes: Bytes, id: String, tags: String, identifier: String)
   145     bytes: Bytes, id: String, tags: String, identifier: String) {
   150   {
       
   151     override def toString: String = identifier
   146     override def toString: String = identifier
   152   }
   147   }
   153 
   148 
   154 
   149 
   155 
   150 
   156   /** generated content **/
   151   /** generated content **/
   157 
   152 
   158   /* bundled components */
   153   /* bundled components */
   159 
   154 
   160   class Bundled(platform: Option[Platform.Family.Value] = None)
   155   class Bundled(platform: Option[Platform.Family.Value] = None) {
   161   {
       
   162     def detect(s: String): Boolean =
   156     def detect(s: String): Boolean =
   163       s.startsWith("#bundled") && !s.startsWith("#bundled ")
   157       s.startsWith("#bundled") && !s.startsWith("#bundled ")
   164 
   158 
   165     def apply(name: String): String =
   159     def apply(name: String): String =
   166       "#bundled" + (platform match { case None => "" case Some(plat) => "-" + plat }) + ":" + name
   160       "#bundled" + (platform match { case None => "" case Some(plat) => "-" + plat }) + ":" + name
   174         case Pattern2(Platform.Family(plat), name) if platform == Some(plat) => Some(name)
   168         case Pattern2(Platform.Family(plat), name) if platform == Some(plat) => Some(name)
   175         case _ => None
   169         case _ => None
   176       }
   170       }
   177   }
   171   }
   178 
   172 
   179   def record_bundled_components(dir: Path): Unit =
   173   def record_bundled_components(dir: Path): Unit = {
   180   {
       
   181     val catalogs =
   174     val catalogs =
   182       List("main", "bundled").map((_, new Bundled())) :::
   175       List("main", "bundled").map((_, new Bundled())) :::
   183       Platform.Family.list.flatMap(platform =>
   176       Platform.Family.list.flatMap(platform =>
   184         List(platform.toString, "bundled-" + platform.toString).
   177         List(platform.toString, "bundled-" + platform.toString).
   185           map((_, new Bundled(platform = Some(platform)))))
   178           map((_, new Bundled(platform = Some(platform)))))
   193           line <- split_lines(File.read(path))
   186           line <- split_lines(File.read(path))
   194           if line.nonEmpty && !line.startsWith("#")
   187           if line.nonEmpty && !line.startsWith("#")
   195         } yield bundled(line)).toList))
   188         } yield bundled(line)).toList))
   196   }
   189   }
   197 
   190 
   198   def get_bundled_components(dir: Path, platform: Platform.Family.Value): (List[String], String) =
   191   def get_bundled_components(dir: Path, platform: Platform.Family.Value): (List[String], String) = {
   199   {
       
   200     val Bundled = new Bundled(platform = Some(platform))
   192     val Bundled = new Bundled(platform = Some(platform))
   201     val components =
   193     val components =
   202       for { Bundled(name) <- Components.read_components(dir) } yield name
   194       for { Bundled(name) <- Components.read_components(dir) } yield name
   203     val jdk_component =
   195     val jdk_component =
   204       components.find(_.startsWith("jdk")) getOrElse error("Missing jdk component")
   196       components.find(_.startsWith("jdk")) getOrElse error("Missing jdk component")
   205     (components, jdk_component)
   197     (components, jdk_component)
   206   }
   198   }
   207 
   199 
   208   def activate_components(
   200   def activate_components(
   209     dir: Path, platform: Platform.Family.Value, more_names: List[String]): Unit =
   201     dir: Path, platform: Platform.Family.Value, more_names: List[String]): Unit = {
   210   {
       
   211     def contrib_name(name: String): String =
   202     def contrib_name(name: String): String =
   212       Components.contrib(name = name).implode
   203       Components.contrib(name = name).implode
   213 
   204 
   214     val Bundled = new Bundled(platform = Some(platform))
   205     val Bundled = new Bundled(platform = Some(platform))
   215     Components.write_components(dir,
   206     Components.write_components(dir,
   229 
   220 
   230   private def build_heaps(
   221   private def build_heaps(
   231     options: Options,
   222     options: Options,
   232     platform: Platform.Family.Value,
   223     platform: Platform.Family.Value,
   233     build_sessions: List[String],
   224     build_sessions: List[String],
   234     local_dir: Path): Unit =
   225     local_dir: Path
   235   {
   226   ): Unit = {
   236     val server_option = "build_host_" + platform.toString
   227     val server_option = "build_host_" + platform.toString
   237     val ssh =
   228     val ssh =
   238       options.string(server_option) match {
   229       options.string(server_option) match {
   239         case "" =>
   230         case "" =>
   240           if (Platform.family == platform) SSH.Local
   231           if (Platform.family == platform) SSH.Local
   242         case SSH.Target(user, host) =>
   233         case SSH.Target(user, host) =>
   243           SSH.open_session(options, host = host, user = user)
   234           SSH.open_session(options, host = host, user = user)
   244         case s => error("Malformed option " + server_option + ": " + quote(s))
   235         case s => error("Malformed option " + server_option + ": " + quote(s))
   245       }
   236       }
   246     try {
   237     try {
   247       Isabelle_System.with_tmp_file("tmp", ext = "tar")(local_tmp_tar =>
   238       Isabelle_System.with_tmp_file("tmp", ext = "tar")(local_tmp_tar => {
   248       {
       
   249         execute_tar(local_dir, "-cf " + File.bash_path(local_tmp_tar) + " .")
   239         execute_tar(local_dir, "-cf " + File.bash_path(local_tmp_tar) + " .")
   250         ssh.with_tmp_dir(remote_dir =>
   240         ssh.with_tmp_dir(remote_dir => {
   251         {
       
   252           val remote_tmp_tar = remote_dir + Path.basic("tmp.tar")
   241           val remote_tmp_tar = remote_dir + Path.basic("tmp.tar")
   253           ssh.write_file(remote_tmp_tar, local_tmp_tar)
   242           ssh.write_file(remote_tmp_tar, local_tmp_tar)
   254           val remote_commands =
   243           val remote_commands =
   255             List(
   244             List(
   256               "cd " + File.bash_path(remote_dir),
   245               "cd " + File.bash_path(remote_dir),
   267   }
   256   }
   268 
   257 
   269 
   258 
   270   /* Isabelle application */
   259   /* Isabelle application */
   271 
   260 
   272   def make_isabelle_options(path: Path, options: List[String], line_ending: String = "\n"): Unit =
   261   def make_isabelle_options(path: Path, options: List[String], line_ending: String = "\n"): Unit = {
   273   {
       
   274     val title = "# Java runtime options"
   262     val title = "# Java runtime options"
   275     File.write(path, (title :: options).map(_ + line_ending).mkString)
   263     File.write(path, (title :: options).map(_ + line_ending).mkString)
   276   }
   264   }
   277 
   265 
   278   def make_isabelle_app(
   266   def make_isabelle_app(
   279     platform: Platform.Family.Value,
   267     platform: Platform.Family.Value,
   280     isabelle_target: Path,
   268     isabelle_target: Path,
   281     isabelle_name: String,
   269     isabelle_name: String,
   282     jdk_component: String,
   270     jdk_component: String,
   283     classpath: List[Path],
   271     classpath: List[Path],
   284     dock_icon: Boolean = false): Unit =
   272     dock_icon: Boolean = false): Unit = {
   285   {
       
   286     val script = """#!/usr/bin/env bash
   273     val script = """#!/usr/bin/env bash
   287 #
   274 #
   288 # Author: Makarius
   275 # Author: Makarius
   289 #
   276 #
   290 # Main Isabelle application script.
   277 # Main Isabelle application script.
   328       isabelle_target + Path.explode(isabelle_name))
   315       isabelle_target + Path.explode(isabelle_name))
   329     Isabelle_System.rm_tree(component_dir)
   316     Isabelle_System.rm_tree(component_dir)
   330   }
   317   }
   331 
   318 
   332 
   319 
   333   def make_isabelle_plist(path: Path, isabelle_name: String, isabelle_rev: String): Unit =
   320   def make_isabelle_plist(path: Path, isabelle_name: String, isabelle_rev: String): Unit = {
   334   {
       
   335     File.write(path, """<?xml version="1.0" ?>
   321     File.write(path, """<?xml version="1.0" ?>
   336 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
   322 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
   337 <plist version="1.0">
   323 <plist version="1.0">
   338 <dict>
   324 <dict>
   339 <key>CFBundleDevelopmentRegion</key>
   325 <key>CFBundleDevelopmentRegion</key>
   392   /* main */
   378   /* main */
   393 
   379 
   394   def use_release_archive(
   380   def use_release_archive(
   395     context: Release_Context,
   381     context: Release_Context,
   396     archive: Release_Archive,
   382     archive: Release_Archive,
   397     id: String = ""): Unit =
   383     id: String = ""
   398   {
   384   ): Unit = {
   399     if (id.nonEmpty && id != archive.id) {
   385     if (id.nonEmpty && id != archive.id) {
   400       error("Mismatch of release identification " + id + " vs. archive " + archive.id)
   386       error("Mismatch of release identification " + id + " vs. archive " + archive.id)
   401     }
   387     }
   402 
   388 
   403     if (!context.isabelle_archive.is_file || Bytes.read(context.isabelle_archive) != archive.bytes) {
   389     if (!context.isabelle_archive.is_file || Bytes.read(context.isabelle_archive) != archive.bytes) {
   406   }
   392   }
   407 
   393 
   408   def build_release_archive(
   394   def build_release_archive(
   409     context: Release_Context,
   395     context: Release_Context,
   410     version: String,
   396     version: String,
   411     parallel_jobs: Int = 1): Unit =
   397     parallel_jobs: Int = 1
   412   {
   398   ): Unit = {
   413     val progress = context.progress
   399     val progress = context.progress
   414 
   400 
   415     val hg = Mercurial.repository(Path.ISABELLE_HOME)
   401     val hg = Mercurial.repository(Path.ISABELLE_HOME)
   416     val id =
   402     val id =
   417       try { hg.id(version) }
   403       try { hg.id(version) }
   495     platform_families: List[Platform.Family.Value] = default_platform_families,
   481     platform_families: List[Platform.Family.Value] = default_platform_families,
   496     more_components: List[Path] = Nil,
   482     more_components: List[Path] = Nil,
   497     website: Option[Path] = None,
   483     website: Option[Path] = None,
   498     build_sessions: List[String] = Nil,
   484     build_sessions: List[String] = Nil,
   499     build_library: Boolean = false,
   485     build_library: Boolean = false,
   500     parallel_jobs: Int = 1): Unit =
   486     parallel_jobs: Int = 1
   501   {
   487   ): Unit = {
   502     val progress = context.progress
   488     val progress = context.progress
   503 
   489 
   504 
   490 
   505     /* release directory */
   491     /* release directory */
   506 
   492 
   508 
   494 
   509     for (path <- List(context.isabelle, ISABELLE)) {
   495     for (path <- List(context.isabelle, ISABELLE)) {
   510       Isabelle_System.rm_tree(context.dist_dir + path)
   496       Isabelle_System.rm_tree(context.dist_dir + path)
   511     }
   497     }
   512 
   498 
   513     Isabelle_System.with_tmp_file("archive", ext = "tar.gz")(archive_path =>
   499     Isabelle_System.with_tmp_file("archive", ext = "tar.gz")(archive_path => {
   514     {
       
   515       Bytes.write(archive_path, archive.bytes)
   500       Bytes.write(archive_path, archive.bytes)
   516       val extract =
   501       val extract =
   517         List("README", "NEWS", "ANNOUNCE", "COPYRIGHT", "CONTRIBUTORS", "doc").
   502         List("README", "NEWS", "ANNOUNCE", "COPYRIGHT", "CONTRIBUTORS", "doc").
   518           map(name => context.dist_name + "/" + name)
   503           map(name => context.dist_name + "/" + name)
   519       execute_tar(context.dist_dir,
   504       execute_tar(context.dist_dir,
   531       val isabelle_name = context.dist_name
   516       val isabelle_name = context.dist_name
   532       val platform = bundle_info.platform
   517       val platform = bundle_info.platform
   533 
   518 
   534       progress.echo("\nApplication bundle for " + platform)
   519       progress.echo("\nApplication bundle for " + platform)
   535 
   520 
   536       Isabelle_System.with_tmp_dir("build_release")(tmp_dir =>
   521       Isabelle_System.with_tmp_dir("build_release")(tmp_dir => {
   537       {
       
   538         // release archive
   522         // release archive
   539 
   523 
   540         execute_tar(tmp_dir, "-xzf " + File.bash_path(context.isabelle_archive))
   524         execute_tar(tmp_dir, "-xzf " + File.bash_path(context.isabelle_archive))
   541         val other_isabelle = context.other_isabelle(tmp_dir)
   525         val other_isabelle = context.other_isabelle(tmp_dir)
   542         val isabelle_target = other_isabelle.isabelle_home
   526         val isabelle_target = other_isabelle.isabelle_home
   576           yield {
   560           yield {
   577             val s = "-Dapple.awt.application.name="
   561             val s = "-Dapple.awt.application.name="
   578             if (opt.startsWith(s)) s + isabelle_name else opt
   562             if (opt.startsWith(s)) s + isabelle_name else opt
   579           }) ::: List("-Disabelle.jedit_server=" + isabelle_name)
   563           }) ::: List("-Disabelle.jedit_server=" + isabelle_name)
   580 
   564 
   581         val classpath: List[Path] =
   565         val classpath: List[Path] = {
   582         {
       
   583           val base = isabelle_target.absolute
   566           val base = isabelle_target.absolute
   584           val classpath1 = Path.split(other_isabelle.getenv("ISABELLE_CLASSPATH"))
   567           val classpath1 = Path.split(other_isabelle.getenv("ISABELLE_CLASSPATH"))
   585           val classpath2 = Path.split(other_isabelle.getenv("ISABELLE_SETUP_CLASSPATH"))
   568           val classpath2 = Path.split(other_isabelle.getenv("ISABELLE_SETUP_CLASSPATH"))
   586           (classpath1 ::: classpath2).map(path =>
   569           (classpath1 ::: classpath2).map(path => {
   587           {
       
   588             val abs_path = path.absolute
   570             val abs_path = path.absolute
   589             File.relative_path(base, abs_path) match {
   571             File.relative_path(base, abs_path) match {
   590               case Some(rel_path) => rel_path
   572               case Some(rel_path) => rel_path
   591               case None => error("Bad classpath element: " + abs_path)
   573               case None => error("Bad classpath element: " + abs_path)
   592             }
   574             }
   831     if (build_library) {
   813     if (build_library) {
   832       if (context.isabelle_library_archive.is_file) {
   814       if (context.isabelle_library_archive.is_file) {
   833         progress.echo_warning("Library archive already exists: " + context.isabelle_library_archive)
   815         progress.echo_warning("Library archive already exists: " + context.isabelle_library_archive)
   834       }
   816       }
   835       else {
   817       else {
   836         Isabelle_System.with_tmp_dir("build_release")(tmp_dir =>
   818         Isabelle_System.with_tmp_dir("build_release")(tmp_dir => {
   837           {
       
   838             val bundle =
   819             val bundle =
   839               context.dist_dir + Path.explode(context.dist_name + "_" + Platform.family + ".tar.gz")
   820               context.dist_dir + Path.explode(context.dist_name + "_" + Platform.family + ".tar.gz")
   840             execute_tar(tmp_dir, "-xzf " + File.bash_path(bundle))
   821             execute_tar(tmp_dir, "-xzf " + File.bash_path(bundle))
   841 
   822 
   842             val other_isabelle = context.other_isabelle(tmp_dir)
   823             val other_isabelle = context.other_isabelle(tmp_dir)
   860 
   841 
   861 
   842 
   862 
   843 
   863   /** command line entry point **/
   844   /** command line entry point **/
   864 
   845 
   865   def main(args: Array[String]): Unit =
   846   def main(args: Array[String]): Unit = {
   866   {
       
   867     Command_Line.tool {
   847     Command_Line.tool {
   868       var afp_rev = ""
   848       var afp_rev = ""
   869       var components_base: Path = Components.default_components_base
   849       var components_base: Path = Components.default_components_base
   870       var target_dir = Path.current
   850       var target_dir = Path.current
   871       var release_name = ""
   851       var release_name = ""