From a8762ee6b5f2218fffa7e197c93821d9e350d4c8 Mon Sep 17 00:00:00 2001 From: ReservedField Date: Tue, 23 Aug 2016 21:41:09 +0200 Subject: [PATCH] Rewrite build system The new build system is much more flexible and designed with porting to other devices in mind. The multiple device support is just a stub to be completed when the first porting happens, but almost everything is there. See README.md for more details. --- Makefile | 379 +++++++------ README.md | 283 ++++++++-- linker/fpu.ld | 10 - linker/{common.ld => linker.ld} | 23 + linker/nofpu.ld | 10 - make/Base.mk | 303 +++++----- make/Common.mk | 337 ++++++++++++ make/Device.mk | 24 + make/Helper.mk | 239 ++++++++ make/gmsl/__gmsl | 940 ++++++++++++++++++++++++++++++++ make/gmsl/gmsl | 85 +++ src/startup/sdktag.s | 19 + 12 files changed, 2257 insertions(+), 395 deletions(-) delete mode 100644 linker/fpu.ld rename linker/{common.ld => linker.ld} (70%) delete mode 100644 linker/nofpu.ld create mode 100644 make/Common.mk create mode 100644 make/Device.mk create mode 100644 make/Helper.mk create mode 100644 make/gmsl/__gmsl create mode 100644 make/gmsl/gmsl create mode 100644 src/startup/sdktag.s diff --git a/Makefile b/Makefile index fe7985d..22ac637 100644 --- a/Makefile +++ b/Makefile @@ -1,24 +1,35 @@ -# We make the following assumptions on Windows: -# arm-none-eabi gcc and binutils are compiled for Windows, -# so if you are using Cygwin, we will need path translations -# NUVOSDK must be lazily evaluated, so that we can later -# change EVICSDK when building include paths. - -# Small fix to bug where cygpath -w mistranslates paths with mixed slashes (/, \) -EVICSDK := $(subst \,/,$(EVICSDK)) -NUVOSDK = $(EVICSDK)/nuvoton-sdk/Library - -OBJS := $(NUVOSDK)/Device/Nuvoton/M451Series/Source/system_M451Series.o \ - $(NUVOSDK)/StdDriver/src/clk.o \ - $(NUVOSDK)/StdDriver/src/fmc.o \ - $(NUVOSDK)/StdDriver/src/gpio.o \ - $(NUVOSDK)/StdDriver/src/spi.o \ - $(NUVOSDK)/StdDriver/src/sys.o \ - $(NUVOSDK)/StdDriver/src/timer.o \ - $(NUVOSDK)/StdDriver/src/rtc.o \ - $(NUVOSDK)/StdDriver/src/usbd.o \ - $(NUVOSDK)/StdDriver/src/eadc.o \ - $(NUVOSDK)/StdDriver/src/pwm.o \ +# This file is part of eVic SDK. +# +# eVic SDK is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# eVic SDK is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with eVic SDK. If not, see . +# +# Copyright (C) 2016 ReservedField + +include $(EVICSDK)/make/Helper.mk +include $(EVICSDK)/make/Common.mk + +# Output targets. +TARGET_SDK := libevicsdk +TARGET_NUVO := libnuvosdk +TARGET_CRT0 := evicsdk-crt0 + +# SDK objects that are always compiled as no-FPU. +OBJS_SDK_NOFPU := \ + src/thread/Thread.o \ + src/thread/Queue.o + +# SDK objects. +OBJS_SDK := \ src/startup/initfini.o \ src/startup/sbrk.o \ src/startup/init.o \ @@ -37,176 +48,196 @@ OBJS := $(NUVOSDK)/Device/Nuvoton/M451Series/Source/system_M451Series.o \ src/usb/USB_VirtualCOM.o \ src/adc/ADC.o \ src/battery/Battery.o \ - src/atomizer/Atomizer.o - -OBJS_NOFPU := src/thread/Thread.o \ - src/thread/Queue.o + src/atomizer/Atomizer.o \ + $(OBJS_SDK_NOFPU) -TAGNAME := src/startup/evicsdk_tag -OBJS_CRT0 := src/startup/startup.o \ - src/thread/ContextSwitch.o \ - $(TAGNAME).o +# SDK objects only compiled in debug builds. +OBJS_SDK_DBG := src/startup/fault.o -AEABI_OBJS := src/aeabi/aeabi_memset-thumb2.o \ +# SDK objects to build in case of missing __aeabi functions. +OBJS_SDK_AEABI := \ + src/aeabi/aeabi_memset-thumb2.o \ src/aeabi/aeabi_memclr.o -OUTDIR := lib -DOCDIR := doc - -ifneq ($(EVICSDK_FAULT_HANDLER),) - OBJS_CRT0 += src/startup/fault.o -endif -ifneq ($(EVICSDK_FPU_SUPPORT),) - OBJS_CRT0 += src/thread/UsageFault_fpu.o -endif - -# We need to find out if on cygwin or not -ifeq ($(OS),Windows_NT) - ifeq (, $(findstring cygwin, $(shell gcc -dumpmachine))) - WIN_CYG := 0 - else - WIN_CYG := 1 - endif - -endif - -ifeq ($(shell $(CC) -v 2>&1 | grep -c "clang version"), 1) - CC_IS_CLANG := 1 -endif - -ifeq ($(ARMGCC),) - ARMGCC := $(shell cd $(shell arm-none-eabi-gcc --print-search-dir | grep 'libraries' | \ - tr '=$(if $(filter Windows_NT,$(OS)),;,:)' '\n' | \ - grep -E '/arm-none-eabi/lib/?$$' | head -1)/../.. && pwd) -endif - -ifeq ($(OS),Windows_NT) - # Always fix binutils path - ifneq ($(ARMGCC),) - # If using cygwin, use cygpath - ifeq ($(WIN_CYG),1) - ARMGCC := $(shell cygpath -w $(ARMGCC)) - endif - - endif - - ifndef CC_IS_CLANG - NEED_FIXPATH := 1 - endif +# Nuvoton SDK roots (relative). +NUVOSDK_LOCAL := nuvoton-sdk/Library +NUVOSDK_DEVSRC := $(NUVOSDK_LOCAL)/Device/Nuvoton/M451Series/Source +NUVOSDK_STDSRC := $(NUVOSDK_LOCAL)/StdDriver/src + +# Nuvoton SDK objects. +OBJS_NUVO := \ + $(NUVOSDK_DEVSRC)/system_M451Series.o \ + $(NUVOSDK_STDSRC)/clk.o \ + $(NUVOSDK_STDSRC)/fmc.o \ + $(NUVOSDK_STDSRC)/gpio.o \ + $(NUVOSDK_STDSRC)/spi.o \ + $(NUVOSDK_STDSRC)/sys.o \ + $(NUVOSDK_STDSRC)/timer.o \ + $(NUVOSDK_STDSRC)/rtc.o \ + $(NUVOSDK_STDSRC)/usbd.o \ + $(NUVOSDK_STDSRC)/eadc.o \ + $(NUVOSDK_STDSRC)/pwm.o + +# SDK tag object file. +SDKTAG_OBJ := src/startup/sdktag.o + +# Crt0 objects. +OBJS_CRT0 := \ + src/startup/startup.o \ + src/thread/ContextSwitch.o \ + $(SDKTAG_OBJ) \ + $(if $(EVICSDK_FPU_DISABLE),,src/thread/UsageFault_fpu.o) + +# Extra C/C++ flags for SDK/crt0 compilation. +# TODO: fixup warnings for GCC and Clang before enabling this. +# Also need -Wno-bitwise-op-parentheses for Clang + Nuvo SDK. +#CCFLAGS_EXTRA := -Wall + +# Find path to libc. Using find would be better, but on Cygwin we'd have to +# convert the native style library paths to Cygwin style. This hack avoids it +# by (ab)using binutils to do the search. +LIBC_PATH_FIXBU := $(shell \ + arm-none-eabi-ld --verbose $(foreach d,$(ARM_LIBDIRS_FIXBU),-L$d) -lc \ + $(NULLDEV_TC) 2>&1 | \ + sed -n 's/attempt to open \(.*libc\.a\) succeeded/\1/p' | head -1) +ifndef LIBC_PATH_FIXBU +$(error Could not detect libc path) endif - -ifneq ($(ARMGCC),) - ifdef CC_IS_CLANG - CFLAGS += -target armv7em-none-eabi -fshort-enums - - AEABI_COUNT := $(shell arm-none-eabi-nm -g $(ARMGCC)/arm-none-eabi/lib/armv7e-m/libc.a | grep -Ec 'T __aeabi_mem(set|clr)[48]?$$') - ifeq ($(AEABI_COUNT), 0) - # __aeabi_memset* and __aeabi_memclr* are not exported by libc - # We provide our own implementations - OBJS += $(AEABI_OBJS) - else ifneq ($(AEABI_COUNT), 6) - # Only part of __aeabi_memset* and __aeabi_memclr* are exported by libc - # This should never happen, bail out in env_check - AEABI_ERROR := 1 - endif - else - CC := arm-none-eabi-gcc - endif - - ifdef NEED_FIXPATH - ifeq ($(WIN_CYG), 0) - OBJS_FIXPATH := $(OBJS) - OBJS_NOFPU_FIXPATH := $(OBJS_NOFPU) - OBJS_CRT0_FIXPATH := $(OBJS_CRT0) - else - OBJS_FIXPATH := $(shell cygpath -w $(OBJS)) - OBJS_NOFPU_FIXPATH := $(shell cygpath -w $(OBJS_NOFPU)) - OBJS_CRT0_FIXPATH := $(shell cygpath -w $(OBJS_CRT0)) - EVICSDK := $(shell cygpath -w $(EVICSDK)) - endif - else - OBJS_FIXPATH := $(OBJS) - OBJS_NOFPU_FIXPATH := $(OBJS_NOFPU) - OBJS_CRT0_FIXPATH := $(OBJS_CRT0) - endif +ifdef EVICSDK_MAKE_DEBUG +$(info Libc path (raw): $(LIBC_PATH_FIXBU)) +$(info Libc path (canonical): \ + $(call path-canon,$(call unfixpath-tc,$(LIBC_PATH_FIXBU)))) endif -ifeq ($(WIN_CYG),0) - SDKTAG := $(shell git describe --abbrev --dirty --always --tags 2> NUL ) # Fix for Windows w/o cygwin (NUL instead of /dev/null) -else - SDKTAG := $(shell git describe --abbrev --dirty --always --tags 2> /dev/null ) +# Old newlib versions don't have __aeabi_memset* and __aeabi_memclr* (added in +# commit 24e054c). If this is the case, we bake our __aeabi objects into the +# SDK to avoid undefined references (especially with Clang). +AEABI_COUNT := $(shell arm-none-eabi-nm -g $(LIBC_PATH_FIXBU) | \ + grep -Ec 'T __aeabi_mem(set|clr)[48]?$$') +ifndef AEABI_COUNT +$(error Could not detect state of __aeabi symbols) endif -ifeq ($(SDKTAG),) - SDKTAG := unknown +$(if $(EVICSDK_MAKE_DEBUG),$(info __aeabi count: $(AEABI_COUNT))) +ifeq ($(AEABI_COUNT),0) + OBJS_SDK += $(OBJS_SDK_AEABI) +else ifneq ($(AEABI_COUNT),6) +$(error Libc is exporting only part of __aeabi symbols) endif -AS := arm-none-eabi-as -LD := arm-none-eabi-ld -AR := arm-none-eabi-ar -OBJCOPY := arm-none-eabi-objcopy - -INCDIRS := $(foreach d,$(shell arm-none-eabi-gcc -x c -v -E /dev/null 2>&1 | sed -n -e '/<\.\.\.>/,/End/ p' | tail -n +2 | head -n -1 | sed 's/^\s*//'),-I$d) \ - -I$(NUVOSDK)/CMSIS/Include \ - -I$(NUVOSDK)/Device/Nuvoton/M451Series/Include \ - -I$(NUVOSDK)/StdDriver/inc \ - -Iinclude - -CPUFLAGS_NOFPU := -mcpu=cortex-m4 -mthumb - -ifneq ($(EVICSDK_FPU_SUPPORT),) - CPUFLAGS := $(CPUFLAGS_NOFPU) -mfloat-abi=hard -mfpu=fpv4-sp-d16 - CFLAGS += -DEVICSDK_FPU_SUPPORT - ASFLAGS += -DEVICSDK_FPU_SUPPORT - TARGET := libevicsdk_fpu -else - CPUFLAGS := $(CPUFLAGS_NOFPU) - TARGET := libevicsdk -endif - -TARGET_CRT0 := $(TARGET)_crt0 - -CFLAGS += -Wall -Os -fdata-sections -ffunction-sections -CFLAGS += $(INCDIRS) - -all: env_check gen_tag $(TARGET_CRT0).o $(TARGET).a +# Generates the SDK tag from git. Since this is an expensive operation, the +# result is cached after the first invocation. +get-sdktag = $(or $(__SDKTAG),$(eval __SDKTAG := $(__get-sdktag))$(__SDKTAG)) +__get-sdktag = evic-sdk-$(or $(strip $(shell \ + git describe --abbrev --dirty --always --tags 2> $(NULLDEV))),unknown) +$(if $(EVICSDK_MAKE_DEBUG),$(info SDK tag: $(get-sdktag))) -$(OBJS_NOFPU_FIXPATH): CPUFLAGS := $(CPUFLAGS_NOFPU) +# All objects, excluding debug-only objects. +OBJS_ALL := $(OBJS_SDK) $(OBJS_NUVO) $(OBJS_CRT0) +# All debug-only objects. +OBJS_ALL_DBG := $(OBJS_SDK_DBG) -%.o: %.c - $(CC) $(CPUFLAGS) $(CFLAGS) -c $< -o $@ - -%.o: %.s - $(CC) $(CPUFLAGS) $(ASFLAGS) -c -x assembler-with-cpp $< -o $@ - -$(TARGET).a: $(OBJS_FIXPATH) $(OBJS_NOFPU_FIXPATH) - test -d $(OUTDIR) || mkdir $(OUTDIR) - $(AR) -rv $(OUTDIR)/$(TARGET).a $(OBJS_FIXPATH) $(OBJS_NOFPU_FIXPATH) - -$(TARGET_CRT0).o: $(OBJS_CRT0_FIXPATH) - test -d $(OUTDIR) || mkdir $(OUTDIR) - $(LD) -r $(OBJS_CRT0_FIXPATH) -o $(OUTDIR)/$(TARGET_CRT0).o +# Documentation output directory. +DOCDIR := doc +# Output directory clean template. +clean-sdkdir-tmpl = $(call clean-dir-tmpl,$1,$2,$(SDKDIR)) +# Library output path template. Extra argument: library name. +lib-tmpl = $(call sdkdir-tmpl,$1,$2)/$3.a +# SDK library output path template. +sdk-tmpl = $(call lib-tmpl,$1,$2,$(TARGET_SDK)) +# Nuvoton SDK library output path template. +nuvo-tmpl = $(call lib-tmpl,$1,$2,$(TARGET_NUVO)) +# Crt0 object output path template +crt0-tmpl = $(call sdkdir-tmpl,$1,$2)/$(TARGET_CRT0).o + +# Object targets for all devices and flavors. +objs-all := $(call tmpl-all,objs-tmpl,$(OBJS_ALL)) \ + $(call tmpl-flavor,objs-tmpl,$(BUILD_FLAVOR_DBG),$(OBJS_ALL_DBG)) +# SDK library output paths for all devices and flavors. +sdk-all := $(call tmpl-all,sdk-tmpl) +# SDK library output paths for all devices, debug flavor. +sdk-dbg := $(call tmpl-flavor,sdk-tmpl,$(BUILD_FLAVOR_DBG)) +# Nuvoton SDK library output paths for all device and flavors. +nuvo-all := $(call tmpl-all,nuvo-tmpl) +# All library output paths for all devices and flavors. +lib-all := $(sdk-all) $(nuvo-all) +# Crt0 object output paths for all devices and flavors. +crt0-all := $(call tmpl-all,crt0-tmpl) +# No-FPU objects for all devices and flavors. +nofpu-objs-all := $(call tmpl-all,objs-tmpl,$(OBJS_SDK_NOFPU)) +# SDK tag object for all devices and flavors. +sdktag-all := $(call tmpl-all,objs-tmpl,$(SDKTAG_OBJ)) + +# Cache all needed paths for fixpath. +$(call fixpath-cache, \ + $(call objs-fixpath-cache,$(OBJS_ALL)) \ + $(call objs-fixpath-cache,$(OBJS_ALL_DBG),$(BUILD_FLAVOR_DBG)) \ + $(lib-all) $(crt0-all)) + +# Add outputs to clean templates. +CLEAN_PATH_TMPL += clean-sdkdir-tmpl + +# Enable secondary expansion. +.SECONDEXPANSION: + +# Rule to archive prerequisite objects into a library. +$(lib-all): | $$(@D) + $(call info-cmd,LIB) + @$(call trace, \ + $(AR) -rc $(call fixpath-bu,$@) $(call fixpath-bu,$^)) + +# Build SDK objects for SDK library. +$(sdk-all): $$(call tmpl-build,objs-tmpl,$$(OBJS_SDK)) +# Build debug-only SDK objects for debug SDK builds. +$(sdk-dbg): $$(call tmpl-build,objs-tmpl,$$(OBJS_SDK_DBG)) + +# Build Nuvoton SDK objects for Nuvoton SDK library. +$(nuvo-all): $$(call tmpl-build,objs-tmpl,$$(OBJS_NUVO)) + +# Rule to link crt0 objects into a partially linked object. +$(crt0-all): $$(call tmpl-build,objs-tmpl,$$(OBJS_CRT0)) | $$(@D) + $(call info-cmd,LNK) + @$(call trace, \ + $(LD) -r $(call fixpath-bu,$^) -o $(call fixpath-bu,$@)) + +# Define EVICSDK_SDKTAG for the SDK tag target (asm). +$(sdktag-all): ASFLAGS += -DEVICSDK_SDKTAG=\"$(get-sdktag)\" +# Always rebuild SDK tag (.PHONY doesn't play well with pattern rules). +$(sdktag-all): .FORCE + +# Build no-FPU objects with no-FPU CPU flags. +$(nofpu-objs-all): CPUFLAGS := $(CPUFLAGS_NOFPU) + +# Set extra C/C++ flags for SDK/crt0 objects. +# TODO: see CCFLAGS_EXTRA. +#$(sdk-all) $(crt0-all): CFLAGS += $(CCFLAGS_EXTRA) +#$(sdk-all) $(crt0-all): CXXFLAGS += $(CCFLAGS_EXTRA) + +# Device-flavor target: build all outputs. +$(devfla-all): \ + $$(call tmpl-build,crt0-tmpl) \ + $$(call tmpl-build,nuvo-tmpl) \ + $$(call tmpl-build,sdk-tmpl) + +# Rule to build documentation via doxygen. docs: doxygen -clean: - rm -rf $(OBJS) $(OBJS_NOFPU) $(OBJS_CRT0) $(AEABI_OBJS) $(OUTDIR)/$(TARGET).a $(OUTDIR)/$(TARGET_CRT0).o $(OUTDIR) $(DOCDIR) +# Rule to clean documentation. +clean-docs: + rm -rf $(DOCDIR) -env_check: -ifeq ($(ARMGCC),) - $(error You must set the ARMGCC environment variable) -endif -ifneq ($(AEABI_ERROR),) - $(error Your libc is exporting only part of __aeabi symbols) -endif +# Set BUILD_* for output targets. +$(call build-vars-rules,sdk-tmpl) +$(call build-vars-rules,nuvo-tmpl) +$(call build-vars-rules,crt0-tmpl) -gen_tag: - @rm -f $(TAGNAME).s $(TAGNAME).o -ifeq ($(WIN_CYG),0) - @printf ".section .evicsdk_tag\n.asciz \"evic-sdk-$(SDKTAG)\"\n" > $(TAGNAME).s -else - @printf '.section .evicsdk_tag\n.asciz "evic-sdk-$(SDKTAG)"\n' > $(TAGNAME).s -endif +# Generate directory targets. +$(call mkdir-rules,objs-dirs-tmpl,$(OBJS_ALL) $(OBJS_ALL_DBG)) +$(call mkdir-rules,sdkdir-tmpl) +# Set object directories as order-only prerequisites for object targets. +$(objs-all): | $$(@D) -.PHONY: all clean docs env_check gen_tag +.FORCE: +.PHONY: .FORCE docs diff --git a/README.md b/README.md index 2f05a33..128e74b 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,9 @@ brew install gcc-arm-none-eabi On Windows, first install the [precompiled ARM toolchain](https://launchpad.net/gcc-arm-embedded). Choose an installation path without spaces to avoid problems with the build process. -If you already have a working Windows `make`, along with utilities such as `awk`, `grep`, `sed`, -`tr`, `git`, etc. you can go ahead, but make sure those binaries are in your `PATH`. Otherwise, -install [Cygwin](https://www.cygwin.com/) and add the following packages on top of the base install: +If you already have a working Windows `make` and `git` along with standard GNU utilities you can go +ahead, but make sure those binaries are in your `PATH`. Otherwise, install [Cygwin](https://www.cygwin.com/) +and add the following packages on top of the base install: ``` make git @@ -101,8 +101,8 @@ Installation from Nuvoton and copy the `Library` folder inside `evic-sdk/nuvoton-sdk`, as to have `evic-sdk/nuvoton-sdk/Library`. -3. Point the `EVICSDK` environment variable to the `evic-sdk` folder. This should do (assuming your - current directory is evic-sdk): +3. Point the `EVICSDK` environment variable to the `evic-sdk` folder. For example, if you're using + bash and are in the SDK directory: ``` echo "export EVICSDK=$(pwd)" >> $HOME/.bashrc ``` @@ -113,45 +113,25 @@ Installation make ``` -At this point, the SDK should be fully set up. You can also generate Doxygen documentation with: -``` -make docs -``` -To clean up the build (for example if you want to do a full rebuild), use the standard: -``` -make clean -``` - Building your first APROM -------------------------- The `helloworld` example should be the first thing you try compiling and flashing, -to check that everything is working correctly. -Building is as easy as: +to check that everything is working correctly. Build it like: ``` cd example/helloworld make ``` -To clean you can use `make clean`, as usual. -If the build succeeds, you should now have a `bin/helloworld.bin` file ready to flash. +If the build succeeds, you should now have a `bin/rel/DEVICE/helloworld.bin` file ready to flash. This file is encrypted and compatible with the official updater. -You can also generate a unencrypted binary: -``` -make helloworld_unencrypted.bin -``` Flashing -------- You can flash the output binary using the official updater. For development, -using `python-evic` is quicker and simpler. -I suggest to backup your dataflash before flashing, in case anything goes south: +using `python-evic` is quicker and simpler: ``` -evic-usb dump-dataflash -o data.bin -``` -Now, flash: -``` -evic-usb upload bin/helloworld.bin +evic-usb upload bin/rel/DEVICE/helloworld.bin ``` If everything went well you should see the "Hello, World." message. @@ -163,14 +143,216 @@ over USB is more convenient (as long as the APROM doesn't require significant po it doesn't fire the atomizer). Similiarly, holding the left button during powerup will force the system to boot from APROM. -If `python-evic` fails and the eVic won't flash back to a functioning state, don't panic. -Find a Windows/Mac machine (or virtualize one), boot the eVic to LDROM and flash an original -firmware using the official Joyetech updater. It has always worked for me. +Unless you're messing with the LDROM, this is practically unbrickable - you can always boot to +LDROM and restore. Actually, APROM update is always done from LDROM: the official APROM reboots +to LDROM and flashing happens from there. + +The build system +---------------- + +The build system, used both by the SDK and APROMs, works around *specifiers*. A specifier is a +string in the form `device-flavor`, where `device` is a supported device and `flavor` is a build +flavor. + +**Supported devices:** + +- `evic`: Joyetech eVic VTC Mini +- `all`: all of the above + +**Build flavors:** + +- `dbg`: debug build +- `rel`: release build +- `all`: all of the above + +See below for the differences between debug and release builds. The `all-all` specifier is also +aliased to `all`. There are two classes of make targets: + +- `specifier`: performs a build for the specified device and flavor +- `clean-specifier`: cleans the build for the specified device and flavor + +Some examples (if more than one command is given, they are alternatives): +``` +# Build everything +make all +make all-all +# Clean everything +make clean-all +make clean-all-all +# Build for eVic VTC Mini, debug flavor +make evic-dbg +# Build for eVic VTC Mini, all flavors +make evic-all +# Clean for eVic VTC Mini, release flavor +make clean-evic-rel +# Build for all devices, release flavor +make all-rel +# Clean for all devices, debug flavor +make clean-all-dbg +# Mix & match +make all-rel evic-dbg +``` + +#### Default targets + +While the specifier system lets you specify exactly what you want, it can feel too verbose for +day-to-day development. For this reason, the build system accepts two environment variables: + +- `EVICSDK_MAKE_DEFAULT_DEVICE`: default device(s) +- `EVICSDK_MAKE_DEFAULT_FLAVOR`: default flavor(s) + +Those can be set to a single device/flavor or to a list of them (for example, default flavors +`dbg rel` and `all` are the same). You can omit the device, the flavor or both from any specifier +and they will be filled in from those variables. Unset or empty variables default to `all`. The +empty target is also aliased to `def`. Some examples: +``` +# Build for default device(s) and flavor(s) +make +make def +# Clean for default device(s) and flavor(s) +make clean +# Build for default device(s), debug flavor +make dbg +# Build for eVic VTC Mini, default flavor(s) +make evic +# Clean for default devices(s), release flavor +make clean-rel +``` +If you're writing scripts around the build system don't assume the user's defaults and use the +full specifiers (unless you actually want to use the user's defaults). + +#### Object files + +Objects files for a specific device and flavor live in the `obj/FLAVOR/DEVICE` directory. The +source tree is replicated in there, to avoid conflicts between files with the same name. If +you wanted to compile the file `src/foo/bar.c` for an eVic VTC Mini, debug flavor, you could +issue `make obj/dbg/evic/src/foo/bar.o`. -Unless you're messing with the LDROM, this is practically unbrickable - you can always boot -to LDROM and restore. Actually, APROM update is always done from LDROM - the official firmware -doesn't even contain flash writing routines, it only provides access to the dataflash and the -actual APROM upload happens in LDROM after a reset. +#### SDK builds + +SDK output files live in the `lib/FLAVOR/DEVICE` directory. Debug builds enable debug info +generation from the compiler and a fault handler that will display crash and register info, which +can be decoded using the script in `tools/fault-decode`. + +You can generate Doxygen documentation (`doc` directory) for the SDK using `make docs`. To clean it +use `make clean-docs`. + +#### APROM builds + +APROM output files live in the `bin/FLAVOR/DEVICE` directory. The standard Makefile for APROMs +follows this scheme: +``` +TARGET = helloworld +OBJS = main.o +include $(EVICSDK)/make/Base.mk +``` +`TARGET` is the base name for outputs. `OBJS` is a list of objects to be built. Notice that, even +though objects live in `obj/FLAVOR/DEVICE`, the object list is written as if they were in the same +directory as the sources. For example, `src/foo/bar.c` would appear as `src/foo/bar.o` in the list. +This is done so that you can let the build system worry about devices and flavors (and to preserve +compatibility with Makefiles written for the old build system). Since the source tree is replicated +for the objects, **always use relative paths without `..` components** (you can't have sources at +a higher level than the Makefile). Finally, `make/Base.mk` has to be included (**after** setting +those variables). See the top of `make/Base.mk` for more variables you can use. + +The main output is `bin/FLAVOR/DEVICE/TARGET.bin`, which is an encrypted binary compatible with the +official updater. Debug builds enable debug info generation from the compiler and generate some +additional outputs: + +- `bin/dbg/DEVICE/TARGET.elf`: ELF output from compilation +- `bin/dbg/DEVICE/TARGET_dec.bin`: unencrypted binary +- `bin/dbg/DEVICE/TARGET.map`: linker map + +Also, they are built against the debug SDK, so the fault handler is enabled. All the outputs except +the linker map are targets (so you could `make bin/dbg/DEVICE/TARGET.elf`, for example). + +Note that APROMs for a certain device and flavor combination are built against the SDK for that +same device and flavor, so you'll need to have that SDK built for it to succeed. If you see linker +errors about missing `evicsdk-crt0.o`, `-levicsdk` or `-lnuvosdk` chances are you don't have the +needed SDK built. + +#### Parallel make + +Parallel make (`-j` option) is supported and works as you would expect. However, problems can arise +when you mix build and clean targets. For example, a default rebuild can be performed with +`make clean && make`. For single-threaded make this could be shortened to `make clean def`, because +targets will be made one at a time from left to right. With parallel make you will need to use the +full form because the two targets would likely be made concurrently, breaking the build. Of course, +mixing multiple build targets or multiple clean targets poses no issues. + +#### Clang support + +Clang is supported: just build with `CC=clang make`. At the moment, the support still depends on +arm-none-eabi GCC for some standard headers and libraries. It will also generate a fair amount of +warnings for SDK builds. **On Cygwin Clang is assumed to be compiled for Cygwin** and not for +Windows (e.g. downloaded via the Cygwin package manager). + +#### C++ support + +C++ is supported, just name your sources with `.cpp` extensions. The C++ standard library is not +supported. Exception handling and RTTI are disabled. It's more like C with classes than C++. You'll +need to have `arm-none-eabi-g++` installed to compile C++ code (even if you use Clang - we still +need GCC's headers). + +#### Legacy clean + +If you're migrating from the old build system to the new one you'll probably want to get rid of +the old cruft. Aside from running `make clean` with an old SDK, you can do it manually: + +- For the SDK, remove the `lib` directory and all `.o` files (recursively). +- For APROMs, remove the `bin` directory and all `.o` files (recursively). + +#### Misc + +The following environment/make variables are available: + +- `EVICSDK_MAKE_DEBUG`: set to non-empty to enable extra debug output from the build system. + Useful when investigating or reporting bugs. +- `EVICSDK_FPU_DISABLE`: set to non-empty to disable FPU support and lazy stacking. Needs a full + rebuild to avoid mixing FPU and no-FPU objects. Can be useful when debugging very rare, tricky + FP bugs. Do *not* use this unless you fully understand what it means (no, it won't make you + binaries smaller or faster, even if you don't use the FPU). + +Reporting bugs +-------------- + +I invite you to report bugs using the Issues page. Please confirm the bug against the latest SDK +commit and include all information needed to reproduce the bug, like: + +- OS, make/compiler/binutils/newlib versions; +- Head commit and branch of the SDK you're using; +- Device and flavor (does it happen in release builds? Debug? Both?); +- Code (if applicable), preferably reduced to a Minimal, Complete and Verifiable (MCV) example; +- Things you tried and didn't work; +- For crashes, the crash dump generated by a debug build; +- For build system bugs, the debug output (i.e. `EVICSDK_MAKE_DEBUG=1 make ...`). Try minimizing + it: if `make evic-dbg` is enough to make it happen, don't send output for `make all`. + +Of course if you've already identified the bug in the SDK code you don't need to include as much +information. Be sensible and everyone involved will be happy. + +Pull requests +------------- + +Pull requests are welcome. A few rules: + +- Respect the coding and documentation style of the SDK and refer to the tips & tricks. +- PRs must merge cleanly against the branch they target (preferably rebased on top of the latest + head for that branch). +- No useless commits. Multiple related commits in the same PR are okay, but no merge/revert commits + and no "Add foo" "Fix foo" "Fix foo again" stuff. Familiarize yourself with `git rebase [-i]` and + use it. +- Respect the commit message style (look at the history). The title should tell what the *effect* + of a commit is, not how it does it. Titles should be imperative: "Add foo", not "Adds foo". For + non-trivial commits you're welcome to add further technical details in the commit body. Stick to + 80 columns per row. +- If you're fixing a bug [reference it](https://help.github.com/articles/closing-issues-via-commit-messages/) + in the title. If you're fixing a bug that hasn't been reported yet, create an issue first and + reference it in your commit title (you don't need to wait for an answer on the issue - go ahead + with the PR). + +It is understood that big or complex changes often won't be perfect, no need to worry about it. +Again, just use common sense and everything will work out. USB debugging ------------- @@ -185,22 +367,25 @@ An example on how to use the port is given in `example/usbdebug`. You can commun using your favorite serial port terminal. All the line coding parameters (baud rate, parity, stop bits, data bits) are ignored, so you don't need to worry about them. -Coding guidelines ------------------ +Tips & tricks +------------- While the SDK does a fairly good job of abstracting the low-level details, you still need to -remember that you're coding on an embedded platform. A few tips that might be useful: - -- You should declare all variables shared between your main code and callbacks/interrupt - handlers as `volatile`. -- Try to minimize dynamic memory allocation: all memory not used by data or stack is assigned - to heap, but RAM is only 32kB. -- Declare constant data (such as lookup tables) as `const`: the compiler will place it in - the ROM, reducing RAM usage. +remember that you're coding on an embedded platform. A few tips that might be helpful: + +- You should declare variables shared between threads or with callbacks/interrupts as `volatile`. +- Resources are limited: be frugal. Write efficient code. +- Minimize dynamic memory allocation: all memory not used by data or stack is assigned to heap, + but RAM is only 32kB and nobody likes a failed `malloc`. +- Declare constant data (such as lookup tables) as `const`: the compiler will place it in ROM, + reducing RAM usage. - Prefer `siprintf` over `sprintf`, as it produces much smaller binaries by stripping out the floating point printing routines. Of course `siprintf` doesn't support floating point numbers, so if you need to print them and cannot use a fixed-point representation you'll have to live with the increased binary size. -- C++ is supported, just name your C++ files with `.cpp` extensions. The C++ standard library is - NOT (yet?) supported. It's really bulky and it hardly fits in the ROM/RAM space we have. - Exception handling and RTTI are disabled. +- When using floating point variables, prefer `float` over `double`, because `float` has hardware + support. Using `double`s will pull in some floating point emulation code, making your binaries + larger. +- When using standard floating point functions, prefer the ones with the `f` suffix: hardware + support for `float`s means faster and smaller binaries. If you use `double` functions, they'll + pull in tons of floating point emulation code. diff --git a/linker/fpu.ld b/linker/fpu.ld deleted file mode 100644 index 1d8b331..0000000 --- a/linker/fpu.ld +++ /dev/null @@ -1,10 +0,0 @@ -/* Library configurations */ -INPUT(libevicsdk_fpu_crt0.o) -GROUP( - armv7e-m/fpu/libgcc.a - armv7e-m/fpu/libc.a - armv7e-m/fpu/libm.a - libevicsdk_fpu.a -) - -INCLUDE common.ld diff --git a/linker/common.ld b/linker/linker.ld similarity index 70% rename from linker/common.ld rename to linker/linker.ld index 9da6d86..30df1d4 100644 --- a/linker/common.ld +++ b/linker/linker.ld @@ -1,3 +1,26 @@ +/* + * This file is part of eVic SDK. + * + * eVic SDK is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * eVic SDK is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with eVic SDK. If not, see . + * + * Copyright (C) 2015-2016 ReservedField + */ + +/* Library configurations */ +INPUT(evicsdk-crt0.o) +GROUP(-lgcc -lc -lm -lnuvosdk -levicsdk) + /* Memory regions */ MEMORY { ROM (rx) : ORIGIN = 0x00000000, LENGTH = 128K diff --git a/linker/nofpu.ld b/linker/nofpu.ld deleted file mode 100644 index a6f5a70..0000000 --- a/linker/nofpu.ld +++ /dev/null @@ -1,10 +0,0 @@ -/* Library configurations */ -INPUT(libevicsdk_crt0.o) -GROUP( - armv7e-m/libgcc.a - armv7e-m/libc.a - armv7e-m/libm.a - libevicsdk.a -) - -INCLUDE common.ld diff --git a/make/Base.mk b/make/Base.mk index 9b199eb..5f55f7d 100644 --- a/make/Base.mk +++ b/make/Base.mk @@ -1,154 +1,153 @@ -# Supported vars: -# TARGET -# OBJS -# CFLAGS -# CPPFLAGS -# ASFLAGS -# LDFLAGS - -# We make the following assumptions on Windows: -# arm-none-eabi gcc and binutils are compiled for Windows, -# so if you are using Cygwin, we will need path translations -# NUVOSDK must be lazily evaluated, so that we can later -# change EVICSDK when building include paths. - -NUVOSDK = $(EVICSDK)/nuvoton-sdk/Library - -# Force OBJS immediate expansion, since we'll be -# changing EVICSDK later. -OBJS := $(OBJS) - -# We need to find out if on cygwin or not -ifeq ($(OS),Windows_NT) - ifeq (, $(findstring cygwin, $(shell gcc -dumpmachine))) - WIN_CYG := 0 - else - WIN_CYG := 1 - endif - -endif - -ifeq ($(shell $(CC) -v 2>&1 | grep -c "clang version"), 1) - CC_IS_CLANG := 1 -endif - -ifeq ($(ARMGCC),) - ARMGCC := $(shell cd $(shell arm-none-eabi-gcc --print-search-dir | grep 'libraries' | \ - tr '=$(if $(filter Windows_NT,$(OS)),;,:)' '\n' | \ - grep -E '/arm-none-eabi/lib/?$$' | head -1)/../.. && pwd) -endif - -ifeq ($(OS),Windows_NT) - # Always fix binutils path - ifneq ($(ARMGCC),) - # If using cygwin, use cygpath - ifeq ($(WIN_CYG),1) - ARMGCC := $(shell cygpath -w $(ARMGCC)) - endif - - endif - - ifndef CC_IS_CLANG - NEED_FIXPATH := 1 - endif -endif - -ifdef CC_IS_CLANG - CFLAGS += -target armv7em-none-eabi -fshort-enums -else - CC := arm-none-eabi-gcc -endif - -ifdef NEED_FIXPATH - ifeq ($(WIN_CYG), 0) - OBJS_FIXPATH := $(OBJS) - else - OBJS_FIXPATH := $(shell cygpath -w $(OBJS)) - EVICSDK := $(shell cygpath -w $(EVICSDK)) - endif - else - OBJS_FIXPATH := $(OBJS) -endif - -GCC_VERSION := $(shell arm-none-eabi-gcc -dumpversion) - -AS := arm-none-eabi-as -LD := arm-none-eabi-ld -OBJCOPY := arm-none-eabi-objcopy - +# This file is part of eVic SDK. +# +# eVic SDK is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# eVic SDK is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with eVic SDK. If not, see . +# +# Copyright (C) 2016 ReservedField + +# Supported variables: +# TARGET: output target name. +# OBJS: objects to compile. +# INCDIRS: include directories. +# INCDIRS_C: include directories (C only). +# INCDIRS_CXX: include directories (C++ only). +# LIBDIRS: library directories. +# CFLAGS: C compiler flags. +# CXXFLAGS: C++ compiler flags. +# ASFLAGS: assembler flags. +# LDFLAGS: linker flags. + +ifndef __evicsdk_make_base_inc +__evicsdk_make_base_inc := 1 + +include $(EVICSDK)/make/Helper.mk +include $(EVICSDK)/make/Common.mk + +# Binary output directory. BINDIR := bin -INCDIRS := $(foreach d,$(shell arm-none-eabi-gcc -x c -v -E /dev/null 2>&1 | sed -n -e '/<\.\.\.>/,/End/ p' | tail -n +2 | head -n -1 | sed 's/^\s*//'),-I$d) \ - -I$(NUVOSDK)/CMSIS/Include \ - -I$(NUVOSDK)/Device/Nuvoton/M451Series/Include \ - -I$(NUVOSDK)/StdDriver/inc \ - -I$(EVICSDK)/include - -LIBDIRS := -L$(ARMGCC)/arm-none-eabi/lib \ - -L$(ARMGCC)/arm-none-eabi/newlib \ - -L$(ARMGCC)/lib/arm-none-eabi/newlib \ - -L$(ARMGCC)/gcc/arm-none-eabi/$(GCC_VERSION) \ - -L$(ARMGCC)/lib/gcc/arm-none-eabi/$(GCC_VERSION) \ - -L$(EVICSDK)/lib \ - -L$(EVICSDK)/linker - -CPUFLAGS := -mcpu=cortex-m4 -mthumb - -ifneq ($(EVICSDK_FPU_SUPPORT),) - CPUFLAGS += -mfloat-abi=hard -mfpu=fpv4-sp-d16 - CFLAGS += -DEVICSDK_FPU_SUPPORT - ASFLAGS += -DEVICSDK_FPU_SUPPORT - LDSCRIPT := $(EVICSDK)/linker/fpu.ld -else - LDSCRIPT := $(EVICSDK)/linker/nofpu.ld -endif - -CFLAGS += -Wall $(CPUFLAGS) -Os -fdata-sections -ffunction-sections -CFLAGS += $(INCDIRS) - -CPPFLAGS += -fno-exceptions -fno-rtti - -ASFLAGS += $(CPUFLAGS) - -# Yes, I know what I'm doing with --no-warn-mismatch. -# The thread library is compiled without FPU support to avoid issues with FPU -# context switching, which would normally result in a linker error due to -# different ABIs (soft/hard) when the SDK is compiled with FPU support. Since -# no function in the thread library accepts FP arguments, they will work fine -# together. Of course this trainwrecks when SDK is compiled with FPU support -# and APROM is not. Oh well... -LDFLAGS += $(LIBDIRS) -LDFLAGS += -nostdlib -nostartfiles -T$(LDSCRIPT) --gc-sections --no-warn-mismatch - -all: env_check $(TARGET).bin - -%.o: %.c - $(CC) $(CFLAGS) -c $< -o $@ - -%.o: %.cpp - $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@ - -%.o: %.s - $(CC) $(ASFLAGS) -c -x assembler-with-cpp $< -o $@ - -$(TARGET).elf: $(OBJS_FIXPATH) - test -d $(BINDIR) || mkdir $(BINDIR) - $(LD) $(OBJS_FIXPATH) $(LDFLAGS) -o $(BINDIR)/$(TARGET).elf - -$(TARGET)_unencrypted.bin: $(TARGET).elf - $(OBJCOPY) -O binary -j .text -j .data $(BINDIR)/$(TARGET).elf $(BINDIR)/$(TARGET)_unencrypted.bin - rm -f $(BINDIR)/$(TARGET).elf - -$(TARGET).bin: $(TARGET)_unencrypted.bin - evic convert $(BINDIR)/$(TARGET)_unencrypted.bin -o $(BINDIR)/$(TARGET).bin - rm -f $(BINDIR)/$(TARGET)_unencrypted.bin - -clean: - rm -rf $(OBJS) $(BINDIR) - -env_check: -ifeq ($(ARMGCC),) - $(error You must set the ARMGCC environment variable) -endif - -.PHONY: all clean env_check +# Linker script. +LDSCRIPT := $(EVICSDK)/linker/linker.ld + +# Binary output directory template. +bindir-tmpl = $(call dir-tmpl,$1,$2,$(BINDIR)) +# Binary output directory clean template. +clean-bindir-tmpl = $(call clean-dir-tmpl,$1,$2,$(BINDIR)) +# ELF output path template. +elf-tmpl = $(call bindir-tmpl,$1,$2)/$(TARGET).elf +# Binary output path template. Extra argument: binary name. +bin-tmpl = $(call bindir-tmpl,$1,$2)/$3.bin +# Unecrypted binary output path template. +bin-dec-tmpl = $(call bin-tmpl,$1,$2,$(TARGET)_dec) +# Encrypted binary output path template. +bin-enc-tmpl = $(call bin-tmpl,$1,$2,$(TARGET)) +# Linker map output path template. +ldmap-tmpl = $(call bindir-tmpl,$1,$2)/$(TARGET).map + +# Object targets for all devices and flavors. +objs-all := $(call tmpl-all,objs-tmpl,$(OBJS)) +# ELF targets for all devices and flavors. +elf-all := $(call tmpl-all,elf-tmpl) +# ELF targets for all devices, debug flavor. +elf-dbg := $(call tmpl-flavor,elf-tmpl,$(BUILD_FLAVOR_DBG)) +# ELF targets for all devices, release flavor. +elf-rel := $(call tmpl-flavor,elf-tmpl,$(BUILD_FLAVOR_REL)) +# Unencrypted binary targets for all devices and flavors. +bin-dec-all := $(call tmpl-all,bin-dec-tmpl) +# Unencrypted binary targets for all devices, release flavor. +bin-dec-rel := $(call tmpl-flavor,bin-dec-tmpl,$(BUILD_FLAVOR_REL)) +# Encrypted binary targets for all devices and flavors. +bin-enc-all := $(call tmpl-all,bin-enc-tmpl) +# Linker map output paths for all device, debug flavor. +ldmap-dbg := $(call tmpl-flavor,ldmap-tmpl,$(BUILD_FLAVOR_DBG)) +# SDK output directories for all devices and flavors. +sdk-all := $(call tmpl-all,sdkdir-tmpl,$(EVICSDK)) + +# Cache all needed paths for fixpath. +$(call fixpath-cache, \ + $(call objs-fixpath-cache,$(OBJS)) \ + $(elf-all) $(bin-dec-all) $(bin-enc-all) $(ldmap-dbg) $(sdk-all) \ + $(LIBDIRS) $(LDSCRIPT)) + +# Set up linker flags. +# We're linking with --no-warn-mismatch because the thread library is compiled +# without FPU support to avoid issues with FPU context switching, which would +# normally result in a linker error due to different FP ABIs (soft/hard). Since +# no function in the thread library accepts FP arguments or calls FP library +# functions, they will work fine together. This may trainwreck if SDK and APROM +# are compiled with different FP ABIs, but no-FPU builds are just a rarely used +# debugging tool, so it's good enough. +LDFLAGS += \ + -T$(call fixpath-bu,$(LDSCRIPT)) \ + -nostdlib -nostartfiles --gc-sections --no-warn-mismatch +# We keep -L flags separated because we want to specify them before LDFLAGS so +# that -l flags work correctly. Also, we're going to add the SDK library path +# to this on a device/flavor basis. +LDFLAGS_LIBDIRS := \ + $(foreach d,$(ARM_LIBDIRS_FIXBU) $(call fixpath-bu,$(LIBDIRS)),-L$d) + +# Objcopy flags for converting ELF to unencrypted binary. +OBJCOPYFLAGS += -O binary -j .text -j .data + +# Add binaries to clean templates. +CLEAN_PATH_TMPL += clean-bindir-tmpl + +# Enable secondary expansion. +.SECONDEXPANSION: + +# Set BUILD_* for all our targets. +$(call build-vars-rules,elf-tmpl) +$(call build-vars-rules,bin-dec-tmpl) +$(call build-vars-rules,bin-enc-tmpl) + +# Rule to link objects into an ELF. +$(elf-all): $$(call tmpl-build,objs-tmpl,$$(OBJS)) | $$(@D) + $(call info-cmd,LD) + @$(call trace, \ + $(LD) $(call fixpath-bu,$^) $(LDFLAGS_LIBDIRS) $(LDFLAGS) \ + -o $(call fixpath-bu,$@)) + +# Add the SDK to LDFLAGS_LIBDIRS for all ELF targets. +$(elf-all): LDFLAGS_LIBDIRS += \ + -L$(call fixpath-bu,$(call tmpl-build,sdkdir-tmpl,$(EVICSDK))) + +# Generate a linker map for debug ELF targets. +$(elf-dbg): LDFLAGS += -Map=$(call fixpath-bu,$(call tmpl-build,ldmap-tmpl)) + +# Rule to generate an unencrypted binary from the ELF. +$(bin-dec-all): $$(call tmpl-build,elf-tmpl) | $$(@D) + $(call info-cmd,BIN) + @$(call trace, \ + $(OBJCOPY) $(OBJCOPYFLAGS) $(call fixpath-bu,$<) $(call fixpath-bu,$@)) + +# Rule to generate an encrypted binary from the unencrypted one. +$(bin-enc-all): $$(call tmpl-build,bin-dec-tmpl) | $$(@D) + $(call info-cmd,ENC) +# Silence evic-convert, errors will still get through. + @$(call trace, \ + evic-convert $< -o $@ > $(NULLDEV)) + +# Device-flavor target rule: build encrypted binary. +$(devfla-all): $$(call tmpl-build,bin-enc-tmpl) + +# Set object directories as order-only prerequisites for object targets. +$(objs-all): | $$(@D) + +# Generate directory targets. +$(call mkdir-rules,objs-dirs-tmpl,$(OBJS)) +$(call mkdir-rules,bindir-tmpl) + +# Mark all release ELFs and unencrypted binaries as intermediate files. +.INTERMEDIATE: $(elf-rel) $(bin-dec-rel) + +endif # __evicsdk_make_base_inc diff --git a/make/Common.mk b/make/Common.mk new file mode 100644 index 0000000..41167b1 --- /dev/null +++ b/make/Common.mk @@ -0,0 +1,337 @@ +# This file is part of eVic SDK. +# +# eVic SDK is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# eVic SDK is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with eVic SDK. If not, see . +# +# Copyright (C) 2016 ReservedField + +# If you need to, set INCDIRS(_*) *before* including this file. + +ifndef __evicsdk_make_common_inc +__evicsdk_make_common_inc := 1 + +include $(EVICSDK)/make/gmsl/gmsl +include $(EVICSDK)/make/Helper.mk +include $(EVICSDK)/make/Device.mk + +# Check make version. The sort/firstword trick fails if MAKE_VERSION is empty +# (make < 3.69), so we $(and) it with MAKE_VERSION. +$(if $(EVICSDK_MAKE_DEBUG),$(info Make version: $(MAKE_VERSION))) +ifneq ($(and $(MAKE_VERSION),$(firstword $(sort $(MAKE_VERSION) 3.81))),3.81) +$(error Make >= 3.81 required) +endif + +# Disable all builtin rules (they only slow us down). +MAKEFLAGS += --no-builtin-rules +.SUFFIXES: + +# Detect the OS, setting the following variables: +# IS_WIN_OS: 1 on Windows, empty otherwise. +# IS_CYGWIN: 1 on Windows + Cygwin, empty otherwise. +# On Windows + Cygwin both will be 1. +# On Windows + MinGW only IS_WIN_OS will be 1. +IS_WIN_OS := $(if $(findstring windows,$(call lc,$(OS))),1) +IS_CYGWIN := $(if $(findstring cygwin,$(call lc,$(shell uname -s))),1) +$(if $(EVICSDK_MAKE_DEBUG),$(info Detected OS: \ + $(if $(IS_WIN_OS),Windows ($(if $(IS_CYGWIN),Cygwin,native)),Unix-like))) + +# OS null device (e.g. /dev/null on Linux and Cygwin, NUL on Windows). +NULLDEV := $(if $(and $(IS_WIN_OS),$(call not,$(IS_CYGWIN))),NUL,/dev/null) +# GCC/binutils null device (e.g. /dev/null on Linux, NUL on Cygwin, Windows). +NULLDEV_TC := $(if $(IS_WIN_OS),NUL,/dev/null) + +# Detect the compiler based on CC, setting the following variables: +# CC_IS_CLANG: 1 if Clang, empty if GCC. +# If the compiler is not Clang, CC is set to arm-none-eabi-gcc. +CC_IS_CLANG := $(if $(findstring clang version,$(shell $(CC) -v 2>&1)),1) +ifndef CC_IS_CLANG + CC := arm-none-eabi-gcc +endif +$(if $(EVICSDK_MAKE_DEBUG),$(info Detected compiler: \ + $(if $(CC_IS_CLANG),Clang,GCC))) + +# Set up CPU flags for the compiler. Use EVICSDK_FPU_DISABLE to disable FPU. +# Sets the following variables: +# CPUFLAGS_GCC_NOFPU: flags for no-FPU build, GCC compiler. +# CPUFLAGS_GCC_FPU: flags for FPU build, GCC compiler. +# CPUFLAGS_GCC: flags for current build, GCC compiler. +# CPUFLAGS_NOFPU: flags for no-FPU build, detected compiler. +# CPUFLAGS_FPU: flags for FPU build, detected compiler. +# CPUFLAGS: flags for current build, detected compiler. +CPUFLAGS_GCC_NOFPU := -mcpu=cortex-m4 -mthumb +CPUFLAGS_GCC_FPU := $(CPUFLAGS_GCC_NOFPU) -mfloat-abi=hard -mfpu=fpv4-sp-d16 +CPUFLAGS_GCC := \ + $(if $(EVICSDK_FPU_DISABLE),$(CPUFLAGS_GCC_NOFPU),$(CPUFLAGS_GCC_FPU)) +ifndef CC_IS_CLANG + CPUFLAGS_NOFPU := $(CPUFLAGS_GCC_NOFPU) + CPUFLAGS_FPU := $(CPUFLAGS_GCC_FPU) +else + CPUFLAGS_FPU := -target armv7em-none-eabi + CPUFLAGS_NOFPU := $(CPUFLAGS_FPU) -mfpu=none +endif +CPUFLAGS := $(if $(EVICSDK_FPU_DISABLE),$(CPUFLAGS_NOFPU),$(CPUFLAGS_FPU)) + +# Detect library paths for the selected CPU flags, setting ARM_LIBDIRS_FIXBU. +# This is already fixed for binutils (see the fixpath-* docs in Helper.mk). +# The linker search path must follow the exact order of ARM_LIBDIRS_FIXBU. +ARM_LIBDIRS_FIXBU := $(subst $(if $(IS_WIN_OS),;,:), ,$(shell \ + arm-none-eabi-gcc $(CPUFLAGS_GCC) --print-search-dirs | \ + sed -n 's/^libraries:\s*=\?\(.*\)/\1/p')) +ifndef ARM_LIBDIRS_FIXBU +$(error Could not detect arm-none-eabi library paths) +endif +ifdef EVICSDK_MAKE_DEBUG +$(info ARM library paths (raw): $(ARM_LIBDIRS_FIXBU)) +$(info ARM library paths (canonical): \ + $(call path-canon,$(call unfixpath-tc,$(ARM_LIBDIRS_FIXBU)))) +endif + +# Nuvoton SDK root path. +NUVOSDK := $(EVICSDK)/nuvoton-sdk/Library + +# Gets GCC include directories for a language. +# Argument 1: language. +get-gcc-incdirs = $(shell \ + arm-none-eabi-gcc $(CPUFLAGS_GCC) -x $1 -v -E $(NULLDEV_TC) 2>&1 | \ + sed -n '/<\.\.\.>/,/End/p' | tail -n +2 | head -n -1 | sed 's/^\s*//') + +# Clang doesn't know about standard headers for arm-none-eabi. +# Fix it by using GCC's headers. We'll add -nostdinc later. +# TODO: this works, but generates warnings. Find a better way. +ifdef CC_IS_CLANG + __INCDIRS_GCC_C := $(call get-gcc-incdirs,c) + __INCDIRS_GCC_CXX := $(call get-gcc-incdirs,c++) + __INCDIRS_GCC_C_UNFIX := $(call unfixpath-tc,$(__INCDIRS_GCC_C)) + __INCDIRS_GCC_CXX_UNFIX := $(call unfixpath-tc,$(__INCDIRS_GCC_CXX)) +ifdef EVICSDK_MAKE_DEBUG +$(info GCC C include paths (raw): $(__INCDIRS_GCC_C)) +$(info GCC C include paths (canonical): \ + $(call path-canon,$(__INCDIRS_GCC_C_UNFIX))) +$(info GCC C++ include paths (raw): $(__INCDIRS_GCC_CXX)) +$(info GCC C++ include paths (canonical): \ + $(call path-canon,$(__INCDIRS_GCC_CXX_UNFIX))) +endif + INCDIRS_C += $(__INCDIRS_GCC_C_UNFIX) + INCDIRS_CXX += $(__INCDIRS_GCC_CXX_UNFIX) +endif + +# Add include directories for SDKs to INCDIRS. +INCDIRS += \ + $(NUVOSDK)/CMSIS/Include \ + $(NUVOSDK)/Device/Nuvoton/M451Series/Include \ + $(NUVOSDK)/StdDriver/inc \ + $(EVICSDK)/include + +# Cache all needed paths for fixpath. +$(call fixpath-cache,$(INCDIRS) $(INCDIRS_C) $(INCDIRS_CXX)) + +# Gets the compiler flags for the specified include paths. +# Argument 1: list of include paths. +get-incflags = $(foreach d,$(call fixpath-cc,$1),-I$d) + +# Set up toolchain flags. Appends to the following variables: +# CFLAGS: C compiler flags. +# CXXFLAGS: C++ compiler flags. +# ASFLAGS: assembler flags. +# Note that those are NOT inclusive of CPU flags. +__CC_FLAGS := \ + $(call get-incflags,$(INCDIRS)) \ + -Os -fdata-sections -ffunction-sections \ + $(if $(CC_IS_CLANG),-nostdinc) +ifndef EVICSDK_FPU_DISABLE + __CC_FLAGS += -DEVICSDK_FPU_SUPPORT + ASFLAGS += -DEVICSDK_FPU_SUPPORT +endif +__CFLAGS_INCDIRS := $(call get-incflags,$(INCDIRS_C)) +__CXXFLAGS_INCDIRS := $(call get-incflags,$(INCDIRS_CXX)) +CFLAGS += $(__CFLAGS_INCDIRS) $(__CC_FLAGS) +CXXFLAGS += $(__CXXFLAGS_INCDIRS) $(__CC_FLAGS) -fno-exceptions -fno-rtti +__EXTRA_FLAGS_DBG := -g + +# Set up toolchain tool names. CC is already set. +# We use CC as the assembler to take advantage of preprocessing. +CXX := $(CC) +AS := $(CC) +LD := arm-none-eabi-ld +AR := arm-none-eabi-ar +OBJCOPY := arm-none-eabi-objcopy + +# Default device: all devices. +EVICSDK_MAKE_DEFAULT_DEVICE ?= all +# Default flavor: all flavors. +EVICSDK_MAKE_DEFAULT_FLAVOR ?= all + +# Template that wraps the template specified by the extra argument, converting +# empty arguments to "all". Doesn't support extra arguments to the template. +ea-wrap = $(call $3,$(or $1,all),$(or $2,all)) +# Template that wraps the template specified by the extra argument, ignoring +# flavor and always passing in an empty one. Doesn't support extra arguments to +# the template. +dev-wrap = $(call $3,$1) +# Template that wraps the template specified by the extra argument, ignoring +# device and always passing in an empty one. Doesn't support extra arguments to +# the template. +fla-wrap = $(call $3,,$2) + +# Generates phony rules for a template that default: +# - device-all targets to all device-flavor targets for the specified device; +# - all-flavor targets to all device-flavor targets for the specified flavor; +# - all-all and all targets to all device-flavor targets. +# Argument 1: template name. Must produce the correct target names when called +# with one empty argument. +# Argument 2: extra template argument (optional). +alias-devfla-rules = $(eval $(call __alias-devfla-rules,$1,$2)) +define __alias-devfla-rules +$(call tmpl-rule-all-device,prereq-rule,$1,ea-wrap $1,$2) +$(call tmpl-rule-all-flavor,prereq-rule,$1,ea-wrap $1,$2) +$(call $1,all,all,$2) $(call $1,all,,$2): $(call tmpl-all,$1,$2) +.PHONY: $(call tmpl-flavor,$1,all,$2) $(call tmpl-device,$1,all,$2) \ + $(call $1,all,all,$2) $(call $1,all,,$2) +endef + +# Generates phony rules for a template that default: +# - device targets to device-flavor targets for the default flavor(s); +# - flavor targets to device-flavor targets for the default device(s); +# - the empty target to device-flavor targets for the default flavor(s) and +# device(s). +# Argument 1: template name. Must produce the correct target names when called +# with one or both empty arguments. +# Argument 2: extra template argument (optional). +alias-default-rules = $(eval $(call __alias-default-rules,$1,$2)) +define __alias-default-rules +$(foreach f,$(EVICSDK_MAKE_DEFAULT_FLAVOR),$(call \ + tmpl-rule-flavor,prereq-rule,$f,$1,dev-wrap $1,$2)) +$(foreach d,$(EVICSDK_MAKE_DEFAULT_DEVICE),$(call \ + tmpl-rule-device,prereq-rule,$d,$1,fla-wrap $1,$2)) +$(call $1,,,$2): $(foreach f,$(EVICSDK_MAKE_DEFAULT_FLAVOR),$(foreach \ + d,$(EVICSDK_MAKE_DEFAULT_DEVICE),$(call $1,$d,$f,$2))) +.PHONY: $(call tmpl-flavor,$1,,$2) $(call tmpl-device,$1,,$2) +endef + +# Helper that calls both alias-devfla-rules and alias-default-rules. +# Argument 1: template name. Must produce the correct target names when called +# with one or both empty arguments. +# Argument 2: extra template argument (optional). +alias-rules = $(call alias-devfla-rules,$1,$2) \ + $(call alias-default-rules,$1,$2) + +# SDK output directory. +SDKDIR = lib +# Base object output directory. +OBJDIR = obj + +# SDK output directory template. +# Extra argument: SDK root (empty for a relative path). +sdkdir-tmpl = $(if $3,$3/)$(call dir-tmpl,$1,$2,$(SDKDIR)) +# Object output directory template. +objdir-tmpl = $(call dir-tmpl,$1,$2,$(OBJDIR)) +# Object output directory template for clean. Accepts empty arguments. +clean-objdir-tmpl = $(call clean-dir-tmpl,$1,$2,$(OBJDIR)) +# Objects output path template. Extra argument: object list. +objs-tmpl = $(addprefix $(call objdir-tmpl,$1,$2)/,$3) +# Objects output directory template (no trailing slash, duplicates removed). +# Extra argument: object list. +objs-dirs-tmpl = $(patsubst %/,%,$(sort $(dir $(call objs-tmpl,$1,$2,$3)))) +# Object pattern template. +objpat-tmpl = $(call objdir-tmpl,$1,$2)/%.o +# Device-flavor template. Accepts empty arguments. +# If both device and flavor are empty, "def" is returned. +devfla-tmpl = $(or $1$(and $1,$2,-)$2,def) +# Clean-device-flavor template. Accepts empty arguments. +clean-devfla-tmpl = clean$(if $1,-$1)$(if $2,-$2) + +# Gets the paths to all possible sources for the given objects. +# Argument 1: object list. +get-srcs = $(foreach e,c cpp s,$(patsubst %.o,%.$e,$1)) + +# Gets object and source paths to cache for fixpath. +# Argument 1: object list. +# Argument 2: flavor (optional). +objs-fixpath-cache = $(if $2,$(call tmpl-flavor,objs-tmpl,$2,$1),$(call \ + tmpl-all,objs-tmpl,$1)) $(call get-srcs,$1) + +# Object patterns for all devices, debug flavor. +objpat-dbg := $(call tmpl-flavor,objpat-tmpl,$(BUILD_FLAVOR_DBG)) +# Device-flavor targets for all devices and flavors. +devfla-all := $(call tmpl-all,devfla-tmpl) +# Clean-device-flavor targets for all devices and flavors. +clean-devfla-all := $(call tmpl-all,clean-devfla-tmpl) +# Clean-device-all targets for all devices. +clean-devall-all := $(call tmpl-flavor,clean-devfla-tmpl,all) +# Clean-all-flavor targets for all flavors. +clean-allfla-all := $(call tmpl-device,clean-devfla-tmpl,all) +# Clean-all-all and clean-all targets. +clean-all := $(call clean-devfla-tmpl,all,all) $(call clean-devfla-tmpl,all) + +# Templates for paths to be removed on clean. Each template must support being +# called with one or both empty arguments and can return multiple paths. +# Add objects to clean templates. +CLEAN_PATH_TMPL += clean-objdir-tmpl + +# Object compilation rules macro. +# Since those are pattern rules, make assumes all targets are built within a +# single invocation when multiple targets are present. Because of this we need +# to redefine the rules for each device and flavor instead of writing them once +# with $(call tmpl-all,objpat-tmpl) as target. +define compile-rules +$3: %.c + $$(call info-cmd,CC) + @$$(call trace, \ + $$(CC) $$(CPUFLAGS) $$(CFLAGS) \ + -c $$(call fixpath-cc,$$<) -o $$(call fixpath-cc,$$@)) +$3: %.cpp + $$(call info-cmd,CXX) + @$$(call trace, \ + $$(CXX) $$(CPUFLAGS) $$(CXXFLAGS) \ + -c $$(call fixpath-cc,$$<) -o $$(call fixpath-cc,$$@)) +$3: %.s + $$(call info-cmd,AS) + @$$(call trace, \ + $$(AS) $$(CPUFLAGS) $$(ASFLAGS) -c -x assembler-with-cpp \ + $$(call fixpath-cc,$$<) -o $$(call fixpath-cc,$$@)) +endef + +# Compilation rules for all object targets. +$(call tmpl-rule-all,compile-rules,objpat-tmpl) + +# Set target-specific variables for debug targets. +$(objpat-dbg): CFLAGS += $(__EXTRA_FLAGS_DBG) +$(objpat-dbg): CXXFLAGS += $(__EXTRA_FLAGS_DBG) +$(objpat-dbg): ASFLAGS += $(__EXTRA_FLAGS_DBG) + +# Common rule for all clean targets. +$(clean-devfla-all) $(clean-devall-all) $(clean-allfla-all) $(clean-all): + rm -rf $(call tmpl-cat,$(CLEAN_PATH_TMPL),$(BUILD_DEVICE),$(BUILD_FLAVOR)) + +# All aliases for device-flavor targets. +$(call alias-rules,devfla-tmpl) +# Default aliases for clean targets. +$(call alias-default-rules,clean-devfla-tmpl) + +# Set BUILD_* for object, device-flavor and clean-device-flavor targets. +# We don't have to split up object patterns for target-specific variables. +$(call build-vars-rules,objpat-tmpl) +$(call build-vars-rules,devfla-tmpl) +$(call build-vars-rules,clean-devfla-tmpl) +# Set BUILD_* for clean-device-all targets (empty flavor). +$(call tmpl-rule-flavor,build_device_flavor-rules,,ea-wrap,,clean-devfla-tmpl) +# Set BUILD_* for clean-all-flavor targets (empty device). +$(call tmpl-rule-device,build_device_flavor-rules,,ea-wrap,,clean-devfla-tmpl) + +# Default target: empty device-flavor target. +.DEFAULT_GOAL := $(call devfla-tmpl) + +.PHONY: $(devfla-all) \ + $(clean-devfla-all) $(clean-devall-all) $(clean-allfla-all) $(clean-all) + +endif # __evicsdk_make_common_inc diff --git a/make/Device.mk b/make/Device.mk new file mode 100644 index 0000000..24b8835 --- /dev/null +++ b/make/Device.mk @@ -0,0 +1,24 @@ +# This file is part of eVic SDK. +# +# eVic SDK is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# eVic SDK is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with eVic SDK. If not, see . +# +# Copyright (C) 2016 ReservedField + +ifndef __evicsdk_make_devices_inc +__evicsdk_make_devices_inc := 1 + +# Supported devices list. +DEVICES := evic + +endif # __evicsdk_make_devices_inc diff --git a/make/Helper.mk b/make/Helper.mk new file mode 100644 index 0000000..6d905c7 --- /dev/null +++ b/make/Helper.mk @@ -0,0 +1,239 @@ +# This file is part of eVic SDK. +# +# eVic SDK is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# eVic SDK is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with eVic SDK. If not, see . +# +# Copyright (C) 2016 ReservedField + +ifndef __evicsdk_make_helper_inc +__evicsdk_make_helper_inc := 1 + +include $(EVICSDK)/make/gmsl/gmsl +include $(EVICSDK)/make/Device.mk + +# Terms used in documentation: +# OPTIONAL: an optional argument can be empty. Make syntax allows you to omit +# empty arguments if they are the last ones. +# TEMPLATE: an user-defined function that generates some kind of string from +# the following arguments: +# Argument 1: device name. +# Argument 2: flavor. +# Argument 3: extra argument (if specified for tmpl-*). +# RULE MACRO: an user-defined function that will be eval'ed (escape your $s!). +# Called a rule macro because it's usually used to define rules. +# It takes the following arguments: +# Argument 1: device name. +# Argument 2: flavor. +# Argument 3: template output (if a template was specified for +# tmpl-rule-*). +# Argument 4: extra argument (if specified for tmpl-rule-*). + +# Build flavors. +BUILD_FLAVOR_DBG := dbg +BUILD_FLAVOR_REL := rel +BUILD_FLAVORS := $(BUILD_FLAVOR_DBG) $(BUILD_FLAVOR_REL) + +# Converts a list of Cygwin style paths to native Windows style. +# Argument 1: list of paths to convert. +cygpath-w = $(if $(strip $1),$(shell cygpath -w $(strip $1))) + +# Converts a list of native Windows style paths to Cygwin style. +# Argument 1: list of paths to convert. +cygpath-u = $(if $(strip $1),$(shell cygpath -u $(strip $1))) + +# Converts a list of paths to the needed system style for binutils (BU) or the +# C compiler (CC). A few assumptions need to be made for Windows + Cygwin, due +# to path style differences between native and Cygwin binaries. We assume +# arm-none-eabi-gcc and binutils are native binaries, while Clang is a Cygwin +# binary. This is consistent with the available packages. This means we'll +# fixup paths through cygpath-w when passing them to GCC/binutils on Cygwin, +# but not when passing them to Clang. +# IS_CYGWIN and CC_IS_CLANG must be set prior to calling those. Since the +# conversion is expensive, paths are internally cached. They can be manually +# cached through fixpath-cache, which expands to empty. +# Argument 1: list of paths to fix. +fixpath-bu = $(if $(IS_CYGWIN),$(call memoize-list,cygpath-w,$1),$1) +fixpath-cc = $(if $(and $(IS_CYGWIN),$(call not,$(CC_IS_CLANG))),$(call \ + memoize-list,cygpath-w,$1),$1) +fixpath-cache = $(if $(IS_CYGWIN),$(call memoize-list-update,cygpath-w,$1)) + +# Converts a list of paths from GCC/binutils to shell style. Same assumptions +# are made as for fixpath-*. The paths are not internally cached (Windows uses +# colons in paths, which are not supported by memoize-list). +# Argument 1: list of paths to unfix. +unfixpath-tc = $(if $(IS_CYGWIN),$(call cygpath-u,$1),$1) + +# Canonicalizes a list of paths. +# If a path doesn't exist it's retuned unchanged and surrounded by (). +# Note: $(realpath) is broken for absolute paths on Windows make 3.81. Cygwin +# make is fine (Unix-style paths). This is only used for debugging anyway. +# Argument 1: list of paths to canonicalize. +path-canon = $(foreach p,$1,$(or $(realpath $p),($p))) + +# Prints a message formatted like: +# [device-flavor] command target +# This function is to be used only inside rules with BUILD_* variables set. +# Argument 1: command name (at most 3 characters). +info-cmd = $(info [$(BUILD_DEVICE)-$(BUILD_FLAVOR)] $(call \ + substr,$1 ,1,3) $@) + +# Prints a string if EVICSDK_MAKE_DEBUG is defined. +# Always evaluates to the provided string. +# This is meant to be used for tracing recipe commands. Using the --trace +# option of make >= 4.0 is simpler, but we support make >= 3.81. +# Argument 1: string to trace and return. +trace = $(if $(EVICSDK_MAKE_DEBUG),$(info $(strip $1)))$(strip $1) + +# Generates template output for all flavors for the specified device. +# Argument 1: template name. +# Argument 2: device name (passed as-is to template, can be anything). +# Argument 3: extra template argument (optional). +tmpl-device = $(foreach f,$(BUILD_FLAVORS),$(call $1,$2,$f,$3)) + +# Generates template output for all devices for the specified flavor. +# Argument 1: template name. +# Argument 2: flavor (passed as-is to template, can be anything). +# Argument 3: extra template argument (optional). +tmpl-flavor = $(foreach d,$(DEVICES),$(call $1,$d,$2,$3)) + +# Generates template output for all devices and flavors. +# Argument 1: template name. +# Argument 2: extra template argument (optional). +tmpl-all = $(foreach f,$(BUILD_FLAVORS),$(call tmpl-flavor,$1,$f,$2)) + +# Generates template output based on BUILD_* variables. +# Argument 1: template name. +# Argument 2: extra template argument (optional). +tmpl-build = $(call $1,$(BUILD_DEVICE),$(BUILD_FLAVOR),$2) + +# Generates rules for all flavors for the specified device. +# Argument 1: rule macro name. +# Argument 2: device name (passed as-is to template and rule, can be anything). +# Argument 3: template name (optional). +# Argument 4: extra rule argument (optional). +# Argument 5: extra template argument (optional). +tmpl-rule-device = $(foreach f,$(BUILD_FLAVORS),$(eval $(call $1,$2,$f,$(call \ + $3,$2,$f,$5),$4))) + +# Generates rules for all devices for the specified flavor. +# Argument 1: rule macro name. +# Argument 2: flavor (passed as-is to template and rule, can be anything). +# Argument 3: template name (optional). +# Argument 4: extra rule argument (optional). +# Argument 5: extra template argument (optional). +tmpl-rule-flavor = $(foreach d,$(DEVICES),$(eval $(call $1,$d,$2,$(call \ + $3,$d,$2,$5),$4))) + +# Generates rules for all devices and flavors. +# Argument 1: rule macro name. +# Argument 2: template name (optional). +# ARgument 3: extra rule argument (optional). +# Argument 4: extra template argument (optional). +tmpl-rule-all = $(foreach f,$(BUILD_FLAVORS),$(call \ + tmpl-rule-flavor,$1,$f,$2,$3,$4)) + +# Generates rules for all devices and flavors, grouping them by device. This +# means that if a template is specified, the template output passed to the rule +# macro will be the output of tmpl-device. Since multiple flavors will be in +# the template output, the rule will be passed an empty flavor argument. +# Argument 1: rule macro name. +# Argument 2: template name (optional). +# Argument 3: extra rule argument (optional). +# Argument 4: extra template argument (optional). +tmpl-rule-all-device = $(foreach d,$(DEVICES),$(eval $(call $1,$d,,$(call \ + tmpl-device,$2,$d,$4),$3))) + +# Generates rules for all devices and flavors, grouping them by flavor. This +# means that if a template is specified, the template output passed to the rule +# macro will be the output of tmpl-flavor. Since multiple devices will be in +# the template output, the rule will be passed an empty device argument. +# Argument 1: rule macro name. +# Argument 2: template name (optional). +# Argument 3: extra rule argument (optional). +# Argument 4: extra template argument (optional). +tmpl-rule-all-flavor = $(foreach f,$(BUILD_FLAVORS),$(eval $(call \ + $1,,$f,$(call tmpl-flavor,$2,$f,$4),$3))) + +# Makes a list out of the outputs of a list of templates. +# Argument 1: list of template names. +# Argument 2: device name (passed as-is to template, can be anything). +# Argument 3: flavor (passed as-is to template, can be anything). +tmpl-cat = $(foreach t,$1,$(call $t,$2,$3)) + +# Memoizes a function that accepts and returns lists. The order of the list +# returned by the function is assumed to match the input list. There will be +# a single function call, but each input-output value pair will be cached +# separately. Already seen input values will be satisfied by the cache and +# filtered out from the function input. Returns the output list. +# Argument 1: function name. +# Argument 2: input list (colons are not allowed). +memoize-list = $(call memoize-list-update,$1,$(filter-out $(call \ + keys,__memoize-map-$1),$2))$(foreach k,$2,$(call get,__memoize-map-$1,$k)) + +# Caches values for memoize-list. The function semantics are the same, but the +# input-output pairs will always be forcefully cached. Expands to empty. +# Argument 1: function name. +# Argument 2: input list (colons are not allowed). +memoize-list-update = $(if $(strip $2),$(eval __memoize-$1 = $$(call \ + set,__memoize-map-$1,$$1,$$2))$(call \ + pairmap,__memoize-$1,$2,$(call $1,$2))) + +# Rule macro to set BUILD_DEVICE and BUILD_FLAVOR for the targets specified by +# the template output. +# Note: this could be split into two rules and used with tmpl-rule-all-*. While +# it looks cleaner, it takes two calls (i.e. two times the same device-flavor +# loop) and make will split them up into a single target per rule internally, +# so there's no performance gain. It also becomes tricky for clean targets. +define build_device_flavor-rules +$3: BUILD_DEVICE := $1 +$3: BUILD_FLAVOR := $2 +endef + +# Generates rules to set BUILD_DEVICE and BUILD_FLAVOR +# for the targets generated by the specified template. +# Argument 1: template name. +# Argument 2: extra template argument (optional). +build-vars-rules = $(call tmpl-rule-all,build_device_flavor-rules,$1,,$2) + +# Rule macro to create the target directories specified +# by the template output. +define mkdir-target-rule +$3: + @mkdir -p $$@ +endef + +# Generates rules to create the directories generated +# by the specified template. +# Argument 1: template name. +# Argument 2: extra template argument (optional). +mkdir-rules = $(call tmpl-rule-all,mkdir-target-rule,$1,,$2) + +# Rule macro to set the template output as prerequisite for the target +# generated by the template specified by the extra rule argument. You can +# specify an extra argument to the template by separating it with a space +# from the template name in the extra rule argument. +define prereq-rule +$(call $(word 1,$4),$1,$2,$(word 2,$4)): $3 +endef + +# Output directory template (no trailing slash). +# Extra argument: base directory. +dir-tmpl = $3/$2/$1 + +# Output directory clean template (no trailing slash). +# Extra argument: base directory. +# Accepts one or both empty arguments. May return a list. +clean-dir-tmpl = $(if $(and $1,$(call not,$2)),$(call \ + tmpl-device,clean-dir-tmpl,$1,$3),$3$(if $2,/$2$(if $1,/$1))) + +endif # __evicsdk_make_helper_inc diff --git a/make/gmsl/__gmsl b/make/gmsl/__gmsl new file mode 100644 index 0000000..3db20ad --- /dev/null +++ b/make/gmsl/__gmsl @@ -0,0 +1,940 @@ +# ---------------------------------------------------------------------------- +# +# GNU Make Standard Library (GMSL) +# +# A library of functions to be used with GNU Make's $(call) that +# provides functionality not available in standard GNU Make. +# +# Copyright (c) 2005-2014 John Graham-Cumming +# +# This file is part of GMSL +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# Neither the name of the John Graham-Cumming nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# ---------------------------------------------------------------------------- + +# This is the GNU Make Standard Library version number as a list with +# three items: major, minor, revision + +gmsl_version := 1 1 7 + +__gmsl_name := GNU Make Standard Library + +# Used to output warnings and error from the library, it's possible to +# disable any warnings or errors by overriding these definitions +# manually or by setting GMSL_NO_WARNINGS or GMSL_NO_ERRORS + +ifdef GMSL_NO_WARNINGS +__gmsl_warning := +else +__gmsl_warning = $(if $1,$(warning $(__gmsl_name): $1)) +endif + +ifdef GMSL_NO_ERRORS +__gmsl_error := +else + __gmsl_error = $(if $1,$(error $(__gmsl_name): $1)) +endif + +# If GMSL_TRACE is enabled then calls to the library functions are +# traced to stdout using warning messages with their arguments + +ifdef GMSL_TRACE +__gmsl_tr1 = $(warning $0('$1')) +__gmsl_tr2 = $(warning $0('$1','$2')) +__gmsl_tr3 = $(warning $0('$1','$2','$3')) +else +__gmsl_tr1 := +__gmsl_tr2 := +__gmsl_tr3 := +endif + +# See if spaces are valid in variable names (this was the case until +# GNU Make 3.82) +ifeq ($(MAKE_VERSION),3.82) +__gmsl_spaced_vars := $(false) +else +__gmsl_spaced_vars := $(true) +endif + +# Figure out whether we have $(eval) or not (GNU Make 3.80 and above) +# if we do not then output a warning message, if we do then some +# functions will be enabled. + +__gmsl_have_eval := $(false) +__gmsl_ignore := $(eval __gmsl_have_eval := $(true)) + +# If this is being run with Electric Cloud's emake then warn that +# their $(eval) support is incomplete in 1.x, 2.x, 3.x, 4.x and 5.0, +# 5.1, 5.2 and 5.3 + +ifdef ECLOUD_BUILD_ID +__gmsl_emake_major := $(word 1,$(subst ., ,$(EMAKE_VERSION))) +__gmsl_emake_minor := $(word 2,$(subst ., ,$(EMAKE_VERSION))) +ifneq ("$(findstring $(__gmsl_emake_major),1 2 3 4)$(findstring $(__gmsl_emake_major)$(__gmsl_emake_minor),50 51 52 53)","") +$(warning You are using a version of Electric Cloud's emake which has incomplete $$(eval) support) +__gmsl_have_eval := $(false) +endif +endif + +# See if we have $(lastword) (GNU Make 3.81 and above) + +__gmsl_have_lastword := $(lastword $(false) $(true)) + +# See if we have native or and and (GNU Make 3.81 and above) + +__gmsl_have_or := $(if $(filter-out undefined, \ + $(origin or)),$(call or,$(true),$(false))) +__gmsl_have_and := $(if $(filter-out undefined, \ + $(origin and)),$(call and,$(true),$(true))) + +ifneq ($(__gmsl_have_eval),$(true)) +$(call __gmsl_warning,Your make version $(MAKE_VERSION) does not support $$$$(eval): some functions disabled) +endif + +__gmsl_dollar := $$ +__gmsl_hash := \# + +# ---------------------------------------------------------------------------- +# ---------------------------------------------------------------------------- +# Function: gmsl_compatible +# Arguments: List containing the desired library version number (maj min rev) +# Returns: $(true) if this version of the library is compatible +# with the requested version number, otherwise $(false) +# ---------------------------------------------------------------------------- +gmsl_compatible = $(strip \ + $(if $(call gt,$(word 1,$1),$(word 1,$(gmsl_version))), \ + $(false), \ + $(if $(call lt,$(word 1,$1),$(word 1,$(gmsl_version))), \ + $(true), \ + $(if $(call gt,$(word 2,$1),$(word 2,$(gmsl_version))), \ + $(false), \ + $(if $(call lt,$(word 2,$1),$(word 2,$(gmsl_version))), \ + $(true), \ + $(call lte,$(word 3,$1),$(word 3,$(gmsl_version)))))))) + +# ########################################################################### +# LOGICAL OPERATORS +# ########################################################################### + +# not is defined in gmsl + +# ---------------------------------------------------------------------------- +# Function: and +# Arguments: Two boolean values +# Returns: Returns $(true) if both of the booleans are true +# ---------------------------------------------------------------------------- +ifneq ($(__gmsl_have_and),$(true)) +and = $(__gmsl_tr2)$(if $1,$(if $2,$(true),$(false)),$(false)) +endif + +# ---------------------------------------------------------------------------- +# Function: or +# Arguments: Two boolean values +# Returns: Returns $(true) if either of the booleans is true +# ---------------------------------------------------------------------------- +ifneq ($(__gmsl_have_or),$(true)) +or = $(__gmsl_tr2)$(if $1$2,$(true),$(false)) +endif + +# ---------------------------------------------------------------------------- +# Function: xor +# Arguments: Two boolean values +# Returns: Returns $(true) if exactly one of the booleans is true +# ---------------------------------------------------------------------------- +xor = $(__gmsl_tr2)$(if $1,$(if $2,$(false),$(true)),$(if $2,$(true),$(false))) + +# ---------------------------------------------------------------------------- +# Function: nand +# Arguments: Two boolean values +# Returns: Returns value of 'not and' +# ---------------------------------------------------------------------------- +nand = $(__gmsl_tr2)$(if $1,$(if $2,$(false),$(true)),$(true)) + +# ---------------------------------------------------------------------------- +# Function: nor +# Arguments: Two boolean values +# Returns: Returns value of 'not or' +# ---------------------------------------------------------------------------- +nor = $(__gmsl_tr2)$(if $1$2,$(false),$(true)) + +# ---------------------------------------------------------------------------- +# Function: xnor +# Arguments: Two boolean values +# Returns: Returns value of 'not xor' +# ---------------------------------------------------------------------------- +xnor =$(__gmsl_tr2)$(if $1,$(if $2,$(true),$(false)),$(if $2,$(false),$(true))) + +# ########################################################################### +# LIST MANIPULATION FUNCTIONS +# ########################################################################### + +# ---------------------------------------------------------------------------- +# Function: first (same as LISP's car, or head) +# Arguments: 1: A list +# Returns: Returns the first element of a list +# ---------------------------------------------------------------------------- +first = $(__gmsl_tr1)$(firstword $1) + +# ---------------------------------------------------------------------------- +# Function: last +# Arguments: 1: A list +# Returns: Returns the last element of a list +# ---------------------------------------------------------------------------- +ifeq ($(__gmsl_have_lastword),$(true)) +last = $(__gmsl_tr1)$(lastword $1) +else +last = $(__gmsl_tr1)$(if $1,$(word $(words $1),$1)) +endif + +# ---------------------------------------------------------------------------- +# Function: rest (same as LISP's cdr, or tail) +# Arguments: 1: A list +# Returns: Returns the list with the first element removed +# ---------------------------------------------------------------------------- +rest = $(__gmsl_tr1)$(wordlist 2,$(words $1),$1) + +# ---------------------------------------------------------------------------- +# Function: chop +# Arguments: 1: A list +# Returns: Returns the list with the last element removed +# ---------------------------------------------------------------------------- +chop = $(__gmsl_tr1)$(wordlist 2,$(words $1),x $1) + +# ---------------------------------------------------------------------------- +# Function: map +# Arguments: 1: Name of function to $(call) for each element of list +# 2: List to iterate over calling the function in 1 +# Returns: The list after calling the function on each element +# ---------------------------------------------------------------------------- +map = $(__gmsl_tr2)$(strip $(foreach a,$2,$(call $1,$a))) + +# ---------------------------------------------------------------------------- +# Function: pairmap +# Arguments: 1: Name of function to $(call) for each pair of elements +# 2: List to iterate over calling the function in 1 +# 3: Second list to iterate over calling the function in 1 +# Returns: The list after calling the function on each pair of elements +# ---------------------------------------------------------------------------- +pairmap = $(strip $(__gmsl_tr3)\ + $(if $2$3,$(call $1,$(call first,$2),$(call first,$3)) \ + $(call pairmap,$1,$(call rest,$2),$(call rest,$3)))) + +# ---------------------------------------------------------------------------- +# Function: leq +# Arguments: 1: A list to compare against... +# 2: ...this list +# Returns: Returns $(true) if the two lists are identical +# ---------------------------------------------------------------------------- +leq = $(__gmsl_tr2)$(strip $(if $(call seq,$(words $1),$(words $2)), \ + $(call __gmsl_list_equal,$1,$2),$(false))) + +__gmsl_list_equal = $(if $(strip $1), \ + $(if $(call seq,$(call first,$1),$(call first,$2)), \ + $(call __gmsl_list_equal, \ + $(call rest,$1), \ + $(call rest,$2)), \ + $(false)), \ + $(true)) + +# ---------------------------------------------------------------------------- +# Function: lne +# Arguments: 1: A list to compare against... +# 2: ...this list +# Returns: Returns $(true) if the two lists are different +# ---------------------------------------------------------------------------- +lne = $(__gmsl_tr2)$(call not,$(call leq,$1,$2)) + +# ---------------------------------------------------------------------------- +# Function: reverse +# Arguments: 1: A list to reverse +# Returns: The list with its elements in reverse order +# ---------------------------------------------------------------------------- +reverse =$(__gmsl_tr1)$(strip $(if $1,$(call reverse,$(call rest,$1)) \ + $(call first,$1))) + +# ---------------------------------------------------------------------------- +# Function: uniq +# Arguments: 1: A list from which to remove repeated elements +# Returns: The list with duplicate elements removed without reordering +# ---------------------------------------------------------------------------- +uniq = $(strip $(__gmsl_tr1) $(if $1,$(firstword $1) \ + $(call uniq,$(filter-out $(firstword $1),$1)))) + +# ---------------------------------------------------------------------------- +# Function: length +# Arguments: 1: A list +# Returns: The number of elements in the list +# ---------------------------------------------------------------------------- +length = $(__gmsl_tr1)$(words $1) + +# ########################################################################### +# STRING MANIPULATION FUNCTIONS +# ########################################################################### + +# Helper function that translates any GNU Make 'true' value (i.e. a +# non-empty string) to our $(true) + +__gmsl_make_bool = $(if $(strip $1),$(true),$(false)) + +# ---------------------------------------------------------------------------- +# Function: seq +# Arguments: 1: A string to compare against... +# 2: ...this string +# Returns: Returns $(true) if the two strings are identical +# ---------------------------------------------------------------------------- +seq = $(__gmsl_tr2)$(if $(subst x$1,,x$2)$(subst x$2,,x$1),$(false),$(true)) + +# ---------------------------------------------------------------------------- +# Function: sne +# Arguments: 1: A string to compare against... +# 2: ...this string +# Returns: Returns $(true) if the two strings are not the same +# ---------------------------------------------------------------------------- +sne = $(__gmsl_tr2)$(call not,$(call seq,$1,$2)) + +# ---------------------------------------------------------------------------- +# Function: split +# Arguments: 1: The character to split on +# 2: A string to split +# Returns: Splits a string into a list separated by spaces at the split +# character in the first argument +# ---------------------------------------------------------------------------- +split = $(__gmsl_tr2)$(strip $(subst $1, ,$2)) + +# ---------------------------------------------------------------------------- +# Function: merge +# Arguments: 1: The character to put between fields +# 2: A list to merge into a string +# Returns: Merges a list into a single string, list elements are separated +# by the character in the first argument +# ---------------------------------------------------------------------------- +merge = $(__gmsl_tr2)$(strip $(if $2, \ + $(if $(call seq,1,$(words $2)), \ + $2,$(call first,$2)$1$(call merge,$1,$(call rest,$2))))) + +ifdef __gmsl_have_eval +# ---------------------------------------------------------------------------- +# Function: tr +# Arguments: 1: The list of characters to translate from +# 2: The list of characters to translate to +# 3: The text to translate +# Returns: Returns the text after translating characters +# ---------------------------------------------------------------------------- +tr = $(strip $(__gmsl_tr3)$(call assert_no_dollar,$0,$1$2$3) \ + $(eval __gmsl_t := $3) \ + $(foreach c, \ + $(join $(addsuffix :,$1),$2), \ + $(eval __gmsl_t := \ + $(subst $(word 1,$(subst :, ,$c)),$(word 2,$(subst :, ,$c)), \ + $(__gmsl_t))))$(__gmsl_t)) + +# Common character classes for use with the tr function. Each of +# these is actually a variable declaration and must be wrapped with +# $() or ${} to be used. + +[A-Z] := A B C D E F G H I J K L M N O P Q R S T U V W X Y Z # +[a-z] := a b c d e f g h i j k l m n o p q r s t u v w x y z # +[0-9] := 0 1 2 3 4 5 6 7 8 9 # +[A-F] := A B C D E F # + +# ---------------------------------------------------------------------------- +# Function: uc +# Arguments: 1: Text to upper case +# Returns: Returns the text in upper case +# ---------------------------------------------------------------------------- +uc = $(__gmsl_tr1)$(call assert_no_dollar,$0,$1)$(call tr,$([a-z]),$([A-Z]),$1) + +# ---------------------------------------------------------------------------- +# Function: lc +# Arguments: 1: Text to lower case +# Returns: Returns the text in lower case +# ---------------------------------------------------------------------------- +lc = $(__gmsl_tr1)$(call assert_no_dollar,$0,$1)$(call tr,$([A-Z]),$([a-z]),$1) + +# ---------------------------------------------------------------------------- +# Function: strlen +# Arguments: 1: A string +# Returns: Returns the length of the string +# ---------------------------------------------------------------------------- + +# This results in __gmsl_tab containing a tab + +__gmsl_tab := # + +__gmsl_characters := A B C D E F G H I J K L M N O P Q R S T U V W X Y Z +__gmsl_characters += a b c d e f g h i j k l m n o p q r s t u v w x y z +__gmsl_characters += 0 1 2 3 4 5 6 7 8 9 +__gmsl_characters += ` ~ ! @ \# $$ % ^ & * ( ) - _ = + +__gmsl_characters += { } [ ] \ : ; ' " < > , . / ? | + +# This results in __gmsl_space containing just a space + +__gmsl_space := +__gmsl_space += + +strlen = $(__gmsl_tr1)$(call assert_no_dollar,$0,$1)$(strip $(eval __temp := $(subst $(__gmsl_space),x,$1))$(foreach a,$(__gmsl_characters),$(eval __temp := $$(subst $$a,x,$(__temp))))$(eval __temp := $(subst x,x ,$(__temp)))$(words $(__temp))) + +# This results in __gmsl_newline containing just a newline + +define __gmsl_newline + + +endef + +# ---------------------------------------------------------------------------- +# Function: substr +# Arguments: 1: A string +# 2: Start position (first character is 1) +# 3: End position (inclusive) +# Returns: A substring. +# Note: The string in $1 must not contain a § +# ---------------------------------------------------------------------------- + +substr = $(if $2,$(__gmsl_tr3)$(call assert_no_dollar,$0,$1$2$3)$(strip $(eval __temp := $$(subst $$(__gmsl_space),§ ,$$1))$(foreach a,$(__gmsl_characters),$(eval __temp := $$(subst $$a,$$a$$(__gmsl_space),$(__temp))))$(eval __temp := $(wordlist $2,$3,$(__temp))))$(subst §,$(__gmsl_space),$(subst $(__gmsl_space),,$(__temp)))) + +endif # __gmsl_have_eval + +# ########################################################################### +# SET MANIPULATION FUNCTIONS +# ########################################################################### + +# Sets are represented by sorted, deduplicated lists. To create a set +# from a list use set_create, or start with the empty_set and +# set_insert individual elements + +# This is the empty set +empty_set := + +# ---------------------------------------------------------------------------- +# Function: set_create +# Arguments: 1: A list of set elements +# Returns: Returns the newly created set +# ---------------------------------------------------------------------------- +set_create = $(__gmsl_tr1)$(sort $1) + +# ---------------------------------------------------------------------------- +# Function: set_insert +# Arguments: 1: A single element to add to a set +# 2: A set +# Returns: Returns the set with the element added +# ---------------------------------------------------------------------------- +set_insert = $(__gmsl_tr2)$(sort $1 $2) + +# ---------------------------------------------------------------------------- +# Function: set_remove +# Arguments: 1: A single element to remove from a set +# 2: A set +# Returns: Returns the set with the element removed +# ---------------------------------------------------------------------------- +set_remove = $(__gmsl_tr2)$(filter-out $1,$2) + +# ---------------------------------------------------------------------------- +# Function: set_is_member, set_is_not_member +# Arguments: 1: A single element +# 2: A set +# Returns: (set_is_member) Returns $(true) if the element is in the set +# (set_is_not_member) Returns $(false) if the element is in the set +# ---------------------------------------------------------------------------- +set_is_member = $(__gmsl_tr2)$(if $(filter $1,$2),$(true),$(false)) +set_is_not_member = $(__gmsl_tr2)$(if $(filter $1,$2),$(false),$(true)) + +# ---------------------------------------------------------------------------- +# Function: set_union +# Arguments: 1: A set +# 2: Another set +# Returns: Returns the union of the two sets +# ---------------------------------------------------------------------------- +set_union = $(__gmsl_tr2)$(sort $1 $2) + +# ---------------------------------------------------------------------------- +# Function: set_intersection +# Arguments: 1: A set +# 2: Another set +# Returns: Returns the intersection of the two sets +# ---------------------------------------------------------------------------- +set_intersection = $(__gmsl_tr2)$(filter $1,$2) + +# ---------------------------------------------------------------------------- +# Function: set_is_subset +# Arguments: 1: A set +# 2: Another set +# Returns: Returns $(true) if the first set is a subset of the second +# ---------------------------------------------------------------------------- +set_is_subset = $(__gmsl_tr2)$(call set_equal,$(call set_intersection,$1,$2),$1) + +# ---------------------------------------------------------------------------- +# Function: set_equal +# Arguments: 1: A set +# 2: Another set +# Returns: Returns $(true) if the two sets are identical +# ---------------------------------------------------------------------------- +set_equal = $(__gmsl_tr2)$(call seq,$1,$2) + +# ########################################################################### +# ARITHMETIC LIBRARY +# ########################################################################### + +# Integers a represented by lists with the equivalent number of x's. +# For example the number 4 is x x x x. + +# ---------------------------------------------------------------------------- +# Function: int_decode +# Arguments: 1: A number of x's representation +# Returns: Returns the integer for human consumption that is represented +# by the string of x's +# ---------------------------------------------------------------------------- +int_decode = $(__gmsl_tr1)$(words $1) + +# ---------------------------------------------------------------------------- +# Function: int_encode +# Arguments: 1: A number in human-readable integer form +# Returns: Returns the integer encoded as a string of x's +# ---------------------------------------------------------------------------- +__int_encode = $(if $1,$(if $(call seq,$(words $(wordlist 1,$1,$2)),$1),$(wordlist 1,$1,$2),$(call __int_encode,$1,$(if $2,$2 $2,x)))) +__strip_leading_zero = $(if $1,$(if $(call seq,$(patsubst 0%,%,$1),$1),$1,$(call __strip_leading_zero,$(patsubst 0%,%,$1))),0) +int_encode = $(__gmsl_tr1)$(call __int_encode,$(call __strip_leading_zero,$1)) + +# The arithmetic library functions come in two forms: one form of each +# function takes integers as arguments and the other form takes the +# encoded form (x's created by a call to int_encode). For example, +# there are two plus functions: +# +# plus Called with integer arguments and returns an integer +# int_plus Called with encoded arguments and returns an encoded result +# +# plus will be slower than int_plus because its arguments and result +# have to be translated between the x's format and integers. If doing +# a complex calculation use the int_* forms with a single encoding of +# inputs and single decoding of the output. For simple calculations +# the direct forms can be used. + +# Helper function used to wrap an int_* function into a function that +# takes a pair of integers, perhaps a function and returns an integer +# result +__gmsl_int_wrap = $(call int_decode,$(call $1,$(call int_encode,$2),$(call int_encode,$3))) +__gmsl_int_wrap1 = $(call int_decode,$(call $1,$(call int_encode,$2))) +__gmsl_int_wrap2 = $(call $1,$(call int_encode,$2),$(call int_encode,$3)) + +# ---------------------------------------------------------------------------- +# Function: int_plus +# Arguments: 1: A number in x's representation +# 2: Another number in x's represntation +# Returns: Returns the sum of the two numbers in x's representation +# ---------------------------------------------------------------------------- +int_plus = $(strip $(__gmsl_tr2)$1 $2) + +# ---------------------------------------------------------------------------- +# Function: plus (wrapped version of int_plus) +# Arguments: 1: An integer +# 2: Another integer +# Returns: Returns the sum of the two integers +# ---------------------------------------------------------------------------- +plus = $(__gmsl_tr2)$(call __gmsl_int_wrap,int_plus,$1,$2) + +# ---------------------------------------------------------------------------- +# Function: int_subtract +# Arguments: 1: A number in x's representation +# 2: Another number in x's represntation +# Returns: Returns the difference of the two numbers in x's representation, +# or outputs an error on a numeric underflow +# ---------------------------------------------------------------------------- +int_subtract = $(strip $(__gmsl_tr2)$(if $(call int_gte,$1,$2), \ + $(filter-out xx,$(join $1,$2)), \ + $(call __gmsl_warning,Subtraction underflow))) + +# ---------------------------------------------------------------------------- +# Function: subtract (wrapped version of int_subtract) +# Arguments: 1: An integer +# 2: Another integer +# Returns: Returns the difference of the two integers, +# or outputs an error on a numeric underflow +# ---------------------------------------------------------------------------- +subtract = $(__gmsl_tr2)$(call __gmsl_int_wrap,int_subtract,$1,$2) + +# ---------------------------------------------------------------------------- +# Function: int_multiply +# Arguments: 1: A number in x's representation +# 2: Another number in x's represntation +# Returns: Returns the product of the two numbers in x's representation +# ---------------------------------------------------------------------------- +int_multiply = $(strip $(__gmsl_tr2)$(foreach a,$1,$2)) + +# ---------------------------------------------------------------------------- +# Function: multiply (wrapped version of int_multiply) +# Arguments: 1: An integer +# 2: Another integer +# Returns: Returns the product of the two integers +# ---------------------------------------------------------------------------- +multiply = $(__gmsl_tr2)$(call __gmsl_int_wrap,int_multiply,$1,$2) + +# ---------------------------------------------------------------------------- +# Function: int_divide +# Arguments: 1: A number in x's representation +# 2: Another number in x's represntation +# Returns: Returns the result of integer division of argument 1 divided +# by argument 2 in x's representation +# ---------------------------------------------------------------------------- +int_divide = $(__gmsl_tr2)$(strip $(if $1,$(if $2, \ + $(if $(call int_gte,$1,$2), \ + x $(call int_divide,$(call int_subtract,$1,$2),$2),), \ + $(call __gmsl_error,Division by zero)))) + +# ---------------------------------------------------------------------------- +# Function: divide (wrapped version of int_divide) +# Arguments: 1: An integer +# 2: Another integer +# Returns: Returns the integer division of the first argument by the second +# ---------------------------------------------------------------------------- +divide = $(__gmsl_tr2)$(call __gmsl_int_wrap,int_divide,$1,$2) + +# ---------------------------------------------------------------------------- +# Function: int_max, int_min +# Arguments: 1: A number in x's representation +# 2: Another number in x's represntation +# Returns: Returns the maximum or minimum of its arguments in x's +# representation +# ---------------------------------------------------------------------------- +int_max = $(__gmsl_tr2)$(subst xx,x,$(join $1,$2)) +int_min = $(__gmsl_tr2)$(subst xx,x,$(filter xx,$(join $1,$2))) + +# ---------------------------------------------------------------------------- +# Function: max, min +# Arguments: 1: An integer +# 2: Another integer +# Returns: Returns the maximum or minimum of its integer arguments +# ---------------------------------------------------------------------------- +max = $(__gmsl_tr2)$(call __gmsl_int_wrap,int_max,$1,$2) +min = $(__gmsl_tr2)$(call __gmsl_int_wrap,int_min,$1,$2) + +# ---------------------------------------------------------------------------- +# Function: int_gt, int_gte, int_lt, int_lte, int_eq, int_ne +# Arguments: Two x's representation numbers to be compared +# Returns: $(true) or $(false) +# +# int_gt First argument greater than second argument +# int_gte First argument greater than or equal to second argument +# int_lt First argument less than second argument +# int_lte First argument less than or equal to second argument +# int_eq First argument is numerically equal to the second argument +# int_ne First argument is not numerically equal to the second argument +# ---------------------------------------------------------------------------- +int_gt = $(__gmsl_tr2)$(call __gmsl_make_bool, \ + $(filter-out $(words $2), \ + $(words $(call int_max,$1,$2)))) +int_gte = $(__gmsl_tr2)$(call __gmsl_make_bool, \ + $(call int_gt,$1,$2)$(call int_eq,$1,$2)) +int_lt = $(__gmsl_tr2)$(call __gmsl_make_bool, \ + $(filter-out $(words $1), \ + $(words $(call int_max,$1,$2)))) +int_lte = $(__gmsl_tr2)$(call __gmsl_make_bool, \ + $(call int_lt,$1,$2)$(call int_eq,$1,$2)) +int_eq = $(__gmsl_tr2)$(call __gmsl_make_bool, \ + $(filter $(words $1),$(words $2))) +int_ne = $(__gmsl_tr2)$(call __gmsl_make_bool, \ + $(filter-out $(words $1),$(words $2))) + +# ---------------------------------------------------------------------------- +# Function: gt, gte, lt, lte, eq, ne +# Arguments: Two integers to be compared +# Returns: $(true) or $(false) +# +# gt First argument greater than second argument +# gte First argument greater than or equal to second argument +# lt First argument less than second argument +# lte First argument less than or equal to second argument +# eq First argument is numerically equal to the second argument +# ne First argument is not numerically equal to the second argument +# ---------------------------------------------------------------------------- +gt = $(__gmsl_tr2)$(call __gmsl_int_wrap2,int_gt,$1,$2) +gte = $(__gmsl_tr2)$(call __gmsl_int_wrap2,int_gte,$1,$2) +lt = $(__gmsl_tr2)$(call __gmsl_int_wrap2,int_lt,$1,$2) +lte = $(__gmsl_tr2)$(call __gmsl_int_wrap2,int_lte,$1,$2) +eq = $(__gmsl_tr2)$(call __gmsl_int_wrap2,int_eq,$1,$2) +ne = $(__gmsl_tr2)$(call __gmsl_int_wrap2,int_ne,$1,$2) + +# increment adds 1 to its argument, decrement subtracts 1. Note that +# decrement does not range check and hence will not underflow, but +# will incorrectly say that 0 - 1 = 0 + +# ---------------------------------------------------------------------------- +# Function: int_inc +# Arguments: 1: A number in x's representation +# Returns: The number incremented by 1 in x's representation +# ---------------------------------------------------------------------------- +int_inc = $(strip $(__gmsl_tr1)$1 x) + +# ---------------------------------------------------------------------------- +# Function: inc +# Arguments: 1: An integer +# Returns: The argument incremented by 1 +# ---------------------------------------------------------------------------- +inc = $(__gmsl_tr1)$(call __gmsl_int_wrap1,int_inc,$1) + +# ---------------------------------------------------------------------------- +# Function: int_dec +# Arguments: 1: A number in x's representation +# Returns: The number decremented by 1 in x's representation +# ---------------------------------------------------------------------------- +int_dec = $(__gmsl_tr1)$(strip \ + $(if $(call sne,0,$(words $1)), \ + $(wordlist 2,$(words $1),$1))) + +# ---------------------------------------------------------------------------- +# Function: dec +# Arguments: 1: An integer +# Returns: The argument decremented by 1 +# ---------------------------------------------------------------------------- +dec = $(__gmsl_tr1)$(call __gmsl_int_wrap1,int_dec,$1) + +# double doubles its argument, and halve halves it + +# ---------------------------------------------------------------------------- +# Function: int_double +# Arguments: 1: A number in x's representation +# Returns: The number doubled (i.e. * 2) and returned in x's representation +# ---------------------------------------------------------------------------- +int_double = $(strip $(__gmsl_tr1)$1 $1) + +# ---------------------------------------------------------------------------- +# Function: double +# Arguments: 1: An integer +# Returns: The integer times 2 +# ---------------------------------------------------------------------------- +double = $(__gmsl_tr1)$(call __gmsl_int_wrap1,int_double,$1) + +# ---------------------------------------------------------------------------- +# Function: int_halve +# Arguments: 1: A number in x's representation +# Returns: The number halved (i.e. / 2) and returned in x's representation +# ---------------------------------------------------------------------------- +int_halve = $(__gmsl_tr1)$(strip $(subst xx,x,$(filter-out xy x y, \ + $(join $1,$(foreach a,$1,y x))))) + +# ---------------------------------------------------------------------------- +# Function: halve +# Arguments: 1: An integer +# Returns: The integer divided by 2 +# ---------------------------------------------------------------------------- +halve = $(__gmsl_tr1)$(call __gmsl_int_wrap1,int_halve,$1) + +# ---------------------------------------------------------------------------- +# Function: sequence +# Arguments: 1: An integer +# 2: An integer +# Returns: The sequence [arg1, arg2] of integers if arg1 < arg2 or +# [arg2, arg1] if arg2 > arg1. If arg1 == arg1 return [arg1] +# ---------------------------------------------------------------------------- +sequence = $(__gmsl_tr2)$(strip $(if $(call lte,$1,$2), \ + $(call __gmsl_sequence_up,$1,$2), \ + $(call __gmsl_sequence_dn,$2,$1))) + +__gmsl_sequence_up = $(if $(call seq,$1,$2),$1,$1 $(call __gmsl_sequence_up,$(call inc,$1),$2)) +__gmsl_sequence_dn = $(if $(call seq,$1,$2),$1,$2 $(call __gmsl_sequence_dn,$1,$(call dec,$2))) + +# ---------------------------------------------------------------------------- +# Function: dec2hex, dec2bin, dec2oct +# Arguments: 1: An integer +# Returns: The decimal argument converted to hexadecimal, binary or +# octal +# ---------------------------------------------------------------------------- + +__gmsl_digit = $(subst 15,f,$(subst 14,e,$(subst 13,d,$(subst 12,c,$(subst 11,b,$(subst 10,a,$1)))))) + +dec2hex = $(call __gmsl_dec2base,$(call int_encode,$1),$(call int_encode,16)) +dec2bin = $(call __gmsl_dec2base,$(call int_encode,$1),$(call int_encode,2)) +dec2oct = $(call __gmsl_dec2base,$(call int_encode,$1),$(call int_encode,8)) + +__gmsl_base_divide = $(subst $2,X ,$1) +__gmsl_q = $(strip $(filter X,$1)) +__gmsl_r = $(words $(filter x,$1)) + +__gmsl_dec2base = $(eval __gmsl_temp := $(call __gmsl_base_divide,$1,$2))$(call __gmsl_dec2base_,$(call __gmsl_q,$(__gmsl_temp)),$(call __gmsl_r,$(__gmsl_temp)),$2) +__gmsl_dec2base_ = $(if $1,$(call __gmsl_dec2base,$(subst X,x,$1),$3))$(call __gmsl_digit,$2) + +ifdef __gmsl_have_eval +# ########################################################################### +# ASSOCIATIVE ARRAYS +# ########################################################################### + +# Magic string that is very unlikely to appear in a key or value + +__gmsl_aa_magic := faf192c8efbc25c27992c5bc5add390393d583c6 + +# ---------------------------------------------------------------------------- +# Function: set +# Arguments: 1: Name of associative array +# 2: The key value to associate +# 3: The value associated with the key +# Returns: Nothing +# ---------------------------------------------------------------------------- +set = $(__gmsl_tr3)$(call assert_no_space,$0,$1$2)$(call assert_no_dollar,$0,$1$2$3)$(eval __gmsl_aa_$1_$(__gmsl_aa_magic)_$2_gmsl_aa_$1 := $3) + +# Only used internally by memoize function + +__gmsl_set = $(call set,$1,$2,$3)$3 + +# ---------------------------------------------------------------------------- +# Function: get +# Arguments: 1: Name of associative array +# 2: The key to retrieve +# Returns: The value stored in the array for that key +# ---------------------------------------------------------------------------- +get = $(strip $(__gmsl_tr2)$(call assert_no_space,$0,$1$2)$(call assert_no_dollar,$0,$1$2)$(__gmsl_aa_$1_$(__gmsl_aa_magic)_$2_gmsl_aa_$1)) + +# ---------------------------------------------------------------------------- +# Function: keys +# Arguments: 1: Name of associative array +# Returns: Returns a list of all defined keys in the array +# ---------------------------------------------------------------------------- +keys = $(__gmsl_tr1)$(call assert_no_space,$0,$1)$(call assert_no_dollar,$0,$1)$(sort $(patsubst __gmsl_aa_$1_$(__gmsl_aa_magic)_%_gmsl_aa_$1,%, \ + $(filter __gmsl_aa_$1_$(__gmsl_aa_magic)_%_gmsl_aa_$1,$(.VARIABLES)))) + +# ---------------------------------------------------------------------------- +# Function: defined +# Arguments: 1: Name of associative array +# 2: The key to test +# Returns: Returns true if the key is defined (i.e. not empty) +# ---------------------------------------------------------------------------- +defined = $(__gmsl_tr2)$(call assert_no_space,$0,$1$2)$(call assert_no_dollar,$0,$1$2)$(call sne,$(call get,$1,$2),) + +endif # __gmsl_have_eval + +ifdef __gmsl_have_eval +# ########################################################################### +# NAMED STACKS +# ########################################################################### + +# ---------------------------------------------------------------------------- +# Function: push +# Arguments: 1: Name of stack +# 2: Value to push onto the top of the stack (must not contain +# a space) +# Returns: None +# ---------------------------------------------------------------------------- +push = $(__gmsl_tr2)$(call assert_no_space,$0,$1$2)$(call assert_no_dollar,$0,$1$2)$(eval __gmsl_stack_$1 := $2 $(if $(filter-out undefined,\ + $(origin __gmsl_stack_$1)),$(__gmsl_stack_$1))) + +# ---------------------------------------------------------------------------- +# Function: pop +# Arguments: 1: Name of stack +# Returns: Top element from the stack after removing it +# ---------------------------------------------------------------------------- +pop = $(__gmsl_tr1)$(call assert_no_space,$0,$1)$(call assert_no_dollar,$0,$1)$(strip $(if $(filter-out undefined,$(origin __gmsl_stack_$1)), \ + $(call first,$(__gmsl_stack_$1)) \ + $(eval __gmsl_stack_$1 := $(call rest,$(__gmsl_stack_$1))))) + +# ---------------------------------------------------------------------------- +# Function: peek +# Arguments: 1: Name of stack +# Returns: Top element from the stack without removing it +# ---------------------------------------------------------------------------- +peek = $(__gmsl_tr1)$(call assert_no_space,$0,$1)$(call assert_no_dollar,$0,$1)$(call first,$(__gmsl_stack_$1)) + +# ---------------------------------------------------------------------------- +# Function: depth +# Arguments: 1: Name of stack +# Returns: Number of items on the stack +# ---------------------------------------------------------------------------- +depth = $(__gmsl_tr1)$(call assert_no_space,$0,$1)$(call assert_no_dollar,$0,$1)$(words $(__gmsl_stack_$1)) + +endif # __gmsl_have_eval + +ifdef __gmsl_have_eval +# ########################################################################### +# STRING CACHE +# ########################################################################### + +# ---------------------------------------------------------------------------- +# Function: memoize +# Arguments: 1. Name of the function to be called if the string +# has not been previously seen +# 2. A string +# Returns: Returns the result of a memo function (which the user must +# define) on the passed in string and remembers the result. +# +# Example: Set memo = $(shell echo "$1" | md5sum) to make a cache +# of MD5 hashes of strings. $(call memoize,memo,foo bar baz) +# ---------------------------------------------------------------------------- +__gmsl_memoize = $(subst $(__gmsl_space),§,$1)cc2af1bb7c4482f2ba75e338b963d3e7$(subst $(__gmsl_space),§,$2) +memoize = $(__gmsl_tr2)$(strip $(if $(call defined,__gmsl_m,$(__gmsl_memoize)),\ + $(call get,__gmsl_m,$(__gmsl_memoize)), \ + $(call __gmsl_set,__gmsl_m,$(__gmsl_memoize),$(call $1,$2)))) + +endif # __gmsl_have_eval + +# ########################################################################### +# DEBUGGING FACILITIES +# ########################################################################### + +# ---------------------------------------------------------------------------- +# Target: gmsl-print-% +# Arguments: The % should be replaced by the name of a variable that you +# wish to print out. +# Action: Echos the name of the variable that matches the % and its value. +# For example, 'make gmsl-print-SHELL' will output the value of +# the SHELL variable +# ---------------------------------------------------------------------------- +gmsl-print-%: ; @echo $* = $($*) + +# ---------------------------------------------------------------------------- +# Function: assert +# Arguments: 1: A boolean that must be true or the assertion will fail +# 2: The message to print with the assertion +# Returns: None +# ---------------------------------------------------------------------------- +assert = $(if $2,$(if $1,,$(call __gmsl_error,Assertion failure: $2))) + +# ---------------------------------------------------------------------------- +# Function: assert_exists +# Arguments: 1: Name of file that must exist, if it is missing an assertion +# will be generated +# Returns: None +# ---------------------------------------------------------------------------- +assert_exists = $(if $0,$(call assert,$(wildcard $1),file '$1' missing)) + +# ---------------------------------------------------------------------------- +# Function: assert_no_dollar +# Arguments: 1: Name of a function being executd +# 2: Arguments to check +# Returns: None +# ---------------------------------------------------------------------------- +assert_no_dollar = $(call __gmsl_tr2)$(call assert,$(call not,$(findstring $(__gmsl_dollar),$2)),$1 called with a dollar sign in argument) + +# ---------------------------------------------------------------------------- +# Function: assert_no_space +# Arguments: 1: Name of a function being executd +# 2: Arguments to check +# Returns: None +# ---------------------------------------------------------------------------- +ifeq ($(__gmsl_spaced_vars),$(false)) +assert_no_space = $(call assert,$(call not,$(findstring $(__gmsl_aa_magic),$(subst $(__gmsl_space),$(__gmsl_aa_magic),$2))),$1 called with a space in argument) +else +assert_no_space = +endif diff --git a/make/gmsl/gmsl b/make/gmsl/gmsl new file mode 100644 index 0000000..17891f1 --- /dev/null +++ b/make/gmsl/gmsl @@ -0,0 +1,85 @@ +# ---------------------------------------------------------------------------- +# +# GNU Make Standard Library (GMSL) +# +# A library of functions to be used with GNU Make's $(call) that +# provides functionality not available in standard GNU Make. +# +# Copyright (c) 2005-2014 John Graham-Cumming +# +# This file is part of GMSL +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# Neither the name of the John Graham-Cumming nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# ---------------------------------------------------------------------------- + +# Determine if the library has already been included and if so don't +# bother including it again + +ifndef __gmsl_included + +# Standard definitions for true and false. true is any non-empty +# string, false is an empty string. These are intended for use with +# $(if). + +true := T +false := + +# ---------------------------------------------------------------------------- +# Function: not +# Arguments: 1: A boolean value +# Returns: Returns the opposite of the arg. (true -> false, false -> true) +# ---------------------------------------------------------------------------- +not = $(if $1,$(false),$(true)) + +# Prevent reinclusion of the library + +__gmsl_included := $(true) + +# Try to determine where this file is located. If the caller did +# include /foo/gmsl then extract the /foo/ so that __gmsl gets +# included transparently + +__gmsl_root := + +ifneq ($(MAKEFILE_LIST),) +__gmsl_root := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)) + +# If there are any spaces in the path in __gmsl_root then give up + +ifeq (1,$(words $(__gmsl_root))) +__gmsl_root := $(patsubst %gmsl,%,$(__gmsl_root)) +endif + +endif + +include $(__gmsl_root)__gmsl + +endif # __gmsl_included + diff --git a/src/startup/sdktag.s b/src/startup/sdktag.s new file mode 100644 index 0000000..f409ec1 --- /dev/null +++ b/src/startup/sdktag.s @@ -0,0 +1,19 @@ +@ This file is part of eVic SDK. +@ +@ eVic SDK is free software: you can redistribute it and/or modify +@ it under the terms of the GNU General Public License as published by +@ the Free Software Foundation, either version 3 of the License, or +@ (at your option) any later version. +@ +@ eVic SDK is distributed in the hope that it will be useful, +@ but WITHOUT ANY WARRANTY; without even the implied warranty of +@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +@ GNU General Public License for more details. +@ +@ You should have received a copy of the GNU General Public License +@ along with eVic SDK. If not, see . +@ +@ Copyright (C) 2016 ReservedField + +.section .evicsdk_tag +.asciz EVICSDK_SDKTAG