Started new build system

This commit is contained in:
Kyler Olsen 2025-12-05 11:23:27 -07:00
parent 4c9aa78dc8
commit 9169d8027b
20 changed files with 485 additions and 515 deletions

View File

@ -1,515 +0,0 @@
#!/usr/bin/env python3
import os
import sys
import subprocess
from pathlib import Path
import shutil
import platform
# ---------------------------------------------------------------------
# CONFIG
# ---------------------------------------------------------------------
SRC_DIR = Path("src")
TEST_DIR = Path("tests")
OBJ_DIR = Path("obj")
BIN_DIR = Path("bin")
TARGET = BIN_DIR / "sls"
TEST_TARGET = BIN_DIR / "sls_tests"
# Platform-specific settings
PICO_SDK_PATH = os.environ.get("PICO_SDK_PATH", Path.home() / "pico/pico-sdk")
PICO_BUILD_DIR = Path("build_pico")
PICO_TOOLCHAIN_PATH = Path("pico_arm_gcc_toolchain.cmake")
# Unix gcc/clang flags
COMMON_FLAGS = ["-std=c99", "-Wall", "-Wextra", "-Werror", "-Iinclude", "-g"]
TEST_FLAGS = ["-std=c99", "-Wall", "-Wextra", "-Wno-unused-function", "-Werror",
"-Iinclude", "-g", "-O0"]
# macOS-specific flags (for cross-compilation if needed)
MACOS_FLAGS = ["-std=c99", "-Wall", "-Wextra", "-Werror", "-Iinclude", "-g",
"-mmacosx-version-min=10.13"]
# Windows MSVC flags
MSVC_FLAGS = ["/std:c11", "/Zi", "/Iinclude"]
MSVC_TEST_FLAGS = MSVC_FLAGS + []
# RP2040 toolchain file template
PICO_TOOLCHAIN_TEMPLATE = """set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR cortex-m0plus)
# Specify the cross compiler
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
# Compiler flags for Cortex-M0+
set(CMAKE_C_FLAGS_INIT "-mcpu=cortex-m0plus -mthumb")
set(CMAKE_CXX_FLAGS_INIT "-mcpu=cortex-m0plus -mthumb")
set(CMAKE_ASM_FLAGS_INIT "-mcpu=cortex-m0plus -mthumb")
# Don't run the linker on compiler check
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
# Adjust the default behavior of the FIND_XXX() commands:
# search programs in the host environment
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# Search headers and libraries in the target environment
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
"""
# RP2040 settings
RP2040_CMAKE_TEMPLATE = """cmake_minimum_required(VERSION 3.13)
# Pico SDK initialization
set(PICO_SDK_PATH "{pico_sdk_path}")
include({pico_sdk_path}/external/pico_sdk_import.cmake)
project({project_name} C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
# Main executable
add_executable({project_name}
{source_files}
)
# Set output name with .elf extension
set_target_properties({project_name} PROPERTIES
OUTPUT_NAME "{project_name}.elf"
SUFFIX ""
)
# Add include directories
target_include_directories({project_name} PRIVATE
${{CMAKE_CURRENT_LIST_DIR}}/include
)
# Add compile definitions
target_compile_definitions({project_name} PRIVATE
PICO_BUILD=1
GIT_COMMIT_HASH="{git_hash}"
)
# Link libraries
target_link_libraries({project_name}
pico_stdlib
hardware_uart
hardware_gpio
)
# Enable USB/UART output
pico_enable_stdio_usb({project_name} 1)
pico_enable_stdio_uart({project_name} 1)
# Create map/bin/hex/uf2 files
pico_add_extra_outputs({project_name})
"""
# ---------------------------------------------------------------------
# PLATFORM DETECTION
# ---------------------------------------------------------------------
def detect_platform():
"""Detect the current operating system"""
system = platform.system()
if system == "Darwin":
return "macos"
elif system == "Windows":
return "windows"
elif system == "Linux":
return "linux"
return "unknown"
PLATFORM = detect_platform()
# ---------------------------------------------------------------------
# COMPILER DETECTION
# ---------------------------------------------------------------------
def detect_compiler(target_platform=None):
"""Detect appropriate compiler for the target platform"""
if target_platform == "rp2040":
return ("arm-none-eabi-gcc", "gcc")
target = target_platform or PLATFORM
if target == "windows" or os.name == "nt":
return ("cl", "msvc")
elif target == "macos":
# Prefer clang on macOS
if shutil.which("clang"):
return ("clang", "gcc")
return ("gcc", "gcc")
else:
return ("gcc", "gcc")
CC, CC_KIND = detect_compiler()
# ---------------------------------------------------------------------
# PYTHON DETECTION
# ---------------------------------------------------------------------
def detect_python():
if os.name == "nt":
return "python"
return "python3"
PYTHON = detect_python()
# ---------------------------------------------------------------------
# GIT COMMIT HASH
# ---------------------------------------------------------------------
def git_commit_hash():
try:
result_hash = subprocess.check_output(
["git", "describe", "--always", "--dirty", "--abbrev=7"],
cwd=".",
stderr=subprocess.DEVNULL,
text=True
).strip()
result_date = subprocess.check_output(
["git", "show", "-s", "--format=%ci"],
cwd=".",
stderr=subprocess.DEVNULL,
text=True
).strip()
return f"{result_hash} {result_date}"
except Exception:
return "unknown"
GIT_HASH = git_commit_hash()
# ---------------------------------------------------------------------
# UTILS
# ---------------------------------------------------------------------
def mkdir(p: Path):
p.mkdir(parents=True, exist_ok=True)
def run(cmd):
print(">>", " ".join(str(c) for c in cmd))
subprocess.check_call(cmd)
def is_up_to_date(src, obj, dep):
if "meta" in [src.stem, obj.stem, dep.stem]:
return False
if not obj.exists():
return False
if dep.exists() and dep.stat().st_mtime > obj.stat().st_mtime:
return False
return src.stat().st_mtime < obj.stat().st_mtime
# ---------------------------------------------------------------------
# BUILD RULES
# ---------------------------------------------------------------------
def compile_source(src: Path, is_test=False, target_platform=None):
mkdir(OBJ_DIR)
obj = OBJ_DIR / (src.stem + ".o")
dep = OBJ_DIR / (src.stem + ".d")
if is_up_to_date(src, obj, dep):
return obj
compiler, kind = detect_compiler(target_platform)
if kind == "msvc":
flags = MSVC_TEST_FLAGS if is_test else MSVC_FLAGS
cmd = [compiler] + flags + ["/Fo" + str(obj), "/c", str(src),
f"/DGIT_COMMIT_HASH=\"{GIT_HASH}\""]
else:
if target_platform == "macos":
flags = MACOS_FLAGS if not is_test else TEST_FLAGS + ["-mmacosx-version-min=10.13"]
else:
flags = TEST_FLAGS if is_test else COMMON_FLAGS
cmd = [compiler] + flags + [
f"-DGIT_COMMIT_HASH=\"{GIT_HASH}\"",
"-MMD", "-MP",
"-c", str(src), "-o", str(obj)
]
run(cmd)
return obj
def link_executable(objects, output: Path, target_platform=None):
mkdir(BIN_DIR)
compiler, kind = detect_compiler(target_platform)
if kind == "msvc":
cmd = [compiler] + list(map(str, objects)) + ["/Fe" + str(output)]
else:
cmd = [compiler] + list(map(str, objects)) + ["-lm", "-o", str(output)]
run(cmd)
# ---------------------------------------------------------------------
# TEST CASE GENERATION
# ---------------------------------------------------------------------
def generate_tests():
script = Path("../SLS_Tests/yaml_to_c_tests.py")
yaml = Path("../SLS_Tests/cases.yaml")
out = TEST_DIR / "lexer_tests.c"
if not script.exists() or not yaml.exists():
print("Test generation skipped (missing files).")
return False
if out.exists() and out.stat().st_mtime > yaml.stat().st_mtime:
return True
run([PYTHON, str(script), str(yaml), str(out)])
return True
# ---------------------------------------------------------------------
# RP2040 BUILD
# ---------------------------------------------------------------------
def check_pico_sdk():
"""Check if Pico SDK is available"""
sdk_path = Path(PICO_SDK_PATH)
if not sdk_path.exists():
print(f"ERROR: Pico SDK not found at {sdk_path}")
print("Please set PICO_SDK_PATH environment variable or install SDK at ~/pico/pico-sdk")
return False
# Check for ARM toolchain
if not shutil.which("arm-none-eabi-gcc"):
print("ERROR: ARM toolchain not found!")
print("Please install arm-none-eabi-gcc:")
print(" Ubuntu/Debian: sudo apt install gcc-arm-none-eabi")
print(" macOS: brew install arm-none-eabi-gcc")
return False
return True
def generate_pico_toolchain():
"""Generate ARM GCC toolchain file for CMake"""
with open(PICO_TOOLCHAIN_PATH, "w") as f:
f.write(PICO_TOOLCHAIN_TEMPLATE)
print(f"Generated {PICO_TOOLCHAIN_PATH}")
def generate_pico_cmake(project_name="sls"):
"""Generate CMakeLists.txt for RP2040 build"""
sources = list(SRC_DIR.glob("*.c"))
# Exclude main.c, repl.c, file.c and test files for Pico build
# pico_main.c will provide its own REPL implementation
sources = [s for s in sources
if s.name not in ["main.c", "repl.c", "file.c"]
and "test" not in s.stem.lower()]
# Check for pico_main.c
pico_main = SRC_DIR / "pico_main.c"
if not pico_main.exists():
print(f"WARNING: {pico_main} not found! Please create it.")
print("The Pico build requires a pico_main.c file.")
return False
sources.append(pico_main)
source_files = "\n".join(f" {s}" for s in sources)
cmake_content = RP2040_CMAKE_TEMPLATE.format(
project_name=project_name,
source_files=source_files,
git_hash=GIT_HASH,
pico_sdk_path=PICO_SDK_PATH
)
cmake_file = Path("CMakeLists.txt")
with open(cmake_file, "w") as f:
f.write(cmake_content)
print(f"Generated {cmake_file}")
return True
def build_rp2040():
"""Build for RP2040 using CMake"""
if not check_pico_sdk():
return False
print("\n=== Building for RP2040 ===\n")
# Generate toolchain file
generate_pico_toolchain()
# Generate CMakeLists.txt
if not generate_pico_cmake():
return False
# Create build directory
mkdir(PICO_BUILD_DIR)
# Run CMake with explicit toolchain
cmake_cmd = [
"cmake",
"-B", str(PICO_BUILD_DIR),
"-S", ".",
f"-DCMAKE_TOOLCHAIN_FILE={PICO_TOOLCHAIN_PATH}"
]
run(cmake_cmd)
# Build
build_cmd = ["cmake", "--build", str(PICO_BUILD_DIR), "-j4"]
run(build_cmd)
# Show output files
uf2_file = PICO_BUILD_DIR / "sls.uf2"
if uf2_file.exists():
print(f"\nBuild successful! UF2 file: {uf2_file}")
print("To flash: Copy this file to your Pico in BOOTSEL mode")
return True
# ---------------------------------------------------------------------
# MACOS BUILD
# ---------------------------------------------------------------------
def build_macos():
"""Build for macOS (can be run on macOS or cross-compile on Linux)"""
print("\n=== Building for macOS ===\n")
if PLATFORM != "macos" and PLATFORM != "linux":
print("ERROR: macOS builds require macOS or Linux with osxcross")
return False
sources = list(SRC_DIR.glob("*.c"))
objects = [compile_source(s, is_test=False, target_platform="macos") for s in sources]
macos_target = BIN_DIR / "sls_macos"
link_executable(objects, macos_target, target_platform="macos")
print(f"\nBuild successful! Binary: {macos_target}")
return True
# ---------------------------------------------------------------------
# TARGETS
# ---------------------------------------------------------------------
def build_main():
sources = list(SRC_DIR.glob("*.c"))
objects = [compile_source(s, is_test=False) for s in sources]
link_executable(objects, TARGET)
def build_tests():
generate_tests()
test_sources = list(TEST_DIR.glob("*.c"))
main_sources = [s for s in SRC_DIR.glob("*.c") if s.name != "main.c"]
test_objects = [compile_source(s, is_test=True) for s in test_sources]
shared_objects = [compile_source(s, is_test=False) for s in main_sources]
link_executable(test_objects + shared_objects, TEST_TARGET)
def clean():
shutil.rmtree(OBJ_DIR, ignore_errors=True)
shutil.rmtree(BIN_DIR, ignore_errors=True)
shutil.rmtree(PICO_BUILD_DIR, ignore_errors=True)
cmake_file = Path("CMakeLists.txt")
if cmake_file.exists():
cmake_file.unlink()
toolchain_file = PICO_TOOLCHAIN_PATH
if toolchain_file.exists():
toolchain_file.unlink()
print("Cleaned.")
def run_main():
build_main()
print("\n--- Running program ---\n")
subprocess.call([str(TARGET)])
def run_tests():
build_tests()
print("\n--- Running tests ---\n")
subprocess.call([str(TEST_TARGET)])
def debug_tests():
build_tests()
if os.name == "nt":
print("Debugging on Windows requires Visual Studio debugger.")
else:
subprocess.call(["gdb", str(TEST_TARGET)])
def show_help():
help_text = """
Build Script for Multi-Platform Compilation
============================================
Usage: python3 build.py [command]
Commands:
all, main, build Build for current platform
run Build and run program
test Build and run tests
debug Build tests and run debugger
clean Remove build artifacts
macos Build for macOS
pico, rp2040 Build for RP2040 (Raspberry Pi Pico)
help Show this help message
Environment Variables:
PICO_SDK_PATH Path to Pico SDK (default: ~/pico/pico-sdk)
Examples:
python3 build.py build # Build for current platform
python3 build.py pico # Build for RP2040
python3 build.py macos # Build for macOS
python3 build.py clean # Clean all build files
"""
print(help_text)
# ---------------------------------------------------------------------
# ENTRY POINT
# ---------------------------------------------------------------------
def main():
if len(sys.argv) < 2:
show_help()
return
cmd = sys.argv[1]
match cmd:
case "all" | "main" | "build":
build_main()
case "run":
run_main()
case "test":
run_tests()
case "debug":
debug_tests()
case "clean":
clean()
case "macos":
build_macos()
case "pico" | "rp2040":
build_rp2040()
case "help" | "-h" | "--help":
show_help()
case _:
print(f"Unknown target: {cmd}")
print("Run 'python3 build.py help' for usage information")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,5 @@
# build/__init__.py
__all__ = [
"config",
"utils",
]

