Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 55 additions & 6 deletions slc/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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"


Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
128 changes: 101 additions & 27 deletions slc/sl_setup_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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")
Expand All @@ -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)
Expand All @@ -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}")

Expand Down Expand Up @@ -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"))
Expand Down Expand Up @@ -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}")
Expand All @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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()


Expand Down
Loading