Best approach for backporting a framework fix

I need to backport a fix that’s present on Zephyr RTOS 3.x to Zephyr 2.x, since that’s the version PlatformIO supports. If you’re curious, this is the fix. I’m having trouble figuring out how best to accomplish this.

I first tried forking PlatformIO’s fork of Zephyr and using platform_packages to install my fork instead of the normal fork:

platform_packages = framework-zephyr @ https://github.com/UrbanMachine/zephyr.git#v2.7.0-1um1

To make this install, I had to add a package.json to the repository, but after doing that it installed fine. However, when building I get this error:

 #include <fsl_common.h>
          ^~~~~~~~~~~~~~
compilation terminated.
*** [.pio/build/big_bird_top/lib617/SmartSteppers/SmartStepper.o] Error 1
In file included from /home/tyler/.platformio/packages/framework-zephyr/include/arch/arm/aarch32/cortex_m/cmsis.h:17,
                 from /home/tyler/.platformio/packages/framework-zephyr/include/arch/arm/aarch32/mpu/arm_mpu_v7m.h:11,
                 from /home/tyler/.platformio/packages/framework-zephyr/include/arch/arm/aarch32/mpu/arm_mpu.h:14,
                 from /home/tyler/.platformio/packages/framework-zephyr/include/arch/arm/aarch32/arch.h:187,
                 from /home/tyler/.platformio/packages/framework-zephyr/include/arch/cpu.h:19,
                 from /home/tyler/.platformio/packages/framework-zephyr/include/kernel_includes.h:33,
                 from /home/tyler/.platformio/packages/framework-zephyr/include/kernel.h:17,
                 from /home/tyler/.platformio/packages/framework-zephyr/include/zephyr.h:18,
                 from lib/FirmwareBasics/FirmwareBasics.h:22,
                 from lib/Communicator/Communicator.h:15,
                 from lib/Communicator/Communicator.cpp:1:
/home/tyler/.platformio/packages/framework-zephyr/soc/arm/nxp_imx/rt/soc.h:14:10: fatal error: fsl_common.h: No such file or directory

My understanding is that this is part of the HAL for my platform, which is a Teensy 4.1.

This is when I realized that PlatformIO’s packaging of Zephyr is quite a bit more sophisticated then I at first thought. If I understand it correctly, a number of scripts take care of constructing packages for every supported package and framework combination, where the framework and the HAL for the platform are combined. That’s pretty cool! However, I’m confused as to where the best place would be for me to put my patch. Any suggestions would be really appreciated!

I have done something similar with a before_script calling a Python script which finds the lines to change and changes them.

import errno
import pathlib
import os

# Find .platformio
if os.path.isdir( str( pathlib.Path.home() ) + "/.platformio" ):
    basepath = str(pathlib.Path.home())
elif os.path.isdir( "C:/.platformio" ):
    basepath = "C:"
else:
    raise FileNotFoundError(
        errno.ENOENT, os.strerror(errno.ENOENT), ".platformio")

filepath = basepath + "/.platformio/packages/framework-zephyr/soc/arm/st_stm32/stm32h7/soc.h"

try:
    with open( filepath, 'r' ) as file:
        lines = file.readlines()
    # Check if the lines are already there
    lines.index("#include <stm32h7xx_ll_dac.h>\n")
except ValueError:
    # Insert if not
    insert_line = lines.index("#endif /* CONFIG_ADC_STM32 */\n") + 1
    
    missing_lines = [
        "\n",
        "#ifdef CONFIG_DAC_STM32\n",
        "#include <stm32h7xx_ll_dac.h>\n",
        "#endif /* CONFIG_DAC_STM32 */\n"        
        ]
    for idx, line in enumerate(missing_lines):
        lines.insert(insert_line + idx, line)
    with open( filepath, 'w' ) as file:
        for line in lines:
            file.write(line)

Thank you for the suggestion! Applying the patch in-place is a great idea. Here’s what I ended up doing:

I made a script at shared/zephyr_patches/apply.py that looks like this:

"""Applies patch files to the installed Zephyr RTOS framework. This script is run by
PlatformIO before a build starts.
"""

from pathlib import Path
import subprocess


def is_patch_applied(patch: Path, zephyr_dir: Path) -> bool:
    try:
        subprocess.run(
            ["git", "apply", "--reverse", "--check", str(patch.absolute())],
            check=True,
            cwd=zephyr_dir,
        )
        return True
    except subprocess.SubprocessError:
        return False


def apply_patches() -> None:
    core_dir = Path.home() / ".platformio"
    if not core_dir.is_dir():
        raise RuntimeError(
            f"Could not find PlatformIO's core_dir at {core_dir}. Is PlatformIO "
            f"installed?"
        )

    zephyr_dir = core_dir / "packages" / "framework-zephyr"
    if not zephyr_dir.is_dir():
        raise RuntimeError(f"Could not find the Zephyr package at {zephyr_dir}")

    patches = (Path("shared") / "zephyr_patches").glob("*.patch")
    patches = sorted(patches)

    for patch in patches:
        if is_patch_applied(patch, zephyr_dir):
            print(f"Patch already applied: {patch.name}")
        else:
            subprocess.run(
                ["git", "apply", str(patch.absolute())], cwd=zephyr_dir, check=True
            )
            print(f"Applied patch to framework: {patch.name}")


apply_patches()

It gets patch files in the same directory as the script and applies them in order if they have not already been applied.

Then, I have the script run before each build by adding this line to the [env] section in my platformio.ini.

extra_scripts =
	pre:shared/zephyr_patches/apply.py