View File

@ -0,0 +1,4 @@
if __name__ == "__main__":
from .cli import main
main()

View File

@ -0,0 +1,21 @@
# build/build_targets/base.py
from abc import ABC, abstractmethod
from pathlib import Path
from ..utils import mkdir
class BuildTarget(ABC):
def __init__(self, platform, config, utils):
self.platform = platform
self.config = config
self.utils = utils
@abstractmethod
def sources(self) -> list[Path]:
raise NotImplementedError
@abstractmethod
def output(self) -> Path:
raise NotImplementedError
def build(self):
raise NotImplementedError

View File

@ -0,0 +1,25 @@
# build/build_targets/main.py
from .base import BuildTarget
from pathlib import Path
from ..utils import mkdir
class MainBuild(BuildTarget):
def sources(self):
return [s for s in self.config.SRC_DIR.glob("*.c") if s.name not in ["pico_main.c"]]
def output(self):
return self.config.TARGET
def build(self):
mkdir(self.config.OBJ_DIR)
compiler = self.platform.compiler()
objects = []
for s in self.sources():
obj = self.config.OBJ_DIR / (s.stem + ".o")
deps = self.config.OBJ_DIR / (s.stem + ".d")
flags = self.platform.cflags(test=False)
extra = [f'-DGIT_COMMIT_HASH="{self.utils.git_commit_hash()}"']
compiler.compile(s, obj, flags, extra_defines=extra, deps_out=deps)
objects.append(obj)
mkdir(self.config.BIN_DIR)
compiler.link(objects, self.output(), libs=["-lm"])

