[toolchain] Added `--dest-dir` switch

- Changed `--target-dir` to `--install-dir`
  - Updated references in other files.
- Added `--dest-dir` switch to allow a staged installation
- Added `--latest-available-version` switch to return the latest version
  - This will be used to construct the `--install-dir` path when
  performing the staged installation.
- Fixes #4219
Signed-off-by: Srikrishna Iyer <sriyer@google.com>
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 1a5f881..120075b 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -271,7 +271,7 @@
   - bash: |
       set -x
       sudo util/get-toolchain.py \
-        --target-dir="$TOOLCHAIN_PATH" \
+        --install-dir="$TOOLCHAIN_PATH" \
         --release-version="$TOOLCHAIN_VERSION" \
         --update
     displayName: Install toolchain
@@ -302,7 +302,7 @@
   - bash: |
       set -x
       sudo util/get-toolchain.py \
-        --target-dir="$TOOLCHAIN_PATH" \
+        --install-dir="$TOOLCHAIN_PATH" \
         --release-version="$TOOLCHAIN_VERSION" \
         --update
     displayName: Install toolchain
@@ -400,7 +400,7 @@
   - bash: |
       set -x
       sudo util/get-toolchain.py \
-        --target-dir="$TOOLCHAIN_PATH" \
+        --install-dir="$TOOLCHAIN_PATH" \
         --release-version="$TOOLCHAIN_VERSION" \
         --update
       echo "##vso[task.prependpath]$TOOLCHAIN_PATH/bin"
diff --git a/ci/run-riscv-compliance.yml b/ci/run-riscv-compliance.yml
index 4f4d8bb..4e15474 100644
--- a/ci/run-riscv-compliance.yml
+++ b/ci/run-riscv-compliance.yml
@@ -22,7 +22,7 @@
     - bash: |
         set -x
         sudo util/get-toolchain.py \
-          --target-dir="${TOOLCHAIN_PATH}" \
+          --install-dir="${TOOLCHAIN_PATH}" \
           --release-version="${TOOLCHAIN_VERSION}" \
           --update
       displayName: Install toolchain
