################################################################################ # Desc: Mihrtec Standard Transpilation Makefile # Author: Mihrtec LLC # Date: 2016 ################################################################################ # This makefile manages a build environment targeting native platforms with gcc # and web platforms with either emscripten or binaryen (js or wasm). ################################################################################ # Each .c file is automatically compiled into an environment-dependent object # file of compiler-specific format (.o for gcc, .bc for llvm/emcc/em++). ################################################################################ default: all # Source languages handled by this build system LANGS := c cpp go # Language-specific compilers and flags passed in from environment c_C := $(strip $(notdir $(CC))) c_FLAGS := $(strip $(CFLAGS)) -I. c_LIBS := SDL2 wolfssl c_SRCL := y cpp_C := $(strip $(notdir $(CXX))) cpp_FLAGS := $(strip $(CXXFLAGS)) $(c_FLAGS) cpp_LIBS := $(c_LIBS) go_C := gccgo go_FLAGS := $(c_FLAGS) # Source to source languages y_C := bison y_FLAGS := -d y_STEM := tab y_DUP := h y_CHDIR := t # Compiler-specific associations. Each compiler has a binary object suffix # (OBJ), an archiver (AR), and an archiver object suffix (AROBJ). Each compiler # may optionally have defined a linker (LD), and a binary output suffix (OUT). cc_OBJ := o cc_LD := $(LD) cc_AR := $(AR) cc_AROBJ := a $(cpp_C)_LD := $(cc_LD) gcc_OBJ := $(cc_OBJ) gcc_LD := $(cc_LD) gcc_AR := $(cc_AR) gcc_AROBJ := $(cc_AROBJ) emcc_OBJ := bc emcc_AR := emar emcc_AROBJ := $(emcc_OBJ) #emar is just a python script that reparses shit for emcc emcc_OUT := .js g++_OBJ := $(cc_OBJ) em++_OBJ := $(emcc_OBJ) em++_AR := $(emcc_AR) em++_AROBJ := $(emcc_AROBJ) em++_OUT := $(emcc_OUT) gccgo_OBJ := o gccgo_LD := $(cc_LD) gccgo_AR := $(cc_AR) gccgo_AROBJ := $(cc_AROBJ) # Shell functions to determine what libraries can be linked by the compiler cc_LDLIBS := $(shell ls /usr/lib | grep ".o" | sed -e 's@^.*lib\([_\+a-zA-Z0-9\-]*\)\..*@\1@g') gcc_LDLIBS := $(cc_LDLIBS) emcc_LDLIBS := g++_LDLIBS := $(cc_LDLIBS) em++_LDLIBS := go_LDLIBS := $(cc_LDLIBS) # Directories # The directory in './' containing app drivers and compilation info DRIVER_DIR := bin ROOT_DIR := .. LIB_DIR := $(ROOT_DIR)/lib$(shell uname -m)/$($(c_C)_OBJ) LIBDL_DIR := $(LIB_DIR)/.cache LIBINC_DIR := $(ROOT_DIR)/include # The makefile MUST be capable of generating these directories and all of their # contents. These directories are removed during the 'scrub' rule MAKE_DIRS := $(LIB_DIR) $(LIBDL_DIR) $(LCLLIB_DIR) # Set up lib inclusions, and scan for built libs c_FLAGS += -I$(LIBINC_DIR) c_OBJ := $($(c_C)_OBJ) # Modules are any directories other than 'DRIVER_MODULE' in './' and produce a # single object file in './' containing all of the module's symbols and # binaries. MODULES := $(filter-out $(DRIVER_DIR),$(subst /,,$(shell ls -d */))) # Backup the CVS password and CVSROOT environment vars in case we change them $(if $(wildcard ~/.cvspass),\ $(eval CVSPASS_BAK := $(file <~/.cvspass))) $(if $(CVSROOT),\ $(eval CVSROOT_BAK := $(CVSROOT))) # The following awk program reverses the order of a list while also removing # duplicate entries. The effect of this when run on the dependency tree is that # it will remove duplicates occurring in reverse order, allowing the most deeply # nested libraries to be built, and linked, first. define AWK_REVERSE_SQUASH = awk \ ' { for(i=NF;i>0;i--) printf (!a[$$i]++) ? $$i FS : ""; i=split("",a); print "" } ' endef # Given a source and a language, generate a rule to make the object. The second # invocation of gcc per file lists only its local includes, and filters out # anything already caught. Anything remaining should be considered to be # available in the current working directory of the source file(i.e. if you # '#include "something.h"' in 'mymodule/horseradish.c', you would expect to # include 'mymodule/something.h'. this check lets that happen), otherwise it is # some kind of user error define SRC_LANG_RULE = $(eval MOD := $(firstword $(subst /, ,$(dir $1))))\ $(eval DEPS := $(filter $(MODULES:%=%/%),$(shell $($2_C) -I$(MOD) -M -MG $1 2> /dev/null)))\ $(eval LOST := $(filter-out %: $(MODULES:%=%/%),$(shell $($2_C) -I$(MOD) -MM -MG $1 2> /dev/null)))\ $(eval DEPS += $(LOST:%=$(dir $1)%))\ $(if $($1),,$(eval $1 := t)\ $(basename $1).$($2_OBJ): $(DEPS) $($2_C) $$($2_FLAGS) -I$(MOD) -c -o $$@ $1 ) endef # establish a build and link rule given a source driver and language ############ define SRC_LANG_DRVRULE = # turn the non-existent third argument into an identifier for our link data $(eval 3 := $(notdir $(basename $1))) # generate the dependecies list of the driver itself $(eval $3_DRV_OBJ := $(1:%.$2=%.$($2_OBJ))) $(eval $3_DRV_DEP := $(filter-out $3:,$(shell $($2_C) -M -MG $1))) # Implicit and Manual LD Options to allow .ld files to specify manual linking to # one of the built-in modules. Implicit LDOs are ones we can surmise from # include rules generated with 'SRC_LANG_DEPS', but these are only caught if # there was an explicitly included header, or some other file, within the source # file of the driver to one of the internal modules. This practice is a # reliable way to automatically link an internal module, but if desired an # associated '.ld' file for the driver can contain a list of whitespace # separated module names to ensure they link properly $(foreach mfile,$(filter $(MODULES:%=%/%),$($3_DRV_DEP)),\ $(eval $3_MODULES += $(firstword $(subst /, ,$(mfile))))) # Find the .ld files, if present, and include their 'links' to our internal libs $(if $(wildcard $(1:.$2=.ld)),\ $(eval $3_MODULES += $(basename $(strip $(file <$(1:.$2=.ld)))))) # Remove duplicates and sort the module list $(eval $3_MODULES := $(sort $($3_MODULES))) # If the compiler supports linking, distinguish static from dynamic linking $(if $($($2_C)_LD),\ $(eval $3_STLIBS := $(filter-out $($($2_C)_LDLIBS),$($2_LIBS)))\ $(eval $3_DLIBS := $(filter-out $($3_STLIBS),$($2_LIBS))),\ $(eval $3_STLIBS := $($2_LIBS))) # Directory setup $(eval $3_DIR := $(dir $(1:%.$2=$(ROOT_DIR)/%))) $(eval MAKE_DIRS += $($3_DIR)) # Setup the sources for this object $(eval $3_SRC := $($3_DRV_OBJ)) # Find the dependencies $(eval $3_DEP := $(filter-out $1,$($3_DRV_DEPS))) $(eval $3_DEP += $($3_SRC)) # Preserver ordering and build out the linking order (don't use -Wstatic/dynamic) $(foreach lib,$($2_LIBS),\ $(if $(findstring $(lib),$($3_STLIBS)),\ $(eval $3_SRC := $(LIB_DIR)/lib$(lib).$($($2_C)_AROBJ) $($3_SRC)),\ $(eval $3_SRC := -l$(lib) $($3_SRC)))) $(eval $3_SRC += $($3_MODULES:%=%.$($2_AROBJ))) # Output the driver object file rule $(eval $2_TARGETS += $($3_DRV_OBJ)) # Filter the dependencies of the object to only include deps inside our modules $($3_DRV_OBJ): $(filter $(MODULES:%=%/%),$($3_DRV_DEP)) $($2_C) $($2_FLAGS) -c -o $$@ $1 # Output the driver link rule $(eval $2_TARGETS += $($3_DIR)$3$($2_OUT)) $($3_DIR)$3$($2_OUT): $($3_DEP) $($3_SRC) | $($3_DIR) $($2_C) $($2_FLAGS) $($3_SRC) -o $$@ #/SRC_LANG_DRVRULE############################################################## endef # generate rule for turning an entire module's collection of binary objects into # a single locally-linked (no external -L libs) object (for simplified linking # modules as static libs). define MODULE_ARCRULE = $(eval c_TARGETS += $1.$($(c_C)_AROBJ))\ $(eval $1_ARCDEPS := $(filter $1/%,$(foreach lang,$(LANGS),$($(lang)_MOD_TRG))))\ $1.$($(c_C)_AROBJ): $($1_ARCDEPS) | $(LCLLIB_DIR) $($(c_C)_AR) cr $$@ $$^ endef # LANG_LIB_PARENT_BUILDRULE###################################################### # define rules for creating unknown libraries in the local build environment for # either binary or bytecode representation given a library name and, optionally, # a parent lib that included it for error reporting ############################# define LANG_LIB_PARENT_BUILDRULE = # Setup installation rule steps for this lib $(eval BUILDSTEPS := $(if $(AUTOGEN),$(AUTOGEN) && ) BUILDSTEPS += $(if $(CONFIGURE),$(CONFIGURE) && ) $(if $(MKCMD),\ BUILDSTEPS += $(MKCMD), $(error $2.mk requires a valid MKCMD definition)) $(if $(MKINSTALL),\ INSTALLCMD := $(MKINSTALL),\ $(error $2.mk requires a valid MKINSTALL definition)) CLEANCMD := $(MKCLEAN) ) # Construct the download method, or error if there isn't a valid one $(if $(GITADDR),$(eval LIBDL := git clone $(GITADDR) $2),\ $(if $(HGADDR),$(eval LIBDL := hg clone $(HGADDR) $2),\ $(if $(CVSADDR),\ $(eval LIBDL := export CVSROOT=$(CVSADDR))\ $(eval LIBDL += && echo '$(CVSPASS)' > ~/.cvspass)\ $(eval LIBDL += && cvs $($2_CVSGET)) $(eval LIBDL += && $(if $(CVSPASS_BAK),echo $(CVSPASS_BAK) > ~/.cvspass,rm ~/.cvspass)),\ $(if $(WEBADDR),$(eval LIBDL := wget -O $(WEBTARG) $(WEBADDR)),\ $(eval $(error No way to download $2 needed by $3 for $1 language )))))) # '$2' Download Rule $(LIBDL_DIR)/$2: mkdir -p $$@ cd $(LIBDL_DIR) && $(LIBDL) $(if $(WEBINIT),&& $(WEBINIT)) # '$2' Make and install rule, and ensure it isn't marked "clean" $(LIB_DIR)/lib$2.$($1_AROBJ): $(LIBDEPS:%=$(LIB_DIR)/lib%.$($1_AROBJ)) | $(LIBDL_DIR)/$2 @mkdir -p $(LIB_DIR) cd $(LIBDL_DIR)/$2 && export LD_RUN_PATH=$(LIBLDPATH) && $(BUILDSTEPS) cd $(LIBDL_DIR)/$2 && $(INSTALLCMD) @rm -f $(LIBDL_DIR)/$2.clean # '$2' Clean rule - drop a marker in the directory so we don't bother cleaning # things that are clean... by default there is no real way of tracking this in a # makefile clean_$2: $$(if $$(wildcard $(LIBDL_DIR)/$2),$$(if $$(wildcard $(LIBDL_DIR)/$2.clean),,\ cd $(LIBDL_DIR)/$2 && $(CLEANCMD) && touch $(abspath $(LIBDL_DIR))/$2.clean)) #/LANG_LIB_PARENT_BUILDRULE###################################################### endef #LANG_LIB_INIT################################################################### # For every library in this language, we need to determine how, and if, to link # its data. This could potentially be done recursively as libraries have # dependencies upon each other. We call a recursive macro here to expand out # all the libs for this language, and then flatten the tree by removing # duplicates with shell commands. We can't use make's 'sort' function because # we actually care about the ordering so that interdependent libraries can link # back up the tree ############################################################## define LANG_LIB_INIT = # Initialize per-library data, then import data from an accompanying file that # can overwrite these vars $(eval # Implicit defaults for lib.mk files AUTOGEN := ./autogen.sh CONFIGURE := ./configure CONFIGURE += --prefix=$(abspath $(LIB_DIR)/.trash) CONFIGURE += --includedir=$(abspath $(LIBINC_DIR)) CONFIGURE += --libdir=$(abspath $(LIB_DIR)) MKCMD := make -k MKINSTALL := make -k install MKCLEAN := make clean LIBDEPS := LIBLANGS := c cpp go LIBLDPATH := $(if $($($1_C)_LD),$(if $(LD_LIBRARY_PATH),$(LD_LIBRARY_PATH):,/usr/lib:)$(abspath $(LIB_DIR))) # One of these must be defined in the .mk file imported $(foreach var, GITADDR WEBADDR HGADDR CVSADDR,\ $(eval undefine $(var))) # Other, optional, variables to clear $(foreach var, WEBTARG WEBINIT CVSGET CVSPASS, $(eval undefine $(var))) ) # Include and evaluate such that it may be called and evaluated for immediate # evaluation of its contents define EVALINCLUDE = $(eval include $2.mk) endef # If there is a .mk file for this lib, import it. Otherwise, we'll try to link # it later, unless there is no linker for the language's compiler. $(if $(wildcard $2.mk),$(eval $(call EVALINCLUDE)),\ $(if $($($1_C)_LD),\ $(warning No $2.mk for lib$2, linking from the system),\ $(error No $2.mk for lib$2 and '$($1_C)' is not configured for linking)))\ # Add dependencies we found (potentially recursively, ignore repeats until later # so we can preserve ordering information for interdependency build ordering) $(if $(LIBDEPS),\ $(eval $1_LIBS += $(LIBDEPS))) # languages that can link this library (typically any lang that shares the # object format (ELF by default, but this is compiler-dependent (e.g. emcc uses # llvm bitcode, which is just an intermediary representation of data that can # link to anything else statically, or as an archive))) $(if $(findstring $1,$(LIBLANGS)),,\ $(error $2.mk must specifically set LIBLANGS := $1 if it is compatible with $1)) # Mark this lib as available for all its other compatible languages. This list # is assumed to be sorted later, so we will ignore repeats for speed $(foreach lang,$(LIBLANGS),\ $(if $(findstring $2,$($(lang)_LIBS)),,\ $(eval $(lang)_LIBS += $2))) # Generate a build rule for this lib, unless it's already built $(if $(findstring $2,$(BUILTLIBS)),,\ $(eval $(call LANG_LIB_PARENT_BUILDRULE,$1,$2,$3))\ $(eval BUILTLIBS += $2)) # Recurse this function for every libdep encountered $(foreach libdep,$(LIBDEPS),\ $(call LANG_LIB_INIT,$1,$(libdep),$2)) #/LANG_LIB_INIT################################################################# endef # Initialize data for supported lanaguages ###################################### define LANG_INIT = $(eval # Initialize all relevant (although not necessarily used) language relative data define $1_VARS_INIT = $(eval $1_OBJ := $($($1_C)_OBJ) $1_OUT := $($($1_C)_OUT) $1_AROBJ := $($($1_C)_AROBJ) $1_SOURCES := $(subst ./,,$(shell find -name "*.$1")) ) # For each source language that can compile to this language, add the expected # result of such a transformation to the sources of this language. Then, if the # source-to-source compiler also creates duplicate files of other formats # (i.e. yacc/bison also produce accompanying .h files), add them to the list of # source files that this makefile can generate ('$1_TARGETS'), to later be deleted # during the clean rule. $(foreach srcl,$($1_SRCL),\ $(eval $(srcl)_SOURCES := $(shell find -name "*.$(srcl)" | \ sed -e 's@^\(.*\)\.$(srcl)@\1$($(srcl)_STEM:%=.%).$1@g' -e 's@\./@@'))\ $1_SOURCES += $($(srcl)_SOURCES) $(srcl)_TARGETS += $($(srcl)_SOURCES:%.$(srcl)=.$1) $(foreach dup,$($(srcl)_DUP),\ $(srcl)_TARGETS += $($(srcl)_SOURCES:%.$1=%.$(dup)) )) endef )\ $(eval $($1_VARS_INIT))\ # Find save language-specific driver and module sources and targets $(eval $1_DRV_SRC := $(filter $(DRIVER_DIR)/%,$($1_SOURCES))) $(eval $1_DRV_TRG := $(patsubst %.$1,$(ROOT_DIR)/%$($1_OUT),$($1_DRV_SRC))) $(eval $1_MOD_SRC := $(filter-out $(DRIVER_DIR)/%,$($1_SOURCES))) $(eval $1_MOD_TRG := $(subst .$1,.$($1_OBJ),$($1_MOD_SRC))) # Add those sources and targets to a language-irreverant var $(eval DRV_SRC += $($1_DRV_SRC) DRV_TRG += $($1_DRV_TRG) MOD_SRC += $($1_MOD_SRC) MOD_TRG += $($1_MOD_TRG) )\ # First, initialize language-module, module, and language relative data. Then, # filter out each source file from the module and generate Dependency Rules and # Module Object Rules for each of them. $(foreach module,$(MODULES), # Evaluate the module sources for this language $(eval $(module)_$1_SRC := $(filter $(module)/%,$($1_MOD_SRC))) # Evaluate module targets for this language $(eval $(module)_$1_TRG := $($(module)_$1_SRC:%.$1=%.$($1_OBJ))) # Add these language-specific sources and targets to the module's src/targs $(eval $(module)_SRC += $($(module)_$1_SRC) $(module)_TRG += $($(module)_$1_TRG) ) # Add these targest to the language's target list $(eval $1_TARGETS += $($(module)_TRG) ) # For every source file, create a build rule for it for our language $(foreach src,$(filter $(module)/%,$($1_MOD_SRC)),\ $(eval $(call SRC_LANG_RULE,$(src),$1))\ )) # For all the listed libraries, initialize the library, which will set up all of # its local ($1_LIBS, etc) data $(foreach lib,$($1_LIBS),\ $(eval $(call LANG_LIB_INIT,$1,$(lib),$1))) # The initializer produces an ordered list of interlinked dependencies in groups # by depth, so when reading backwards and compressing repeats we are left with a # reliable build order for library interlinking. this awk program just looks # through a list of space separated words backwards failing to print when it # encounters a repeat $1_LIBS := $(shell echo "$($1_LIBS)" | $(call AWK_REVERSE_SQUASH)) #/LANG_INIT###################################################################### endef # The recursive library dependecy traversal constructs a tree ordered by nested # dependency depth. when linking, this order is reversed. # Initialize each language and look for its files $(eval $(foreach lang,$(LANGS),\ $(eval $(call LANG_INIT,$(lang)))\ $(eval $(lang)_LIBS := $(shell echo $($(lang)_LIBS) | $(call AWK_REVERSE_SQUASH)))\ )) # Create module object rules for each module $(foreach module,$(MODULES),\ $(eval $(call MODULE_ARCRULE,$(module)))\ ) # Create lang-specific rules for producing final (linked) targets $(foreach lang,$(LANGS),\ $(foreach drvsrc,$($(lang)_DRV_SRC),\ $(eval $(call SRC_LANG_DRVRULE,$(drvsrc),$(lang)))\ )) # Make any dirs that we're in charge of $(MAKE_DIRS): @mkdir -p $@ # Source-to-source build rules # Compilers that don't specify an output location can be made to change # directories with 'cd' to the target file's directory, update the command line # source files, and allow the compiler to output in its default current working # directory 'cwd'. this is done by setting the $1_CHDIR flag to 't' define SRCLANG_TRGLANG_BUILDRULE = %$($1_STEM:%=.%).$2$(if $($1_DUP), %$($1_STEM:%=.%).$($1_DUP)): %.$1 $(if $($1_CHDIR),cd $$(shell dirname $$@) && )$$($1_C) $$($1_FLAGS)$(if $($1_CHDIR),\ $$(shell echo $$@ | sed -e 's@^.*/\([^\.]*\).*@\1@').$1,\ $$<) endef # Create rules for all the source-to-source compilation, and poll all of our # make targets while we're at it $(foreach lang,$(LANGS),\ $(foreach srcl,$($(lang)_SRCL),\ $(eval $(call SRCLANG_TRGLANG_BUILDRULE,$(srcl),$(lang)))\ $(eval MAKE_TARGETS += $($(srcl)_TARGETS))\ )\ $(eval MAKE_TARGETS += $($(lang)_TARGETS))\ ) # Create driver rules for each driver we found, let users call make specifying # the basename of the driver, and just route to the correct linkrule we made in # the last paragraph DRV_FNAMES := $(notdir $(DRV_SRC)) .PHONY: all $(basename $(DRV_NAMES)) $(foreach fname,$(DRV_FNAMES),\ $(eval $(basename $(fname)): \ $(filter %/$(basename $(fname))$($(lastword $(subst ., ,$(fname))_OUT)),$(DRV_TRG)))\ ) all: $(basename $(DRV_FNAMES)) @echo Build Complete clean: $(BUILTLIBS:%=clean_%) rm -f $(MAKE_TARGETS) scrub: clean rm -Rf $(MAKE_DIRS)