View File

@ -0,0 +1,38 @@
# build/build_targets/rp2040.py
from .base import BuildTarget
from ..config import RP2040_CMAKE_TEMPLATE, PICO_TOOLCHAIN_TEMPLATE
from pathlib import Path
from ..utils import run, mkdir
class RP2040Build(BuildTarget):
def sources(self) -> list[Path]:
sources = list(self.config.SRC_DIR.glob("*.c"))
sources = [s for s in sources if s.name not in ["main.c", "repl.c", "file.c"] and "test" not in s.stem.lower()]
return sources
def prepare(self, project_name: str = "sls"):
# write toolchain
with open(self.config.PICO_TOOLCHAIN_PATH, "w") as f:
f.write(self.config.PICO_TOOLCHAIN_TEMPLATE)
# gather sources
sources = self.sources()
source_files = "\n".join(f" {s}" for s in sources)
cmake_content = self.config.RP2040_CMAKE_TEMPLATE.format(
project_name=project_name,
source_files=source_files,
git_hash=self.utils.git_commit_hash(),
pico_sdk_path=str(self.config.PICO_SDK_PATH)
)
with open("CMakeLists.txt", "w") as f:
f.write(cmake_content)
def build(self):
if not self.platform.supports_rp2040():
raise RuntimeError("RP2040 build requirements not met")
print('\n=== Building for RP2040 ===\n')
self.prepare()
mkdir(self.config.PICO_BUILD_DIR)
run(["cmake", "-B", str(self.config.PICO_BUILD_DIR), "-S", ".", f"-DCMAKE_TOOLCHAIN_FILE={self.config.PICO_TOOLCHAIN_PATH}"])
run(["cmake", "--build", str(self.config.PICO_BUILD_DIR), "-j4"])

