繼上次「GNU make - The Developer is Crazy, a Little Complicated Example」
並提出問題之後
為了增加對特定模組特製化,寫了個輔助小工具程式
大概兩三天吧,新版本就差不多好了
只是寫文章說明實在太花時間,前一篇花了一個晚上寫
原PO還有很多自High的開發計劃要玩
剛好近期又發現有bug和新功能要加,增修之後想想已很久沒寫東西了
直接把原始碼放上來吧
設計概念和之前差不多,只是多了和輔助程式互動的部分
有興趣程式碼自己慢慢看,有不專業的註解,BJ4了啦齁
Makefile
# Makefile to make all module listed in $(MODULE). # # Each module represent as a folder with the same name in the $(root). # # This Makefile automatically generates all rules needed by the modules. # When you want to add a new module, just create the module folder in $(source_path), # include folder with module name in $(include_path), if needed, # and add the module name to $(MODULE) macro. # # All object files will be put into $(object_path). # All built module files will be put into $(product_path). # # the exported related Makefile target of a module contains: # [module_name]: make the module # listsrc-[module_name]: list all the source files (.cpp and .h) of the module # listobj-[module_name]: list all the object files of the module and check the existing state (using ls) # export-[module_name]: put the public include files and library files into "export" folder in $(root) # clean-[module_name]: clean all made files of a module # # Ex. modules "sockets" will generate "sockets", "export-sockets", "listsrc-sockets", "listobj-sockets" and "clean-sockets" # # # If you want to customize the making for some modules, add [module_name].cfg file in $(config_path). # The content can be one or more of the following settings # DEFINE: additional macro defines # INCLUDE: additional include files # MAKE_ARGUMENTS: only effect when build type is "mk", the value will be passed to make command. # If not assigned, $(DEFAULT_MAKE_ARGUMENTS) provided. # MODULE_PATH: additional library path to search # MODULE: addtional dynamic library to link # STATIC_MODULE: additional static library to link # TYPE: build type, must be one of "exe", "lib" and "mk". If not assigned, $(DEFAULT_BUILD_TYPE) provided # exe: build an executable files # lib: build libraries of static and dynamic version # mk: call Makefile in $(source_path)/[module_name] with make arguments
# # Ex. DEFINE=DEBUG LINUX # INCLUDE = stdio.h stdlib.h # # # Author: WYL (Yi-Li Wu) # Version: 1.2 # Last Changed: 2014/07/08 export DEBUG := false override empty = override co := , override sp := $(empty) $(empty) #Modules to make MODULE := sockets process file DEFAULT_BUILD_TYPE := lib DEFAULT_MAKE_ARGUMENTS := override internal_module := config-read override target := $(MODULE) define newline endef # Get the absolute path of where this Makefile located override root := $(dir $(realpath $(lastword $(MAKEFILE_LIST)))) override sroot := $(root)source/ override iroot := $(root)public/include/ override inroot := $(root)internal/ override source_path := $(addprefix $(sroot), $(target)) $(addprefix $(inroot)src/, $(internal_module)) override include_path := $(iroot) override object_path := $(root)object/ override link_path := $(root)public/bin/ override product_path := $(root)public/bin/ override export_path := $(root)export/ override internal_path := $(root)internal/ override config_path := $(root)config/ #------------------------------------------------- # Internal Executable config_read := $(internal_path)bin/config-read #------------------------------------------------ # Gerneral template # print colored text define dumpInfo $(info $(shell echo -e "\033[$(strip $1)m$(strip $2)\033[m")) endef define listsrc $(filter $(sroot)$(strip $1)/%, $(src)) \ $(filter %(sroot)$(strip $1)/% $(iroot)$(strip $1)/%, $(hdr)) endef # Generate string of Makefile command which will be the following text form # $(addprefix [object_path], $(basename $(notdir [passed text]))) # We must evaluate it after the text is generated # use info function to see the calling result # # In this define, we cannot put any newline character into the text define tmplGenObjectName $(addsuffix $(strip $2), $(addprefix $(object_path), $(basename $(notdir $(strip $1))))) endef #------------------------------------------------- # All absolute path of source files of all modules override src := $(shell find $(source_path) -type f -name '*.cpp' -o -name '*.c' -a ! -name 'test*') # Corresponding object file absolute path of sources files # If we do not use the eval function, $(eval override obj := $(call tmplGenObjectName, $(src:%.cpp=%.o), .o)) # All absolute path of header files of all modules override hdr := $(shell find $(source_path) $(include_path) -type f -name '*.h') ifeq ($(DEBUG), true) $(call dumpInfo, 1;31, Variable root = $(root)) $(call dumpInfo, 1;32, Variable sroot = $(sroot)) $(call dumpInfo, 1;33, Variable iroot = $(iroot)) $(call dumpInfo, 1;36, Variable eroot = $(eroot)) $(call dumpInfo, 1;35, Variable src = $(src)) # if "obj" is not assigned by using eval function # $(obj) will be # $(info $(shell echo -e "\033[1;34m$(addsuffix .o, $(addprefix, [object_path], $(base $(notdir $(strip [sources])))))\033m")) # And the $(...) string will be interpreted as sub-shell command $(call dumpInfo, 1;34, Variable obj = $(obj)) $(call dumpInfo, 1;32, Variable hdr = $(hdr)) endif #------------------------------------------------ override build := gcc override INCLUDE_SEARCH += $(addprefix -I, $(include_path)) BIT_MODE := INCLUDE := util/define.h DEFINE := OPTIMIZE := WARNING := override CFLAGS := $(BIT_MODE) $(OPTIMIZE) $(WARNING) \ $(INCLUDE_SEARCH) $(addprefix -include , $(INCLUDE)) $(DEFINE) override LDFLAGS += -L$(link_path) -Wl,-Bdynamic -lstdc++ #------------------------------------------------ ################################################################ # MACRO used for generate rule, must be expanded when used # $(strip $1) is the name of target override static_lib_xfix = $(addprefix $(product_path)lib, $(addsuffix ++.a, $(strip $1))) override shared_lib_xfix = $(addprefix $(product_path)lib, $(addsuffix ++.so, $(strip $1))) override exe_xfix = $(addprefix $(product_path), $(addsuffix , $(strip $1))) override target_flag = $(object_path).$(strip $1) # The shell will output the custom settings for one module, with the format # that each setting is seperated by '#' symbol. # We must avoid the foreacch split setting that containg whitespace, # so we substitue '@' for each whitespace first. # The '#' that seperates the setting then is replaced with whitespace # so foreach function could split. # For each element (setting) in foreach function operation part (arg 3), # we should turn the '@'s back to whitespace and evaluate it. override extract_config = $(foreach var,\ $(subst \#,$(sp),$(subst $(sp),@,$(shell [ -f $(config_read) ] && \ $(config_read) --config-path $(config_path) --target $(t) 2>/dev/null \ )\#$(t)_mod_type ?= $(DEFAULT_BUILD_TYPE)\#$(t)_mod_makeArg ?= $(DEFAULT_MAKE_ARGUMENTS)\ )),\ $(eval $(subst @,$(sp),$(var))) \ ) $(foreach t, $(target), $(extract_config)) define maketarget .PHONY: $(strip $1) $(strip $1): internal $(target_flag) @echo "$(strip $1) is made" endef define listsrctarget .PHONY: listsrc-$(strip $1) listsrc-$(strip $1): @echo -e "\033[1;33m" @ls $(call listsrc, $(strip $1)) @echo -e "\033[m" endef define listobjtarget .PHONY: listobj-$(strip $1) listobj-$(strip $1): @echo -e "\033[1;32m" -@ls $(call tmplGenObjectName, $(filter $(sroot)$(strip $1)%, $(src)), .o) \ $(call tmplGenObjectName, $(filter $(sroot)$(strip $1)%, $(src)), -pic.o) @echo -e "\033[m" endef define exporttarget .PHONY: export-$(strip $1) export-$(strip $1): $(strip $1) @rm -rf $(export_path)$(strip $1) mkdir $(export_path)$(strip $1) mkdir $(export_path)$(strip $1)/include mkdir $(export_path)$(strip $1)/bin cp -rf $(iroot)$(strip $1)/* $(export_path)$(strip $1)/include [[ -f $(static_lib_xfix) ]] && cp -f $(static_lib_xfix) $(export_path)$(strip $1)/bin || echo "" [[ -f $(shared_lib_xfix) ]] && cp -f $(shared_lib_xfix) $(export_path)$(strip $1)/bin || echo "" [[ -f $(exe_xfix) ]] && cp -f $(exe_xfix) $(export_path)$(strip $1)/bin || echo "" endef define makeobject $(call tmplGenObjectName, $(strip $2), .o): $(2) $(filter %.h, $(call listsrc, $1)) $(build) -o $$@ -c $$< $(CFLAGS) $(addprefix -D, $($(strip $1)_mod_define)) $(addprefix -include , $($(strip $1)_mod_include)) $(build) -o $(call tmplGenObjectName, $(strip $2), -pic.o) -c $$< $(CFLAGS) \ $(addprefix -D, $($(strip $1)_mod_define)) $(addprefix -include , $($(strip $1)_mod_include)) -fPIC endef objtmp = $(filter $(sroot)$(strip $1)%, $(src)) define realmake ifeq ($($(strip $1)_mod_type),exe) $(target_flag): $(call tmplGenObjectName, $(objtmp), .o) $(build) -o $(exe_xfix) $(addprefix -Wl$(co)-Bdynamic -l, $($(strip $1)_mod_depModule)) \ $(addprefix -L, $($(strip $1)_mod_depModulePath)) \ $(addprefix -Wl$(co)-Bstatic -l, $($(strip $1)_mod_depSModule)) \ $(LDFLAGS) $(call tmplGenObjectName, $(objtmp), .o) touch $(target_flag) else ifeq ($($(strip $1)_mod_type),lib) $(target_flag): $(call tmplGenObjectName, $(objtmp), .o) $(call tmplGenObjectName, $(objtmp), -pic.o) $(build) -o $(shared_lib_xfix) -shared \ $(addprefix -L, $($(strip $1)_mod_depModulePath)) \ $(addprefix -Wl$(co)-Bdynamic -l, $($(strip $1)_mod_depModule)) \ $(addprefix -Wl$(co)-Bstatic -l, $($(strip $1)_mod_depSModule)) \ $(LDFLAGS) $(call tmplGenObjectName, $(objtmp), -pic.o) ar csrv $(static_lib_xfix) \ $(call tmplGenObjectName, $(objtmp), .o) touch $(target_flag) else ifeq ($($(strip $1)_mod_type),mk) .PHONY: $(target_flag) $(target_flag): cd source/$(strip $1) && make $($(strip $1)_mod_makeArg) else .PHONY: $(target_flag) $(target_flag): @echo -e "\033[1;31mUnknown Build Type '$($(strip $1)_mod_type)'!!\033[m" endif endef define cleantarget .PHONY: clean-$(strip $1) clean-$(strip $1): rm -rf $(call tmplGenObjectName, $(filter $(sroot)$(strip $1)%, $(src)), .o) \ $(call tmplGenObjectName, $(filter $(sroot)$(strip $1)%, $(src)), -pic.o) \ $(static_lib_xfix) $(shared_lib_xfix) $(exe_xfix) \ $(target_flag) \ $(export_path)$(strip $1)/* endef define makeinternal .PHONY: $(strip $1) $(internal_path)bin/$(strip $1): $(filter $(internal_path)src/$(strip $1)%, $(src)) $(build) -o $$@ $$^ $(CFLAGS) endef ################################################################ # Rules .PHONY: all help objects export clean nothing internal modules all: internal $(target) @echo "All module are made." nothing: @echo "" modules: @echo -e "\033[1;36m" @for m in $(target); do echo "$$m"; done @echo -e "\033[m" help: @echo -e "" @echo -e "Usage make [MODULE=\"module1 module2 module3 ...\"] [DEFAULT_BUILD_TYPE=lib|exe|mk] [DEBUG=true|false] target1 target2 ..." @echo -e "\tMODULE: the modules to built (will generate corresponding targets, see this Makefile for detail)" @echo -e "\tDEFAULT_BUILD_TYPE: default build type for that module" @echo -e "\tDEFAULT_MAKE_ARGUMENTS: default build argument, only effects when build type is \"mk\"" @echo -e "\tDEBUG: if true, the target generated will show on the screen" @echo -e "\nSpecific target" @echo -e "\tall: default make target, build all given modules" @echo -e "\tclean: clear all files generated by make" @echo -e "\thelp: print help message" @echo -e "\tinternal: build utility programs for this Makefile" @echo -e "\tmodules: list all modules" @echo -e "\tnothing: do nothing" @echo -e "" internal: $(addprefix $(internal_path)bin/, $(internal_module)) @echo "All base modules are made." objects: $(obj) @echo "All objects are made." export: $(addprefix export-, $(target)) @echo "All modules are exported." clean: rm -rf $(object_path)*.o $(product_path)*.a $(product_path)*.so \ $(object_path).[^.] $(object_path).??* $(export_path)* \ $(internal_path)bin/* ### Dynamically Generated Rules # Generate listsrc-[module_name] rules $(foreach \ v, $(target), \ $(eval $(call listsrctarget, $v)) \ ) # Generate listobj-[module_name] rules $(foreach \ v, $(target), \ $(eval $(call listobjtarget, $v)) \ ) # Generate [module_name] rules $(foreach \ v, $(target), \ $(eval $(call maketarget, $v)) \ ) # Genrate export-[module_name] rules $(foreach \ v, $(target), \ $(eval $(call exporttarget, $v)) \ ) # Gnerate .[module_name] rules $(foreach \ v, $(target), \ $(eval $(call realmake, $v)) \ ) # Generate object file rules $(foreach \ t, $(target), \ $(eval $(foreach \ v, $(filter %.cpp, $(call listsrc, $t)), \ $(call makeobject, $t, $v) \ )) \ ) # Generate clean-[module_name] rules $(foreach \ v, $(target), \ $(eval $(call cleantarget, $v)) \ ) # Generate internal module rules $(foreach \ v, $(internal_module), \ $(eval $(call makeinternal, $v)) \ ) ifeq ($(DEBUG), true) $(foreach \ v, $(target), \ $(info $(call listsrctarget, $v)) \ ) $(foreach \ v, $(target), \ $(info $(call listobjtarget, $v)) \ ) $(foreach \ v, $(target), \ $(info $(call maketarget, $v)) \ ) $(foreach \ v, $(target), \ $(info $(call exporttarget, $v)) \ ) $(foreach \ v, $(target), \ $(info $(call realmake, $v)) \ ) $(foreach \ t, $(target), \ $(info $(foreach \ v, $(filter %.cpp, $(call listsrc, $t)), \ $(call makeobject, $t, $v) \ )) \ ) $(foreach \ v, $(target), \ $(info $(call cleantarget, $v)) \ ) $(foreach \ v, $(internal_module), \ $(info $(call makeinternal, $v)) \ ) endif
輔助程式config-reader
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <fcntl.h> #include <getopt.h> #include <sys/stat.h> #include <sys/mman.h> #define DEFAULT_CONFIG_PATH "config/" static struct option _program_opts[] = { { "config-path", 1, NULL, 'p' }, { "target", 1, NULL, 't' }, { NULL } }; typedef struct { char *config_path; char *target; void *fileContent; unsigned int fileLength; char *mod_type; char *mod_include; char *mod_depModulePath; char *mod_depModule; char *mod_depSModule; char *mod_define; char *mod_makeArg; } Main; static Main _main[1] = { { DEFAULT_CONFIG_PATH } }; static int getFileContent(Main *main); static int readVariables(Main *main); static void dumpResult(Main *main); static void trim(char *str); int main(int argc, char **argv){ int opt, index; int ret; while ((opt = getopt_long(argc, argv, "p:t:", _program_opts, &index)) >= 0){ switch (opt){ case 'p': _main->config_path = optarg; break; case 't': _main->target = optarg; break; default: break; } } if (_main->target == NULL){ fprintf(stderr, "Do not specify -p or --config-path argument\n"); exit(-1); } ret = getFileContent(_main); if (ret < 0){ fprintf(stderr, "getFileContent: %s\n", strerror(errno)); exit(-1); } readVariables(_main); dumpResult(_main); return 0; } static int getFileContent(Main *main){ char config[1024]; char *file_map; struct stat state; int fd; errno = 0; sprintf(config, "%s%s.cfg", main->config_path, main->target); fd = open(config, O_RDONLY); if (fd < 0) return -1; if (fstat(fd, &state) < 0) return -2; if ((file_map = mmap(NULL, state.st_size, PROT_READ, MAP_PRIVATE, fd, 0)) == NULL) return -3; main->fileContent = malloc(state.st_size); if (main->fileContent == NULL) return -4; memcpy(main->fileContent, file_map, state.st_size); munmap(file_map, state.st_size); main->fileLength = state.st_size; return 0; } static char* readCommand(Main *main, char* start, char *command); static int readVariables(Main *main){ char command[8192], name[128], value[8192]; char *pos, *end; char *v; pos = main->fileContent; end = main->fileContent + main->fileLength; pos = readCommand(main, pos, command); do { if (*command == '#') continue; trim(command); if (sscanf(command, "%[^=]=%[^\n]", name, value) == 2){ trim(name); trim(value); v = malloc(strlen(value)); strcpy(v, value); printf("%s\n", v); if (!strcmp(name, "TYPE")) main->mod_type; else if (!strcmp(name, "INCLUDE")) main->mod_include = v; else if (!strcmp(name, "MODULE_PATH")) main->mod_depModulePath = v; else if (!strcmp(name, "MODULE")) main->mod_depModule = v; else if (!strcmp(name, "STATIC_MODULE")) main->mod_depSModule = v; else if (!strcmp(name, "DEFINE")) main->mod_define = v; else if (!strcmp(name, "MAKE_ARGUMENTS")) main->mod_makeArg = v; else free(v); } } while ((pos = readCommand(main, pos, command)) <= end); return 0; } static void dumpResult(Main *main){ #define DUMP(name) \ if (main->name){ \ printf("%s_"#name":=%s#", main->target, main->name); \ free(main->name);\ } DUMP(mod_type) DUMP(mod_include) DUMP(mod_depModule) DUMP(mod_depSModule) DUMP(mod_define) DUMP(mod_makeArg) DUMP(mod_depModulePath) #undef DUMP } static char* readCommand(Main *main, char *start, char *command){ char *fpos, *cpos; char *end; size_t len; end = main->fileContent + main->fileLength; for (fpos = start, cpos = command; fpos < end;){ sscanf(fpos, "%[^\n]", cpos); len = strlen(cpos); cpos += len; fpos += len; if (*(fpos - 1) != '\\' || *(fpos++) != '\n') break; } *cpos = '\0'; return ++fpos; } static void trim(char *str){ char *start, *end; char tmp[4096]; start = str; for (; *start == ' '; start++); end = str + strlen(str) - 1; for (; *end == ' '; end--); memcpy(tmp, start, (size_t)(end - start + 1)); tmp[(size_t)(end - start + 1)] = '\0'; strcpy(str, tmp); }
文章標籤
全站熱搜
留言列表