diff --git a/slc/build.sh b/slc/build.sh index 4262520c8..ad0708618 100755 --- a/slc/build.sh +++ b/slc/build.sh @@ -135,7 +135,12 @@ set -a if [ -f "$MATTER_ROOT/slc/tools/.env" ]; then echo "Loading environment variables from $MATTER_ROOT/slc/tools/.env" . "$MATTER_ROOT/slc/tools/.env" - PATH="$TOOLS_PATH:$PATH" + # Windows .env uses ';' in TOOLS_PATH; bash splits PATH on ':' only, so convert for Git Bash. + if [ -n "$TOOLS_PATH" ] && [[ "$TOOLS_PATH" == *';'* ]] && command -v cygpath >/dev/null 2>&1; then + PATH="$(cygpath -p -u "$TOOLS_PATH"):$PATH" + else + PATH="$TOOLS_PATH:$PATH" + fi fi set +a @@ -151,8 +156,44 @@ SILABS_BOARD=$2 CONFIG_ARGS="" BRD_ONLY=$(echo "$SILABS_BOARD" | cut -f1 -d";") if [ -z "$POST_BUILD_EXE" ]; then - export POST_BUILD_EXE=$(which commander) + export POST_BUILD_EXE=$(command -v commander 2>/dev/null || which commander 2>/dev/null || true) fi +# .env often sets POST_BUILD_EXE=commander.exe; recursive make may not keep that in PATH, and +# POST_BUILD_EXE_WIN uses $(USERPROFILE) which is empty in sub-makes -> /.silabs/.../commander.exe. +if [ -n "$POST_BUILD_EXE" ] && [[ "$POST_BUILD_EXE" != */* ]] && [[ "$POST_BUILD_EXE" != *\\* ]]; then + _cmdr=$(command -v "$POST_BUILD_EXE" 2>/dev/null || true) + [ -n "$_cmdr" ] && export POST_BUILD_EXE="$_cmdr" +fi + +# Generated *.Makefile uses ifeq ($(OS),Windows_NT) with USERPROFILE and ARM_GCC_DIR_WIN / +# POST_BUILD_EXE_WIN. Git Bash + recursive make: pass overrides on the command line (MAKEFLAGS). +MAKE_EXTRA_ARGS=() +case "$(uname -s 2>/dev/null)" in +MINGW*|MSYS*) + export OS="${OS:-Windows_NT}" + MAKE_EXTRA_ARGS+=(OS=Windows_NT) + if [ -n "${ARM_GCC_DIR:-}" ]; then + if command -v cygpath >/dev/null 2>&1; then + _ARM_GCC_DIR_FOR_MAKE=$(cygpath -m "$ARM_GCC_DIR" 2>/dev/null) || _ARM_GCC_DIR_FOR_MAKE=$ARM_GCC_DIR + else + _ARM_GCC_DIR_FOR_MAKE=$ARM_GCC_DIR + fi + MAKE_EXTRA_ARGS+=("ARM_GCC_DIR=${_ARM_GCC_DIR_FOR_MAKE}") + fi + if [ -n "${POST_BUILD_EXE:-}" ]; then + if command -v cygpath >/dev/null 2>&1; then + _POST_BUILD_FOR_MAKE=$(cygpath -m "$POST_BUILD_EXE" 2>/dev/null) || _POST_BUILD_FOR_MAKE=$POST_BUILD_EXE + else + _POST_BUILD_FOR_MAKE=$POST_BUILD_EXE + fi + MAKE_EXTRA_ARGS+=("POST_BUILD_EXE=${_POST_BUILD_FOR_MAKE}") + fi + if [ -n "${USERPROFILE:-}" ] && command -v cygpath >/dev/null 2>&1; then + _USERPROFILE_FOR_MAKE=$(cygpath -m "$USERPROFILE" 2>/dev/null) || _USERPROFILE_FOR_MAKE=$USERPROFILE + MAKE_EXTRA_ARGS+=("USERPROFILE=${_USERPROFILE_FOR_MAKE}") + fi + ;; +esac # Determine vars based on project type provided (.slcw solution example or .slcp project example file) if [[ "$SILABS_APP_PATH" == *.slcw ]]; then @@ -275,7 +316,7 @@ fi # Validate required tools and environment -if ! [ -x "$(command -v slc)" ]; then +if ! command -v slc >/dev/null 2>&1 && ! command -v slc.bat >/dev/null 2>&1; then echo "ERROR: please install slc_cli for your host." exit 1 fi @@ -295,6 +336,14 @@ if ! [ -x "$(command -v arm-none-eabi-gcc-12.2.1)" ]; then echo "Please install gcc-arm-none-eabi-12.2.Rel1 for your host." fi +if ! command -v make >/dev/null 2>&1; then + echo "ERROR: GNU Make is not installed or not on PATH." + echo " After SLC generation, this script runs make to compile the project." + echo " Install make for Windows (for example: MSYS2 pacman -S make, or Chocolatey: choco install make)," + echo " then open a new shell and confirm with: which make && make --version" + exit 1 +fi + echo "Building $SILABS_APP for $SILABS_BOARD in $OUTPUT_DIR" @@ -369,7 +418,7 @@ if [ "$GENERATE_BOOTLOADER" = true ] && [ "$GENERATE_APPLICATION" = false ]; the exit 1 fi BOOTLOADER_MAKEFILE_NAME=$(basename "$BOOTLOADER_MAKEFILE") - if ! make all -C "$OUTPUT_DIR/matter-bootloader" -f "$BOOTLOADER_MAKEFILE_NAME" -j13; then + if ! make "${MAKE_EXTRA_ARGS[@]}" all -C "$OUTPUT_DIR/matter-bootloader" -f "$BOOTLOADER_MAKEFILE_NAME" -j13; then echo "ERROR: Failed to build bootloader" exit 1 fi @@ -383,13 +432,13 @@ elif [ "$GENERATE_BOOTLOADER" = false ] && [ "$GENERATE_APPLICATION" = true ]; t fi APP_DIR=$(dirname "$APP_MAKEFILE") APP_MAKEFILE_NAME=$(basename "$APP_MAKEFILE") - if ! make all -C "$APP_DIR" -f "$APP_MAKEFILE_NAME" -j13; then + if ! make "${MAKE_EXTRA_ARGS[@]}" all -C "$APP_DIR" -f "$APP_MAKEFILE_NAME" -j13; then echo "ERROR: Failed to build application" exit 1 fi else echo "Building solution..." - if ! make all -C "$OUTPUT_DIR" -f "$MAKE_FILE" -j13; then + if ! make "${MAKE_EXTRA_ARGS[@]}" all -C "$OUTPUT_DIR" -f "$MAKE_FILE" -j13; then echo "ERROR: Failed to build solution" exit 1 fi diff --git a/slc/sl_setup_env.py b/slc/sl_setup_env.py index ea39124ce..6cafb632a 100644 --- a/slc/sl_setup_env.py +++ b/slc/sl_setup_env.py @@ -32,6 +32,9 @@ * * @note Requires Python 3.9 or higher * @note This script should be run from the root of the Matter SDK directory + * @note SLT selection: set SILABS_SLT or SLT_EXE to an slt executable, or ensure + * `slt` is on PATH (e.g. corporate install). Otherwise the script downloads + * SLT under slc/tools. Use --use-bundled-slt to force the bundled copy. """ import argparse @@ -59,18 +62,21 @@ class MatterEnvSetup: """Class for setting up the Matter development environment with all required tools.""" - def __init__(self, verbose=False, clean_reinstall=False): + def __init__(self, verbose=False, clean_reinstall=False, use_bundled_slt=False): """Initialize MatterEnvSetup instance. Args: verbose: Enable verbose (debug) logging if True clean_reinstall: If True, remove slc/tools before setup (fresh tool install) + use_bundled_slt: If True, always use slc/tools/slt (download if missing). """ self.verbose = verbose self.clean_reinstall = clean_reinstall + self.use_bundled_slt = use_bundled_slt self.setup_logging() self.set_root_paths() self.set_platform_vars() + self.slt_cli_path = self.resolve_slt_cli_path() self.MINIMUM_ZAP_REQUIRED = get_zap_version() def setup_logging(self): @@ -117,10 +123,9 @@ def set_platform_vars(self): logging.error(f"ERROR: Platform {platform} is not supported") sys.exit(1) self.slt_cli_url = f"https://www.silabs.com/documents/public/software/slt-cli-1.1.1-{self.__platform}-x64.zip" - if platform == "win32": - self.slt_cli_path = os.path.join(self.tools_folder_path, "slt.exe") - else: - self.slt_cli_path = os.path.join(self.tools_folder_path, "slt") + self._slt_bundled_path = os.path.join( + self.tools_folder_path, "slt.exe" if platform == "win32" else "slt" + ) self.sisdk_root = os.path.join(self.silabs_chip_root, "third_party", "simplicity_sdk") self.wiseconnect_root = os.path.join(self.silabs_chip_root, "third_party", "wifi_sdk") self.zap_path = os.path.join(self.silabs_chip_root, "slc", "tools", "zap") @@ -137,10 +142,44 @@ def remove_tools_directory(self): sys.exit(1) tools.mkdir(parents=True, exist_ok=True) + def resolve_slt_cli_path(self): + """Resolve path to slt: env override, PATH, or bundled copy under slc/tools.""" + bundled = self._slt_bundled_path + if self.use_bundled_slt: + logging.info(f"Using bundled SLT (--use-bundled-slt): {bundled}") + return bundled + for key in ("SILABS_SLT", "SLT_EXE", "SLT_PATH"): + raw = os.environ.get(key) + if not raw: + continue + candidate = os.path.abspath(os.path.expanduser(raw.strip('"'))) + if os.path.isfile(candidate): + logging.info(f"Using SLT from {key}={candidate}") + return candidate + logging.warning(f"{key} is set but not a file: {raw}") + which_slt = shutil.which("slt") or shutil.which("slt.exe") + if which_slt: + resolved = os.path.abspath(which_slt) + if os.path.isfile(resolved): + logging.info(f"Using SLT from PATH: {resolved}") + return resolved + logging.info(f"Using bundled SLT: {bundled}") + return bundled + + def _slt_is_bundled(self): + return os.path.abspath(self.slt_cli_path) == os.path.abspath(self._slt_bundled_path) + def download_and_extract_slt_cli(self): - """Download and extract SLT CLI tool.""" + """Download and extract SLT CLI tool (bundled layout only).""" + if not self._slt_is_bundled(): + if not os.path.isfile(self.slt_cli_path): + logging.error(f"External SLT not found: {self.slt_cli_path}") + sys.exit(1) + logging.info("Skipping bundled SLT download and slt update --self (external SLT)") + return + if not os.path.isfile(self.slt_cli_path): - logging.info(f"Downloading and unzipping slt-cli...") + logging.info("Downloading and unzipping slt-cli...") slt_zip_path = os.path.join(self.tools_folder_path, "slt.zip") try: dload.save(self.slt_cli_url, slt_zip_path) @@ -155,7 +194,7 @@ def download_and_extract_slt_cli(self): update_cmd = [self.slt_cli_path, "--non-interactive", "update", "--self"] try: - subprocess.run(update_cmd, check=True) + subprocess.run(update_cmd, check=True, stdin=subprocess.DEVNULL) except subprocess.CalledProcessError as e: logging.warning(f"Failed to update slt-cli: {e}") @@ -229,6 +268,24 @@ def check_and_update_zap_version(self): logging.error( f"Error while deleting zap located at {self.zap_path}: {e}. Using older version of zap may lead to errors.") + @staticmethod + def _bash_dotenv_quote_win(value): + """Quote a value for .env lines that Git Bash will `source` (see build.sh). + + Semicolons in TOOLS_PATH, backslashes, and spaces (e.g. Simplicity Commander) + are interpreted by bash unless the value is single-quoted. python-dotenv + accepts the same quoting when used from Python. + """ + if "'" not in value: + return f"'{value}'" + return "'" + value.replace("'", "'\"'\"'") + "'" + + def _env_line(self, key, value): + """Format one KEY=value line; on Windows quote values for bash-safe sourcing.""" + if self.platform == "win32": + return f"{key}={self._bash_dotenv_quote_win(value)}\n" + return f"{key}={value}\n" + def write_env_file(self): """Write environment variables to .env file.""" env_path = os.path.expanduser(os.path.join(self.tools_folder_path, ".env")) @@ -262,21 +319,26 @@ def write_env_file(self): cmake_bin = os.path.join(self.paths.get('cmake'), "bin") + tools_path = ( + f"{arm_gcc_bin}{path_separator}{self.paths.get('slc-cli')}{path_separator}" + f"{os.path.join(java_path, 'bin')}{path_separator}{commander_path}{path_separator}" + f"{self.paths.get('ninja')}{path_separator}{cmake_bin}{path_separator}" + ) + try: with open(env_path, "w") as outfile: - outfile.write(f"STUDIO_ADAPTER_PACK_PATH={self.zap_path}\n") - outfile.write(f"ARM_GCC_DIR={self.paths.get('gcc-arm-none-eabi')}\n") - outfile.write(f"JAVA_HOME={java_path}\n") - outfile.write(f"ZAP_INSTALL_PATH={self.zap_path}\n") - outfile.write( - f"TOOLS_PATH={arm_gcc_bin}{path_separator}{self.paths.get('slc-cli')}{path_separator}{os.path.join(java_path, 'bin')}{path_separator}{commander_path}{path_separator}{self.paths.get('ninja')}{path_separator}{cmake_bin}{path_separator}\n") - outfile.write(f"silabs_chip_root={self.silabs_chip_root}\n") - outfile.write(f"NINJA_PATH={ninja_path}\n") - outfile.write(f"SISDK_ROOT={self.sisdk_root}\n") - outfile.write(f"WISECONNECT_ROOT={self.wiseconnect_root}\n") - outfile.write(f"SLC_EXECUTABLE={slc_executable}\n") - outfile.write(f"COMMANDER_EXECUTABLE={commander_executable}\n") - outfile.write(f"POST_BUILD_EXE={commander_executable}\n") + outfile.write(self._env_line("STUDIO_ADAPTER_PACK_PATH", self.zap_path)) + outfile.write(self._env_line("ARM_GCC_DIR", self.paths.get("gcc-arm-none-eabi"))) + outfile.write(self._env_line("JAVA_HOME", java_path)) + outfile.write(self._env_line("ZAP_INSTALL_PATH", self.zap_path)) + outfile.write(self._env_line("TOOLS_PATH", tools_path)) + outfile.write(self._env_line("silabs_chip_root", self.silabs_chip_root)) + outfile.write(self._env_line("NINJA_PATH", ninja_path)) + outfile.write(self._env_line("SISDK_ROOT", self.sisdk_root)) + outfile.write(self._env_line("WISECONNECT_ROOT", self.wiseconnect_root)) + outfile.write(self._env_line("SLC_EXECUTABLE", slc_executable)) + outfile.write(self._env_line("COMMANDER_EXECUTABLE", commander_executable)) + outfile.write(self._env_line("POST_BUILD_EXE", commander_executable)) logging.info(f"Environment file written to {env_path}") except IOError as e: logging.error(f"Failed to write environment file: {e}") @@ -294,12 +356,12 @@ def install_tools(self, tool): Raises: SystemExit: If tool installation fails """ + slt_kw = {"stdin": subprocess.DEVNULL, "capture_output": True, "text": True} try: result = subprocess.run( [self.slt_cli_path, "--non-interactive", "where", tool], - capture_output=True, - text=True, check=True, + **slt_kw, ) tool_dir = result.stdout.strip() except subprocess.CalledProcessError as e: @@ -309,12 +371,15 @@ def install_tools(self, tool): if not tool_dir: logging.info(f"Downloading {tool}") try: - subprocess.run([self.slt_cli_path, "--non-interactive", "install", tool], check=True) + subprocess.run( + [self.slt_cli_path, "--non-interactive", "install", tool], + check=True, + stdin=subprocess.DEVNULL, + ) result = subprocess.run( [self.slt_cli_path, "--non-interactive", "where", tool], - capture_output=True, - text=True, check=True, + **slt_kw, ) tool_dir = result.stdout.strip() except subprocess.CalledProcessError as e: @@ -366,8 +431,17 @@ def main(): action='store_true', help='Remove slc/tools then run a full tool setup', ) + parser.add_argument( + '--use-bundled-slt', + action='store_true', + help="Download/use slc/tools/slt instead of SILABS_SLT / SLT_EXE / PATH", + ) args = parser.parse_args() - env_setup = MatterEnvSetup(verbose=args.verbose, clean_reinstall=args.clean_reinstall) + env_setup = MatterEnvSetup( + verbose=args.verbose, + clean_reinstall=args.clean_reinstall, + use_bundled_slt=args.use_bundled_slt, + ) env_setup.run_setup()