View File

@ -0,0 +1,27 @@
# build/build_targets/tests.py
from .base import BuildTarget
from ..utils import mkdir
class TestBuild(BuildTarget):
def sources(self):
tests = list(self.config.TEST_DIR.glob("*.c"))
main_sources = [s for s in self.config.SRC_DIR.glob("*.c") if s.name not in ["main.c", "pico_main.c"]]
return tests + main_sources
def output(self):
return self.config.TEST_TARGET
def build(self):
self.utils.mkdir(self.config.OBJ_DIR)
compiler = self.platform.compiler()
objects = []
for s in self.sources():
obj = self.config.OBJ_DIR / (s.stem + ".o")
deps = self.config.OBJ_DIR / (s.stem + ".d")
is_test = s.parent == self.config.TEST_DIR
flags = self.platform.cflags(test=is_test)
extra = [f'-DGIT_COMMIT_HASH="{self.utils.git_commit_hash()}"']
compiler.compile(s, obj, flags, extra_defines=extra, deps_out=deps)
objects.append(obj)
self.utils.mkdir(self.config.BIN_DIR)
compiler.link(objects, self.output(), libs=["-lm"])

View File

@ -0,0 +1,37 @@
# build/builder.py
from .platform import linux as linux_mod, macos as macos_mod, windows as windows_mod, rp2040 as rp_mod
from .build_targets.main import MainBuild
from .build_targets.tests import TestBuild
from .build_targets.rp2040 import RP2040Build
from . import config, utils
PLATFORM_MAP = {
"linux": linux_mod.LinuxPlatform,
"macos": macos_mod.MacOSPlatform,
"windows": windows_mod.WindowsPlatform,
"rp2040": rp_mod.RP2040Platform,
}
def get_platform(name: str | None = None):
name = name or utils.detect_platform_name()
cls = PLATFORM_MAP.get(name, linux_mod.LinuxPlatform)
return cls()
def build_main(platform_name: str | None = None):
plat = get_platform(platform_name)
target = MainBuild(plat, config, utils)
target.build()
def build_tests(platform_name: str | None = None):
plat = get_platform(platform_name)
target = TestBuild(plat, config, utils)
target.build()
def build_rp2040(platform_name: str | None = None):
plat = get_platform(platform_name or "rp2040")
target = RP2040Build(plat, config, utils)
target.build()

