繼上次「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);
}
文章標籤
創作者介紹
創作者 wylokgo101 的頭像
wylokgo101

豆棚瓜架雨如絲 - WYLOKGO101

wylokgo101 發表在 痞客邦 留言(0) 人氣()