Started new build system
This commit is contained in:
parent
4c9aa78dc8
commit
9169d8027b
515
SLS_C/build.py
515
SLS_C/build.py
|
|
@ -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()
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
# build/__init__.py
|
||||||
|
__all__ = [
|
||||||
|
"config",
|
||||||
|
"utils",
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
from .cli import main
|
||||||
|
main()
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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"])
|
||||||
|
|
@ -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"])
|
||||||
|
|
@ -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"])
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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):
|
||||||
|
...
|
||||||
|
|
@ -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")
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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})
|
||||||
|
"""
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
Loading…
Reference in New Issue