33
SLS_C/sls_build/cli.py Normal file
View File

@ -0,0 +1,33 @@
# build/cli.py
import sys
from .builder import build_main, build_tests, build_rp2040
from .utils import detect_platform_name
def show_help():
print("Usage: python -m build.cli <command>")
print("Commands: build, run (not implemented), test, debug (not implemented), clean (not implemented), macos, pico")
def main():
if len(sys.argv) < 2:
show_help()
return
cmd = sys.argv[1]
match cmd:
case "build" | "main":
build_main()
case "test":
build_tests()
case "pico" | "rp2040":
build_rp2040()
case "macos":
build_main("macos")
case "help" | "-h" | "--help":
show_help()
case _:
print(f"Unknown command: {cmd}")
if __name__ == '__main__':
main()

View File

@ -0,0 +1,18 @@
# build/compiler/base.py
from abc import ABC, abstractmethod
from pathlib import Path
class Compiler(ABC):
exe = None
def __init__(self, exe: str | None = None):
if exe:
self.exe = exe
@abstractmethod
def compile(self, src: Path, out: Path, flags: list[str], extra_defines: list[str] | None = None, deps_out: Path | None = None) -> Path:
...
@abstractmethod
def link(self, objects: list[Path], output: Path, libs: list[str] | None = None):
...

