################################################################################ # 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_ttf SDL2_net SDL2_image 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 ".so" | 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 */))) # determine the dependencies of a given source and language, but route link # issues to /dev/null since we will generate them later # can pass an optional third argument for additional opts define SRC_LANG_DEPS = $(shell $($2_C) -MM -MG $(if $3,-I$3) $($2_FLAGS) $1 2> /dev/null\ | sed -e 's@^.*\.o: @@' -e 's@\\@@') endef # create a list of module object archives associated with the given files, # if any, filtering out any drivers or non-module objects define FLIST_MODULEOBJS = $(sort $(shell echo $(filter-out .%,$(filter-out $(DRIVER_DIR)/%,$1))\ | sed -e 's@^\([a-Z\-\+]*\)/*@\1.$(c_OBJ)@g' )) endef # establish a dependency rule given a source and language. if passed a # directory name as the third argument, will prefix all header files in the # dependency with the local lib define SRC_LANG_DEPRULE = $(eval $1_$2_DEPS := $(call SRC_LANG_DEPS,$1,$2,$3)) $(foreach hdr,$(filter %.h,$($1_$2_DEPS)),\ $(if $(word 2,$(subst /, ,$(hdr))),,\ $(eval $1_$2_DEPS := $($1_$2_DEPS:$(hdr)=$3/$(hdr)))\ )) $(basename $1).$($2_OBJ): $($1_$2_DEPS) endef # establish a build and link rule given a source driver and language ############ define SRC_LANG_LINKRULE = # turn the non-existent third argument into an identifier for our link data $(eval 3 := $(notdir $(basename $1))) # generate the dependecies list $(eval $3_DEP := $(filter-out $1,$(call SRC_LANG_DEPS,$1,$2))) # 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 $(eval MODULE_FILES := $(filter-out .% $(DRIVER_DIR)/%,$($3_DEP))) $(eval MODULE_FILES := $(basename $(MODULE_FILES))) $(foreach mfile,$(MODULE_FILES),\ $(eval $3_ILDO += $(firstword $(subst /, ,$(mfile))))) # Find the .ld files, if present, and include their 'links' to our internal libs $(if $(wildcard $(1:.$2=.ld)),\ $(eval $3_MLDO := $(basename $(strip $(file <$(1:.$2=.ld)))))) $(eval $3_LDO := $(patsubst %,%.$($($2_C)_AROBJ),$(sort $($3_ILDO) $($3_MLDO)))) # If we have any libs to generate, make sure their specific shared object file # is available, and that the linker is linking to our expected link directory # for our compiler and architecture (transpilation magic) $(eval $3_SRC_OBJS := $(filter-out $1 %.h,$($3_DEP) $($3_LDO))) # If the compiler supports linking, filter any shared objects out of the list # that can be linked (the -l command option should already be in $2_FLAGS) # otherwise, explicitly include all shared objects in the source objects, while # also putting them on the dep list $(if $($($2_C)_LD),\ $(eval $3_SOB := $(filter-out $($2_LD_LIBS),$($2_LIBS))),\ $(eval $3_SOB := $($2_LIBS:%=$(LIB_DIR)/lib%.so))\ $(eval $3_SRC_OBJS += $($3_SOB))) $(eval $3_SOB += $(filter $(LIB_DIR)/lib%.so,$($2_FLAGS))) # Directory setup $(eval $3_DIR := $(dir $(1:%.$2=$(ROOT_DIR)/%))) $(eval MAKE_DIRS += $($3_DIR)) # Commandline setup. The recursive library dependecy traversal constructs a # tree ordered by nested dependency depth. when linking, this order is reversed # Establish the link tree: $(eval LINK_TREE := $(filter -l% %.so,$($2_FLAGS)) $($3_DEP) $($3_LDO) $1) $(eval LINK_TREE := $(shell echo $(filter-out %.h,$(LINK_TREE)) | awk \ ' BEGIN { OFS = " "; ORS = " " } { for (i=NF; i>1; i--) printf("%s ",$$i); print $$1; } ')) # The actual rule $($3_DIR)$3$($2_OUT): $($3_SOB) $($3_LDO) $($3_DEP) | $($3_DIR) $$($2_C) $$(filter-out $(LINK_TREE),$($2_FLAGS)) $(LINK_TREE) -o $$@ #/SRC_LANG_LINKRULE############################################################## 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 $1_ARCDEPS := $(filter $1/%,$(foreach lang,$(LANGS),$($(lang)_MOD_TRG)))) $1.$($(c_C)_AROBJ): $($1_ARCDEPS) | $(LCLLIB_DIR) $($(c_C)_AR) cr $$@ $$^ endef # define an object creation rule given a module and language define MODULE_LANG_OBJRULE = $1/%.$($2_OBJ): $1/%.$2 $$($2_C) -I$1 $$(filter-out -l%,$$($2_FLAGS)) $$< -c -o $$@ 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 = # Construct the download method, or error if there isn't a valid one $(if $($2_GITADDR),$(eval LIBDL := git clone $($2_GITADDR) $2),\ $(if $($2_HGADDR),$(eval LIBDL := hg clone $($2_HGADDR) $2),\ $(if $($2_CVSADDR),$(shell echo $($2_CVSPASS) > ~/.cvspass)\ $(eval LIBDL := export CVSROOT=$($2_CVSADDR) && cvs $($2_CVSGET)),\ $(if $($2_WEBADDR),$(eval LIBDL := wget -O $($2_WEBTARG) $($2_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 $($2_WEBINIT),&& $($2_WEBINIT)) # '$2' Make and install rule $(LIB_DIR)/lib$2.so: $(LIBDL_DIR)/$2 $($2_LIBDEPS:%=$(LIB_DIR)/lib%.so) @mkdir -p $(LIB_DIR) cd $(LIBDL_DIR)/$2 && $($2_MAKE) cd $(LIBDL_DIR)/$2 && $($2_INSTALL) # each library generated will be included in the libs available list, removed # from the missing libs list, and passed by -l'libname' to the language flags $(foreach lang,$(LIBLANGS),\ $(eval $(lang)_LIBS_AVAILABLE += $2)\ $(eval $(lang)_MISSING_LIBS := $(filter-out $2,$($(lang)_MISSING_LIBS)))\ $(eval $(lang)_SOBS += $(2:%=$(LIB_DIR)/lib%.so))\ ) #/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 = # Implicit defaults for lib.mk files $(eval AUTOGEN := ./autogen.sh) $(eval CONFIGURE := ./configure) $(eval CONFIGURE += --prefix=$(abspath $(LIB_DIR)/.trash)) $(eval CONFIGURE += --includedir=$(abspath $(LIBINC_DIR))) $(eval CONFIGURE += --libdir=$(abspath $(LIB_DIR))) $(eval MKCMD := make -k) $(eval MKINSTALL := make -k install) $(eval MKCLEAN := make clean) $(eval LIBDEPS :=) # 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 $(eval undefine WEBTARG) # File 'WEBADDR' is stored in $(eval undefine WEBINIT) # Method of extracting 'WEBTARG', if necessary $(eval undefine CVSGET) # checkout command $(eval undefine CVSPASS) # auto-put into ~/.cvspass to login automatically # Sneaky expansion to call a file inclusion with unelevated (eval'ed) text 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))) # Store lib specific build info $(foreach var,GITADDR WEBADDR HGADDR CVSADDR WEBTARG WEBINIT CVSGET CVSPASS,\ $(eval $2_$(var) := $($(var)))) $(eval $2_MAKE := $(if $(AUTOGEN),$(AUTOGEN) && )) $(eval $2_MAKE += $(if $(CONFIGURE),$(CONFIGURE) && )) $(if $(MKCMD),\ $(eval $2_MAKE += $(MKCMD)),\ $(error $2.mk requires a valid MKCMD definition)) $(if $(MKINSTALL),\ $(eval $2_INSTALL := $(MKINSTALL)),\ $(error $2.mk requires a valid MKINSTALL definition)) $(eval $2_CLEAN := $(MKCLEAN)) $(eval $2_DEPS := $(LIBDEPS)) # 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))) $(eval LIBLANGS ?= c cpp go) $(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),\ $(eval $(lang)_LD_LIBS += $2)\ $(if $(findstring $2,$($(lang)_LIBS)),,\ $(eval $(lang)_LIBS += $2)\ )) # Recurse this function for every libdep encountered $(foreach libdep,$(LIBDEPS),\ $(eval $(call LANG_LIB_INIT,$1,$(libdep)))\ ) #/LIB_INIT####################################################################### endef # Initialize data for supported lanaguages ###################################### define LANG_INIT = # Initialize all relevant (although not necessarily used) language relative data $(eval $1_OBJ := $($($1_C)_OBJ)) $(eval $1_OUT := $($($1_C)_OUT)) $(eval $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_MKSRC'), to later be deleted # during the clean rule. $(foreach srcl,$($1_SRCL),\ $(eval $1_MKSRC := $(shell find -name "*.$(srcl)" \ | sed -e 's@^\(.*\).$(srcl)@\1$($(srcl)_STEM:%=.%).$1@g' -e 's@\./@@'))\ $(eval $1_SOURCES += $($1_MKSRC))\ $(eval $(srcl)_SOURCES += $($1_MKSRC))\ $(if $($(srcl)_DUP),\ $(eval $1_SOURCES += $($1_MKSRC:%.$1=%.$($(srcl)_DUP))))\ ) $(eval $1_DRV_SRC := $(filter $(DRIVER_DIR)/%,$($1_SOURCES))) $(eval DRV_SRC += $($1_DRV_SRC)) $(eval $1_DRV_TRG := $(patsubst %.$1,$(ROOT_DIR)/%$($1_OUT),$($1_DRV_SRC))) $(eval DRV_TRG += $($1_DRV_TRG)) $(eval $1_MOD_SRC := $(filter-out $(DRIVER_DIR)/%,$($1_SOURCES))) $(eval MOD_SRC += $($1_MOD_SRC)) $(eval $1_MOD_TRG := $(subst .$1,.$($1_OBJ),$($1_MOD_SRC))) $(eval MOD_TRG += $($1_MOD_TRG)) # Generate a list of shared object files this language has access to $(eval $1_SOBS := $(wildcard $(LIB_DIR)/lib*.so)) # 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),\ $(eval $(module)_$1_SRC := $(filter $(module)/%,$($1_MOD_SRC)))\ $(eval $(module)_$1_TRG := $(filter $(module)/%,$($1_MOD_TRG)))\ $(eval $(module)_SRC += $(module)_$1_SRC)\ $(eval $(module)_TRG += $($(module)_$1_TRG))\ $(eval $1_TRG += $($(module)_TRG))\ $(eval \ $(foreach src,$(filter $(module)/%,$($1_MOD_SRC)),\ $(eval $(call SRC_LANG_DEPRULE,$(src),$1,$(module)))\ $(eval $(call MODULE_LANG_OBJRULE,$(module),$1)))\ )) $(eval $1_TARGETS += $($1_TRG)) # For all the listed libraries, initialize the library, which will set up all of # its local ($1_LIBS, etc) data $(foreach lib,$($1_LIBS),\ $(call LANG_LIB_INIT,$1,$(lib))) # 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 (which is suitable as a kind of ad-hoc make lisp function) $(eval $1_LIBS := $(shell echo "$($1_LIBS)" | awk \ ' { for(i=NF;i>0;i--) printf (!a[$$i]++) ? $$i FS : ""; i=split("",a); print "" } ' )) # If our compiler has a linker, find its list of available links and start # adding '-lLIBNAME' options to the language flags if the linker has them, or to # the list of missing libraries for this language if the linker cannot find them. # If this compiler has no linker, mark all libs as missing # $($1_SOBS:$(LIB_DIR)/lib%.so=%)) $(eval $1_LD_LIBS := $(sort $1_LD_LIBS)) $(if $($($1_C)_LD),\ $(eval $1_LD_LIBS := $($($1_C)_LDLIBS))\ $(foreach lib,$($1_LIBS),\ $(if $(findstring $(lib),$($1_LD_LIBS)),\ $(eval $1_FLAGS += -l$(lib)),\ $(eval $1_MISSING_LIBS += $(lib))\ $(eval $1_FLAGS += $(LIB_DIR)/lib$(lib).a)\ )),\ $(eval $1_MISSING_LIBS := $(filter-out $($1_LD_LIBS),$($1_LIBS)))\ ) #/LANG_INIT###################################################################### endef # Initialize each language and look for its files $(foreach lang,$(LANGS),\ $(eval $(call LANG_INIT,$(lang)))) # Generate rules for making missing libs $(foreach lang,$(LANGS),\ $(foreach mlib,$($(lang)_MISSING_LIBS),\ $(eval $(call LANG_LIB_PARENT_BUILDRULE,$(lang),$(mlib),root))\ )) # Create module object rules for each module and scan for interpreted code # sources (yacc, etc) $(foreach module,$(MODULES),\ $(eval $(call MODULE_ARCRULE,$(module)))\ $(eval YS += $(wildcard $(module)/*.y))\ ) # Create lang-specific rules for producing final (linked) targets $(foreach lang,$(LANGS),\ $(foreach drvsrc,$($(lang)_DRV_SRC),\ $(eval $(call SRC_LANG_DEPRULE,$(drvsrc),$(lang)))\ $(eval $(call SRC_LANG_LINKRULE,$(drvsrc),$(lang)))\ )) # Make any dirs that we're in charge of $(MAKE_DIRS): @mkdir -p $@ # 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 # 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 $(foreach lang,$(LANGS),$(if $($(lang)_SRCL),$(foreach srcl,$($(lang)_SRCL),\ $(eval $(call SRCLANG_TRGLANG_BUILDRULE,$(srcl),$(lang)))))) clean: rm -f $(foreach lang,$(LANGS),$($(lang)_TARGETS) $($(lang)_MKSRC)) scrub: clean rm -Rf $(MAKE_DIRS)