From 311aa81c86de73abfe79b69beaa6331d23a58842 Mon Sep 17 00:00:00 2001 From: Robert Grizzell Date: Sun, 26 Jul 2026 00:02:55 -0500 Subject: [PATCH 1/2] builder: honor caller-supplied cross toolchain and PKG_CONFIG Read TARGET_CC/TARGET_CXX/TARGET_AR/TARGET_RANLIB from the environment and override the SCons defaults when set, so external build systems (OpenWRT, Yocto, Debian rules) can cross-compile ArduLinux with their own toolchain. Resolve pkg-config via $PKG_CONFIG so libgpiod is detected against the target sysroot rather than the build host. All overrides are optional; host builds are unaffected. Co-Authored-By: Claude Opus 4.8 --- builder/frameworks/arduino.py | 8 ++++++-- builder/main.py | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/builder/frameworks/arduino.py b/builder/frameworks/arduino.py index d0facaf..d92c9e4 100644 --- a/builder/frameworks/arduino.py +++ b/builder/frameworks/arduino.py @@ -37,9 +37,13 @@ def _overlay_current(d): os.symlink(join(_API_DIR, name), join(API_DIR, name)) # Detect libgpiod via pkg-config; fall back gracefully if pkg-config is absent. +# Honor a caller-supplied $PKG_CONFIG so cross builds resolve libgpiod against +# the target sysroot rather than the build host (otherwise the simulated GPIO/I2C +# drivers get compiled in and the daemon can't drive real hardware). +_pkg_config = os.environ.get("PKG_CONFIG", "pkg-config") try: has_libgpiod = subprocess.call( - ["pkg-config", "--exists", "libgpiod"], + [_pkg_config, "--exists", "libgpiod"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) == 0 @@ -57,7 +61,7 @@ def _overlay_current(d): if has_libgpiod: cppdefines.append("ARDULINUX_HARDWARE") - raw = subprocess.check_output(["pkg-config", "--cflags", "libgpiod"]).decode().split() + raw = subprocess.check_output([_pkg_config, "--cflags", "libgpiod"]).decode().split() cpppath += [f[2:] for f in raw if f.startswith("-I")] env.Append(CPPPATH=cpppath, CPPDEFINES=cppdefines) diff --git a/builder/main.py b/builder/main.py index 15105e1..337b627 100644 --- a/builder/main.py +++ b/builder/main.py @@ -1,8 +1,28 @@ +import os from os.path import basename, join from SCons.Script import AlwaysBuild, Default, DefaultEnvironment env = DefaultEnvironment() +# Honor a caller-supplied cross toolchain from the environment so ArduLinux can +# be built by external build systems (OpenWRT/buildroot, Yocto, Debian rules) +# that provide their own compiler and sysroot. This runs before the framework +# script (main.py -> BuildProgram -> ProcessProgramDeps -> BuildFrameworks -> +# arduino.py), so the override reaches the core/framework objects as well as the +# application's. Each variable is optional; when unset, PlatformIO's native +# gcc/g++ defaults are kept, so ordinary host builds are unaffected. Compiler +# *flags* are intentionally left to the caller (e.g. via PLATFORMIO_BUILD_FLAGS) +# rather than parsed here. +for _scons_var, _env_var in ( + ("CC", "TARGET_CC"), + ("CXX", "TARGET_CXX"), + ("AR", "TARGET_AR"), + ("RANLIB", "TARGET_RANLIB"), +): + _tool = os.environ.get(_env_var) + if _tool: + env.Replace(**{_scons_var: _tool}) + # Allow the application to override the output binary name. # Set board_build.progname in platformio.ini to change the default "program". progname = env.BoardConfig().get("build.progname", "") From bd9ff3f923c09a7f631b58b167fbb34486d32238 Mon Sep 17 00:00:00 2001 From: Robert Grizzell Date: Sun, 2 Aug 2026 22:22:44 -0500 Subject: [PATCH 2/2] LinuxGPIOPin: throw on failed line request under libgpiod v2 The v2 getLine() paths returned the result of gpiod_chip_request_lines() without checking for NULL, so a line that cannot be acquired (already held by another process or a kernel driver) surfaced later as an assertion failure in gpiod_line_request_reconfigure_lines() rather than a clear error. Mirror the v1 path: throw std::invalid_argument naming the line and chip so the caller sees "cannot acquire GPIO line N on (already in use?)". Co-Authored-By: Claude Opus 4.8 --- cores/ardulinux/linux/gpio/LinuxGPIOPin.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/cores/ardulinux/linux/gpio/LinuxGPIOPin.cpp b/cores/ardulinux/linux/gpio/LinuxGPIOPin.cpp index b2ebcde..d732034 100644 --- a/cores/ardulinux/linux/gpio/LinuxGPIOPin.cpp +++ b/cores/ardulinux/linux/gpio/LinuxGPIOPin.cpp @@ -198,6 +198,16 @@ gpiod_line *LinuxGPIOPin::getLine(const char *chipLabel, const char *linuxPinNam gpiod_line_settings_free(settings); gpiod_chip_close(chip); chip = NULL; // prevent double-close in ~LinuxGPIOPin() + // gpiod_chip_request_lines() returns NULL when the line cannot be acquired + // (e.g. already claimed by another process or a kernel driver). Fail loudly + // here instead of returning NULL and asserting later in a reconfigure call. + if (!line) { + char msg[128]; + snprintf(msg, sizeof(msg), + "Error, cannot acquire GPIO line '%s' on %s (already in use?)", + linuxPinName ? linuxPinName : "?", chipLabel ? chipLabel : "?"); + throw std::invalid_argument(msg); + } return line; #else auto line = gpiod_chip_find_line(chip, linuxPinName); @@ -240,6 +250,16 @@ gpiod_line *LinuxGPIOPin::getLine(const char *chipLabel, const int linuxPinNum) gpiod_line_settings_free(settings); gpiod_chip_close(chip); chip = NULL; // prevent double-close in ~LinuxGPIOPin() + // gpiod_chip_request_lines() returns NULL when the line cannot be acquired + // (e.g. already claimed by another process or a kernel driver). Fail loudly + // here instead of returning NULL and asserting later in a reconfigure call. + if (!line) { + char msg[128]; + snprintf(msg, sizeof(msg), + "Error, cannot acquire GPIO line %d on %s (already in use?)", + linuxPinNum, chipLabel ? chipLabel : "?"); + throw std::invalid_argument(msg); + } return line; #else auto line = gpiod_chip_get_line(chip, linuxPinNum);