View File

@ -0,0 +1,6 @@
# build/compiler/clang.py
from .gcc import GCCCompiler
class ClangCompiler(GCCCompiler):
def __init__(self, exe: str | None = None):
super().__init__(exe or "clang")

View File

@ -0,0 +1,25 @@
# build/compiler/gcc.py
from .base import Compiler
from pathlib import Path
from ..utils import run
class GCCCompiler(Compiler):
def __init__(self, exe: str | None = None):
super().__init__(exe or "gcc")
def compile(self, src: Path, out: Path, flags: list[str], extra_defines: list[str] | None = None, deps_out: Path | None = None) -> Path:
cmd = [self.exe] + flags[:]
if extra_defines:
cmd += extra_defines
if deps_out:
cmd += ["-MMD", "-MP"]
cmd += ["-c", str(src), "-o", str(out)]
run(cmd)
return out
def link(self, objects: list[Path], output: Path, libs: list[str] | None = None):
cmd = [self.exe] + [str(p) for p in objects]
if libs:
cmd += libs
cmd += ["-o", str(output)]
run(cmd)

View File

@ -0,0 +1,21 @@
# build/compiler/msvc.py
from .base import Compiler
from pathlib import Path
from ..utils import run
class MSVCCompiler(Compiler):
def __init__(self, exe: str | None = None):
super().__init__(exe or "cl")
def compile(self, src: Path, out: Path, flags: list[str], extra_defines: list[str] | None = None, deps_out: Path | None = None) -> Path:
# MSVC outputs object with /Fo
cmd = [self.exe] + flags[:]
if extra_defines:
cmd += extra_defines
cmd += ["/c", str(src), "/Fo" + str(out)]
run(cmd)
return out
def link(self, objects: list[Path], output: Path, libs: list[str] | None = None):
cmd = [self.exe] + [str(p) for p in objects] + ["/Fe" + str(output)]
run(cmd)

101
SLS_C/sls_build/config.py Normal file
View File

@ -0,0 +1,101 @@
# build/config.py
from pathlib import Path
import os
SRC_DIR = Path("src")
TEST_DIR = Path("tests")
OBJ_DIR = Path("obj")
BIN_DIR = Path("bin")
TARGET = BIN_DIR / "sls"
TEST_TARGET = BIN_DIR / "sls_tests"
PICO_SDK_PATH = Path(os.environ.get("PICO_SDK_PATH", Path.home() / "pico/pico-sdk"))
PICO_BUILD_DIR = Path("build_pico")
PICO_TOOLCHAIN_PATH = Path("pico_arm_gcc_toolchain.cmake")
COMMON_FLAGS = ["-std=c99", "-Wall", "-Wextra", "-Werror", "-Iinclude", "-g"]
TEST_FLAGS = ["-std=c99", "-Wall", "-Wextra", "-Wno-unused-function", "-Werror", "-Iinclude", "-g", "-O0"]
MACOS_FLAGS = ["-std=c99", "-Wall", "-Wextra", "-Werror", "-Iinclude", "-g", "-mmacosx-version-min=10.13"]
MSVC_FLAGS = ["/std:c11", "/Zi", "/Iinclude"]
MSVC_TEST_FLAGS = MSVC_FLAGS + []
PICO_TOOLCHAIN_TEMPLATE = """set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR cortex-m0plus)
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
set(CMAKE_C_FLAGS_INIT "-mcpu=cortex-m0plus -mthumb")
set(CMAKE_CXX_FLAGS_INIT "-mcpu=cortex-m0plus -mthumb")
set(CMAKE_ASM_FLAGS_INIT "-mcpu=cortex-m0plus -mthumb")
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
"""
RP2040_CMAKE_TEMPLATE = """cmake_minimum_required(VERSION 3.13)
set(PICO_SDK_PATH "{pico_sdk_path}")
include({pico_sdk_path}/external/pico_sdk_import.cmake)
project({project_name} C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
add_executable({project_name}
{source_files}
)
set_target_properties({project_name} PROPERTIES
OUTPUT_NAME "{project_name}.elf"
SUFFIX ""
)
target_include_directories({project_name} PRIVATE
${{CMAKE_CURRENT_LIST_DIR}}/include
)
target_compile_definitions({project_name} PRIVATE
PICO_BUILD=1
GIT_COMMIT_HASH="{git_hash}"
)
target_link_libraries({project_name}
pico_stdlib
hardware_uart
hardware_gpio
)
pico_enable_stdio_usb({project_name} 1)
pico_enable_stdio_uart({project_name} 1)
pico_add_extra_outputs({project_name})
"""

