diff options
| author | Richard Qin <richard.qin.x@gmail.com> | 2026-07-10 23:20:03 +0800 |
|---|---|---|
| committer | Richard Qin <richard.qin.x@gmail.com> | 2026-07-10 23:20:03 +0800 |
| commit | 48ef95c24e9ac3d11200b61af617a01b10b507d1 (patch) | |
| tree | 57f0e26ab98e795b19839b6eece98ca58a4b702d | |
| parent | b9ff206e6bd3f4d67851263a6f2acb5f575cf3b0 (diff) | |
| download | lume-48ef95c24e9ac3d11200b61af617a01b10b507d1.tar.gz lume-48ef95c24e9ac3d11200b61af617a01b10b507d1.tar.xz | |
build: introduce declarative non-recursive build graph
47 files changed, 1205 insertions, 499 deletions
@@ -1,4 +1,5 @@ -build/ +/build/ +/out/ lume-legacy/build/ *.o *.d diff --git a/Build.mk b/Build.mk new file mode 100644 index 0000000..f2b374a --- /dev/null +++ b/Build.mk @@ -0,0 +1,57 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +# Explicit directory traversal, following the Kbuild model. New directories do +# not enter a build merely because a file appeared in the source tree. +include arch/$(ARCH)/Build.mk +include kernel/Build.mk +include mm/Build.mk +include fs/Build.mk +include drivers/Build.mk +include fdt/Build.mk +include lib/Build.mk +include test/Build.mk +include usr/Build.mk +include scripts/build/Build.mk + +# The image root is an interface target: it carries the complete dependency +# set but contributes no sources of its own. +targets += generated/autoconf generated/asm-offsets generated/linker-script \ + generated/initramfs-cpio blob/initramfs kernel/image +target.generated/autoconf.kind := generated +target.generated/autoconf.outputs := $(BUILD)/generated/autoconf.h +target.generated/autoconf.inputs := scripts/Kconfig.mk $(KCONFIG_CONFIG) +target.generated/autoconf.action := autoconf + +target.generated/asm-offsets.kind := generated +target.generated/asm-offsets.outputs := $(BUILD)/generated/asm-offsets.h +target.generated/asm-offsets.inputs := arch/$(ARCH)/kernel/asm-offsets.c \ + scripts/extract-asm-offsets.sed +target.generated/asm-offsets.generated-deps := generated/autoconf +target.generated/asm-offsets.action := asm-offsets + +target.generated/linker-script.kind := generated +target.generated/linker-script.outputs := $(BUILD)/lumeos.lds +target.generated/linker-script.inputs := arch/$(ARCH)/kernel/lumeos.lds.S \ + include/lume/lumeos.lds.h +target.generated/linker-script.action := linker-script + +target.generated/initramfs-cpio.kind := generated +target.generated/initramfs-cpio.outputs := $(BUILD)/initramfs.cpio +target.generated/initramfs-cpio.inputs := $(CONFIG_INITRAMFS_SOURCE) \ + scripts/gen_initramfs.sh +target.generated/initramfs-cpio.tool-deps := host/gen_init_cpio +target.generated/initramfs-cpio.action := initramfs + +target.blob/initramfs.kind := binary-blob +target.blob/initramfs.outputs := $(BUILD)/initramfs.o +target.blob/initramfs.inputs := usr/initramfs.S +target.blob/initramfs.generated-deps := generated/initramfs-cpio + +target.kernel/image.kind := product +target.kernel/image.deps := \ + arch/$(ARCH) kernel/core mm/core fs/core drivers/core fdt/core lib/core \ + $(if $(filter y,$(CONFIG_SELFTEST)),test/kernel) +target.kernel/image.generated-deps := \ + generated/autoconf generated/asm-offsets generated/linker-script \ + blob/initramfs +target.kernel/image.outputs := $(VMLUME) $(IMAGE) @@ -16,24 +16,30 @@ # ============================================================ # Build Configuration # ============================================================ -BUILD := build +O ?= build +BUILD := $(patsubst %/,%,$(O)) # Make sure `make` with no arguments builds the kernel image, not the -# first rule encountered during makefile parsing. scripts/Kconfig.mk -# (included next) defines `defconfig:` at the top of its rule list, -# which would otherwise become the default goal and silently overwrite -# .config from arch/$(ARCH)/defconfig. +# first rule encountered during makefile parsing. .DEFAULT_GOAL := all -# Load Kbuild helpers (Q, $(call cmd,...), quiet_cmd_* / cmd_* templates, -# kbuild-objs translator). Must come before Kconfig.mk so subsequent -# rules can use $(call cmd,...) immediately. +# Load command helpers before Kconfig and target evaluation. include scripts/Kbuild.include -# Load Kconfig infrastructure (reads .config, sets KCPPFLAGS) +# Load the O=-scoped configuration and derive compiler definitions. include scripts/Kconfig.mk ARCH ?= $(CONFIG_ARCH) +PLATFORM ?= qemu-virt +BUILD_VARIANT ?= debug + +ifneq ($(filter $(BUILD_VARIANT),debug release),$(BUILD_VARIANT)) +$(error unsupported BUILD_VARIANT '$(BUILD_VARIANT)' (expected debug or release)) +endif + +# Toolchain selection needs the configured architecture but must precede flag +# and module evaluation. +include scripts/build/toolchain.mk # Two boot products — same pair as Linux RISC-V's arch/riscv/boot/Makefile # (vmlinux + Image), but named LumeOS-style: @@ -46,6 +52,7 @@ IMAGE := $(BUILD)/vmlume.bin INITRAMFS_DIR := $(BUILD)/initramfs INITRAMFS_CPIO := $(BUILD)/initramfs.cpio INITRAMFS_OBJ := $(BUILD)/initramfs.o +INITRAMFS_ASM := usr/initramfs.S USR_INIT_ELF := $(BUILD)/usr/init # Relocations: linker emits .rela.dyn under -shared -Bsymbolic; boot # code in arch/riscv/mm/init.cc::relocate_kernel walks it at startup. @@ -53,36 +60,6 @@ LDS_SRC := arch/$(ARCH)/kernel/lumeos.lds.S LINKER_SCRIPT := $(BUILD)/lumeos.lds PYTHON ?= python3 -# ============================================================ -# Toolchain Detection -# ============================================================ -ifndef TOOLCHAIN_PREFIX -ifeq ($(ARCH),riscv) - # Prefer the linux-gnu toolchain — it supports -shared / -pie which - # are required to emit .rela.dyn (Linux RISC-V kernel uses the same). - # The bare-metal `riscv64-unknown-elf-` is kept only as a last-resort - # fallback; with that one, `-shared` fails ("not supported") and the - # kernel can't relocate at runtime. - TOOLCHAIN_PREFIX := $(shell \ - if command -v riscv64-unknown-linux-gnu-g++ >/dev/null 2>&1; then \ - echo riscv64-unknown-linux-gnu-; \ - elif command -v riscv64-linux-gnu-g++ >/dev/null 2>&1; then \ - echo riscv64-linux-gnu-; \ - else \ - echo riscv64-unknown-linux-gnu-; \ - fi) -else ifeq ($(ARCH),aarch64) - TOOLCHAIN_PREFIX := aarch64-linux-gnu- -else ifeq ($(ARCH),x86_64) - TOOLCHAIN_PREFIX := x86_64-linux-gnu- -endif -endif - -CC := $(TOOLCHAIN_PREFIX)gcc -CXX := $(TOOLCHAIN_PREFIX)g++ -LD := $(TOOLCHAIN_PREFIX)ld -OBJCOPY := $(TOOLCHAIN_PREFIX)objcopy -OBJDUMP := $(TOOLCHAIN_PREFIX)objdump # Objcopy output formats for binary-to-ELF relocation blob OBJCOPY_ELF_riscv := elf64-littleriscv OBJCOPY_ELF_aarch64 := elf64-littleaarch64 @@ -94,92 +71,117 @@ OBJCOPY_BINARCH_aarch64 := aarch64 OBJCOPY_BINARCH_x86_64 := i386:x86-64 OBJCOPY_BINARCH := $(OBJCOPY_BINARCH_$(ARCH)) -# Common flags (base, before module includes) -WARNINGS := -Wall -Wextra -Wno-unused-parameter +# Common include roots. Environment-specific flags are defined after target +# usage requirements have been collected. INCLUDES := -Iinclude -I. # ============================================================ -# Source Collection (Non-Recursive Module Fragments) -# Module .mk files may append to SRCS_CC, SRCS_S, INCLUDES +# Declarative Target Graph # ============================================================ -SRCS_CC := -SRCS_C := -SRCS_S := -SRCS_s := - --include arch/$(ARCH)/module.mk -include kernel/module.mk -include mm/module.mk -include fs/module.mk -include drivers/base/module.mk -include drivers/module.mk -include drivers/tty/module.mk -include fdt/module.mk -include lib/module.mk -include test/module.mk - -# Compiler flags (defined AFTER includes so INCLUDES is complete) -CFLAGS := $(WARNINGS) -O2 -g \ - -ffreestanding -nostdlib \ - -mcmodel=medany \ - -fPIE \ - -MMD -MP \ - $(KCPPFLAGS) \ - $(INCLUDES) \ - -I$(BUILD)/generated - -# -fPIE makes the compiler emit position-independent code (PC-relative -# addressing + GOT for globals), which is what `-shared` linking expects. -# Mirrors Linux's arch/riscv/Makefile under CONFIG_RELOCATABLE=y: -# KBUILD_CFLAGS += -fPIE - -# C flags for musl libc sources: add libc-shim headers first -CFLAGS_LIBC := $(CFLAGS) -Iinclude/libc-shim \ - -Wno-parentheses -Wno-sign-compare - -CXXFLAGS := $(CFLAGS) \ - -std=c++17 \ - -fno-exceptions -fno-rtti -fno-use-cxa-atexit \ - -fno-threadsafe-statics - -# Assembler flags MUST NOT include -fPIE. -# Linux's arch/riscv/Makefile adds -fPIE only to KBUILD_CFLAGS, NOT to -# KBUILD_AFLAGS: with -fPIE the gcc driver puts the assembler in PIC -# mode, where the `la` macro silently expands to a GOT-indirect load -# (`auipc + ld` reading a slot that holds the link-time VA). In our -# pre-MMU boot path that VA is unmapped, so any `la sym, sym` faults -# and reboots into a `_start` loop. Stripping -fPIE makes `la` emit -# direct PC-relative addressing (`auipc + addi`) which works at PA. -ASFLAGS := $(filter-out -fPIE,$(CFLAGS)) - -# Link as a position-independent shared object so the linker emits -# `.rela.dyn` with R_RISCV_RELATIVE entries that the boot stub applies -# at load time. Mirrors Linux's arch/riscv/Makefile §10: -# LDFLAGS_vmlinux += -shared -Bsymbolic -z notext -# `-Bsymbolic` resolves all global refs at link time (no dynamic -# resolution); `-z notext` lets us keep relocations in code pages. -LDFLAGS := -nostdlib -shared -Bsymbolic -z notext -z norelro --no-dynamic-linker +include scripts/build/targets.mk +include Build.mk +$(eval $(call validate-targets)) + +KERNEL_ROOT := kernel/image +KERNEL_TARGETS := $(sort $(call target-closure,$(KERNEL_ROOT))) +KERNEL_LINK_TARGET_ORDER := $(call unique-words,$(call target-link-order,$(KERNEL_ROOT))) +KERNEL_SOURCE_KINDS := object-library source-set static-library whole-archive kernel-test +KERNEL_SOURCE_TARGETS := $(foreach target,$(KERNEL_TARGETS),\ + $(if $(filter $(KERNEL_SOURCE_KINDS),$(target.$(target).kind)),$(target))) +KERNEL_SOURCES := $(sort $(foreach target,$(KERNEL_SOURCE_TARGETS),\ + $(target.$(target).sources))) + +SRCS_CC := $(filter %.cc,$(KERNEL_SOURCES)) +SRCS_C := $(filter %.c,$(KERNEL_SOURCES)) +SRCS_S := $(filter %.S,$(KERNEL_SOURCES)) +SRCS_s := $(filter %.s,$(KERNEL_SOURCES)) + +# Only public usage requirements propagate. Private include paths and flags are +# attached to the objects belonging to the declaring target below. +INCLUDES += $(addprefix -I,$(call target-public-includes,$(KERNEL_ROOT))) +INCLUDES += $(foreach dir,$(call target-public-after-includes,$(KERNEL_ROOT)),-idirafter $(dir)) + +include scripts/build/environments/kernel.mk +include scripts/build/environments/phys.mk # ============================================================ # Object File Computation # ============================================================ -OBJS := $(patsubst %.cc,$(BUILD)/%.o,$(SRCS_CC)) \ - $(patsubst %.c,$(BUILD)/%.o,$(SRCS_C)) \ - $(patsubst %.S,$(BUILD)/%.o,$(SRCS_S)) \ - $(patsubst %.s,$(BUILD)/%.o,$(SRCS_s)) - +# A source may be compiled by multiple targets/environments. Object identity +# therefore includes both the logical target and source path. +source-object = $(BUILD)/obj/$(call target-key,$(1))/$(basename $(2)).o +define apply-target-usage +target.$(1).objects := $$(foreach src,$$(target.$(1).sources),$$(call source-object,$(1),$$(src))) +$$(target.$(1).objects): PRIVATE_INCLUDES := \ + $$(addprefix -I,$$(call target-compile-includes,$(1))) \ + $$(foreach dir,$$(call target-compile-after-includes,$(1)),-idirafter $$(dir)) +$$(target.$(1).objects): PRIVATE_DEFINES := $$(addprefix -D,$$(call target-compile-defines,$(1))) +$$(target.$(1).objects): PRIVATE_CFLAGS := $$(target.$(1).cflags) +$$(target.$(1).objects): PRIVATE_CXXFLAGS := $$(target.$(1).cxxflags) +$$(target.$(1).objects): PRIVATE_ASFLAGS := $$(target.$(1).asflags) +endef +$(foreach target,$(KERNEL_SOURCE_TARGETS),$(eval $(call apply-target-usage,$(target)))) + +define apply-source-identity +$(call source-object,$(1),$(2)): PRIVATE_SOURCE := $(2) +endef +$(foreach target,$(KERNEL_SOURCE_TARGETS),$(foreach src,$(target.$(target).sources),\ + $(eval $(call apply-source-identity,$(target),$(src))))) + +OBJS := $(foreach target,$(KERNEL_SOURCE_TARGETS),$(target.$(target).objects)) DEPS := $(OBJS:.o=.d) +# libfdt is freestanding but intentionally does not use the libc-shim include +# overlay used by the imported string implementation. +$(target.lib/libfdt.objects): CFLAGS_LIBC = $(CFLAGS) -std=gnu23 + +KERNEL_STATIC_TARGETS := $(foreach target,$(KERNEL_SOURCE_TARGETS),\ + $(if $(filter static-library,$(target.$(target).kind)),$(target))) +KERNEL_WHOLE_TARGETS := $(foreach target,$(KERNEL_SOURCE_TARGETS),\ + $(if $(filter whole-archive,$(target.$(target).kind)),$(target))) +KERNEL_OBJECT_TARGETS := $(filter-out $(KERNEL_STATIC_TARGETS) $(KERNEL_WHOLE_TARGETS),\ + $(KERNEL_SOURCE_TARGETS)) + +define define-archive +target.$(1).archive := $(BUILD)/lib/$(call target-key,$(1)).a +$$(target.$(1).archive): PRIVATE_ARCHIVE_OBJECTS := $$(target.$(1).objects) +$$(target.$(1).archive): $$(target.$(1).objects) FORCE + $$(Q)mkdir -p $$(dir $$@) + $$(call if_changed,archive) +endef +$(foreach target,$(KERNEL_STATIC_TARGETS) $(KERNEL_WHOLE_TARGETS),\ + $(eval $(call define-archive,$(target)))) + +KERNEL_LINK_OBJECTS := $(foreach target,$(KERNEL_LINK_TARGET_ORDER),\ + $(if $(filter $(target),$(KERNEL_OBJECT_TARGETS)),$(target.$(target).objects))) +KERNEL_LINK_ARCHIVES := $(foreach target,$(KERNEL_LINK_TARGET_ORDER),\ + $(if $(filter $(target),$(KERNEL_STATIC_TARGETS)),$(target.$(target).archive))) +KERNEL_LINK_WHOLE := $(foreach target,$(KERNEL_LINK_TARGET_ORDER),\ + $(if $(filter $(target),$(KERNEL_WHOLE_TARGETS)),$(target.$(target).archive))) +KERNEL_LINK_INPUTS := $(KERNEL_LINK_OBJECTS) $(KERNEL_LINK_ARCHIVES) \ + $(if $(KERNEL_LINK_WHOLE),--whole-archive $(KERNEL_LINK_WHOLE) --no-whole-archive) + # ============================================================ # Build Rules # ============================================================ -.PHONY: all clean run debug fmt disasm defconfig savedefconfig showconfig +.PHONY: all clean run debug fmt disasm defconfig savedefconfig showconfig \ + help info check-toolchain check-image targets target-info \ + host-libraries host-tests kernel-tests compile_commands \ + phys-libraries toolchain-probe graph FORCE + +FORCE: + +all: $(VMLUME) $(IMAGE) check-image -all: $(VMLUME) $(IMAGE) +check-image: $(VMLUME) + @RE=$(READELF); NM=$(NM); \ + scripts/build/check-kernel-elf.sh $(VMLUME) "$$RE" "$$NM" $(ARCH) # Generate autoconf.h before any compilation -$(AUTOCONF_H): scripts/Kconfig.mk $(wildcard .config) - $(generate-autoconf) +$(AUTOCONF_H): scripts/Kconfig.mk $(wildcard $(KCONFIG_CONFIG)) \ + scripts/build/generate-autoconf.sh FORCE + $(Q)mkdir -p $(dir $@) + $(call if_changed,autoconf) # ============================================================ # Generated header: asm-offsets.h @@ -199,27 +201,20 @@ ASM_OFFSETS_H := $(BUILD)/generated/asm-offsets.h ASM_OFFSETS_SED := scripts/extract-asm-offsets.sed # Compile asm-offsets.c to *.s only — we never want the .o linked in. -$(ASM_OFFSETS_S): $(ASM_OFFSETS_C) $(AUTOCONF_H) - @echo " CC -S $<" - @mkdir -p $(dir $@) - @$(CC) $(CFLAGS_LIBC) -S $< -o $@ +$(ASM_OFFSETS_S): PRIVATE_SOURCE := $(ASM_OFFSETS_C) +$(ASM_OFFSETS_S): $(ASM_OFFSETS_C) $(AUTOCONF_H) FORCE + $(Q)mkdir -p $(dir $@) + $(call if_changed,cc_s_c) # Rewrite ->NAME markers into #define lines. Header guard hand-rolled # at the top, sed body in the middle, closing #endif at the bottom. -$(ASM_OFFSETS_H): $(ASM_OFFSETS_S) $(ASM_OFFSETS_SED) - @echo " GEN $@" - @mkdir -p $(dir $@) - @(echo "/* AUTO-GENERATED — DO NOT EDIT */"; \ - echo "/* Generated from $(ASM_OFFSETS_C) by scripts/extract-asm-offsets.sed */"; \ - echo "#ifndef _LUME_GENERATED_ASM_OFFSETS_H"; \ - echo "#define _LUME_GENERATED_ASM_OFFSETS_H"; \ - echo ""; \ - sed -n -f $(ASM_OFFSETS_SED) $<; \ - echo ""; \ - echo "#endif /* _LUME_GENERATED_ASM_OFFSETS_H */") > $@ +$(ASM_OFFSETS_H): $(ASM_OFFSETS_S) $(ASM_OFFSETS_SED) \ + scripts/build/generate-asm-offsets.sh FORCE + $(Q)mkdir -p $(dir $@) + $(call if_changed,gen_asm_offsets) # All objects depend on autoconf.h and asm-offsets.h -$(OBJS): $(AUTOCONF_H) $(ASM_OFFSETS_H) +$(OBJS): $(AUTOCONF_H) $(ASM_OFFSETS_H) FORCE # ============================================================ # Generic %.pi.o post-processing rule @@ -265,10 +260,10 @@ $(BUILD)/%.pi.o: $(BUILD)/%.o $(call cmd,objcopy) # Preprocess linker script template (.lds.S → .lds) -$(LINKER_SCRIPT): $(LDS_SRC) include/lume/lumeos.lds.h - @echo " LDS $@" - @mkdir -p $(dir $@) - @$(CC) -E -P -x c $(INCLUDES) $< -o $@ +$(LINKER_SCRIPT): PRIVATE_SOURCE := $(LDS_SRC) +$(LINKER_SCRIPT): $(LDS_SRC) include/lume/lumeos.lds.h FORCE + $(Q)mkdir -p $(dir $@) + $(call if_changed,linker_script) # ============================================================ # Initramfs: pack a root filesystem into a newc cpio. @@ -282,84 +277,143 @@ $(LINKER_SCRIPT): $(LDS_SRC) include/lume/lumeos.lds.h # Sourced from $(CONFIG_INITRAMFS_SOURCE) — see scripts/Kconfig.mk. # ============================================================ -# Host compiler — distinct from $(CC), which may be the cross compiler. -HOSTCC ?= cc -HOSTCFLAGS ?= -O2 -Wall - -# Output path matches Linux convention (usr/gen_init_cpio) so the -# verbatim copy of scripts/gen_initramfs.sh from Linux finds it via its -# hardcoded relative path when invoked from the kernel source root. -GEN_INIT_CPIO := usr/gen_init_cpio - -$(GEN_INIT_CPIO): usr/gen_init_cpio.c - @echo " HOSTCC $@" - @$(HOSTCC) $(HOSTCFLAGS) -o $@ $< +# Host compiler environment is distinct from the cross kernel environment. +include scripts/build/environments/host.mk + +# Host tools are registered targets and use the host compiler environment. +HOST_TOOL_TARGETS := $(foreach target,$(targets),\ + $(if $(filter host-tool,$(target.$(target).kind)),$(target))) +HOST_LIBRARY_TARGETS := $(foreach target,$(targets),\ + $(if $(filter host-library,$(target.$(target).kind)),$(target))) +HOST_TEST_TARGETS := $(foreach target,$(targets),\ + $(if $(filter host-test,$(target.$(target).kind)),$(target))) +PHYS_LIBRARY_TARGETS := $(foreach target,$(targets),\ + $(if $(filter phys-library,$(target.$(target).kind)),$(target))) +define define-host-tool +target.$(1).output := $(BUILD)/host/$(call target-key,$(1)) +$$(target.$(1).output): PRIVATE_HOST_SOURCE := $$(firstword $$(target.$(1).sources)) +$$(target.$(1).output): PRIVATE_HOST_CFLAGS := $$(target.$(1).host-cflags) +$$(target.$(1).output): $$(target.$(1).sources) FORCE + $$(Q)mkdir -p $$(dir $$@) + $$(call if_changed,host_tool) +endef +$(foreach target,$(HOST_TOOL_TARGETS),$(eval $(call define-host-tool,$(target)))) + +host-source-object = $(BUILD)/host/obj/$(call target-key,$(1))/$(basename $(2)).o +define define-host-source +$(call host-source-object,$(1),$(2)): PRIVATE_SOURCE := $(2) +$(call host-source-object,$(1),$(2)): $(2) FORCE + $$(Q)mkdir -p $$(dir $$@) + $$(call if_changed,$(if $(filter %.cc,$(2)),host_cxx_o_cc,host_cc_o_c)) +endef +define define-host-library +target.$(1).objects := $$(foreach src,$$(target.$(1).sources),$$(call host-source-object,$(1),$$(src))) +target.$(1).archive := $(BUILD)/host/lib/$(call target-key,$(1)).a +$$(foreach src,$$(target.$(1).sources),$$(eval $$(call define-host-source,$(1),$$(src)))) +$$(target.$(1).archive): PRIVATE_ARCHIVE_OBJECTS := $$(target.$(1).objects) +$$(target.$(1).archive): $$(target.$(1).objects) FORCE + $$(Q)mkdir -p $$(dir $$@) + $$(call if_changed,host_archive) +endef +$(foreach target,$(HOST_LIBRARY_TARGETS),$(eval $(call define-host-library,$(target)))) +HOST_DEPS := $(foreach target,$(HOST_LIBRARY_TARGETS),$(target.$(target).objects:.o=.d)) + +define define-host-test +target.$(1).objects := $$(foreach src,$$(target.$(1).sources),$$(call host-source-object,$(1),$$(src))) +target.$(1).output := $(BUILD)/host/test/$(call target-key,$(1)) +$$(foreach src,$$(target.$(1).sources),$$(eval $$(call define-host-source,$(1),$$(src)))) +$$(target.$(1).output): PRIVATE_HOST_OBJECTS := $$(target.$(1).objects) +$$(target.$(1).output): PRIVATE_HOST_LIBS := $$(foreach dep,$$(call target-deps,$(1)),$$(target.$$(dep).archive)) +$$(target.$(1).output): $$(target.$(1).objects) \ + $$(foreach dep,$$(call target-deps,$(1)),$$(target.$$(dep).archive)) FORCE + $$(Q)mkdir -p $$(dir $$@) + $$(call if_changed,host_link) +endef +$(foreach target,$(HOST_TEST_TARGETS),$(eval $(call define-host-test,$(target)))) +HOST_TEST_DEPS := $(foreach target,$(HOST_TEST_TARGETS),$(target.$(target).objects:.o=.d)) +DEPS += $(HOST_DEPS) $(HOST_TEST_DEPS) + +host-libraries: $(foreach target,$(HOST_LIBRARY_TARGETS),$(target.$(target).archive)) + +host-tests: $(foreach target,$(HOST_TEST_TARGETS),$(target.$(target).output)) + @set -e; $(foreach target,$(HOST_TEST_TARGETS),\ + echo " TEST $(target)"; $(target.$(target).output);) + +phys-source-object = $(BUILD)/phys/obj/$(call target-key,$(1))/$(basename $(2)).o +define define-phys-source +$(call phys-source-object,$(1),$(2)): PRIVATE_SOURCE := $(2) +$(call phys-source-object,$(1),$(2)): $(2) $(AUTOCONF_H) FORCE + $$(Q)mkdir -p $$(dir $$@) + $$(call if_changed,$(if $(filter %.cc,$(2)),phys_cxx_o_cc,$(if $(filter %.c,$(2)),phys_cc_o_c,phys_as_o_S))) +endef +define define-phys-library +target.$(1).objects := $$(foreach src,$$(target.$(1).sources),$$(call phys-source-object,$(1),$$(src))) +target.$(1).archive := $(BUILD)/phys/lib/$(call target-key,$(1)).a +$$(foreach src,$$(target.$(1).sources),$$(eval $$(call define-phys-source,$(1),$$(src)))) +$$(target.$(1).archive): PRIVATE_ARCHIVE_OBJECTS := $$(target.$(1).objects) +$$(target.$(1).archive): $$(target.$(1).objects) FORCE + $$(Q)mkdir -p $$(dir $$@) + $$(call if_changed,archive) +endef +$(foreach target,$(PHYS_LIBRARY_TARGETS),$(eval $(call define-phys-library,$(target)))) +PHYS_DEPS := $(foreach target,$(PHYS_LIBRARY_TARGETS),$(target.$(target).objects:.o=.d)) +DEPS += $(PHYS_DEPS) + +phys-libraries: $(foreach target,$(PHYS_LIBRARY_TARGETS),$(target.$(target).archive)) + +kernel-tests: all + @test "$(CONFIG_SELFTEST)" = y || { \ + echo "kernel-tests requires CONFIG_SELFTEST=y in $(KCONFIG_CONFIG)"; exit 2; } + +GEN_INIT_CPIO := $(target.host/gen_init_cpio.output) INITRAMFS_SOURCE := $(CONFIG_INITRAMFS_SOURCE) -# Record the chosen source so changing CONFIG_INITRAMFS_SOURCE on the -# command line forces a rebuild — without this, `make` only compares -# file mtimes and would happily reuse a cpio packed from a different -# source as long as that other source isn't newer. Evaluated at -# Makefile parse time so the dep edge is correct from the first rule -# check. Mirrors the idea behind Linux's $(call if_changed,...). -INITRAMFS_SRC_STAMP := $(BUILD)/.initramfs_src.stamp -$(shell mkdir -p $(BUILD) 2>/dev/null; \ - prev=$$([ -f "$(INITRAMFS_SRC_STAMP)" ] && cat "$(INITRAMFS_SRC_STAMP)" || echo); \ - if [ "$$prev" != "$(INITRAMFS_SOURCE)" ]; then \ - printf '%s\n' "$(INITRAMFS_SOURCE)" > "$(INITRAMFS_SRC_STAMP)"; \ - fi) +# Encode the selected source in the stamp filename. Changing the source changes +# the dependency without writing anything during Makefile parsing. +INITRAMFS_SOURCE_KEY := $(shell printf '%s' '$(INITRAMFS_SOURCE)' | cksum | cut -d' ' -f1) +INITRAMFS_SRC_STAMP := $(BUILD)/.initramfs-source-$(INITRAMFS_SOURCE_KEY) -$(INITRAMFS_CPIO): $(INITRAMFS_SOURCE) $(INITRAMFS_SRC_STAMP) \ - $(GEN_INIT_CPIO) scripts/gen_initramfs.sh - @echo " INITRAMFS $(INITRAMFS_SOURCE)" +$(INITRAMFS_SRC_STAMP): @mkdir -p $(dir $@) - @./scripts/gen_initramfs.sh -o $@ \ - -u $(CONFIG_INITRAMFS_ROOT_UID) \ - -g $(CONFIG_INITRAMFS_ROOT_GID) \ - $(INITRAMFS_SOURCE) + @touch $@ -$(INITRAMFS_OBJ): $(INITRAMFS_CPIO) - @echo " OBJCOPY $@ (initramfs)" - @$(OBJCOPY) -I binary -O $(OBJCOPY_ELF) -B $(OBJCOPY_BINARCH) \ - --rename-section .data=.init.ramfs,alloc,load,readonly,data,contents \ - $< $@ +$(INITRAMFS_CPIO): $(INITRAMFS_SOURCE) $(INITRAMFS_SRC_STAMP) \ + $(GEN_INIT_CPIO) scripts/gen_initramfs.sh \ + scripts/build/generate-initramfs.sh FORCE + $(Q)mkdir -p $(dir $@) + $(call if_changed,initramfs) + +$(INITRAMFS_OBJ): PRIVATE_SOURCE := $(INITRAMFS_ASM) +$(INITRAMFS_OBJ): $(INITRAMFS_CPIO) $(INITRAMFS_ASM) FORCE + $(Q)mkdir -p $(dir $@) + $(call if_changed,initramfs_obj) # Single-pass kernel link. With `-shared -Bsymbolic`, the linker # emits `.rela.dyn` containing R_RISCV_RELATIVE entries directly into # the output ELF — no separate gen_reloc pass. -$(VMLUME): $(OBJS) $(INITRAMFS_OBJ) $(LINKER_SCRIPT) - @echo " LD $@" - @mkdir -p $(dir $@) - @$(LD) $(LDFLAGS) -T $(LINKER_SCRIPT) -o $@ $(OBJS) $(INITRAMFS_OBJ) - @$(TOOLCHAIN_PREFIX)size $@ +$(VMLUME): $(OBJS) $(foreach target,$(KERNEL_STATIC_TARGETS) $(KERNEL_WHOLE_TARGETS),\ + $(target.$(target).archive)) \ + $(INITRAMFS_OBJ) $(LINKER_SCRIPT) FORCE + $(Q)mkdir -p $(dir $@) + $(call if_changed,link_vmlume) # Image = raw binary, mirrors Linux's `arch/riscv/boot/Image` (objcopy # vmlinux to raw bytes, no ELF header — what U-Boot or QEMU's `-bios` # bare-loading would consume). Linux always builds both vmlinux and # Image; LumeOS follows the same convention. -$(IMAGE): $(VMLUME) - @echo " OBJCOPY $@" - @$(OBJCOPY) -O binary $< $@ - -# Compile C++ sources. Recipe goes through $(call cmd,...) so that -# `make V=1` echoes the full command line. $(Q) prefix on mkdir keeps -# the directory-creation step silent in quiet mode but visible at V=1. -$(BUILD)/%.o: %.cc - $(Q)mkdir -p $(dir $@) - $(call cmd,cxx_o_cc) - -$(BUILD)/%.o: %.c - $(Q)mkdir -p $(dir $@) - $(call cmd,cc_o_c) - -$(BUILD)/%.o: %.S - $(Q)mkdir -p $(dir $@) - $(call cmd,as_o_S) - -$(BUILD)/%.o: %.s - $(Q)mkdir -p $(dir $@) - $(call cmd,as_o_S) +$(IMAGE): $(VMLUME) FORCE + $(call if_changed,objcopy_bin) + +# Materialize one explicit action per target/source pair. This avoids accidental +# object reuse when the same source is built in more than one environment. +define define-kernel-source +$(call source-object,$(1),$(2)): $(2) + $$(Q)mkdir -p $$(dir $$@) + $$(call if_changed,$(if $(filter %.cc,$(2)),cxx_o_cc,$(if $(filter %.c,$(2)),cc_o_c,as_o_S))) +endef +$(foreach target,$(KERNEL_SOURCE_TARGETS),$(foreach src,$(target.$(target).sources),\ + $(eval $(call define-kernel-source,$(target),$(src))))) # Auto-dependency tracking -include $(DEPS) @@ -412,8 +466,128 @@ lint: # Print build info (for debugging Makefile issues) info: - @echo "TOOLCHAIN_PREFIX = $(TOOLCHAIN_PREFIX)" - @echo "SRCS_CC = $(SRCS_CC)" - @echo "SRCS_S = $(SRCS_S)" - @echo "OBJS = $(OBJS)" - @echo "KCPPFLAGS = $(KCPPFLAGS)" + @echo "LumeOS build configuration" + @echo " ARCH $(ARCH)" + @echo " PLATFORM $(PLATFORM)" + @echo " VARIANT $(BUILD_VARIANT)" + @echo " OUTPUT $(BUILD)" + @echo " COMPILER $(COMPILER_FAMILY): $(CXX)" + @echo " TARGET $(TARGET_TRIPLE)" + @echo " GCC_TOOLCHAIN $(GCC_TOOLCHAIN)" + @echo " SYSROOT $(GCC_SYSROOT)" + @echo " LINKER $(LINKER_FAMILY): $(LD)" + @echo " OBJCOPY $(OBJCOPY)" + @echo " INITRAMFS $(INITRAMFS_SOURCE)" + @echo " C++ STANDARD C++23" + @echo " C++ LIBRARY $(CXX_STDLIB)" + @echo " C++ ABI $(CXX_ABI)" + @echo " COMPILER RUNTIME $(COMPILER_RUNTIME)" + @echo " CONFIG $(KCONFIG_CONFIG)" + @echo " BUILD ROOT $(KERNEL_ROOT)" + @echo " TARGETS $(words $(KERNEL_TARGETS)) selected / $(words $(targets)) registered" + +targets: + @$(foreach target,$(targets),printf '%-24s %-16s %s\n' \ + '$(target)' '$(target.$(target).kind)' '$(target.$(target).deps)';) + +target-info: + @test -n "$(TARGET)" || { echo "usage: make target-info TARGET=<name>"; exit 2; } + @$(if $(filter $(TARGET),$(targets)),,echo "unknown target: $(TARGET)"; exit 2;) + @echo "Target: $(TARGET)" + @echo "Kind: $(target.$(TARGET).kind)" + @echo "Dependencies: $(target.$(TARGET).deps)" + @echo "Public deps: $(target.$(TARGET).public-deps)" + @echo "Private deps: $(target.$(TARGET).private-deps)" + @echo "Link deps: $(target.$(TARGET).link-deps)" + @echo "Tool deps: $(target.$(TARGET).tool-deps)" + @echo "Generated deps: $(target.$(TARGET).generated-deps)" + @echo "Closure: $(sort $(call target-closure,$(TARGET)))" + @echo "Public includes: $(target.$(TARGET).public-includes)" + @echo "Private includes: $(target.$(TARGET).private-includes)" + @echo "Sources: $(target.$(TARGET).sources)" + +TARGET_GRAPH := $(BUILD)/targets.dot +graph: + @mkdir -p $(BUILD) + @$(file >$(TARGET_GRAPH),digraph lume_build {) + @$(foreach target,$(targets),$(file >>$(TARGET_GRAPH), "$(target)" [label="$(target)\n$(target.$(target).kind)"];)) + @$(foreach target,$(targets),$(foreach dep,$(call target-deps,$(target)),\ + $(file >>$(TARGET_GRAPH), "$(target)" -> "$(dep)";))) + @$(file >>$(TARGET_GRAPH),}) + @echo " GRAPH $(TARGET_GRAPH)" + +COMPDB := $(BUILD)/compile_commands.json +COMPDB_INPUT := $(BUILD)/.compile_commands.tsv +compile-command = $(if $(filter %.cc,$(2)),$(CXX) $(CXXFLAGS) \ + $(addprefix -I,$(call target-compile-includes,$(1))) \ + $(foreach dir,$(call target-compile-after-includes,$(1)),-idirafter $(dir)) \ + $(addprefix -D,$(call target-compile-defines,$(1))) $(target.$(1).cxxflags),\ + $(CC) $(if $(filter %.c,$(2)),$(CFLAGS_LIBC) \ + $(addprefix -I,$(call target-compile-includes,$(1))) \ + $(foreach dir,$(call target-compile-after-includes,$(1)),-idirafter $(dir)) \ + $(addprefix -D,$(call target-compile-defines,$(1))) $(target.$(1).cflags),\ + $(ASFLAGS) $(addprefix -I,$(call target-compile-includes,$(1))) \ + $(foreach dir,$(call target-compile-after-includes,$(1)),-idirafter $(dir)) \ + $(addprefix -D,$(call target-compile-defines,$(1))) \ + $(target.$(1).asflags))) -c $(2) -o $(call source-object,$(1),$(2)) + +$(BUILD): + @mkdir -p $@ + +$(COMPDB_INPUT): FORCE | $(BUILD) + @$(file >$(COMPDB_INPUT)) + @$(foreach target,$(KERNEL_SOURCE_TARGETS),$(foreach src,$(target.$(target).sources),\ + $(file >>$(COMPDB_INPUT),$(src) $(call source-object,$(target),$(src)) $(call compile-command,$(target),$(src))))) + +compile_commands: $(COMPDB_INPUT) + @$(PYTHON) scripts/build/write-compdb.py $(COMPDB_INPUT) $(COMPDB) + @echo " COMPDB $(COMPDB)" + +check-toolchain: + @command -v $(firstword $(CC)) >/dev/null || { echo "missing C compiler: $(CC)"; exit 1; } + @command -v $(firstword $(CXX)) >/dev/null || { echo "missing C++ compiler: $(CXX)"; exit 1; } + @command -v $(firstword $(LD)) >/dev/null || { echo "missing linker: $(LD)"; exit 1; } + @command -v $(firstword $(OBJCOPY)) >/dev/null || { echo "missing objcopy: $(OBJCOPY)"; exit 1; } + @$(CXX) $(TOOLCHAIN_CFLAGS) $(TOOLCHAIN_CXXFLAGS) -std=c++23 \ + -ffreestanding -fno-exceptions -fno-rtti -fsyntax-only -x c++ /dev/null + @echo "Toolchain OK: $$($(CXX) --version | head -n 1)" + +TOOLCHAIN_REPORT := $(BUILD)/toolchain.report +$(TOOLCHAIN_REPORT): FORCE scripts/build/probe-toolchain.sh + $(Q)mkdir -p $(dir $@) + $(call if_changed,toolchain_probe) + +toolchain-probe: $(TOOLCHAIN_REPORT) + @cat $(TOOLCHAIN_REPORT) + +check-toolchain: $(TOOLCHAIN_REPORT) + +help: + @echo "LumeOS build targets:" + @echo " all Build vmlume.elf and vmlume.bin (default)" + @echo " run Run the selected image in QEMU" + @echo " debug Run QEMU with a GDB server on :1234" + @echo " disasm Generate an interleaved source disassembly" + @echo " info Show the effective build configuration" + @echo " check-toolchain Validate compiler, linker, and C++23 support" + @echo " check-image Validate ELF architecture, symbols, segments, and relocations" + @echo " targets List registered build targets and dependencies" + @echo " target-info Explain TARGET=<name>" + @echo " compile_commands Generate an O= scoped compilation database" + @echo " graph Write the target dependency graph as DOT" + @echo " host-libraries Build registered native static libraries" + @echo " host-tests Build and run registered native tests" + @echo " phys-libraries Build early physical-environment libraries" + @echo " kernel-tests Build the test-enabled kernel (CONFIG_SELFTEST=y)" + @echo " toolchain-probe Probe and cache required compiler capabilities" + @echo " clean Remove only the selected O= output tree" + @echo "" + @echo "Common options:" + @echo " O=<dir> Output directory (default: build)" + @echo " LLVM=1 Use Clang with GNU ld" + @echo " LLVM=1 LD_TYPE=lld Use Clang with LLD" + @echo " LLVM_VERSION=<version> Versioned LLVM tools (default: 20)" + @echo " CROSS_COMPILE=<prefix> Override GNU cross-tool prefix" + @echo " GCC_TOOLCHAIN=<dir> GCC installation used by Clang for target headers/builtins" + @echo " V=1 Print full command lines" + @echo " CONFIG_INITRAMFS_SOURCE=<path> Select initramfs input" diff --git a/arch/riscv/Build.mk b/arch/riscv/Build.mk new file mode 100644 index 0000000..0a18344 --- /dev/null +++ b/arch/riscv/Build.mk @@ -0,0 +1,19 @@ +targets += arch/riscv +target.arch/riscv.kind := source-set +target.arch/riscv.public-includes := arch/riscv/include +target.arch/riscv.sources := \ + arch/riscv/mm/pmap.cc \ + arch/riscv/mm/init.cc \ + arch/riscv/config.cc \ + arch/riscv/trap/trap_md.cc \ + arch/riscv/syscall/syscall_md.cc \ + arch/riscv/lib/random.cc \ + arch/riscv/mm/tlb_md.cc \ + arch/riscv/kernel/head.S \ + arch/riscv/trap/swtch.S \ + arch/riscv/trap/fpu.S \ + arch/riscv/trap/trap_vector.S \ + arch/riscv/uaccess/uaccess_memcpy.S \ + arch/riscv/trap/extable.cc \ + arch/riscv/kernel/signal.cc + diff --git a/arch/riscv/include/asm/sections.h b/arch/riscv/include/asm/sections.h index 454d9de..7067444 100644 --- a/arch/riscv/include/asm/sections.h +++ b/arch/riscv/include/asm/sections.h @@ -23,7 +23,7 @@ extern "C" { #endif extern char _start[]; -extern char _end[]; +extern char _kernel_end[]; extern char _stext[]; extern char _etext[]; diff --git a/arch/riscv/mm/init.cc b/arch/riscv/mm/init.cc index 3cebcd9..30ea45e 100644 --- a/arch/riscv/mm/init.cc +++ b/arch/riscv/mm/init.cc @@ -88,7 +88,7 @@ extern "C" void __init setup_vm(uintptr_t load_pa, uintptr_t dtb_pa) kernel_map.virt_offset = 0; kernel_map.virt_addr = KERNEL_LINK_ADDR + kernel_map.virt_offset; kernel_map.phys_addr = load_pa; - kernel_map.size = (uintptr_t)_end - (uintptr_t)_start; + kernel_map.size = (uintptr_t)_kernel_end - (uintptr_t)_start; kernel_map.page_offset = PAGE_OFFSET; /* Mirrors Linux: va_pa_offset is the constant added to a physical diff --git a/arch/riscv/module.mk b/arch/riscv/module.mk deleted file mode 100644 index 97d22de..0000000 --- a/arch/riscv/module.mk +++ /dev/null @@ -1,18 +0,0 @@ -# arch/riscv/module.mk -SRCS_CC += arch/riscv/mm/pmap.cc -SRCS_CC += arch/riscv/mm/init.cc -SRCS_CC += arch/riscv/config.cc -SRCS_CC += arch/riscv/trap/trap_md.cc -SRCS_CC += arch/riscv/syscall/syscall_md.cc -SRCS_CC += arch/riscv/lib/random.cc -SRCS_CC += arch/riscv/mm/tlb_md.cc -SRCS_S += arch/riscv/kernel/head.S -SRCS_S += arch/riscv/trap/swtch.S -SRCS_S += arch/riscv/trap/fpu.S -SRCS_S += arch/riscv/trap/trap_vector.S -SRCS_S += arch/riscv/uaccess/uaccess_memcpy.S -SRCS_CC += arch/riscv/trap/extable.cc -SRCS_CC += arch/riscv/kernel/signal.cc - -# Arch-specific headers (added to include path) -INCLUDES += -Iarch/riscv/include diff --git a/drivers/Build.mk b/drivers/Build.mk new file mode 100644 index 0000000..342f02b --- /dev/null +++ b/drivers/Build.mk @@ -0,0 +1,17 @@ +targets += drivers/base drivers/tty drivers/core +target.drivers/base.kind := source-set +target.drivers/base.sources := \ + drivers/base/bus.cc drivers/base/core.cc drivers/base/driver.cc \ + drivers/base/platform.cc + +target.drivers/tty.kind := source-set +target.drivers/tty.sources := \ + drivers/tty/tty_core.cc drivers/tty/n_tty.cc drivers/tty/tty_uart.cc + +target.drivers/core.kind := source-set +target.drivers/core.deps := drivers/base drivers/tty +target.drivers/core.sources := drivers/uart.cc drivers/console.cc +ifeq ($(ARCH),riscv) +target.drivers/core.sources += drivers/plic.cc +endif + diff --git a/drivers/base/module.mk b/drivers/base/module.mk deleted file mode 100644 index 1d8fb05..0000000 --- a/drivers/base/module.mk +++ /dev/null @@ -1,9 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin -# -# Driver model core (mirrors Linux drivers/base/) - -SRCS_CC += drivers/base/bus.cc -SRCS_CC += drivers/base/core.cc -SRCS_CC += drivers/base/driver.cc -SRCS_CC += drivers/base/platform.cc diff --git a/drivers/module.mk b/drivers/module.mk deleted file mode 100644 index 9326f30..0000000 --- a/drivers/module.mk +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin -# -# Drivers module - -SRCS_CC += drivers/uart.cc -SRCS_CC += drivers/console.cc - -ifeq ($(ARCH),riscv) -SRCS_CC += drivers/plic.cc -endif diff --git a/drivers/tty/module.mk b/drivers/tty/module.mk deleted file mode 100644 index 49190d4..0000000 --- a/drivers/tty/module.mk +++ /dev/null @@ -1,5 +0,0 @@ -# drivers/tty/module.mk — TTY subsystem build rules - -SRCS_CC += drivers/tty/tty_core.cc -SRCS_CC += drivers/tty/n_tty.cc -SRCS_CC += drivers/tty/tty_uart.cc diff --git a/fdt/Build.mk b/fdt/Build.mk new file mode 100644 index 0000000..ee8be88 --- /dev/null +++ b/fdt/Build.mk @@ -0,0 +1,4 @@ +targets += fdt/core +target.fdt/core.kind := source-set +target.fdt/core.sources := fdt/fdt.cc fdt/device_node.cc + diff --git a/fdt/module.mk b/fdt/module.mk deleted file mode 100644 index 3e490ca..0000000 --- a/fdt/module.mk +++ /dev/null @@ -1,3 +0,0 @@ -# fdt/module.mk -SRCS_CC += fdt/fdt.cc -SRCS_CC += fdt/device_node.cc diff --git a/fs/Build.mk b/fs/Build.mk new file mode 100644 index 0000000..057d1df --- /dev/null +++ b/fs/Build.mk @@ -0,0 +1,8 @@ +targets += fs/core +target.fs/core.kind := source-set +target.fs/core.sources := \ + fs/super.cc fs/inode.cc fs/dcache.cc fs/file_table.cc fs/fd.cc \ + fs/fs_struct.cc fs/namei.cc fs/ramfs/inode.cc fs/ramfs/file.cc \ + fs/ramfs/super.cc fs/vfs_init.cc fs/chardev.cc fs/devfs.cc fs/pipe.cc \ + fs/initramfs.cc + diff --git a/fs/module.mk b/fs/module.mk deleted file mode 100644 index f1be157..0000000 --- a/fs/module.mk +++ /dev/null @@ -1,17 +0,0 @@ -# fs/module.mk — VFS subsystem build rules - -SRCS_CC += fs/super.cc -SRCS_CC += fs/inode.cc -SRCS_CC += fs/dcache.cc -SRCS_CC += fs/file_table.cc -SRCS_CC += fs/fd.cc -SRCS_CC += fs/fs_struct.cc -SRCS_CC += fs/namei.cc -SRCS_CC += fs/ramfs/inode.cc -SRCS_CC += fs/ramfs/file.cc -SRCS_CC += fs/ramfs/super.cc -SRCS_CC += fs/vfs_init.cc -SRCS_CC += fs/chardev.cc -SRCS_CC += fs/devfs.cc -SRCS_CC += fs/pipe.cc -SRCS_CC += fs/initramfs.cc diff --git a/kernel/Build.mk b/kernel/Build.mk new file mode 100644 index 0000000..8f8ffa0 --- /dev/null +++ b/kernel/Build.mk @@ -0,0 +1,11 @@ +targets += kernel/core +target.kernel/core.kind := source-set +target.kernel/core.sources := \ + kernel/main.cc kernel/sync/spinlock.cc kernel/sync/mutex.cc \ + kernel/trap.cc kernel/irq.cc kernel/sched_core.cc \ + kernel/sched_class_o1.cc kernel/timer.cc kernel/pid.cc kernel/pgrp.cc \ + kernel/task.cc kernel/syscall.cc kernel/sys_process.cc kernel/sys_mm.cc \ + kernel/sys_info.cc kernel/sys_fs.cc kernel/uaccess.cc kernel/waitqueue.cc \ + kernel/signal.cc kernel/sys_signal.cc kernel/sys_misc.cc kernel/exec.cc \ + kernel/poll.cc kernel/ptrace.cc kernel/smp.cc + diff --git a/kernel/main.cc b/kernel/main.cc index db5843c..c84f3ad 100644 --- a/kernel/main.cc +++ b/kernel/main.cc @@ -82,7 +82,9 @@ void tty_init(); // 15.5. TTY subsystem (console TTY + UART RX IRQ) void populate_rootfs(); // 15.6. Unpack embedded initramfs CPIO into ramfs // Boot self-test runner +#ifdef CONFIG_SELFTEST void selftest_run_all(); +#endif // AP per-CPU init void vmm_init_ap(); // Switch to BSP-built kernel page table @@ -100,6 +102,7 @@ static void init_thread_func(void *) { printk(KERN_INFO "[init] PID 1 started\n"); /* Run post-SMP tests — APs are online by now */ +#ifdef CONFIG_SELFTEST extern void selftest_smp(); kprintf("\n========================================\n"); kprintf(" Post-SMP Test Suite\n"); @@ -108,6 +111,7 @@ static void init_thread_func(void *) { kprintf("========================================\n"); kprintf(" Post-SMP tests PASSED\n"); kprintf("========================================\n\n"); +#endif /* * Set up init's own VFS context (like Linux kernel_init). @@ -326,7 +330,9 @@ extern "C" [[noreturn]] void kernel_main(uint64 cpu_id, uint64 fdt_paddr) { // 17. Boot self-tests — verify subsystem correctness. // Tests that create kthreads must wait() for them (freeing PIDs). +#ifdef CONFIG_SELFTEST selftest_run_all(); +#endif // 18. Spawn PID 1 (init) — the first persistent task. // Like Linux's rest_init() → user_mode_thread(kernel_init). diff --git a/kernel/module.mk b/kernel/module.mk deleted file mode 100644 index 754a14c..0000000 --- a/kernel/module.mk +++ /dev/null @@ -1,26 +0,0 @@ -# kernel/module.mk -SRCS_CC += kernel/main.cc -SRCS_CC += kernel/sync/spinlock.cc -SRCS_CC += kernel/sync/mutex.cc -SRCS_CC += kernel/trap.cc -SRCS_CC += kernel/irq.cc -SRCS_CC += kernel/sched_core.cc -SRCS_CC += kernel/sched_class_o1.cc -SRCS_CC += kernel/timer.cc -SRCS_CC += kernel/pid.cc -SRCS_CC += kernel/pgrp.cc -SRCS_CC += kernel/task.cc -SRCS_CC += kernel/syscall.cc -SRCS_CC += kernel/sys_process.cc -SRCS_CC += kernel/sys_mm.cc -SRCS_CC += kernel/sys_info.cc -SRCS_CC += kernel/sys_fs.cc -SRCS_CC += kernel/uaccess.cc -SRCS_CC += kernel/waitqueue.cc -SRCS_CC += kernel/signal.cc -SRCS_CC += kernel/sys_signal.cc -SRCS_CC += kernel/sys_misc.cc -SRCS_CC += kernel/exec.cc -SRCS_CC += kernel/poll.cc -SRCS_CC += kernel/ptrace.cc -SRCS_CC += kernel/smp.cc diff --git a/lib/Build.mk b/lib/Build.mk new file mode 100644 index 0000000..77d380c --- /dev/null +++ b/lib/Build.mk @@ -0,0 +1,35 @@ +targets += lib/runtime lib/string lib/libfdt lib/core + +target.lib/runtime.kind := source-set +target.lib/runtime.sources := \ + lib/cxx_abi.cc lib/rbtree.cc lib/kobject.cc lib/kprintf.cc lib/klog.cc \ + lib/random.cc lib/printf.c + +target.lib/string.kind := static-library +target.lib/string.sources := $(sort $(wildcard lib/string/*.c)) + +# Architecture assembly replaces the matching generic C implementation. +string-arch.riscv := +string-arch.x86_64 := x86_64 +string-arch.aarch64 := aarch64 +string-arch.i386 := i386 +string-arch.arm := arm +string-arch := $(string-arch.$(ARCH)) +ifneq ($(string-arch),) +string-asm := $(sort $(wildcard lib/string/$(string-arch)/*.S) \ + $(wildcard lib/string/$(string-arch)/*.s)) +string-asm-names := $(sort $(basename $(notdir $(string-asm)))) +target.lib/string.sources := $(filter-out \ + $(foreach name,$(string-asm-names),lib/string/$(name).c), \ + $(target.lib/string.sources)) $(string-asm) +endif + +target.lib/libfdt.kind := static-library +target.lib/libfdt.sources := \ + lib/fdt.c lib/fdt_ro.c lib/fdt_wip.c lib/fdt_rw.c lib/fdt_sw.c \ + lib/fdt_strerror.c lib/fdt_empty_tree.c lib/fdt_addresses.c +target.lib/libfdt.private-includes := scripts/dtc/libfdt +target.lib/libfdt.cflags := -Wno-parentheses -Wno-sign-compare + +target.lib/core.kind := header-library +target.lib/core.deps := lib/runtime lib/string lib/libfdt diff --git a/lib/module.mk b/lib/module.mk deleted file mode 100644 index 0b178f8..0000000 --- a/lib/module.mk +++ /dev/null @@ -1,66 +0,0 @@ -# lib/module.mk -SRCS_CC += lib/cxx_abi.cc -SRCS_CC += lib/rbtree.cc -SRCS_CC += lib/kobject.cc -SRCS_CC += lib/kprintf.cc -SRCS_CC += lib/klog.cc -SRCS_CC += lib/random.cc -SRCS_C += lib/printf.c - -# ============================================================ -# musl string library (cross-platform with arch asm overrides) -# ============================================================ - -# Map LumeOS ARCH to musl string arch directory -# riscv has no musl asm optimizations → pure C fallback -MUSL_STRING_ARCH_riscv := -MUSL_STRING_ARCH_x86_64 := x86_64 -MUSL_STRING_ARCH_i386 := i386 -MUSL_STRING_ARCH_aarch64:= aarch64 -MUSL_STRING_ARCH_arm := arm - -MUSL_STRING_ARCH := $(MUSL_STRING_ARCH_$(ARCH)) - -# Collect arch-specific asm sources (if any for this arch) -MUSL_ASM_SRCS := -ifneq ($(MUSL_STRING_ARCH),) - MUSL_ASM_SRCS += $(wildcard lib/string/$(MUSL_STRING_ARCH)/*.S) \ - $(wildcard lib/string/$(MUSL_STRING_ARCH)/*.s) -endif - -# Derive which C files are overridden by asm (strip path+ext to get base name) -MUSL_ASM_BASENAMES := $(sort $(basename $(notdir $(MUSL_ASM_SRCS)))) - -# All C sources, then filter out those with asm replacements -MUSL_ALL_C := $(wildcard lib/string/*.c) -MUSL_EXCLUDED_C := $(foreach name,$(MUSL_ASM_BASENAMES),lib/string/$(name).c) -MUSL_C_SRCS := $(filter-out $(MUSL_EXCLUDED_C),$(MUSL_ALL_C)) - -SRCS_C += $(MUSL_C_SRCS) -SRCS_S += $(filter %.S,$(MUSL_ASM_SRCS)) -SRCS_s += $(filter %.s,$(MUSL_ASM_SRCS)) - -# ============================================================ -# libfdt — kernel adapter shims around scripts/dtc/libfdt/. -# -# Each shim is two lines: -# #include <linux/libfdt_env.h> -# #include "../scripts/dtc/libfdt/fdt*.c" -# matching Linux lib/fdt*.c verbatim. The compile recipe needs: -# - <linux/libfdt_env.h> reachable via the default -Iinclude path -# - -I scripts/dtc/libfdt so the upstream source's #include <libfdt.h> -# and friends resolve to scripts/dtc/libfdt/libfdt.h -# Overriding CFLAGS_LIBC (which the %.o: %.c rule uses) avoids dragging -# in libc-shim headers — libfdt is freestanding-compatible and uses -# only the string functions forward-declared in <linux/libfdt_env.h>. -# ============================================================ -LIBFDT_FILES := fdt.c fdt_ro.c fdt_wip.c fdt_rw.c fdt_sw.c \ - fdt_strerror.c fdt_empty_tree.c fdt_addresses.c -SRCS_C += $(addprefix lib/,$(LIBFDT_FILES)) - -# Per-target CFLAGS override: plain CFLAGS plus the upstream include dir. -# Use `=` (recursive) not `:=` because $(CFLAGS) is defined in the top -# Makefile AFTER this file is included — `:=` would expand it now (empty) -# and lose -Iinclude, breaking <linux/libfdt_env.h>. -$(addprefix $(BUILD)/lib/,$(LIBFDT_FILES:.c=.o)): CFLAGS_LIBC = \ - $(CFLAGS) -I scripts/dtc/libfdt diff --git a/mm/Build.mk b/mm/Build.mk new file mode 100644 index 0000000..b6d816d --- /dev/null +++ b/mm/Build.mk @@ -0,0 +1,6 @@ +targets += mm/core +target.mm/core.kind := source-set +target.mm/core.sources := \ + mm/memblock.cc mm/pmm.cc mm/slab.cc mm/vmm.cc mm/vma.cc \ + mm/page_cache.cc mm/tlb.cc + diff --git a/mm/module.mk b/mm/module.mk deleted file mode 100644 index 93cef80..0000000 --- a/mm/module.mk +++ /dev/null @@ -1,7 +0,0 @@ -SRCS_CC += mm/memblock.cc -SRCS_CC += mm/pmm.cc -SRCS_CC += mm/slab.cc -SRCS_CC += mm/vmm.cc -SRCS_CC += mm/vma.cc -SRCS_CC += mm/page_cache.cc -SRCS_CC += mm/tlb.cc diff --git a/scripts/Kbuild.include b/scripts/Kbuild.include index 76466bd..7ccc03d 100644 --- a/scripts/Kbuild.include +++ b/scripts/Kbuild.include @@ -9,14 +9,7 @@ # - Q / KBUILD_VERBOSE : `make V=1` shows full commands, # default V=0 prints a one-line summary # - $(call cmd,<name>) : echo a quiet summary, then run cmd_<name> -# - $(call if_changed,<name>) : currently a thin alias for $(cmd) — -# no .cmd-file hashing yet (Linux's full -# if_changed uses fixdep to detect both -# recipe-text and header changes; LumeOS -# relies on `-MMD -MP` for headers and -# on `make clean` for flag changes). -# - $(call kbuild-objs,...) : translate Kbuild-style `obj-y := foo.o` -# entries into SRCS_C / SRCS_CC. +# - $(call if_changed,<name>) : rebuild when inputs or the command change. # # Naming convention adopted from Linux: each command has both a # `quiet_cmd_<name>` (single-line summary) and a `cmd_<name>` (full @@ -49,11 +42,11 @@ echo-cmd = $(if $($(quiet)cmd_$(1)),echo ' $($(quiet)cmd_$(1))';) cmd = @$(echo-cmd) $(cmd_$(1)) -# if_changed currently degenerates to `cmd` — full Linux semantics -# require tracking the command-line via per-target .<file>.cmd files -# and comparing on every rebuild, which needs fixdep. Until LumeOS -# grows that infrastructure, `make clean` after editing CFLAGS suffices. -if_changed = $(cmd) +# Record a checksum of the exact argv next to each output. FORCE makes Make +# reconsider the action; run-if-changed executes it only for a changed input or +# command signature. +if_changed = @scripts/build/run-if-changed.sh \ + $(dir $@).$(notdir $@).cmd $@ '$($(quiet)cmd_$(1))' $^ -- $(cmd_$(1)) # --- Standard quiet_cmd_* / cmd_* templates --------------------------- # @@ -61,21 +54,99 @@ if_changed = $(cmd) # name via $(call cmd,<name>). Add new pairs here as new recipe shapes # show up (e.g., cmd_lz / cmd_gz / cmd_zstd for compressed Image targets). - quiet_cmd_cc_o_c = CC $< - cmd_cc_o_c = $(CC) $(CFLAGS_LIBC) -c $< -o $@ + quiet_cmd_cc_o_c = CC $(PRIVATE_SOURCE) + cmd_cc_o_c = $(CC) $(CFLAGS_LIBC) $(PRIVATE_INCLUDES) $(PRIVATE_DEFINES) $(PRIVATE_CFLAGS) -c $(PRIVATE_SOURCE) -o $@ - quiet_cmd_cxx_o_cc = CXX $< - cmd_cxx_o_cc = $(CXX) $(CXXFLAGS) -c $< -o $@ + quiet_cmd_cxx_o_cc = CXX $(PRIVATE_SOURCE) + cmd_cxx_o_cc = $(CXX) $(CXXFLAGS) $(PRIVATE_INCLUDES) $(PRIVATE_DEFINES) $(PRIVATE_CXXFLAGS) -c $(PRIVATE_SOURCE) -o $@ - quiet_cmd_as_o_S = AS $< - cmd_as_o_S = $(CC) $(ASFLAGS) -c $< -o $@ + quiet_cmd_as_o_S = AS $(PRIVATE_SOURCE) + cmd_as_o_S = $(CC) $(ASFLAGS) $(PRIVATE_INCLUDES) $(PRIVATE_DEFINES) $(PRIVATE_ASFLAGS) -c $(PRIVATE_SOURCE) -o $@ quiet_cmd_cc_s_c = CC -S $< - cmd_cc_s_c = $(CC) $(CFLAGS_LIBC) -S $< -o $@ + cmd_cc_s_c = $(CC) $(CFLAGS_LIBC) -S $(PRIVATE_SOURCE) -o $@ + + quiet_cmd_gen_asm_offsets = GEN $@ + cmd_gen_asm_offsets = scripts/build/generate-asm-offsets.sh $@ \ + $(ASM_OFFSETS_S) $(ASM_OFFSETS_SED) $(ASM_OFFSETS_C) + + quiet_cmd_autoconf = GEN $@ + cmd_autoconf = scripts/build/generate-autoconf.sh $@ \ + CONFIG_NR_CPUS $(CONFIG_NR_CPUS) value \ + CONFIG_HZ $(CONFIG_HZ) value \ + CONFIG_NR_PRIORITIES $(CONFIG_NR_PRIORITIES) value \ + CONFIG_DEFAULT_PRIORITY $(CONFIG_DEFAULT_PRIORITY) value \ + CONFIG_IDLE_PRIORITY $(CONFIG_IDLE_PRIORITY) value \ + CONFIG_DEFAULT_TIMESLICE $(CONFIG_DEFAULT_TIMESLICE) value \ + CONFIG_PAGE_SIZE $(CONFIG_PAGE_SIZE) value \ + CONFIG_MAX_ORDER $(CONFIG_MAX_ORDER) value \ + CONFIG_KERNEL_STACK_SIZE $(CONFIG_KERNEL_STACK_SIZE) value \ + CONFIG_KASLR $(CONFIG_KASLR) bool \ + CONFIG_KASLR_POLICY_ID $(CONFIG_KASLR_POLICY_ID) value \ + CONFIG_KASLR_LOG_LEVEL $(CONFIG_KASLR_LOG_LEVEL) value \ + CONFIG_STACK_CANARY $(CONFIG_STACK_CANARY) bool \ + CONFIG_DEBUG $(CONFIG_DEBUG) bool \ + CONFIG_DEBUG_LOCKS $(CONFIG_DEBUG_LOCKS) bool \ + CONFIG_SELFTEST $(CONFIG_SELFTEST) bool \ + CONFIG_KLOG $(CONFIG_KLOG) bool \ + CONFIG_KERNEL_COMPRESSED $(CONFIG_KERNEL_COMPRESSED) bool + + quiet_cmd_linker_script = LDS $@ + cmd_linker_script = $(CC) -E -P -x c $(INCLUDES) $(PRIVATE_SOURCE) -o $@ + + quiet_cmd_initramfs = INITRAMFS $(INITRAMFS_SOURCE) + cmd_initramfs = scripts/build/generate-initramfs.sh $(GEN_INIT_CPIO) \ + ./scripts/gen_initramfs.sh $@ \ + $(CONFIG_INITRAMFS_ROOT_UID) $(CONFIG_INITRAMFS_ROOT_GID) \ + $(INITRAMFS_SOURCE) + + quiet_cmd_initramfs_obj = AS $@ (initramfs) + cmd_initramfs_obj = $(CC) $(ASFLAGS) \ + -DINITRAMFS_IMAGE='"$(INITRAMFS_CPIO)"' \ + -c $(PRIVATE_SOURCE) -o $@ quiet_cmd_objcopy_bin = OBJCOPY $@ cmd_objcopy_bin = $(OBJCOPY) -O binary $< $@ + quiet_cmd_archive = AR $@ + cmd_archive = scripts/build/archive.sh $@ $(AR) $(PRIVATE_ARCHIVE_OBJECTS) + + quiet_cmd_host_tool = HOSTCC $@ + cmd_host_tool = $(HOSTCC) $(HOSTCFLAGS) $(PRIVATE_HOST_CFLAGS) \ + -o $@ $(PRIVATE_HOST_SOURCE) + + quiet_cmd_host_cc_o_c = HOSTCC $(PRIVATE_SOURCE) + cmd_host_cc_o_c = $(HOSTCC) $(HOSTCFLAGS) -MMD -MP -c $(PRIVATE_SOURCE) -o $@ + + quiet_cmd_host_cxx_o_cc = HOSTCXX $(PRIVATE_SOURCE) + cmd_host_cxx_o_cc = $(HOSTCXX) $(HOSTCXXFLAGS) -MMD -MP -c $(PRIVATE_SOURCE) -o $@ + + quiet_cmd_host_archive = HOSTAR $@ + cmd_host_archive = scripts/build/archive.sh $@ $(HOSTAR) $(PRIVATE_ARCHIVE_OBJECTS) + + quiet_cmd_host_link = HOSTLD $@ + cmd_host_link = $(HOSTCXX) $(HOSTCXXFLAGS) -o $@ \ + $(PRIVATE_HOST_OBJECTS) $(PRIVATE_HOST_LIBS) + + quiet_cmd_toolchain_probe = PROBE toolchain + cmd_toolchain_probe = scripts/build/probe-toolchain.sh $@ \ + $(CC) $(CXX) $(LD) $(COMPILER_FAMILY) $(TARGET_TRIPLE) \ + '$(GCC_TOOLCHAIN)' '$(GCC_SYSROOT)' + + quiet_cmd_phys_cc_o_c = PHYSCC $(PRIVATE_SOURCE) + cmd_phys_cc_o_c = $(CC) $(PHYS_CFLAGS) -MMD -MP -c $(PRIVATE_SOURCE) -o $@ + + quiet_cmd_phys_cxx_o_cc = PHYSCXX $(PRIVATE_SOURCE) + cmd_phys_cxx_o_cc = $(CXX) $(PHYS_CXXFLAGS) -MMD -MP -c $(PRIVATE_SOURCE) -o $@ + + quiet_cmd_phys_as_o_S = PHYSAS $(PRIVATE_SOURCE) + cmd_phys_as_o_S = $(CC) $(PHYS_ASFLAGS) -MMD -MP -c $(PRIVATE_SOURCE) -o $@ + + quiet_cmd_link_vmlume = LD $@ + cmd_link_vmlume = scripts/build/link-kernel.sh $@ $(SIZE) \ + $(LD) $(LDFLAGS) -T $(LINKER_SCRIPT) -o $@ \ + $(KERNEL_LINK_INPUTS) $(INITRAMFS_OBJ) + # Objcopy with custom flags via per-target OBJCOPYFLAGS variable — # used by the .pi.o post-processing rule (--prefix-symbols=__pi_ etc.). quiet_cmd_objcopy = OBJCOPY $@ @@ -83,28 +154,3 @@ if_changed = $(cmd) quiet_cmd_ld_kernel = LD $@ cmd_ld_kernel = $(LD) $(LDFLAGS) -T $(LINKER_SCRIPT) -o $@ $^ - -# --- kbuild-objs: obj-y -> SRCS_C / SRCS_CC --------------------------- -# -# Usage from a subdir Makefile (e.g. arch/riscv/kernel/pi/Makefile): -# -# obj-dir := arch/riscv/kernel/pi -# obj-y := fdt_early.pi.o cmdline_early.pi.o archrandom_early.pi.o -# $(eval $(call kbuild-objs,$(obj-dir),$(obj-y:.pi.o=.o))) -# -# For each <name>.o entry, looks up <dir>/<name>.cc first, then -# <dir>/<name>.c, and appends to SRCS_CC / SRCS_C respectively. -# Pure .pi.o entries should be passed through `:.pi.o=.o` first by the -# caller so the .pi suffix doesn't confuse the source-file lookup. - -define _kbuild-find-src -$(if $(wildcard $(1)/$(basename $(2)).cc),\ - $(eval SRCS_CC += $(1)/$(basename $(2)).cc),\ - $(if $(wildcard $(1)/$(basename $(2)).c),\ - $(eval SRCS_C += $(1)/$(basename $(2)).c),\ - $(error kbuild-objs: no $(1)/$(basename $(2)).{c,cc} for obj $(2)))) -endef - -define kbuild-objs -$(foreach _obj,$(2),$(call _kbuild-find-src,$(1),$(_obj))) -endef diff --git a/scripts/Kconfig.mk b/scripts/Kconfig.mk index 2bdc934..3db7a2f 100644 --- a/scripts/Kconfig.mk +++ b/scripts/Kconfig.mk @@ -6,15 +6,15 @@ # This file is the heart of the LumeOS build-time configuration system. # It mirrors the role of Linux's Kconfig + auto.conf pipeline: # -# 1. Developer edits `.config` (or copies from arch/$(ARCH)/defconfig) -# 2. This file reads `.config`, applies defaults for anything missing, +# 1. Developer edits `$(O)/.config` (or runs `make O=... defconfig`) +# 2. This file reads that configuration and applies missing defaults, # validates constraints, and exports two things: # a) KCPPFLAGS — a set of -DCONFIG_xxx=value flags for the compiler # b) $(BUILD)/generated/autoconf.h — the same values as C #defines # # Design Principles (matching Linux conventions): -# - Every CONFIG_ symbol has a default defined HERE, so a bare `make` -# with no .config still builds a sane kernel. +# - Every CONFIG_ symbol has a default defined HERE, so an empty output +# directory still builds a sane kernel. # - Boolean options use 'y' / 'n'. Numeric options are plain integers. # - The generated autoconf.h uses the exact same names so that # `#ifdef CONFIG_DEBUG` works identically in Makefile and C. @@ -23,7 +23,8 @@ # ------------------------------------------------------------------ # §1 Read user configuration (if it exists) # ------------------------------------------------------------------ --include .config +KCONFIG_CONFIG ?= $(BUILD)/.config +-include $(KCONFIG_CONFIG) # ------------------------------------------------------------------ # §2 Provide defaults for every CONFIG_ symbol @@ -143,42 +144,6 @@ endif # ------------------------------------------------------------------ AUTOCONF_H := $(BUILD)/generated/autoconf.h -define generate-autoconf - @mkdir -p $(dir $(AUTOCONF_H)) - @echo " GEN $(AUTOCONF_H)" - @echo "/* AUTO-GENERATED — DO NOT EDIT */" > $(AUTOCONF_H) - @echo "/* Generated by scripts/Kconfig.mk */" >> $(AUTOCONF_H) - @echo "#pragma once" >> $(AUTOCONF_H) - @echo "" >> $(AUTOCONF_H) - @echo "/* Platform */" >> $(AUTOCONF_H) - @echo "#define CONFIG_NR_CPUS $(CONFIG_NR_CPUS)" >> $(AUTOCONF_H) - @echo "" >> $(AUTOCONF_H) - @echo "/* Scheduler */" >> $(AUTOCONF_H) - @echo "#define CONFIG_HZ $(CONFIG_HZ)" >> $(AUTOCONF_H) - @echo "#define CONFIG_NR_PRIORITIES $(CONFIG_NR_PRIORITIES)" >> $(AUTOCONF_H) - @echo "#define CONFIG_DEFAULT_PRIORITY $(CONFIG_DEFAULT_PRIORITY)" >> $(AUTOCONF_H) - @echo "#define CONFIG_IDLE_PRIORITY $(CONFIG_IDLE_PRIORITY)" >> $(AUTOCONF_H) - @echo "#define CONFIG_DEFAULT_TIMESLICE $(CONFIG_DEFAULT_TIMESLICE)" >> $(AUTOCONF_H) - @echo "" >> $(AUTOCONF_H) - @echo "/* Memory Management */" >> $(AUTOCONF_H) - @echo "#define CONFIG_PAGE_SIZE $(CONFIG_PAGE_SIZE)" >> $(AUTOCONF_H) - @echo "#define CONFIG_MAX_ORDER $(CONFIG_MAX_ORDER)" >> $(AUTOCONF_H) - @echo "#define CONFIG_KERNEL_STACK_SIZE $(CONFIG_KERNEL_STACK_SIZE)" >> $(AUTOCONF_H) - @echo "" >> $(AUTOCONF_H) - @echo "/* Security */" >> $(AUTOCONF_H) - $(if $(filter y,$(CONFIG_KASLR)), @echo "#define CONFIG_KASLR 1" >> $(AUTOCONF_H)) - @echo "#define CONFIG_KASLR_POLICY_ID $(CONFIG_KASLR_POLICY_ID)" >> $(AUTOCONF_H) - @echo "#define CONFIG_KASLR_LOG_LEVEL $(CONFIG_KASLR_LOG_LEVEL)" >> $(AUTOCONF_H) - $(if $(filter y,$(CONFIG_STACK_CANARY)), @echo "#define CONFIG_STACK_CANARY 1" >> $(AUTOCONF_H)) - @echo "" >> $(AUTOCONF_H) - @echo "/* Debug */" >> $(AUTOCONF_H) - $(if $(filter y,$(CONFIG_DEBUG)), @echo "#define CONFIG_DEBUG 1" >> $(AUTOCONF_H)) - $(if $(filter y,$(CONFIG_DEBUG_LOCKS)), @echo "#define CONFIG_DEBUG_LOCKS 1" >> $(AUTOCONF_H)) - $(if $(filter y,$(CONFIG_SELFTEST)), @echo "#define CONFIG_SELFTEST 1" >> $(AUTOCONF_H)) - $(if $(filter y,$(CONFIG_KLOG)), @echo "#define CONFIG_KLOG 1" >> $(AUTOCONF_H)) - $(if $(filter y,$(CONFIG_KERNEL_COMPRESSED)), @echo "#define CONFIG_KERNEL_COMPRESSED 1" >> $(AUTOCONF_H)) -endef - # ------------------------------------------------------------------ # §6 Utility targets # ------------------------------------------------------------------ @@ -187,29 +152,31 @@ endef .PHONY: defconfig savedefconfig showconfig defconfig: - @echo " DEFCFG arch/$(CONFIG_ARCH)/defconfig → .config" - @cp arch/$(CONFIG_ARCH)/defconfig .config + @echo " DEFCFG arch/$(CONFIG_ARCH)/defconfig → $(KCONFIG_CONFIG)" + @mkdir -p $(dir $(KCONFIG_CONFIG)) + @cp arch/$(CONFIG_ARCH)/defconfig $(KCONFIG_CONFIG) # 'make savedefconfig' — save current effective config savedefconfig: - @echo " SAVE .config" - @echo "# LumeOS Kernel Configuration (saved $$(date +%Y-%m-%d))" > .config - @echo "CONFIG_ARCH = $(CONFIG_ARCH)" >> .config - @echo "CONFIG_NR_CPUS = $(CONFIG_NR_CPUS)" >> .config - @echo "CONFIG_HZ = $(CONFIG_HZ)" >> .config - @echo "CONFIG_NR_PRIORITIES = $(CONFIG_NR_PRIORITIES)" >> .config - @echo "CONFIG_PAGE_SIZE = $(CONFIG_PAGE_SIZE)" >> .config - @echo "CONFIG_MAX_ORDER = $(CONFIG_MAX_ORDER)" >> .config - @echo "CONFIG_KERNEL_STACK_SIZE = $(CONFIG_KERNEL_STACK_SIZE)" >> .config - @echo "CONFIG_KASLR = $(CONFIG_KASLR)" >> .config - @echo "CONFIG_KASLR_POLICY_ID = $(CONFIG_KASLR_POLICY_ID)" >> .config - @echo "CONFIG_KASLR_LOG_LEVEL = $(CONFIG_KASLR_LOG_LEVEL)" >> .config - @echo "CONFIG_STACK_CANARY = $(CONFIG_STACK_CANARY)" >> .config - @echo "CONFIG_DEBUG = $(CONFIG_DEBUG)" >> .config - @echo "CONFIG_DEBUG_LOCKS = $(CONFIG_DEBUG_LOCKS)" >> .config - @echo "CONFIG_SELFTEST = $(CONFIG_SELFTEST)" >> .config - @echo "CONFIG_KLOG = $(CONFIG_KLOG)" >> .config - @echo "CONFIG_KERNEL_COMPRESSED = $(CONFIG_KERNEL_COMPRESSED)" >> .config + @echo " SAVE $(KCONFIG_CONFIG)" + @mkdir -p $(dir $(KCONFIG_CONFIG)) + @echo "# LumeOS Kernel Configuration (saved $$(date +%Y-%m-%d))" > $(KCONFIG_CONFIG) + @echo "CONFIG_ARCH = $(CONFIG_ARCH)" >> $(KCONFIG_CONFIG) + @echo "CONFIG_NR_CPUS = $(CONFIG_NR_CPUS)" >> $(KCONFIG_CONFIG) + @echo "CONFIG_HZ = $(CONFIG_HZ)" >> $(KCONFIG_CONFIG) + @echo "CONFIG_NR_PRIORITIES = $(CONFIG_NR_PRIORITIES)" >> $(KCONFIG_CONFIG) + @echo "CONFIG_PAGE_SIZE = $(CONFIG_PAGE_SIZE)" >> $(KCONFIG_CONFIG) + @echo "CONFIG_MAX_ORDER = $(CONFIG_MAX_ORDER)" >> $(KCONFIG_CONFIG) + @echo "CONFIG_KERNEL_STACK_SIZE = $(CONFIG_KERNEL_STACK_SIZE)" >> $(KCONFIG_CONFIG) + @echo "CONFIG_KASLR = $(CONFIG_KASLR)" >> $(KCONFIG_CONFIG) + @echo "CONFIG_KASLR_POLICY_ID = $(CONFIG_KASLR_POLICY_ID)" >> $(KCONFIG_CONFIG) + @echo "CONFIG_KASLR_LOG_LEVEL = $(CONFIG_KASLR_LOG_LEVEL)" >> $(KCONFIG_CONFIG) + @echo "CONFIG_STACK_CANARY = $(CONFIG_STACK_CANARY)" >> $(KCONFIG_CONFIG) + @echo "CONFIG_DEBUG = $(CONFIG_DEBUG)" >> $(KCONFIG_CONFIG) + @echo "CONFIG_DEBUG_LOCKS = $(CONFIG_DEBUG_LOCKS)" >> $(KCONFIG_CONFIG) + @echo "CONFIG_SELFTEST = $(CONFIG_SELFTEST)" >> $(KCONFIG_CONFIG) + @echo "CONFIG_KLOG = $(CONFIG_KLOG)" >> $(KCONFIG_CONFIG) + @echo "CONFIG_KERNEL_COMPRESSED = $(CONFIG_KERNEL_COMPRESSED)" >> $(KCONFIG_CONFIG) # 'make showconfig' — print effective configuration showconfig: diff --git a/scripts/build/Build.mk b/scripts/build/Build.mk new file mode 100644 index 0000000..0a15b38 --- /dev/null +++ b/scripts/build/Build.mk @@ -0,0 +1,10 @@ +targets += build/host-library-smoke build/host-test-smoke build/phys-library-smoke +target.build/host-library-smoke.kind := host-library +target.build/host-library-smoke.sources := scripts/build/tests/host_library.cc + +target.build/host-test-smoke.kind := host-test +target.build/host-test-smoke.sources := scripts/build/tests/host_test.cc +target.build/host-test-smoke.private-deps := build/host-library-smoke + +target.build/phys-library-smoke.kind := phys-library +target.build/phys-library-smoke.sources := scripts/build/tests/phys_library.cc diff --git a/scripts/build/archive.sh b/scripts/build/archive.sh new file mode 100755 index 0000000..79f0bb9 --- /dev/null +++ b/scripts/build/archive.sh @@ -0,0 +1,10 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later + +set -eu +archive=$1 +ar=$2 +shift 2 +rm -f "$archive" +"$ar" rcs "$archive" "$@" + diff --git a/scripts/build/check-kernel-elf.sh b/scripts/build/check-kernel-elf.sh new file mode 100755 index 0000000..eb92b33 --- /dev/null +++ b/scripts/build/check-kernel-elf.sh @@ -0,0 +1,47 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later + +set -eu +elf=$1 +readelf=$2 +nm=$3 +arch=$4 + +case "$arch" in + riscv) expected_machine='RISC-V' ;; + aarch64) expected_machine='AArch64' ;; + x86_64) expected_machine='Advanced Micro Devices X86-64' ;; + *) echo "ELF check: unsupported architecture: $arch" >&2; exit 2 ;; +esac + +machine=$($readelf -h "$elf" | sed -n 's/^[[:space:]]*Machine:[[:space:]]*//p') +[ "$machine" = "$expected_machine" ] || { + echo "ELF check: expected machine '$expected_machine', got '$machine'" >&2 + exit 1 +} + +entry=$($readelf -h "$elf" | sed -n 's/^[[:space:]]*Entry point address:[[:space:]]*//p') +[ -n "$entry" ] && [ "$entry" != 0x0 ] || { + echo "ELF check: kernel has no entry point" >&2 + exit 1 +} + +undefined=$($nm -u "$elf" || true) +[ -z "$undefined" ] || { + echo "ELF check: undefined symbols:" >&2 + printf '%s\n' "$undefined" >&2 + exit 1 +} + +if $readelf -W -l "$elf" | awk '$1 == "LOAD" && $0 ~ /W/ && $0 ~ /E/ { found=1 } END { exit !found }'; then + echo "ELF check: writable and executable LOAD segment" >&2 + exit 1 +fi + +$readelf -S "$elf" | grep -q '[.]rela[.]dyn' || { + echo "ELF check: missing relocation section .rela.dyn" >&2 + exit 1 +} + +echo "Kernel ELF OK: $machine, entry $entry" + diff --git a/scripts/build/environments/host.mk b/scripts/build/environments/host.mk new file mode 100644 index 0000000..0911ad3 --- /dev/null +++ b/scripts/build/environments/host.mk @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +HOSTCC ?= cc +HOSTCXX ?= c++ +HOSTAR ?= ar +HOSTCFLAGS ?= -O2 -g -Wall -Wextra +HOSTCXXFLAGS ?= $(HOSTCFLAGS) -std=c++23 + diff --git a/scripts/build/environments/kernel.mk b/scripts/build/environments/kernel.mk new file mode 100644 index 0000000..088cdf0 --- /dev/null +++ b/scripts/build/environments/kernel.mk @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +WARNINGS ?= -Wall -Wextra -Wno-unused-parameter + +ifeq ($(BUILD_VARIANT),release) +KERNEL_OPT_FLAGS := -O2 -g0 -DNDEBUG +else +KERNEL_OPT_FLAGS := -O2 -g +endif + +CFLAGS := $(WARNINGS) $(KERNEL_OPT_FLAGS) \ + -ffreestanding -nostdlib \ + -mcmodel=medany -fPIE -MMD -MP \ + $(TOOLCHAIN_CFLAGS) $(KCPPFLAGS) $(INCLUDES) \ + -I$(BUILD)/generated + +CFLAGS_LIBC := $(CFLAGS) -std=gnu23 -Iinclude/libc-shim \ + -Wno-parentheses -Wno-sign-compare + +CXXFLAGS := $(CFLAGS) $(TOOLCHAIN_CXXFLAGS) -std=c++23 \ + -fno-exceptions -fno-rtti -fno-use-cxa-atexit \ + -fno-threadsafe-statics + +# Pre-MMU assembly must never use GOT-indirect addressing. +ASFLAGS := $(filter-out -fPIE,$(CFLAGS)) -fno-pie + +LDFLAGS := -nostdlib -shared -Bsymbolic -z notext -z norelro \ + --no-dynamic-linker diff --git a/scripts/build/environments/phys.mk b/scripts/build/environments/phys.mk new file mode 100644 index 0000000..6a11c12 --- /dev/null +++ b/scripts/build/environments/phys.mk @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +# Early physical-address execution environment. It deliberately has no heap, +# exceptions, RTTI, thread-safe statics, or PIE/GOT assumptions. +PHYS_CFLAGS := $(WARNINGS) -Os -g -ffreestanding -nostdlib \ + -fno-pie -fno-stack-protector $(TOOLCHAIN_CFLAGS) \ + $(KCPPFLAGS) $(INCLUDES) -I$(BUILD)/generated +PHYS_CXXFLAGS := $(PHYS_CFLAGS) $(TOOLCHAIN_CXXFLAGS) -std=c++23 \ + -fno-exceptions -fno-rtti -fno-use-cxa-atexit \ + -fno-threadsafe-statics +PHYS_ASFLAGS := $(PHYS_CFLAGS) + diff --git a/scripts/build/generate-asm-offsets.sh b/scripts/build/generate-asm-offsets.sh new file mode 100755 index 0000000..4e90f94 --- /dev/null +++ b/scripts/build/generate-asm-offsets.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later + +set -eu +output=$1 +assembly=$2 +sed_script=$3 +source=$4 +tmp="$output.tmp.$$" +{ + echo '/* AUTO-GENERATED — DO NOT EDIT */' + echo "/* Generated from $source */" + echo '#ifndef _LUME_GENERATED_ASM_OFFSETS_H' + echo '#define _LUME_GENERATED_ASM_OFFSETS_H' + echo + sed -n -f "$sed_script" "$assembly" + echo + echo '#endif /* _LUME_GENERATED_ASM_OFFSETS_H */' +} > "$tmp" +if [ -e "$output" ] && cmp -s "$tmp" "$output"; then + rm -f "$tmp" +else + mv -f "$tmp" "$output" +fi + diff --git a/scripts/build/generate-autoconf.sh b/scripts/build/generate-autoconf.sh new file mode 100755 index 0000000..493d630 --- /dev/null +++ b/scripts/build/generate-autoconf.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later + +set -eu +output=$1 +shift +tmp="$output.tmp.$$" +mkdir -p "$(dirname "$output")" +{ + echo '/* AUTO-GENERATED — DO NOT EDIT */' + echo '#pragma once' + while [ "$#" -gt 0 ]; do + name=$1 + value=$2 + kind=$3 + shift 3 + if [ "$kind" = bool ]; then + [ "$value" = y ] && echo "#define $name 1" + else + echo "#define $name $value" + fi + done +} > "$tmp" +if [ -e "$output" ] && cmp -s "$tmp" "$output"; then + rm -f "$tmp" +else + mv -f "$tmp" "$output" +fi + diff --git a/scripts/build/generate-initramfs.sh b/scripts/build/generate-initramfs.sh new file mode 100755 index 0000000..194e206 --- /dev/null +++ b/scripts/build/generate-initramfs.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later + +set -eu +generator=$1 +wrapper=$2 +output=$3 +uid=$4 +gid=$5 +source=$6 +GEN_INIT_CPIO=$generator "$wrapper" -o "$output" -u "$uid" -g "$gid" "$source" + diff --git a/scripts/build/link-kernel.sh b/scripts/build/link-kernel.sh new file mode 100755 index 0000000..31164e7 --- /dev/null +++ b/scripts/build/link-kernel.sh @@ -0,0 +1,10 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later + +set -eu +output=$1 +size_tool=$2 +shift 2 +"$@" +"$size_tool" "$output" + diff --git a/scripts/build/probe-toolchain.sh b/scripts/build/probe-toolchain.sh new file mode 100755 index 0000000..2f1f600 --- /dev/null +++ b/scripts/build/probe-toolchain.sh @@ -0,0 +1,48 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later + +set -eu +output=$1 +cc=$2 +cxx=$3 +ld=$4 +family=$5 +target=$6 +gcc_toolchain=$7 +sysroot=$8 + +set -- +if [ "$family" = clang ]; then + set -- "$@" "--target=$target" + [ -z "$gcc_toolchain" ] || set -- "$@" "--gcc-toolchain=$gcc_toolchain" + [ -z "$sysroot" ] || set -- "$@" "--sysroot=$sysroot" +fi + +probe() { + language=$1 + standard=$2 + shift 2 + compiler=$cc + [ "$language" = c++ ] && compiler=$cxx + printf '' | "$compiler" "$@" "-std=$standard" -x "$language" \ + -fsyntax-only - >/dev/null 2>&1 +} + +tmp="$output.tmp.$$" +mkdir -p "$(dirname "$output")" +{ + echo "cc=$($cc --version | head -n 1)" + echo "cxx=$($cxx --version | head -n 1)" + echo "ld=$($ld --version | head -n 1)" + echo "target=$target" + printf 'c23='; if probe c gnu23 "$@"; then echo yes; else echo no; fi + printf 'cxx23='; if probe c++ c++23 "$@"; then echo yes; else echo no; fi + printf 'fno-pie='; if probe c++ c++23 "$@" -fno-pie; then echo yes; else echo no; fi + printf 'mcmodel-medany='; if probe c++ c++23 "$@" -mcmodel=medany; then echo yes; else echo no; fi +} > "$tmp" +mv -f "$tmp" "$output" + +grep -q '^c23=yes$' "$output" +grep -q '^cxx23=yes$' "$output" +grep -q '^fno-pie=yes$' "$output" +grep -q '^mcmodel-medany=yes$' "$output" diff --git a/scripts/build/run-if-changed.sh b/scripts/build/run-if-changed.sh new file mode 100755 index 0000000..6cdc7c4 --- /dev/null +++ b/scripts/build/run-if-changed.sh @@ -0,0 +1,45 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later + +set -eu + +cmdfile=$1 +output=$2 +description=$3 +shift 3 + +rebuild=false +if [ ! -e "$output" ] || [ ! -e "$cmdfile" ]; then + rebuild=true +fi + +while [ "$#" -gt 0 ] && [ "$1" != "--" ]; do + prerequisite=$1 + shift + if [ "$prerequisite" != FORCE ] && [ -e "$prerequisite" ] && \ + [ "$prerequisite" -nt "$output" ]; then + rebuild=true + fi +done + +[ "$#" -gt 0 ] || { echo "run-if-changed: missing --" >&2; exit 2; } +shift + +signature=$(printf '%s\0' "$@" | cksum) +old_signature= +if [ -e "$cmdfile" ]; then + old_signature=$(cat "$cmdfile") +fi +if [ "$signature" != "$old_signature" ]; then + rebuild=true +fi + +if [ "$rebuild" = true ]; then + printf ' %s\n' "$description" + mkdir -p "$(dirname "$output")" "$(dirname "$cmdfile")" + "$@" + tmp="$cmdfile.tmp.$$" + printf '%s\n' "$signature" > "$tmp" + mv -f "$tmp" "$cmdfile" +fi + diff --git a/scripts/build/targets.mk b/scripts/build/targets.mk new file mode 100644 index 0000000..1a66379 --- /dev/null +++ b/scripts/build/targets.mk @@ -0,0 +1,81 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 Richard Qin + +# Declarative target registry for the LumeOS build. +# +# Build.mk files populate target.<name>.* and append the name to targets. A +# target name is metadata, not a Make rule, so names may contain '/' and ':'. + +target-kinds := \ + interface-library header-library object-library source-set \ + static-library whole-archive host-library phys-library host-tool generated \ + binary-blob kernel-test host-test product +targets := +generated-actions := autoconf asm-offsets linker-script initramfs + +empty := +space := $(empty) $(empty) +comma := , + +# Convert a logical target/source name into a path-safe component. +target-key = $(subst :,_,$(subst /,_,$(1))) + +# Recursively collect a target and all of its dependencies. Dependency cycles +# are rejected separately before this is evaluated for build roots. +target-deps = $(sort $(target.$(1).deps) $(target.$(1).public-deps) \ + $(target.$(1).private-deps) $(target.$(1).interface-deps) \ + $(target.$(1).link-deps) $(target.$(1).tool-deps) \ + $(target.$(1).generated-deps) $(target.$(1).runtime-deps) \ + $(target.$(1).order-only-deps)) +target-closure = $(strip \ + $(if $(filter $(1),$(2)),$(error target dependency cycle: $(2) -> $(1))) \ + $(1) $(foreach dep,$(call target-deps,$(1)),$(call target-closure,$(dep),$(2) $(1)))) +target-link-deps = $(sort $(target.$(1).deps) $(target.$(1).public-deps) \ + $(target.$(1).private-deps) $(target.$(1).interface-deps) \ + $(target.$(1).link-deps)) +target-link-order = $(strip $(1) $(foreach dep,$(call target-link-deps,$(1)),\ + $(call target-link-order,$(dep)))) +unique-words = $(if $(strip $(1)),$(firstword $(1)) \ + $(call unique-words,$(filter-out $(firstword $(1)),$(1)))) + +duplicate-words = $(sort $(foreach word,$(1),\ + $(if $(word 2,$(filter $(word),$(1))),$(word)))) +all-target-sources = $(foreach target,$(targets),$(target.$(target).sources)) +duplicate-target-sources = $(call duplicate-words,$(all-target-sources)) + +define validate-target +$(if $(filter $(target.$(1).kind),$(target-kinds)),,$(error target '$(1)' has invalid kind '$(target.$(1).kind)')) +$(if $(filter header-library interface-library product generated binary-blob,$(target.$(1).kind)),$(if $(strip $(target.$(1).sources)),$(error target '$(1)' of kind $(target.$(1).kind) declares sources),),) +$(if $(filter host-tool,$(target.$(1).kind)),$(if $(word 2,$(target.$(1).sources)),$(error host-tool '$(1)' currently requires exactly one source),),) +$(foreach src,$(target.$(1).sources),$(if $(wildcard $(src)),,$(error target '$(1)' references missing source '$(src)'))) +$(foreach dep,$(call target-deps,$(1)),$(if $(filter $(dep),$(targets)),,$(error target '$(1)' references unknown dependency '$(dep)'))) +$(foreach dep,$(call target-deps,$(1)),$(if $(strip $(target.$(dep).visibility)),\ + $(if $(filter all $(1),$(target.$(dep).visibility)),,\ + $(error target '$(1)' cannot depend on '$(dep)': visibility is '$(target.$(dep).visibility)')),)) +$(if $(filter generated,$(target.$(1).kind)),$(if $(strip $(target.$(1).outputs)),,$(error generated target '$(1)' has no outputs)),) +$(if $(filter generated,$(target.$(1).kind)),$(if $(filter $(target.$(1).action),$(generated-actions)),,$(error generated target '$(1)' has invalid action '$(target.$(1).action)')),) +endef + +define validate-targets +$(if $(strip $(targets)),,$(error no build targets were registered)) +$(if $(filter-out $(words $(sort $(targets))),$(words $(targets))),$(error duplicate build target name),) +$(foreach target,$(targets),$(call validate-target,$(target))) +endef + +# Usage requirements are deliberately separate from link inputs. Public +# include paths and defines propagate through deps; private values apply only +# while compiling the target itself. +target-public-closure = $(sort $(1) $(foreach dep,$(target.$(1).public-deps) \ + $(target.$(1).deps),$(call target-public-closure,$(dep)))) +target-public-includes = $(sort $(foreach target,$(call target-public-closure,$(1)),$(target.$(target).public-includes))) +target-public-after-includes = $(sort $(foreach target,$(call target-public-closure,$(1)),$(target.$(target).public-after-includes))) +target-public-defines = $(sort $(foreach target,$(call target-public-closure,$(1)),$(target.$(target).public-defines))) + +target-usage-roots = $(sort $(1) $(target.$(1).deps) $(target.$(1).public-deps) \ + $(target.$(1).private-deps) $(target.$(1).interface-deps)) +target-compile-includes = $(sort $(target.$(1).private-includes) \ + $(foreach root,$(call target-usage-roots,$(1)),$(call target-public-includes,$(root)))) +target-compile-after-includes = $(sort $(target.$(1).private-after-includes) \ + $(foreach root,$(call target-usage-roots,$(1)),$(call target-public-after-includes,$(root)))) +target-compile-defines = $(sort $(target.$(1).private-defines) \ + $(foreach root,$(call target-usage-roots,$(1)),$(call target-public-defines,$(root)))) diff --git a/scripts/build/tests/host_library.cc b/scripts/build/tests/host_library.cc new file mode 100644 index 0000000..c545815 --- /dev/null +++ b/scripts/build/tests/host_library.cc @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +namespace lume::build::test { + +int HostLibrarySmokeTestSymbol() { return 0; } + +} // namespace lume::build::test + diff --git a/scripts/build/tests/host_test.cc b/scripts/build/tests/host_test.cc new file mode 100644 index 0000000..6b13417 --- /dev/null +++ b/scripts/build/tests/host_test.cc @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +namespace lume::build::test { +int HostLibrarySmokeTestSymbol(); +} + +int main() { return lume::build::test::HostLibrarySmokeTestSymbol(); } diff --git a/scripts/build/tests/phys_library.cc b/scripts/build/tests/phys_library.cc new file mode 100644 index 0000000..2cf1596 --- /dev/null +++ b/scripts/build/tests/phys_library.cc @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +extern "C" unsigned long LumePhysBuildSmoke(unsigned long value) { + return value ^ 0x4c554d45UL; +} diff --git a/scripts/build/toolchain.mk b/scripts/build/toolchain.mk new file mode 100644 index 0000000..49d4f0a --- /dev/null +++ b/scripts/build/toolchain.mk @@ -0,0 +1,103 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 Richard Qin + +# Toolchain selection: +# make GCC + GNU ld +# make LLVM=1 Clang + GNU ld +# make LLVM=1 LD_TYPE=lld Clang + LLD + +LLVM ?= 0 +LLVM_VERSION ?= 20 +LD_TYPE ?= bfd +LLVM_BINUTILS ?= 0 +CXX_STDLIB ?= libstdc++ +CXX_ABI ?= lume +COMPILER_RUNTIME ?= compiler-default + +GCC_TOOLCHAIN ?= +CROSS_COMPILE ?= + +ifeq ($(ARCH),riscv) +TARGET_TRIPLE := riscv64-unknown-linux-gnu +ifeq ($(strip $(GCC_TOOLCHAIN)),) +ifneq ($(wildcard /opt/riscv64-glibc/bin/riscv64-unknown-linux-gnu-g++),) +GCC_TOOLCHAIN := /opt/riscv64-glibc +endif +endif +ifeq ($(strip $(CROSS_COMPILE)),) +ifneq ($(strip $(GCC_TOOLCHAIN)),) +CROSS_COMPILE := $(GCC_TOOLCHAIN)/bin/$(TARGET_TRIPLE)- +else +CROSS_COMPILE := $(TARGET_TRIPLE)- +endif +endif +else ifeq ($(ARCH),aarch64) +TARGET_TRIPLE := aarch64-linux-gnu +else ifeq ($(ARCH),x86_64) +TARGET_TRIPLE := x86_64-linux-gnu +endif + +ifeq ($(strip $(CROSS_COMPILE)),) +CROSS_COMPILE := $(TARGET_TRIPLE)- +endif +ifeq ($(strip $(GCC_TOOLCHAIN)),) +GCC_TOOLCHAIN := $(patsubst %/bin/,%, $(dir $(CROSS_COMPILE))) +endif + +GCC_SYSROOT := $(strip $(shell $(CROSS_COMPILE)g++ -print-sysroot 2>/dev/null)) + +CLANG_SUFFIX := $(if $(LLVM_VERSION),-$(LLVM_VERSION),) +CLANG ?= clang$(CLANG_SUFFIX) +CLANGXX ?= clang++$(CLANG_SUFFIX) +LLD ?= ld.lld$(CLANG_SUFFIX) + +CLANG_TARGET_FLAGS := --target=$(TARGET_TRIPLE) +ifneq ($(strip $(GCC_TOOLCHAIN)),) +CLANG_TARGET_FLAGS += --gcc-toolchain=$(GCC_TOOLCHAIN) +endif +ifneq ($(strip $(GCC_SYSROOT)),) +CLANG_TARGET_FLAGS += --sysroot=$(GCC_SYSROOT) +endif + +ifeq ($(LLVM),1) +CC := $(CLANG) +CXX := $(CLANGXX) +COMPILER_FAMILY := clang +TOOLCHAIN_CFLAGS := $(CLANG_TARGET_FLAGS) +ifeq ($(CXX_STDLIB),libstdc++) +TOOLCHAIN_CXXFLAGS := -stdlib=libstdc++ +else +$(error unsupported CXX_STDLIB '$(CXX_STDLIB)' for the current imported runtime) +endif +ifeq ($(LD_TYPE),lld) +LD := $(LLD) +LINKER_FAMILY := lld +else +ifneq ($(CXX_STDLIB),libstdc++) +$(error GCC configuration currently requires CXX_STDLIB=libstdc++) +endif +LD := $(CROSS_COMPILE)ld +LINKER_FAMILY := bfd +endif +else +CC := $(CROSS_COMPILE)gcc +CXX := $(CROSS_COMPILE)g++ +LD := $(CROSS_COMPILE)ld +COMPILER_FAMILY := gcc +LINKER_FAMILY := bfd +endif + +ifeq ($(LLVM_BINUTILS),1) +AR := llvm-ar$(CLANG_SUFFIX) +NM := llvm-nm$(CLANG_SUFFIX) +OBJCOPY := llvm-objcopy$(CLANG_SUFFIX) +OBJDUMP := llvm-objdump$(CLANG_SUFFIX) +READELF := llvm-readelf$(CLANG_SUFFIX) +else +AR := $(CROSS_COMPILE)ar +NM := $(CROSS_COMPILE)nm +OBJCOPY := $(CROSS_COMPILE)objcopy +OBJDUMP := $(CROSS_COMPILE)objdump +READELF := $(CROSS_COMPILE)readelf +endif +SIZE := $(CROSS_COMPILE)size diff --git a/scripts/build/write-compdb.py b/scripts/build/write-compdb.py new file mode 100755 index 0000000..9ec5a49 --- /dev/null +++ b/scripts/build/write-compdb.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later + +import json +import pathlib +import sys + +source = pathlib.Path(sys.argv[1]) +output = pathlib.Path(sys.argv[2]) +directory = pathlib.Path.cwd() +entries = [] +for line in source.read_text().splitlines(): + file_name, object_name, command = line.split("\t", 2) + entries.append( + { + "directory": str(directory), + "file": file_name, + "output": object_name, + "command": command, + } + ) +output.parent.mkdir(parents=True, exist_ok=True) +temporary = output.with_suffix(output.suffix + ".tmp") +temporary.write_text(json.dumps(entries, indent=2) + "\n") +temporary.replace(output) + diff --git a/scripts/gen_initramfs.sh b/scripts/gen_initramfs.sh index 7eba2fd..8ad0cd7 100755 --- a/scripts/gen_initramfs.sh +++ b/scripts/gen_initramfs.sh @@ -247,4 +247,4 @@ done # If output_file is set we will generate cpio archive # we are careful to delete tmp files -usr/gen_init_cpio $output $timestamp $cpio_list +"${GEN_INIT_CPIO:-usr/gen_init_cpio}" $output $timestamp $cpio_list diff --git a/test/Build.mk b/test/Build.mk new file mode 100644 index 0000000..cb8f200 --- /dev/null +++ b/test/Build.mk @@ -0,0 +1,12 @@ +targets += test/kernel +target.test/kernel.kind := kernel-test +target.test/kernel.sources := \ + test/selftest.cc test/test_pmm.cc test/test_slab.cc test/test_spinlock.cc \ + test/test_mutex.cc test/test_driver_model.cc test/test_rbtree.cc \ + test/test_vmm.cc test/test_vma.cc test/test_atomic.cc test/test_fdt.cc \ + test/test_trap.cc test/test_sched.cc test/test_klog.cc test/test_random.cc \ + test/test_waitqueue.cc test/test_uaccess.cc test/test_sys_mm.cc \ + test/test_smp.cc test/test_dcache.cc test/test_namei.cc \ + test/test_vfs_io.cc test/test_exec.cc test/test_pipe_devfs.cc \ + test/test_sys_process.cc test/test_signal.cc test/test_pid.cc \ + test/test_sys_fs_extra.cc test/test_page_cache.cc diff --git a/test/module.mk b/test/module.mk deleted file mode 100644 index 47a693c..0000000 --- a/test/module.mk +++ /dev/null @@ -1,30 +0,0 @@ -# test/module.mk -SRCS_CC += test/selftest.cc -SRCS_CC += test/test_pmm.cc -SRCS_CC += test/test_slab.cc -SRCS_CC += test/test_spinlock.cc -SRCS_CC += test/test_mutex.cc -SRCS_CC += test/test_driver_model.cc -SRCS_CC += test/test_rbtree.cc -SRCS_CC += test/test_vmm.cc -SRCS_CC += test/test_vma.cc -SRCS_CC += test/test_atomic.cc -SRCS_CC += test/test_fdt.cc -SRCS_CC += test/test_trap.cc -SRCS_CC += test/test_sched.cc -SRCS_CC += test/test_klog.cc -SRCS_CC += test/test_random.cc -SRCS_CC += test/test_waitqueue.cc -SRCS_CC += test/test_uaccess.cc -SRCS_CC += test/test_sys_mm.cc -SRCS_CC += test/test_smp.cc -SRCS_CC += test/test_dcache.cc -SRCS_CC += test/test_namei.cc -SRCS_CC += test/test_vfs_io.cc -SRCS_CC += test/test_exec.cc -SRCS_CC += test/test_pipe_devfs.cc -SRCS_CC += test/test_sys_process.cc -SRCS_CC += test/test_signal.cc -SRCS_CC += test/test_pid.cc -SRCS_CC += test/test_sys_fs_extra.cc -SRCS_CC += test/test_page_cache.cc diff --git a/usr/Build.mk b/usr/Build.mk new file mode 100644 index 0000000..f734640 --- /dev/null +++ b/usr/Build.mk @@ -0,0 +1,4 @@ +targets += host/gen_init_cpio +target.host/gen_init_cpio.kind := host-tool +target.host/gen_init_cpio.sources := usr/gen_init_cpio.c +target.host/gen_init_cpio.host-cflags := -Wno-sign-compare diff --git a/usr/initramfs.S b/usr/initramfs.S new file mode 100644 index 0000000..fa21553 --- /dev/null +++ b/usr/initramfs.S @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* + * Embed the generated CPIO through the target assembler instead of converting + * it with `objcopy -I binary`. The resulting object inherits the target ABI + * attributes (notably RISC-V float ABI flags), which keeps both GNU ld and LLD + * strict-linking compatible. + */ + +#ifndef INITRAMFS_IMAGE +#error "INITRAMFS_IMAGE must name the generated CPIO archive" +#endif + + .section .init.ramfs, "a", @progbits + .balign 8 + .incbin INITRAMFS_IMAGE + |
