diff --git a/SLS_C/build.py b/SLS_C/build.py deleted file mode 100644 index 24d8c67..0000000 --- a/SLS_C/build.py +++ /dev/null @@ -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() diff --git a/SLS_C/sls_build/__init__.py b/SLS_C/sls_build/__init__.py new file mode 100644 index 0000000..734c387 --- /dev/null +++ b/SLS_C/sls_build/__init__.py @@ -0,0 +1,5 @@ +# build/__init__.py +__all__ = [ +"config", +"utils", +] diff --git a/SLS_C/sls_build/__main__.py b/SLS_C/sls_build/__main__.py new file mode 100644 index 0000000..a59b396 --- /dev/null +++ b/SLS_C/sls_build/__main__.py @@ -0,0 +1,4 @@ + +if __name__ == "__main__": + from .cli import main + main() diff --git a/SLS_C/sls_build/build_targets/base.py b/SLS_C/sls_build/build_targets/base.py new file mode 100644 index 0000000..ec1076e --- /dev/null +++ b/SLS_C/sls_build/build_targets/base.py @@ -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 diff --git a/SLS_C/sls_build/build_targets/main.py b/SLS_C/sls_build/build_targets/main.py new file mode 100644 index 0000000..c17ee4a --- /dev/null +++ b/SLS_C/sls_build/build_targets/main.py @@ -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"]) diff --git a/SLS_C/sls_build/build_targets/rp2040.py b/SLS_C/sls_build/build_targets/rp2040.py new file mode 100644 index 0000000..5dade64 --- /dev/null +++ b/SLS_C/sls_build/build_targets/rp2040.py @@ -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"]) diff --git a/SLS_C/sls_build/build_targets/tests.py b/SLS_C/sls_build/build_targets/tests.py new file mode 100644 index 0000000..ffeb81e --- /dev/null +++ b/SLS_C/sls_build/build_targets/tests.py @@ -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"]) diff --git a/SLS_C/sls_build/builder.py b/SLS_C/sls_build/builder.py new file mode 100644 index 0000000..ac9c0d6 --- /dev/null +++ b/SLS_C/sls_build/builder.py @@ -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() diff --git a/SLS_C/sls_build/cli.py b/SLS_C/sls_build/cli.py new file mode 100644 index 0000000..199889c --- /dev/null +++ b/SLS_C/sls_build/cli.py @@ -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 ") + 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() diff --git a/SLS_C/sls_build/compiler/base.py b/SLS_C/sls_build/compiler/base.py new file mode 100644 index 0000000..ff854b9 --- /dev/null +++ b/SLS_C/sls_build/compiler/base.py @@ -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): + ... diff --git a/SLS_C/sls_build/compiler/clang.py b/SLS_C/sls_build/compiler/clang.py new file mode 100644 index 0000000..fba30d3 --- /dev/null +++ b/SLS_C/sls_build/compiler/clang.py @@ -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") diff --git a/SLS_C/sls_build/compiler/gcc.py b/SLS_C/sls_build/compiler/gcc.py new file mode 100644 index 0000000..2861803 --- /dev/null +++ b/SLS_C/sls_build/compiler/gcc.py @@ -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) diff --git a/SLS_C/sls_build/compiler/msvc.py b/SLS_C/sls_build/compiler/msvc.py new file mode 100644 index 0000000..1d9a259 --- /dev/null +++ b/SLS_C/sls_build/compiler/msvc.py @@ -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) diff --git a/SLS_C/sls_build/config.py b/SLS_C/sls_build/config.py new file mode 100644 index 0000000..0ba2eb9 --- /dev/null +++ b/SLS_C/sls_build/config.py @@ -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}) +""" diff --git a/SLS_C/sls_build/platform/base.py b/SLS_C/sls_build/platform/base.py new file mode 100644 index 0000000..e8f72f8 --- /dev/null +++ b/SLS_C/sls_build/platform/base.py @@ -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 diff --git a/SLS_C/sls_build/platform/linux.py b/SLS_C/sls_build/platform/linux.py new file mode 100644 index 0000000..2e414c6 --- /dev/null +++ b/SLS_C/sls_build/platform/linux.py @@ -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 diff --git a/SLS_C/sls_build/platform/macos.py b/SLS_C/sls_build/platform/macos.py new file mode 100644 index 0000000..9b6be14 --- /dev/null +++ b/SLS_C/sls_build/platform/macos.py @@ -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 diff --git a/SLS_C/sls_build/platform/rp2040.py b/SLS_C/sls_build/platform/rp2040.py new file mode 100644 index 0000000..13597ed --- /dev/null +++ b/SLS_C/sls_build/platform/rp2040.py @@ -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 diff --git a/SLS_C/sls_build/platform/windows.py b/SLS_C/sls_build/platform/windows.py new file mode 100644 index 0000000..7d9f33a --- /dev/null +++ b/SLS_C/sls_build/platform/windows.py @@ -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 diff --git a/SLS_C/sls_build/utils.py b/SLS_C/sls_build/utils.py new file mode 100644 index 0000000..093eb8f --- /dev/null +++ b/SLS_C/sls_build/utils.py @@ -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