diff --git a/doc/ug/install_instructions/index.md b/doc/ug/install_instructions/index.md
index 4d26b0a..0876624 100644
--- a/doc/ug/install_instructions/index.md
+++ b/doc/ug/install_instructions/index.md
@@ -149,8 +149,9 @@
 $ ./util/get-toolchain.py
 ```
 
-This tool will automatically adjust the toolchain configuration if you override the installation directory (by using the `--target-dir` option).
-Alternatively, manually download the file starting with `lowrisc-toolchain-rv32imc-` from [GitHub releases](https://github.com/lowRISC/lowrisc-toolchains/releases/latest) and unpack it to `/tools/riscv`.
+This tool will automatically adjust the toolchain configuration if you override the installation directory (by using the `--install-dir` option).
+It also provides the ability to perform a staged installation (by supplying a `--dest-dir` option), if the toolchain needs to be unpacked first at a temporary staging directory, before it can be moved to the final installation directory.
+Alternatively, manually download the file starting with `lowrisc-toolchain-rv32imc-` from [GitHub releases](https://github.com/lowRISC/lowrisc-toolchains/releases/latest) and unpack it to the desired installation directory.
 
 #### Option 2: Compile your own GCC toolchain
 
diff --git a/util/get-toolchain.py b/util/get-toolchain.py
index 305484e..11ed46c 100755
--- a/util/get-toolchain.py
+++ b/util/get-toolchain.py
@@ -25,7 +25,7 @@
 ASSET_SUFFIX = ".tar.xz"
 RELEASES_URL_BASE = 'https://api.github.com/repos/lowRISC/lowrisc-toolchains/releases'
 
-TARGET_DIR = '/tools/riscv'
+INSTALL_DIR = '/tools/riscv'
 TOOLCHAIN_VERSION = 'latest'
 TOOLCHAIN_KIND = 'combined'
 
@@ -62,12 +62,12 @@
     raise SystemExit(1)
 
 
-def get_installed_toolchain_info(install_path):
+def get_installed_toolchain_info(unpack_dir):
 
     # Try new-style buildinfo.json first
     try:
         buildinfo = {}
-        with open(str(install_path / 'buildinfo.json'), 'r') as f:
+        with open(str(unpack_dir / 'buildinfo.json'), 'r') as f:
             buildinfo = json.loads(f.read())
 
         # Toolchains before 20200602-4 contained a `buildinfo.json` without a
@@ -84,7 +84,7 @@
 
     # If that wasn't successful, try old-style plaintext buildinfo
     version_re = r"(lowRISC toolchain version|Version):\s*\n?(?P<version>[^\n\s]+)"
-    buildinfo_txt_path = install_path / 'buildinfo'
+    buildinfo_txt_path = unpack_dir / 'buildinfo'
     try:
         with open(str(buildinfo_txt_path), 'r') as f:
             match = re.match(version_re, f.read(), re.M)
@@ -106,43 +106,63 @@
     return Path(tmpfile)
 
 
-def install(archive_file, target_dir):
-    target_dir.mkdir(parents=True, exist_ok=True)
+def install(archive_file, unpack_dir):
+    unpack_dir.mkdir(parents=True, exist_ok=True)
 
     cmd = [
-        'tar', '-x', '-f', str(archive_file), '--strip-components=1', '-C', str(target_dir),
+        'tar', '-x', '-f', str(archive_file), '--strip-components=1', '-C', str(unpack_dir),
     ]
     subprocess.run(cmd, check=True)
 
 
-def postinstall_rewrite_configs(install_path):
-    """This rewrites the toolchain configuration files to point to install_path"""
-    if str(install_path) == TARGET_DIR:
+def postinstall_rewrite_configs(unpack_dir, install_dir):
+    """Rewrites the toolchain configuration files to point to install_dir.
+
+    'unpack_dir' is where the toolchain is unpacked by this script.
+    'install_dir' is where the toolchain is eventually invoked from. Typically,
+    these are the same, unless a staged installation is being performed by
+    supplying both, --install-dir and --dest-dir switches. Regardless, if the
+    'install_dir' is different from the default, the config files need to be
+    updated to reflect the correct paths.
+    """
+    if str(install_dir) == INSTALL_DIR:
         return
 
     for file_pattern in FILE_PATTERNS_TO_REWRITE:
-        for config_file_path in install_path.glob(file_pattern):
-            # Rewrite TARGET_DIR to the requested target dir.
+        for config_file_path in unpack_dir.glob(file_pattern):
+            # Rewrite INSTALL_DIR to the requested target dir.
             log.info("Updating toolchain paths in %s",
                         str(config_file_path))
             with open(str(config_file_path)) as f:
                 original = f.read()
             with open(str(config_file_path), "w") as f:
-                f.write(original.replace(TARGET_DIR, str(install_path)))
+                f.write(original.replace(INSTALL_DIR, str(install_dir)))
 
 
 def main():
     parser = argparse.ArgumentParser()
-    parser.add_argument('--target-dir',
-                        '-t',
+    parser.add_argument('--install-dir',
+                        '-i',
                         required=False,
-                        default=TARGET_DIR,
-                        help="Target directory (default: %(default)s)")
+                        default=INSTALL_DIR,
+                        help="Installation directory (default: %(default)s)")
+    parser.add_argument('--dest-dir',
+                        '-d',
+                        required=False,
+                        help="""Destination directory if performing a staged
+                        installation. This is the staging directory where the
+                        toolchain is unpacked.""")
     parser.add_argument('--release-version',
                         '-r',
                         required=False,
                         default=TOOLCHAIN_VERSION,
                         help="Toolchain version (default: %(default)s)")
+    parser.add_argument('--latest-available-version',
+                        '-l',
+                        required=False,
+                        default=False,
+                        action='store_true',
+                        help="Return the latest available toolchain version.")
     parser.add_argument('--kind',
                         required=False,
                         default=TOOLCHAIN_KIND,
@@ -157,20 +177,29 @@
         help="Update to target version if needed (default: %(default)s)")
     args = parser.parse_args()
 
-    target_dir = Path(args.target_dir)
-
     available_toolchain = get_available_toolchain_info(args.release_version,
                                                        args.kind)
+
+    if args.latest_available_version:
+        print(available_toolchain['version'])
+        sys.exit(0)
+
     log.info("Found available %s toolchain version %s, %s",
              available_toolchain['kind'], available_toolchain['version'],
              available_toolchain['name'])
 
-    if args.update and target_dir.is_dir():
-        installed_toolchain = get_installed_toolchain_info(target_dir)
+    install_dir = Path(args.install_dir)
+    if args.dest_dir is None:
+        unpack_dir = install_dir
+    else:
+        unpack_dir = Path(args.dest_dir)
+
+    if args.update and unpack_dir.is_dir():
+        installed_toolchain = get_installed_toolchain_info(unpack_dir)
         if installed_toolchain is None:
             sys.exit('Unable to extract current toolchain version. '
                      'Delete target directory %s and try again.' %
-                     str(target_dir))
+                     str(unpack_dir))
 
         version_matches = available_toolchain['version'] == installed_toolchain['version']
         kind_matches = available_toolchain['kind'] == installed_toolchain['kind']
@@ -181,7 +210,7 @@
                 'same as the %s toolchain installed at %s (version %s).',
                 available_toolchain['kind'], available_toolchain['version'],
                 installed_toolchain['kind'], installed_toolchain['version'],
-                str(target_dir))
+                str(unpack_dir))
             log.warning("Skipping install.")
             sys.exit(0)
 
@@ -190,28 +219,28 @@
             installed_toolchain['kind'], installed_toolchain['version'],
             available_toolchain['kind'], available_toolchain['version'])
     else:
-        if target_dir.exists():
+        if unpack_dir.exists():
             sys.exit('Target directory %s already exists. '
-                     'Delete it first, or use --update.' % str(target_dir))
+                     'Delete it first, or use --update.' % str(unpack_dir))
 
     archive_file = None
     try:
         archive_file = download(available_toolchain['download_url'])
 
-        if args.update and target_dir.exists():
-            # We only reach this point if |target_dir| contained a toolchain
+        if args.update and unpack_dir.exists():
+            # We only reach this point if |unpack_dir| contained a toolchain
             # before, so removing it is reasonably safe.
-            shutil.rmtree(str(target_dir))
+            shutil.rmtree(str(unpack_dir))
 
-        install(archive_file, target_dir)
-        postinstall_rewrite_configs(target_dir.resolve())
+        install(archive_file, unpack_dir)
+        postinstall_rewrite_configs(unpack_dir.resolve(), install_dir.resolve())
     finally:
         if archive_file:
             archive_file.unlink()
 
     log.info('Installed %s toolchain version %s to %s.',
              available_toolchain['kind'], available_toolchain['version'],
-             str(target_dir))
+             str(unpack_dir))
 
 
 if __name__ == "__main__":