View File

@ -0,0 +1,17 @@
# build/platform/base.py
from abc import ABC, abstractmethod
from ..compiler.base import Compiler
class Platform(ABC):
name = "generic"
@abstractmethod
def compiler(self) -> Compiler:
raise NotImplementedError
@abstractmethod
def cflags(self, test: bool = False):
return []
def supports_rp2040(self) -> bool:
return False

View File

@ -0,0 +1,13 @@
# build/platform/linux.py
from .base import Platform
from ..compiler.gcc import GCCCompiler
from ..config import COMMON_FLAGS, TEST_FLAGS
class LinuxPlatform(Platform):
name = "linux"
def compiler(self):
return GCCCompiler()
def cflags(self, test: bool = False):
return TEST_FLAGS if test else COMMON_FLAGS

View File

@ -0,0 +1,13 @@
# build/platform/macos.py
from .base import Platform
from ..compiler.clang import ClangCompiler
from ..config import MACOS_FLAGS, TEST_FLAGS
class MacOSPlatform(Platform):
name = "macos"
def compiler(self):
return ClangCompiler()
def cflags(self, test: bool = False):
return (TEST_FLAGS + ["-mmacosx-version-min=10.13"]) if test else MACOS_FLAGS

View File

@ -0,0 +1,19 @@
# build/platform/rp2040.py
from .base import Platform
from ..compiler.gcc import GCCCompiler
from ..config import COMMON_FLAGS, TEST_FLAGS, PICO_SDK_PATH
from pathlib import Path
import shutil
class RP2040Platform(Platform):
name = "rp2040"
def compiler(self):
return GCCCompiler("arm-none-eabi-gcc")
def cflags(self, test: bool = False):
return TEST_FLAGS if test else COMMON_FLAGS
def supports_rp2040(self) -> bool:
sdk = Path(PICO_SDK_PATH)
return sdk.exists() and shutil.which("arm-none-eabi-gcc") is not None

View File

@ -0,0 +1,13 @@
# build/platform/windows.py
from .base import Platform
from ..compiler.msvc import MSVCCompiler
from ..config import MSVC_FLAGS, MSVC_TEST_FLAGS
class WindowsPlatform(Platform):
name = "windows"
def compiler(self):
return MSVCCompiler()
def cflags(self, test: bool = False):
return MSVC_TEST_FLAGS if test else MSVC_FLAGS

49
SLS_C/sls_build/utils.py Normal file
View File

@ -0,0 +1,49 @@
# build/utils.py
from pathlib import Path
import subprocess
import os
import shutil
def mkdir(p: Path):
p.mkdir(parents=True, exist_ok=True)
def run(cmd, **kwargs):
print(">>", " ".join(str(c) for c in cmd))
subprocess.check_call(cmd, **kwargs)
def detect_platform_name():
import platform
system = platform.system()
if system == "Darwin":
return "macos"
if system == "Windows":
return "windows"
if system == "Linux":
return "linux"
return "unknown"
def git_commit_hash():
try:
result_hash = subprocess.check_output([
"git", "describe", "--always", "--dirty", "--abbrev=7"
], text=True, stderr=subprocess.DEVNULL).strip()
result_date = subprocess.check_output([
"git", "show", "-s", "--format=%ci"
], text=True, stderr=subprocess.DEVNULL).strip()
return f"{result_hash} {result_date}"
except Exception:
return "unknown"
def is_up_to_date(src: Path, obj: Path, dep: Path):
if "meta" in [src.stem, obj.stem, dep.stem]:
return False
if not obj.exists():
return False
if dep.exists() and dep.stat().st_mtime > obj.stat().st_mtime:
return False
return src.stat().st_mtime < obj.stat().st_mtime