[Tizen] Update SDK version to 9.0, install.sh script improvements (#39935)

* [Linux/Tizen] Update SDK version to 9.0, install.sh script improvements

Signed-off-by: Maciej Grela <m.grela@samsung.com>

* Restyled by shellharden

* [Linux/Tizen] Improve help on missing dependencies

Signed-off-by: Maciej Grela <m.grela@samsung.com>

* [Linux/Tizen] Check for $TIZEN_SDK_ROOT being empty before trying to perform the unzip data/ symlink trick

Signed-off-by: Maciej Grela <m.grela@samsung.com>

* [Linux/Tizen] Improve tizen_qemu.py

- warn when pigweed's qemu-system-arm is the only one we have
- apply best practices for `logging` use
- pylint fixes

Signed-off-by: Maciej Grela <m.grela@samsung.com>

* Restyled by autopep8

* [Linux/Tizen] Harmonize help string syntax

Signed-off-by: Maciej Grela <m.grela@samsung.com>

* Restyled by autopep8

* [Linux/Tizen] Improvements to Tizen SDK install.sh

- implify usage, the tool now behaves like python's pip leaving behind a cache directory and reusing it instead of downloading files every time. This behaviour can be controlled by the --purge-pkg-cache which starts an installation from a clean cache
- have more control over script verbosity with commandline flags and DEBUG environment variable
- messages passed to printing functions info(), error(), etc. do not have to be a single arg anymore
- use explicit parameters to pass output paths to wget instead of relying on `cd` - less magical context in the script
- handle unpacking of zips and RPMs where multiple versions of a single component are present in the cache - the most recent file is selected
- don't force unzip to automatically overwrite files, this was weird and was likely needed because one zip was unpacked twice
- generate rpm lists for arm and arm64 architecture from a common source of truth

Signed-off-by: Maciej Grela <m.grela@samsung.com>

* Restyled by shellharden

* [Linux/Tizen] convert chip-build-tizen Dockerfile to new install.sh syntax

Signed-off-by: Maciej Grela <m.grela@samsung.com>

* [Linux/Tizen] Further install.sh fixes and improvements

- pass arrays properly to unpack functions
- secure against shellharden injecting empty argv items into unzip calls, unzip does not like that

Signed-off-by: Maciej Grela <m.grela@samsung.com>

* Restyled by whitespace

* Restyled by shellharden

* [Linux/Tizen] Make the script pass shellharden without issues

Signed-off-by: Maciej Grela <m.grela@samsung.com>

* install.sh: Fix typo

Signed-off-by: Maciej Grela <m.grela@samsung.com>

* Restyled by shfmt

* Address review comments

- do not purge when building docker images
- declare all local vars with `local`
- convert all local var names to lowercase
- fix wrong use of ${@} in dowload()

Signed-off-by: Maciej Grela <m.grela@samsung.com>

* Restyled by shellharden

* Use proper base image in chip-build-tizen

Signed-off-by: Maciej Grela <m.grela@samsung.com>

* Bump Docker image version

Signed-off-by: Maciej Grela <m.grela@samsung.com>

---------

Signed-off-by: Maciej Grela <m.grela@samsung.com>
Co-authored-by: Restyled.io <commits@restyled.io>
diff --git a/integrations/docker/images/base/chip-build/version b/integrations/docker/images/base/chip-build/version
index 3c970c7..c376354 100644
--- a/integrations/docker/images/base/chip-build/version
+++ b/integrations/docker/images/base/chip-build/version
@@ -1 +1 @@
-150 : [Telink] Update Docker image (Zephyr update)
+151 : [Tizen] Update SDK version to 9.0
diff --git a/integrations/docker/images/stage-2/chip-build-tizen/Dockerfile b/integrations/docker/images/stage-2/chip-build-tizen/Dockerfile
index de8ba4d..07dc787 100644
--- a/integrations/docker/images/stage-2/chip-build-tizen/Dockerfile
+++ b/integrations/docker/images/stage-2/chip-build-tizen/Dockerfile
@@ -20,17 +20,20 @@
 
 # ------------------------------------------------------------------------------
 # Install tizen
-ENV TIZEN_VERSION=8.0
+ENV TIZEN_VERSION=9.0
 ENV TIZEN_SDK_ROOT=/opt/tizen-sdk
 
 COPY tizen-sdk-installer $TIZEN_SDK_ROOT/files/installer
 RUN set -x \
     && bash $TIZEN_SDK_ROOT/files/installer/install.sh \
+    --verbose \
     --cpu arm,arm64 \
     --tizen-sdk-path $TIZEN_SDK_ROOT \
     --tizen-sdk-data-path /home/ubuntu/tizen-sdk-data \
     --tizen-version $TIZEN_VERSION  \
+    --pkg-cache-path /tmp/tizen-pkg/$TIZEN_VERSION \
     --override-secret-tool \
+    && rm -rf /tmp/tizen-pkg \
     && : # last line
 
 # ------------------------------------------------------------------------------
diff --git a/integrations/docker/images/stage-2/chip-build-tizen/tizen-sdk-installer/install.sh b/integrations/docker/images/stage-2/chip-build-tizen/tizen-sdk-installer/install.sh
index 1ebfd23..f8eb2c9 100755
--- a/integrations/docker/images/stage-2/chip-build-tizen/tizen-sdk-installer/install.sh
+++ b/integrations/docker/images/stage-2/chip-build-tizen/tizen-sdk-installer/install.sh
@@ -21,12 +21,21 @@
 # Default settings options
 TIZEN_SDK_ROOT=/opt/tizen-sdk
 TIZEN_SDK_DATA_PATH=$HOME/tizen-sdk-data
-TIZEN_VERSION=8.0
-SECRET_TOOL=false
+TIZEN_VERSION=9.0
+unset SECRET_TOOL
+unset PURGE_PKG_CACHE PKG_CACHE_PATH
+unset VERBOSE
+DEBUG=${DEBUG:-}
+
+[ -n "$DEBUG" ] && set -x
+[ -n "$DEBUG" ] && VERBOSE=true
 
 SCRIPT_NAME=$(basename -- "$(readlink -f "${BASH_SOURCE:?}")")
 SCRIPT_DIR=$(dirname -- "$(readlink -f "${BASH_SOURCE:?}")")
-DEPENDENCIES=('7zip' 'cpio' 'openjdk-8-jre-headless' 'wget' 'zip')
+
+DEPENDENCIES=('7zip' 'cpio' 'openjdk-8-jre-headless' 'zip' 'wget')
+# Commands used to check the presence of the above dependencies
+DEPENCENCIES_CMDS=('7z' 'cpio' 'java' 'unzip' 'wget')
 
 # If color is available use colors
 if which tput >/dev/null 2>&1 && [[ $(tput -T $TERM colors) -ge 8 ]]; then
@@ -41,13 +50,16 @@
 # Help display function
 function show_help() {
     echo "Usage: $SCRIPT_NAME [ options .. ]"
-    echo "Example: $SCRIPT_NAME --tizen-sdk-path ~/tizen-sdk --tizen-version 7.0 --install-dependencies"
+    echo "Example: $SCRIPT_NAME --tizen-sdk-path ~/tizen-sdk --tizen-version 9.0 --install-dependencies"
     echo
     echo "Options:"
     echo "  -h, --help                 Display this information"
+    echo "  --verbose                  Verbose output"
     echo "  --cpu                      Comma separated list of CPU architectures. Like arm or arm64"
     echo "  --tizen-sdk-path           Set directory for Tizen SDK installation. Default is $TIZEN_SDK_ROOT"
     echo "  --tizen-sdk-data-path      Set directory for Tizen SDK runtime data. Default is $TIZEN_SDK_DATA_PATH"
+    echo "  --purge-pkg-cache          Purge package cache before download and installation"
+    echo "  --pkg-cache-path           Set the Tizen SDK package cache path, default is ${XDG_CACHE_HOME:-$HOME/.cache}/tizen-pkgs/$TIZEN_VERSION"
     echo "  --install-dependencies     This option installs all required dependencies"
     echo "  --tizen-version            Select Tizen version. Default is $TIZEN_VERSION"
     echo "  --override-secret-tool     Circumvent the requirement of having functional D-Bus Secrets service"
@@ -63,19 +75,19 @@
 # ------------------------------------------------------------------------------
 # Error print function
 function error() {
-    echo "$COLOR_RED[ERROR]: $1$COLOR_NONE"
+    echo "$COLOR_RED[ERROR]: $@$COLOR_NONE"
 }
 
 # ------------------------------------------------------------------------------
 # Info print function
 function info() {
-    echo "$COLOR_GREEN[INFO]: $1$COLOR_NONE"
+    echo "$COLOR_GREEN[INFO]: $@$COLOR_NONE"
 }
 
 # ------------------------------------------------------------------------------
 # Warning print function
 function warning() {
-    echo "$COLOR_YELLOW[WARNING]: $1$COLOR_NONE"
+    echo "$COLOR_YELLOW[WARNING]: $@$COLOR_NONE"
 }
 
 # ------------------------------------------------------------------------------
@@ -83,6 +95,7 @@
 function show_dependencies() {
     warning "Required dependencies for Tizen SDK installation: 7z cpio unzip wget"
     warning "Required dependencies for Tizen SDK: JAVA JRE >=8.0"
+    warning "To install them on Debian or Ubuntu run: sudo apt install ${DEPENDENCIES[*]}"
 }
 
 # ------------------------------------------------------------------------------
@@ -90,21 +103,25 @@
 # Usage: download "url_dir_package" ${package_array[@]}
 function download() {
     echo "$COLOR_BLUE"
+    local url="$1"
+    shift
 
-    PKGS=()
-    for PKG in "${@:2}"; do
-        PKGS+=("-A" "$PKG")
+    local pkg pkgs=()
+    for pkg in "${@}"; do
+        pkgs+=("-A" "$pkg")
     done
 
     # Skip downloading if no packages are specified
-    [[ ${#PKGS[@]} -eq 0 ]] && return
+    [[ ${#pkgs[@]} -eq 0 ]] && return
 
-    wget -r -nd --no-parent -e robots=off --progress=dot:mega "${PKGS[@]}" "$1"
+    local verbose_flag="-nv"
+    [ -n "$VERBOSE" ] && verbose_flag="-v"
+    wget -r "$verbose_flag" -nd --timestamping --no-parent -e robots=off --progress=dot:mega -P "$PKG_CACHE_PATH" "${pkgs[@]}" "$url"
 
     # Check if the files have been downloaded
-    for PKG in "${@:2}"; do
-        if [[ ! $(find . -type f -name "$PKG") ]]; then
-            error "PKG is missing: $PKG"
+    for pkg in "${@}"; do
+        if [[ ! $(find "$PKG_CACHE_PATH" -type f -name "$pkg") ]]; then
+            error "PKG is missing: $pkg"
             return 1
         fi
     done
@@ -127,290 +144,280 @@
 }
 
 # ------------------------------------------------------------------------------
-# Function for unpacking RPM packages.
-function unrpm() {
-    for PKG in "${@}"; do
-        echo "Extracting $PKG..."
-        7z x -so "$PKG" | cpio -idmv
+# Function for unpacking RPM filesets. Implements identical logic like the
+# unzip_globs function below wrt. globs matching multiple RPMs.
+function unrpm_globs() {
+    local destdir="$1"
+    local fn_glob
+    local -n globs="$2"
+    for fn_glob in "${globs[@]}"; do
+        info "Processing filenames matching '$fn_glob' ..."
+
+        local most_recent_filename="$(find "$PKG_CACHE_PATH" -name "$fn_glob" -printf '%T@ %p\n' |
+            sort -nr | head -n 1 | cut -d ' ' -f 2)"
+
+        info "Unpacking '$most_recent_filename' into '$destdir'"
+        7z x -so "$most_recent_filename" | cpio --directory="$destdir" -idmu "${VERBOSE:+-v}"
+    done
+}
+# ------------------------------------------------------------------------------
+# Function for unpacking ZIP filesets. A fileset specifies a number of filename
+# globs. In a situation where multiple zip files matching the same glob are
+# present, for example:
+# $ ls -l cache/tizen-9.0-core-add-ons_*_ubuntu-64.zip
+# -rw-rw-r-- 1 m.grela m.grela 461718 Nov  4  2024 cache/tizen-9.0-core-add-ons_0.0.341_ubuntu-64.zip
+# -rw-rw-r-- 1 m.grela m.grela 461722 Apr 16 01:45 cache/tizen-9.0-core-add-ons_0.0.358_ubuntu-64.zip
+#
+# the function attempts to find the most recent version of a particular zip file.
+#
+# The function assumes that every glob matches at lease one filename.
+function unzip_globs() {
+    local destdir="$1"
+    local fn_glob
+    local -n globs="$2"
+    for fn_glob in "${globs[@]}"; do
+        info "Processing filenames matching '$fn_glob' ..."
+
+        local most_recent_filename="$(find "$PKG_CACHE_PATH" -name "$fn_glob" -printf '%T@ %p\n' |
+            sort -nr | head -n 1 | cut -d ' ' -f 2)"
+
+        info "Unpacking '$most_recent_filename' into '$destdir'"
+
+        # Shellharden puts quotes around ${VERBOSE:+-v} which causes an empty
+        # argv item to be passed to unzip when $VERBOSE is unset.
+        # unzip does not like that and bails out.
+        if [ -n "$VERBOSE" ]; then
+            unzip "$most_recent_filename" 'data/*' -d "$destdir"
+        else
+            unzip -q "$most_recent_filename" 'data/*' -d "$destdir"
+        fi
     done
 }
 
 # ------------------------------------------------------------------------------
 # Function for cleaning up temporary files on exit.
 function cleanup() {
-    rm -rf "${TMP_DIR:?}"
+    rm "$TIZEN_SDK_ROOT/data" || true
+}
+
+# Make symbolic links relative
+function fixup_symlinks() {
+    while [ "$1" ]; do
+        local path="$1"
+        shift
+        [ -d "$path" ] || continue
+
+        find "$path" -maxdepth 1 -type l | while IFS= read -r lnk; do
+            ln -sf "$(basename "$(readlink "$lnk")")" "$lnk"
+        done
+    done
+}
+
+function download_tizen_sdk_common() {
+    # Get Tizen Studio CLI
+    info "Downloading Tizen Studio CLI..."
+    local url
+
+    # Download
+    url="http://download.tizen.org/sdk/tizenstudio/official/binary/"
+    download "$url" "${COMMON_TIZENSTUDIO_ZIPS[@]}" "${COMMON_TOOLCHAIN_ZIPS[@]}"
+
+    # Tizen Developer Platform Certificate
+    url="http://download.tizen.org/sdk/extensions/Tizen_IoT_Headless/binary/"
+    # Tizen site does not have this package available in version 8.0.
+    # Certificates are the same for 7.0 and 8.0, though.
+    download "$url" "${IOT_ZIPS[@]}"
 }
 
 # ------------------------------------------------------------------------------
 # Function for installing common packages for Tizen SDK.
 function install_tizen_sdk_common() {
-    mkdir -p "$TIZEN_SDK_ROOT" || return
 
     info "Tizen SDK installation directory: $TIZEN_SDK_ROOT"
 
-    cd "$TMP_DIR" || return
-
-    # Get Tizen Studio CLI
-    info "Downloading Tizen Studio CLI..."
-
-    # Download
-    URL="http://download.tizen.org/sdk/tizenstudio/official/binary/"
-    PKG_ARR=(
-        'certificate-encryptor_1.0.10_ubuntu-64.zip'
-        'certificate-generator_0.1.4_ubuntu-64.zip'
-        'new-common-cli_2.5.64_ubuntu-64.zip'
-        'new-native-cli_2.5.64_ubuntu-64.zip'
-        'sdb_4.2.23_ubuntu-64.zip'
-        "tizen-$TIZEN_VERSION-core-add-ons_*_ubuntu-64.zip")
-    download "$URL" "${PKG_ARR[@]}"
-
-    # Tizen Developer Platform Certificate
-    URL="http://download.tizen.org/sdk/extensions/Tizen_IoT_Headless/binary/"
-    # Tizen site does not have this package available in version 8.0.
-    # Certificates are the same for 7.0 and 8.0, though.
-    PKG_ARR=(
-        "7.0-iot-things-add-ons_*_ubuntu-64.zip")
-    download "$URL" "${PKG_ARR[@]}"
-
     info "Installing Tizen Studio CLI..."
 
-    unzip -o '*.zip'
-    cp -rf data/* "$TIZEN_SDK_ROOT"
+    unzip_globs "$TIZEN_SDK_ROOT" COMMON_TIZENSTUDIO_ZIPS
 
-    # Cleanup
-    rm -rf -- *
+    info "Installing common toolchain files ..."
+    unzip_globs "$TIZEN_SDK_ROOT" COMMON_TOOLCHAIN_ZIPS
 
+    info "Installing common IoT files ..."
+    unzip_globs "$TIZEN_SDK_ROOT" IOT_ZIPS
 }
 
-# Function for installing Tizen SDK (arm).
-function install_tizen_sdk_arm() {
+# Take care to provide separate globs for binary and devel RPMs
+# The "most-recent-file" detection logic will eat unsuspecting packages
+# when for example capi-network-thread-*.armv7l.rpm matches both
+# binary and devel RPM files. For example:
+# ls -l cache/capi-network-thread-*.armv7l.rpm
+# -rw-rw-r-- 1 m.grela m.grela 55133 Oct 30  2024 cache/capi-network-thread-0.9.0-2.armv7l.rpm
+# -rw-rw-r-- 1 m.grela m.grela 17377 Oct 30  2024 cache/capi-network-thread-devel-0.9.0-2.armv7l.rpm
+# -rw-rw-r-- 1 m.grela m.grela 25965 Oct 30  2024 cache/capi-network-thread-test-0.9.0-2.armv7l.rpm
 
-    SYSROOT="$TIZEN_SDK_ROOT/platforms/tizen-$TIZEN_VERSION/tizen/rootstraps/tizen-$TIZEN_VERSION-device.core"
+TIZEN_SDK_BASE_RPMS=(
+    'iniparser-[0-9]*'
+    'iniparser-devel-*'
+    'libblkid-devel-*'
+    'libcap-[0-9]*'
+    'libcap-devel-*'
+    'libffi-devel-*'
+    'liblzma-[0-9]*'
+    'libmount-devel-*'
+    'libncurses6-[0-9]*'
+    'libreadline-[0-9]*'
+    'libuuid-[0-9]*'
+    'libuuid-devel-*'
+    'pcre-devel-*'
+    'readline-devel-*'
+    'xdgmime-[0-9]*'
+    'xdgmime-devel-*'
+)
 
+# this could have been a function but shellharden does not allow us to
+declare -a TIZEN_SDK_ARM_BASE_RPMS TIZEN_SDK_ARM64_BASE_RPMS
+for rpm in "${TIZEN_SDK_BASE_RPMS[@]}"; do
+    TIZEN_SDK_ARM_BASE_RPMS+=("$rpm.armv7l.rpm")
+    TIZEN_SDK_ARM64_BASE_RPMS+=("$rpm.aarch64.rpm")
+done
+
+TIZEN_SDK_UNIFIED_RPMS=(
+    'app-core-common-[0-9]*'
+    'app-core-common-devel-*'
+    'aul-[0-9]*'
+    'aul-devel-*'
+    'bluetooth-frwk-0*'
+    'bluetooth-frwk-devel-*'
+    'bundle-0*'
+    'bundle-devel-*'
+    'buxton2-[0-9]*'
+    'buxton2-devel-*'
+    'capi-network-bluetooth-0*'
+    'capi-network-bluetooth-devel-*'
+    'capi-network-nsd-[0-9]*'
+    'capi-network-nsd-devel-*'
+    'capi-network-thread-[0-9]*'
+    'capi-network-thread-devel-*'
+    'capi-system-peripheral-io-[0-9]*'
+    'capi-system-peripheral-io-devel-*'
+    'capi-system-resource-1*'
+    'capi-system-resource-devel-*'
+    'cynara-devel-*'
+    'dbus-1*'
+    'dbus-devel-*'
+    'dbus-libs-1*'
+    'glib2-devel-2*'
+    'pcre2-devel-[0-9]*'
+    'hal-api-common-[0-9]*'
+    'hal-api-common-devel-*'
+    'hal-api-sensor-[0-9]*'
+    'hal-api-sensor-devel-*'
+    'json-glib-devel-*'
+    'libcynara-client-[0-9]*'
+    'libcynara-commons-[0-9]*'
+    'libdns_sd-[0-9]*'
+    'libjson-glib-[0-9]*'
+    'libnsd-dns-sd-[0-9]*'
+    'libsessiond-0*'
+    'libsystemd-[0-9]*'
+    'libtzplatform-config-[0-9]*'
+    'libtzplatform-config-devel-*'
+    'parcel-0*'
+    'parcel-devel-*'
+    'pkgmgr-info-[0-9]*'
+    'pkgmgr-info-devel-*'
+    'sensord-[0-9]*'
+    'sensord-devel-*'
+    'sensord-dummy-[0-9]*'
+    'vconf-compat-[0-9]*'
+    'vconf-compat-devel*'
+    'vconf-internal-keys-devel-*'
+)
+
+for rpm in "${TIZEN_SDK_UNIFIED_RPMS[@]}"; do
+    TIZEN_SDK_ARM_UNIFIED_RPMS+=("$rpm.armv7l.rpm")
+    TIZEN_SDK_ARM64_UNIFIED_RPMS+=("$rpm.aarch64.rpm")
+done
+
+function download_tizen_sdk_arm() {
     # Get toolchain
-    info "Downloading Tizen ARM toolchain..."
+    info "Downloading Tizen ARM toolchain and sysroot ..."
+    local url
 
     # Download
-    URL="http://download.tizen.org/sdk/tizenstudio/official/binary/"
-    PKG_ARR=(
-        "cross-arm-gcc-9.2_0.1.9_ubuntu-64.zip"
-        "sbi-toolchain-gcc-9.2.cpp.app_2.2.16_ubuntu-64.zip")
-    download "$URL" "${PKG_ARR[@]}"
-
-    # Get Tizen sysroot
-    info "Downloading Tizen ARM sysroot..."
-
-    # Base sysroot
-    # Different versions of Tizen have different rootstrap versions
-    URL="http://download.tizen.org/sdk/tizenstudio/official/binary/"
-    PKG_ARR=(
-        "tizen-$TIZEN_VERSION-rs-device.core_*_ubuntu-64.zip")
-    download "$URL" "${PKG_ARR[@]}"
+    url="http://download.tizen.org/sdk/tizenstudio/official/binary/"
+    download "$url" "${SDK_ARM_TIZENSTUDIO_ZIPS[@]}"
 
     # Base packages
-    URL="http://download.tizen.org/releases/milestone/TIZEN/Tizen-$TIZEN_VERSION/Tizen-$TIZEN_VERSION-Base/latest/repos/standard/packages/armv7l/"
-    PKG_ARR=(
-        'iniparser-*.armv7l.rpm'
-        'libblkid-devel-*.armv7l.rpm'
-        'libcap-*.armv7l.rpm'
-        'libffi-devel-*.armv7l.rpm'
-        'liblzma-*.armv7l.rpm'
-        'libmount-devel-*.armv7l.rpm'
-        'libncurses6-*.armv7l.rpm'
-        'libreadline-*.armv7l.rpm'
-        'libuuid-*.armv7l.rpm'
-        'pcre-devel-*.armv7l.rpm'
-        'readline-devel-*.armv7l.rpm'
-        'xdgmime-*.armv7l.rpm')
-    download "$URL" "${PKG_ARR[@]}"
+    url="http://download.tizen.org/releases/milestone/TIZEN/Tizen-$TIZEN_VERSION/Tizen-$TIZEN_VERSION-Base/latest/repos/standard/packages/armv7l/"
+    download "$url" "${TIZEN_SDK_ARM_BASE_RPMS[@]}"
 
     # Unified packages
-    URL="http://download.tizen.org/releases/milestone/TIZEN/Tizen-$TIZEN_VERSION/Tizen-$TIZEN_VERSION-Unified/latest/repos/standard/packages/armv7l/"
-    PKG_ARR=(
-        'app-core-common-*.armv7l.rpm'
-        'aul-0*.armv7l.rpm'
-        'aul-devel-*.armv7l.rpm'
-        'bluetooth-frwk-0*.armv7l.rpm'
-        'bundle-0*.armv7l.rpm'
-        'bundle-devel-*.armv7l.rpm'
-        'buxton2-*.armv7l.rpm'
-        'capi-network-bluetooth-0*.armv7l.rpm'
-        'capi-network-bluetooth-devel-*.armv7l.rpm'
-        'capi-network-nsd-*.armv7l.rpm'
-        'capi-network-thread-*.armv7l.rpm'
-        'capi-system-peripheral-io-*.armv7l.rpm'
-        'capi-system-peripheral-io-devel-*.armv7l.rpm'
-        'capi-system-resource-1*.armv7l.rpm'
-        'cynara-devel-*.armv7l.rpm'
-        'dbus-1*.armv7l.rpm'
-        'dbus-devel-*.armv7l.rpm'
-        'dbus-libs-1*.armv7l.rpm'
-        'glib2-devel-2*.armv7l.rpm'
-        'hal-api-common-*.armv7l.rpm'
-        'hal-api-sensor-*.armv7l.rpm'
-        'json-glib-devel-*.armv7l.rpm'
-        'libcynara-client-*.armv7l.rpm'
-        'libcynara-commons-*.armv7l.rpm'
-        'libdns_sd-*.armv7l.rpm'
-        'libjson-glib-*.armv7l.rpm'
-        'libnsd-dns-sd-*.armv7l.rpm'
-        'libsessiond-0*.armv7l.rpm'
-        'libsystemd-*.armv7l.rpm'
-        'libtzplatform-config-*.armv7l.rpm'
-        'parcel-0*.armv7l.rpm'
-        'parcel-devel-*.armv7l.rpm'
-        'pkgmgr-info-*.armv7l.rpm'
-        'sensord-*.armv7l.rpm'
-        'sensord-devel-*.armv7l.rpm'
-        'sensord-dummy-*.armv7l.rpm'
-        'vconf-compat-*.armv7l.rpm'
-        'vconf-internal-keys-devel-*.armv7l.rpm')
-    download "$URL" "${PKG_ARR[@]}"
+    url="http://download.tizen.org/releases/milestone/TIZEN/Tizen-$TIZEN_VERSION/Tizen-$TIZEN_VERSION-Unified/latest/repos/standard/packages/armv7l/"
+    download "$url" "${TIZEN_SDK_ARM_UNIFIED_RPMS[@]}"
+}
 
-    # Unified packages (snapshots)
-    URL="http://download.tizen.org/snapshots/TIZEN/Tizen/Tizen-Unified/latest/repos/standard/packages/armv7l/"
-    PKG_ARR=()
-    download "$URL" "${PKG_ARR[@]}"
+# Function for installing Tizen SDK (armv7l).
+function install_tizen_sdk_arm() {
+
+    local sysroot="$TIZEN_SDK_ROOT/platforms/tizen-$TIZEN_VERSION/tizen/rootstraps/tizen-$TIZEN_VERSION-device.core"
 
     info "Installing Tizen ARM SDK..."
 
-    unzip -o '*.zip'
-    cp -rf data/* "$TIZEN_SDK_ROOT"
+    unzip_globs "$TIZEN_SDK_ROOT" SDK_ARM_TIZENSTUDIO_ZIPS
 
-    unrpm *.rpm
-    cp -rf lib usr "$SYSROOT"
+    info "Installing Tizen ARM sysroot..."
 
-    # Make symbolic links relative
-    find "$SYSROOT/usr/lib" -maxdepth 1 -type l | while IFS= read -r LNK; do
-        ln -sf "$(basename "$(readlink "$LNK")")" "$LNK"
-    done
+    unrpm_globs "$sysroot" TIZEN_SDK_ARM_BASE_RPMS
+    unrpm_globs "$sysroot" TIZEN_SDK_ARM_UNIFIED_RPMS
 
-    ln -sf ../../lib/libcap.so.2 "$SYSROOT/usr/lib/libcap.so"
-    ln -sf openssl3.pc "$SYSROOT/usr/lib/pkgconfig/openssl.pc"
+    fixup_symlinks "$sysroot"/usr/{lib,lib64}
 
-    # Cleanup
-    rm -rf -- *
+    # TODO: Is this required???
+    ln -sf openssl3.pc "$sysroot/usr/lib/pkgconfig/openssl.pc"
+}
 
+function download_tizen_sdk_arm64() {
+    info "Downloading Tizen ARM64 toolchain and sysroot ..."
+    local url
+
+    # Download
+    url="http://download.tizen.org/sdk/tizenstudio/official/binary/"
+    download "$url" "${SDK_ARM64_TIZENSTUDIO_ZIPS[@]}"
+
+    # Base packages
+    url="http://download.tizen.org/releases/milestone/TIZEN/Tizen-$TIZEN_VERSION/Tizen-$TIZEN_VERSION-Base/latest/repos/standard/packages/aarch64/"
+    download "$url" "${TIZEN_SDK_ARM64_BASE_RPMS[@]}"
+
+    # Unified packages
+    url="http://download.tizen.org/releases/milestone/TIZEN/Tizen-$TIZEN_VERSION/Tizen-$TIZEN_VERSION-Unified/latest/repos/standard/packages/aarch64/"
+    download "$url" "${TIZEN_SDK_ARM64_UNIFIED_RPMS[@]}"
 }
 
 # Function for installing Tizen SDK (arm64).
 function install_tizen_sdk_arm64() {
 
-    SYSROOT="$TIZEN_SDK_ROOT/platforms/tizen-$TIZEN_VERSION/tizen/rootstraps/tizen-$TIZEN_VERSION-device64.core"
-
-    # Get toolchain
-    info "Downloading Tizen ARM64 toolchain..."
-
-    # Download
-    URL="http://download.tizen.org/sdk/tizenstudio/official/binary/"
-    PKG_ARR=(
-        "cross-aarch64-gcc-9.2_0.1.9_ubuntu-64.zip"
-        "sbi-toolchain-gcc-9.2.cpp.app_2.2.16_ubuntu-64.zip")
-    download "$URL" "${PKG_ARR[@]}"
-
-    # Get Tizen sysroot
-    info "Downloading Tizen ARM64 sysroot..."
-
-    # Base sysroot
-    # Different versions of Tizen have different rootstrap versions
-    URL="http://download.tizen.org/sdk/tizenstudio/official/binary/"
-    PKG_ARR=(
-        "tizen-$TIZEN_VERSION-rs-device64.core_*_ubuntu-64.zip")
-    download "$URL" "${PKG_ARR[@]}"
-
-    # Base packages
-    URL="http://download.tizen.org/releases/milestone/TIZEN/Tizen-$TIZEN_VERSION/Tizen-$TIZEN_VERSION-Base/latest/repos/standard/packages/aarch64/"
-    PKG_ARR=(
-        'iniparser-*.aarch64.rpm'
-        'libblkid-devel-*.aarch64.rpm'
-        'libcap-*.aarch64.rpm'
-        'libffi-devel-*.aarch64.rpm'
-        'liblzma-*.aarch64.rpm'
-        'libmount-devel-*.aarch64.rpm'
-        'libncurses6-*.aarch64.rpm'
-        'libreadline-*.aarch64.rpm'
-        'libuuid-*.aarch64.rpm'
-        'pcre-devel-*.aarch64.rpm'
-        'readline-devel-*.aarch64.rpm'
-        'xdgmime-*.aarch64.rpm')
-    download "$URL" "${PKG_ARR[@]}"
-
-    # Unified packages
-    URL="http://download.tizen.org/releases/milestone/TIZEN/Tizen-$TIZEN_VERSION/Tizen-$TIZEN_VERSION-Unified/latest/repos/standard/packages/aarch64/"
-    PKG_ARR=(
-        'app-core-common-*.aarch64.rpm'
-        'aul-0*.aarch64.rpm'
-        'aul-devel-*.aarch64.rpm'
-        'bluetooth-frwk-0*.aarch64.rpm'
-        'bundle-0*.aarch64.rpm'
-        'bundle-devel-*.aarch64.rpm'
-        'buxton2-*.aarch64.rpm'
-        'capi-network-bluetooth-0*.aarch64.rpm'
-        'capi-network-bluetooth-devel-*.aarch64.rpm'
-        'capi-network-nsd-*.aarch64.rpm'
-        'capi-network-thread-*.aarch64.rpm'
-        'capi-system-peripheral-io-*.aarch64.rpm'
-        'capi-system-peripheral-io-devel-*.aarch64.rpm'
-        'capi-system-resource-1*.aarch64.rpm'
-        'cynara-devel-*.aarch64.rpm'
-        'dbus-1*.aarch64.rpm'
-        'dbus-devel-*.aarch64.rpm'
-        'dbus-libs-1*.aarch64.rpm'
-        'glib2-devel-2*.aarch64.rpm'
-        'hal-api-common-*.aarch64.rpm'
-        'hal-api-sensor-*.aarch64.rpm'
-        'json-glib-devel-*.aarch64.rpm'
-        'libcynara-client-*.aarch64.rpm'
-        'libcynara-commons-*.aarch64.rpm'
-        'libdns_sd-*.aarch64.rpm'
-        'libjson-glib-*.aarch64.rpm'
-        'libnsd-dns-sd-*.aarch64.rpm'
-        'libsessiond-0*.aarch64.rpm'
-        'libsystemd-*.aarch64.rpm'
-        'libtzplatform-config-*.aarch64.rpm'
-        'parcel-0*.aarch64.rpm'
-        'parcel-devel-*.aarch64.rpm'
-        'pkgmgr-info-*.aarch64.rpm'
-        'sensord-*.aarch64.rpm'
-        'sensord-devel-*.aarch64.rpm'
-        'sensord-dummy-*.aarch64.rpm'
-        'vconf-compat-*.aarch64.rpm'
-        'vconf-internal-keys-devel-*.aarch64.rpm'
-    )
-    download "$URL" "${PKG_ARR[@]}"
-
-    # Unified packages (snapshots)
-    URL="http://download.tizen.org/snapshots/TIZEN/Tizen/Tizen-Unified/latest/repos/standard/packages/aarch64/"
-    PKG_ARR=()
-    download "$URL" "${PKG_ARR[@]}"
+    local sysroot="$TIZEN_SDK_ROOT/platforms/tizen-$TIZEN_VERSION/tizen/rootstraps/tizen-$TIZEN_VERSION-device64.core"
 
     info "Installing Tizen ARM64 SDK..."
 
-    unzip -o '*.zip'
-    cp -rf data/* "$TIZEN_SDK_ROOT"
+    unzip_globs "$TIZEN_SDK_ROOT" SDK_ARM64_TIZENSTUDIO_ZIPS
 
     info "Installing Tizen ARM64 sysroot..."
 
-    unrpm *.rpm
-    cp -rf lib64 usr "$SYSROOT"
+    unrpm_globs "$sysroot" TIZEN_SDK_ARM64_BASE_RPMS
+    unrpm_globs "$sysroot" TIZEN_SDK_ARM64_UNIFIED_RPMS
 
-    # Make symbolic links relative
-    find "$SYSROOT/usr/lib64" -maxdepth 1 -type l | while IFS= read -r LNK; do
-        ln -sf "$(basename "$(readlink "$LNK")")" "$LNK"
-    done
+    fixup_symlinks "$sysroot"/usr/{lib,lib64}
 
-    ln -sf ../../lib64/libcap.so.2 "$SYSROOT/usr/lib64/libcap.so"
-    ln -sf openssl3.pc "$SYSROOT/usr/lib64/pkgconfig/openssl.pc"
-
-    # Cleanup
-    rm -rf -- *
-
+    # TODO: Is this required???
+    ln -sf openssl3.pc "$sysroot/usr/lib64/pkgconfig/openssl.pc"
 }
 
 function install_tizen_sdk_finalize() {
 
     # Install secret tool or not
-    if ("$SECRET_TOOL"); then
+    if [ -n "$SECRET_TOOL" ]; then
         info "Overriding secret tool..."
         install "$SCRIPT_DIR/secret-tool.py" "$TIZEN_SDK_ROOT/tools/certificate-encryptor/secret-tool"
     fi
@@ -453,15 +460,18 @@
             show_help
             exit 0
             ;;
+        --verbose)
+            VERBOSE=true
+            ;;
         --cpu)
             IFS=',' read -r -a array <<<"$2"
-            for CPU in "${array[@]}"; do
-                if [ "$CPU" == "arm" ]; then
+            for cpu in "${array[@]}"; do
+                if [ "$cpu" == "arm" ]; then
                     INSTALL_ARM=true
-                elif [ "$CPU" == "arm64" ]; then
+                elif [ "$cpu" == "arm64" ]; then
                     INSTALL_ARM64=true
                 else
-                    error "Invalid CPU: $CPU. Use --help"
+                    error "Invalid CPU: $cpu. Use --help"
                     exit 1
                 fi
             done
@@ -475,8 +485,15 @@
             TIZEN_SDK_DATA_PATH="$2"
             shift
             ;;
+        --purge-pkg-cache)
+            PURGE_PKG_CACHE=true
+            ;;
+        --pkg-cache-path)
+            PKG_CACHE_PATH="$2"
+            shift
+            ;;
         --tizen-version)
-            TIZEN_VERSION=$2
+            TIZEN_VERSION="$2"
             shift
             ;;
         --install-dependencies)
@@ -500,11 +517,14 @@
     exit 1
 fi
 
-# ------------------------------------------------------------------------------
-# Prepare a temporary directory and cleanup
-trap cleanup EXIT
-TMP_DIR=$(mktemp -d)
-info "Created tmp directory $TMP_DIR"
+[ -z "$PKG_CACHE_PATH" ] && PKG_CACHE_PATH=${XDG_CACHE_HOME:-$HOME/.cache}/tizen-pkgs/$TIZEN_VERSION
+info "Using package cache '$PKG_CACHE_PATH'"
+
+[ ! -d "$PKG_CACHE_PATH" ] && mkdir -p "$PKG_CACHE_PATH"
+if [ -n "$PURGE_PKG_CACHE" ]; then
+    warning "Purging package cache in '$PKG_CACHE_PATH'"
+    rm -rf "${VERBOSE:+-v}" "$PKG_CACHE_PATH"/*
+fi
 
 # ------------------------------------------------------------------------------
 # Checks if the user need install dependencies
@@ -515,31 +535,76 @@
         exit 1
     fi
 fi
-
 # ------------------------------------------------------------------------------
 # Checking dependencies needed to install Tizen platform
-info "Checking required tools: 7z, cpio, java, unzip, wget"
-for PKG in '7z' 'cpio' 'java' 'unzip' 'wget'; do
-    if ! command -v "$PKG" &>/dev/null; then
-        error "Required tool not found: $PKG"
-        dep_lost=1
+info "Checking required tools: ${DEPENDENCIES_CMDS[@]}"
+for cmd in "${DEPENDENCIES_CMDS[@]}"; do
+    if ! command -v "$cmd" &>/dev/null; then
+        error "Required tool not found: $cmd"
+        show_dependencies
+        exit 1
     fi
 done
-if [[ $dep_lost ]]; then
-    echo "[HINT]: sudo apt-get install ${DEPENDENCIES[*]}"
-    exit 1
-fi
+
+# ------------------------------------------------------------------------------
+# Generate zip and RPM lists whose names are dependent upon $TIZEN_VERSION
+
+COMMON_TIZENSTUDIO_ZIPS=(
+    'certificate-encryptor_1.0.10_ubuntu-64.zip'
+    'certificate-generator_0.1.4_ubuntu-64.zip'
+    'new-common-cli_2.5.64_ubuntu-64.zip'
+    'new-native-cli_2.5.64_ubuntu-64.zip'
+    'sdb_4.2.23_ubuntu-64.zip'
+    "tizen-$TIZEN_VERSION-core-add-ons_*_ubuntu-64.zip"
+)
+
+IOT_ZIPS=(
+    "7.0-iot-things-add-ons_*_ubuntu-64.zip"
+)
+
+COMMON_TOOLCHAIN_ZIPS=(
+    'sbi-toolchain-gcc-9.2.cpp.app_2.2.16_ubuntu-64.zip'
+)
+
+SDK_ARM_TIZENSTUDIO_ZIPS=(
+    "cross-arm-gcc-9.2_0.1.9_ubuntu-64.zip"
+    # Base sysroot
+    "tizen-$TIZEN_VERSION-rs-device.core_*_ubuntu-64.zip"
+)
+
+SDK_ARM64_TIZENSTUDIO_ZIPS=(
+    "cross-aarch64-gcc-9.2_0.1.9_ubuntu-64.zip"
+    # Base sysroot
+    "tizen-$TIZEN_VERSION-rs-device64.core_*_ubuntu-64.zip"
+)
 
 # ------------------------------------------------------------------------------
 # Installation Tizen SDK
+mkdir -p "$TIZEN_SDK_ROOT"
+
+if [ -e "$TIZEN_SDK_ROOT"/data ]; then
+    error "'$TIZEN_SDK_ROOT' is not empty, bailing out"
+    exit 1
+fi
+# Trick unzip into junking the first path component:
+# Then unzip -o '*.zip' 'data/*' -d "$TIZEN_SDK_ROOT" creates:
+# $TIZEN_SDK_ROOT/platforms/*
+# not
+# $TIZEN_SDK_ROOT/data/platforms/*
+# Kudos: https://askubuntu.com/a/1088207
+(
+    cd "$TIZEN_SDK_ROOT"
+    [ -L data ] || ln -s . data
+)
+
+download_tizen_sdk_common
+[ "$INSTALL_ARM" = "true" ] && download_tizen_sdk_arm
+[ "$INSTALL_ARM64" = "true" ] && download_tizen_sdk_arm64
+
 install_tizen_sdk_common
-
-if [ "$INSTALL_ARM" = true ]; then
-    install_tizen_sdk_arm
-fi
-
-if [ "$INSTALL_ARM64" = true ]; then
-    install_tizen_sdk_arm64
-fi
+[ "$INSTALL_ARM" = "true" ] && install_tizen_sdk_arm
+[ "$INSTALL_ARM64" = "true" ] && install_tizen_sdk_arm64
 
 install_tizen_sdk_finalize
+
+[ -L "$TIZEN_SDK_ROOT/data" ] && rm "$TIZEN_SDK_ROOT/data"
diff --git a/integrations/docker/images/stage-3/chip-build-tizen-qemu/Dockerfile b/integrations/docker/images/stage-3/chip-build-tizen-qemu/Dockerfile
index 9eb3bcb..b9b5232 100644
--- a/integrations/docker/images/stage-3/chip-build-tizen-qemu/Dockerfile
+++ b/integrations/docker/images/stage-3/chip-build-tizen-qemu/Dockerfile
@@ -101,13 +101,13 @@
     /usr/lib/ \
     # Disable failing systemd services
     && guestfish --rw -a $TIZEN_IOT_IMAGE_ROOT -m /dev/sda \
-    glob rm $SYSTEMD_SYSTEM/clone_partitions.service : \
-    glob rm $SYSTEMD_SYSTEM/deviced.service : \
-    glob rm $SYSTEMD_SYSTEM/mm-resource-managerd.service : \
-    glob rm $SYSTEMD_SYSTEM/mnt-inform.mount : \
-    glob rm $SYSTEMD_SYSTEM/muse-server.* : \
-    glob rm $SYSTEMD_SYSTEM/murphyd.service : \
-    glob rm $SYSTEMD_SYSTEM/pulseaudio.service \
+    rm-f $SYSTEMD_SYSTEM/clone_partitions.service : \
+    rm-f $SYSTEMD_SYSTEM/deviced.service : \
+    rm-f $SYSTEMD_SYSTEM/mm-resource-managerd.service : \
+    rm-f $SYSTEMD_SYSTEM/mnt-inform.mount : \
+    glob rm-f $SYSTEMD_SYSTEM/muse-server.* : \
+    rm-f $SYSTEMD_SYSTEM/murphyd.service : \
+    rm-f $SYSTEMD_SYSTEM/pulseaudio.service \
     # Mount Tizen system partition on /opt-ro instead of /opt
     && SYSTEMD_UNIT_OPT_RO_MOUNT=$SYSTEMD_SYSTEM/opt\\x2dro.mount \
     && guestfish --rw -a $TIZEN_IOT_IMAGE_ROOT -m /dev/sda \
diff --git a/integrations/docker/images/vscode/chip-build-vscode/Dockerfile b/integrations/docker/images/vscode/chip-build-vscode/Dockerfile
index c77e7df..68195ed 100644
--- a/integrations/docker/images/vscode/chip-build-vscode/Dockerfile
+++ b/integrations/docker/images/vscode/chip-build-vscode/Dockerfile
@@ -140,7 +140,7 @@
 # NOTE: This directory is NOT persistent.
 ENV PW_ENVIRONMENT_ROOT=/home/vscode/pigweed/env
 
-ENV TIZEN_VERSION=8.0
+ENV TIZEN_VERSION=9.0
 ENV TIZEN_SDK_ROOT=/opt/tizen-sdk
 ENV TIZEN_SDK_TOOLCHAIN=$TIZEN_SDK_ROOT/tools/arm-linux-gnueabi-gcc-9.2
 ENV TIZEN_SDK_SYSROOT=$TIZEN_SDK_ROOT/platforms/tizen-$TIZEN_VERSION/tizen/rootstraps/tizen-$TIZEN_VERSION-device.core
diff --git a/third_party/tizen/tizen_qemu.py b/third_party/tizen/tizen_qemu.py
index e5ee468..ddd06c0 100755
--- a/third_party/tizen/tizen_qemu.py
+++ b/third_party/tizen/tizen_qemu.py
@@ -23,6 +23,8 @@
 import subprocess
 import sys
 
+log = logging.getLogger(__name__)
+
 # Absolute path to Tizen Studio CLI tool.
 tizen_sdk_root = os.environ.get("TIZEN_SDK_ROOT", "")
 
@@ -42,10 +44,10 @@
     help="run QEMU in interactive mode (no output redirection, no runner)")
 parser.add_argument(
     '--smp', metavar='NUM', type=int, default=2,
-    help=("the number of CPUs available in QEMU; default: %(default)s"))
+    help="the number of CPUs available in QEMU; default: %(default)s")
 parser.add_argument(
     '--memory', metavar='SIZE', type=int, default=512,
-    help=("the size of RAM assigned to QEMU; default: %(default)s"))
+    help="the size of RAM assigned to QEMU; default: %(default)s")
 parser.add_argument(
     '--virtio-net', action='store_true',
     help="enable external network access via virtio-net")
@@ -66,7 +68,7 @@
           "default: $TIZEN_SDK_ROOT/iot-sysdata.img"))
 parser.add_argument(
     '--share', type=str,
-    help=("host directory to share with the guest"))
+    help="host directory to share with the guest")
 parser.add_argument(
     '--runner', type=str,
     help=("path to the runner script which will be executed after boot; "
@@ -98,6 +100,10 @@
     binaries = whereis("qemu-system-arm")
     if len(binaries) > 1:
         qemu_system_arm = binaries[1]
+    else:
+        log.warning(f"The only qemu-system-arm we have is from Pigweed '{pw_install_dir}'")
+        log.warning('This is unexpected and is likely to lead to trouble.')
+        log.warning('Please install qemu-system-arm from Debian using `sudo apt install qemu-system-arm`')
 
 qemu_args = [
     qemu_system_arm,
@@ -113,7 +119,7 @@
     # Add directory sharing.
     qemu_args += [
         '-virtfs',
-        'local,path=%s,mount_tag=host0,security_model=mapped-xattr' % args.share
+        f'local,path={args.share},mount_tag=host0,security_model=mapped-xattr'
     ]
 
 if args.virtio_net:
@@ -126,11 +132,11 @@
 # Add Tizen image block devices.
 qemu_args += [
     '-device', 'virtio-blk-device,drive=virtio-blk2',
-    '-drive', 'file=%s,id=virtio-blk2,if=none,format=raw,readonly=on' % args.image_data,
+    '-drive', f'file={args.image_data},id=virtio-blk2,if=none,format=raw,readonly=on',
     # XXX: Device for the root image has to be added as the last one so we can
     #      use /dev/vda as the root device in the kernel command line arguments.
     '-device', 'virtio-blk-device,drive=virtio-blk1',
-    '-drive', 'file=%s,id=virtio-blk1,if=none,format=raw,readonly=on' % args.image_root,
+    '-drive', f'file={args.image_root},id=virtio-blk1,if=none,format=raw,readonly=on',
 ]
 
 kernel_args = "console=ttyAMA0 earlyprintk earlycon root=/dev/vda"
@@ -139,7 +145,7 @@
     kernel_args += " rootshell"
 
 if args.runner:
-    kernel_args += " runner=/mnt/chip/%s" % args.runner
+    kernel_args += f' runner=/mnt/chip/{args.runner}'
 
 qemu_args += [
     '-kernel', args.kernel,
@@ -153,7 +159,7 @@
 status = 0
 # Run QEMU.
 with open(args.output, "wb") as output:
-    logging.info("run: %s", " ".join(map(shlex.quote, qemu_args)))
+    log.info("run: %s", " ".join(map(shlex.quote, qemu_args)))
     with subprocess.Popen(qemu_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as proc:
         for line in iter(proc.stdout.readline, b''):