Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Creating a professional compilation environment (14)

2025-02-27 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Servers >

Share

Shulou(Shulou.com)06/03 Report--

In some large projects, its structure is very complex. Like the one below.

Let's analyze the architecture of the project, which is divided into several different modules. The code of each module is managed by a folder, which is composed of inc,src,makefile; the external function declaration of each module is uniformly placed in common/inc, such as common.h xxxfunc.h.

Then we need to create the compilation environment is: 1, the source folder can not be changed at compilation time (read-only folder); 2, in the compilation time automatically create a folder (build) to store the compilation results; 3, the compilation process automatically generate dependencies, automatically search for files; 4, each module can have its own independent compilation mode; 5, support the debug version of the compilation option.

Let's see how the solution is designed.

Phase 1: compile the code in each module into a static library file, as follows

Stage 2: link the static library file of each module into the final executable program, as follows

Then the task completed in the first phase is to complete the makefile files that can be compiled for each module, and the compilation result of each module is a static library file (.a file). Then the key implementation points are: a > automatically generate dependencies (gcc-MM); b > automatically search for needed files (vpath); and c > package the target file as a static library file (ar crs). Let's take a look at the makefile composition of the module, as follows

Let's take a look at how this makefile is written

.PHONY: allDIR_BUILD: = / mnt/hgfs/winshare/mentu/make1/14/buildDIR_COMMON_INC: = / mnt/hgfs/winshare/mentu/make1/14/common/incDIR_SRC: = srcDIR_INC: = incTYPE_SRC: = .cTYPE _ INC: = .hTYPE _ OBJ: = .oTYPE _ DEP: = .depar: = arARFLAGS: = crsCC: = gccCFLAGS: =-I $(DIR_INC)-I $(DIR_COMMON_INC) ifeq ($(DEBUG)) True) CFLAGS + =-gendifMODULE: = $(realpath.) MODULE: = $(notdir $(MODULE)) DIR_OUTPUT: = $(addprefix $(DIR_BUILD) /, $(MODULE)) OUTPUT: = $(MODULE). AOUTPUT: = $(addprefix $(DIR_BUILD) /, $(OUTPUT)) SRCS: = $(wildcard $(DIR_SRC) / * $(TYPE_SRC)) OBJS: = $(SRCS:$ (TYPE_SRC) = $(TYPE_OBJ)) OBJS: = $(patsubst $(DIR_SRC) /% $(DIR_OUTPUT) /%, $(OBJS) DEPS: = $(SRCS:$ (TYPE_SRC) = $(TYPE_DEP)) DEPS: = $(patsubst $(DIR_SRC) /%, $(DIR_OUTPUT) /%, $(DEPS)) vpath% $(TYPE_INC) $(DIR_INC) vpath% $(TYPE_INC) $(DIR_COMMON_INC) vpath% $(TYPE_SRC) $(DIR_SRC)-include $(DEPS) all: $(OUTPUT) @ echo "Success! Target = > $(OUTPUT) "$(OUTPUT): $(OBJS) $(AR) $(ARFLAGS) $@ $^ $(DIR_OUTPUT) /% $(TYPE_OBJ):% $(TYPE_SRC) $(CC) $(CFLAGS)-o $@-c $(filter% $(TYPE_SRC), $^) $(DIR_OUTPUT) /% $(TYPE_DEP):% $(TYPE_SRC) @ echo" Creating $@... "@ set-e \ $(CC) $(CFLAGS)-MM-E $(filter% $(TYPE_SRC), $^) | sed's,\ (.*\)\ .o [:] *, $(DIR_OUTPUT) /\ 1 $(TYPE_OBJ) $@:, g'> $@

Create a build folder under the 14 folder to store the generated files, and continue to create a common folder to store the files generated by the common-related compilation. Let's take a look at the compilation results.

We see the .dep file that has generated automatic dependencies, and the .a file that has been packaged. Let's copy this makefile file directly to the main and module folders to see if we can also generate related files (create a main folder under the build folder)

We see the .dep file that has generated automatic dependencies, and the .a file that has been packaged. Let's see if copying to the module folder can also generate related files (create a module folder under the build folder)

Now our first phase of the module auto-compiled makefile has been written, and the functions have been implemented. Let's take a look at the preparation of the second phase, then the tasks of the second phase are as follows: 1, complete the compilation of the makefile file of the entire project; 2, call the module makefile to compile and generate static library files; 3, link the static library files of all modules, and finally get the executable program. The format is as follows

So what are the key points of its implementation? 1. How to automatically create build folders and subfolders? We are through the mkdir command to achieve; 2, how to enter each module folder to compile? Through the cd command to achieve; 3, how to link all the module static libraries after successful compilation? This is achieved through the gcc command. So the biggest question now is how do we determine how many modules there are in this project? In general, each module in the project has been basically determined in the design phase, so it will not be added or decreased frequently or randomly in the subsequent development process.

Let's take a look at the solution. 1, define variables to save the list of module names (module name variables); 2, use the for loop in Shell to traverse the module name variables; 3, enter the module folder in the for loop to compile; 4, link all module static library files after the end of the loop. Let's take a look at how to embed Shell's for loop in makefile, as follows

Let's first try the for loop in Shell. The code is as follows

MODULES= "common main module" for dir in $MODULES;do echo $dirdone

The compilation results are as follows

We see that we have correctly output three variable names. Let's take a look at how it is executed in makefile. The code is as follows

MODULES: = common\ main\ moduletest: @ set-e;\ for dir in $(MODULES);\ do\ echo $$dir;\ done

Let's take a look at the compilation result and see if we correctly output the three variable names as we expected.

We see that the for loop in Shell has been correctly output in makefile. When embedding Shell code in makefile, if you need to use the value of the Shell variable, you must precede the variable name with $$(for example: $$dir)! Let's take a look at what are the components of the engineering makefile? As follows

Let's take a look at how the compile code should be written.

.PHNY: all compileMODULES: = common\ main\ moduleMKDIR: = mkdirRM: = rm-rfDIR_PROJECT: = $(realpath.) DIR_BUILD: = buildDIR_BUILD_SUB: = $(addprefix $(DIR_BUILD) /, $(MODULES)) all: @ echo "Success!" compile: $(DIR_BUILD) $(DIR_BUILD_SUB) @ echo "Begin to compile." @ set-e;\ for dir in $(MODULES) \ do\ cd $$dir & & $(MAKE) all DEBUG:=$ (DEBUG) & & cd. ;\ done @ echo "Compile Success!" $(DIR_BUILD) $(DIR_BUILD_SUB): $(MKDIR) $@

Let's take a look at the compilation results.

We see that the .a file has been compiled correctly, as follows

Let's take a look at how to implement the link, which should be noted when linking: a > gcc must follow strict dependencies when making static library links, such as gcc-o app.out x.a y.a z.a. The dependencies must be: x.a-> y.a.a-> z.a, which follows a left-to-right dependency by default; b > if you are not aware of the dependencies between libraries, you can use-Xlinker to automatically determine the dependencies, such as gcc-o app.out-Xlinker "- (" z.ay.a x.a-Xlinker "-)". Let's take a look at how the last makefile code is written.

.PHONY: all compile link clean rebuildMODULES: = common\ main\ moduleMKDIR: = mkdir RM: = rm-rfCC: = gccLFLAGS: = DIR_PROJECT: = $(realpath.) DIR_BUILD: = buildDIR_BUILD_SUB: = $(addprefix $(DIR_BUILD) /, $(MODULES)) MODULE_LIB: = $(addsuffix .a, $(MODULES)) MODULE_LIB: = $(addprefix $(DIR_BUILD) / (MODULE_LIB) APP: = app.outAPP: = $(addprefix $(DIR_BUILD) /, $(APP)) all: compile $(APP) @ echo "Success! Target = > $(APP) "compile: $(DIR_BUILD) $(DIR_BUILD_SUB) @ echo" Begin to compile... "@ set-e;\ for dir in $(MODULES);\ do\ cd $$dir & & $(MAKE) all DEBUG:=$ (DEBUG) & & cd. ;\ done @ echo "Compile Success!" link $(APP): $(MODULE_LIB) @ echo "Begin to link..." $(CC)-o $(APP)-Xlinker "- (" $^-Xlinker "-)" $(LFLAGS) @ echo "Link Success!" $(DIR_BUILD) $(DIR_BUILD_SUB): $(MKDIR) $@ clean: $(RM) $(DIR_BUILD) rebuild: clean all

Let's look at the effect of the link.

We see that the link is successful and the executable program app.out can be run correctly. Let's make directly to see if we can generate executable programs.

We see that the executable program app.out has been generated and can be run successfully. Our makefile has been written, so are there any potential problems with the current makefile of the entire project? Is there a need for refactoring? Problem 1: our makefile paths in the previous module are all written dead, once the project folder is moved, the compilation will fail! As follows

The solution: a > is to obtain the source path of the project in the project makefile; b > according to the project source path, splice to get the compiled folder path (DIR_BUILD), splicing to get the global inclusion path (DIR_COMMON_INC); c > pass the path to the module makefile through command line variables. Let's take a look at the changed code.

.PHONY: all compile link clean rebuildMODULES: = common\ main\ moduleMKDIR: = mkdir RM: = rm-rfCC: = gccLFLAGS: = DIR_PROJECT: = $(realpath.) DIR_BUILD: = buildDIR_COMMON_INC: = common/incDIR_BUILD_SUB: = $(addprefix $(DIR_BUILD) /, $(MODULES)) MODULE_LIB: = $(addsuffix .a, $(MODULES)) MODULE_LIB: = $(addprefix $(DIR_BUILD) / (MODULE_LIB) APP: = app.outAPP: = $(addprefix $(DIR_BUILD) /, $(APP)) all: compile $(APP) @ echo "Success! Target = > $(APP) "compile: $(DIR_BUILD) $(DIR_BUILD_SUB) @ echo" Begin to compile... "@ set-e;\ for dir in $(MODULES) \ do\ cd $$dir & &\ $(MAKE) all\ DEBUG:=$ (DEBUG)\ DIR_BUILD:=$ (addprefix $(DIR_PROJECT) /, $(DIR_BUILD))\ DIR_COMMON_INC:=$ (addprefix $(DIR_PROJECT) /, $(DIR_COMMON_INC)) & &\ cd. ;\ done @ echo "Compile Success!" link $(APP): $(MODULE_LIB) @ echo "Begin to link..." $(CC)-o $(APP)-Xlinker "- (" $^-Xlinker "-)" $(LFLAGS) @ echo "Link Success!" $(DIR_BUILD) $(DIR_BUILD_SUB): $(MKDIR) $@ clean: $(RM) $(DIR_BUILD) rebuild: clean all

Let's directly delete the absolute paths in the three modules to see if the compilation results are the same as before.

We see that the compilation is successful. Question 2: all module makefile is the same (copy and paste), when the module makefile needs to be changed, it will involve many of the same changes! Solution: a > split the module makefile into two template files, mod-cfg.mk to define variables that may change, mod-rule.mk to define relatively stable variables and rules, and mod-cmd.mk to define command line related variables; b > by default, module makefile reuses template files to implement the function (include). The key question is how does the module makefile know the exact location of the template file? The solution is to pass the location of the template file through command-line variables.

Mod-cfg.mk source code

DIR_SRC: = srcDIR_INC: = incTYPE_SRC: = .cTYPE _ INC: = .hTYPE _ OBJ: = .oTYPE _ DEP: = .dep

Mod-rule.mk source code

.PHONY: allMODULE: = $(realpath.) MODULE: = $(notdir $(MODULE)) DIR_OUTPUT: = $(addprefix $(DIR_BUILD) /, $(MODULE)) OUTPUT: = $(MODULE). AOUTPUT: = $(addprefix $(DIR_BUILD) /, $(OUTPUT)) SRCS: = $(wildcard $(DIR_SRC) / * $(TYPE_SRC)) OBJS: = $(SRCS:$ (TYPE_SRC) = $(TYPE_OBJ) OBJS: = $(patsubst $(DIR_SRC) /% $(DIR_OUTPUT) /%, $(OBJS) DEPS: = $(SRCS:$ (TYPE_SRC) = $(TYPE_DEP)) DEPS: = $(patsubst $(DIR_SRC) /%, $(DIR_OUTPUT) /%, $(DEPS)) vpath% $(TYPE_INC) $(DIR_INC) vpath% $(TYPE_INC) $(DIR_COMMON_INC) vpath% $(TYPE_SRC) $(DIR_SRC)-include $(DEPS) all: $(OUTPUT) @ echo "Success! Target = > $(OUTPUT) "$(OUTPUT): $(OBJS) $(AR) $(ARFLAGS) $@ $^ $(DIR_OUTPUT) /% $(TYPE_OBJ):% $(TYPE_SRC) $(CC) $(CFLAGS)-o $@-c $(filter% $(TYPE_SRC)) $^) $(DIR_OUTPUT) /% $(TYPE_DEP):% $(TYPE_SRC) @ echo "Creating $@..." @ set-e \ $(CC) $(CFLAGS)-MM-E $(filter% $(TYPE_SRC), $^) | sed's,\ (.*\)\ .o [:] *, $(DIR_OUTPUT) /\ 1 $(TYPE_OBJ) $@:, g'> $@

Cmd-cfg.mk source code

AR: = arARFLAGS: = crsCC: = gccLFLAGS: = CFLAGS: =-I $(DIR_INC)-I $(DIR_COMMON_INC) ifeq ($(DEBUG), true) CFLAGS + =-gendifMKDIR: = mkdirRM: = rm-fr

The makefile in the module now looks like this

Include $(MOD_CFG) # Custmization Begin# # DIR_SRC: = src# DIR_INC: = inc## TYPE_INC: = .h # TYPE_SRC: = .c # TYPE_OBJ: = .o # TYPE_DEP: = .dep # # Custmization Endinclude $(CMD_CFG) include $(MOD_RULE)

Let's take a look at the compilation results.

We see that the result has not changed, so the present change is correct. But why is our makefile in the module annotated rather than deleted directly? It is so that the makefile that can be directly in the module can change the src and inc folders, so that we can easily develop later. Next we ReFactor the project makefile by splitting command variables and other variables and rules into different files. Cmd-cfg.mk files define variables related to commands, pro-cfg.mk defines project variables and compilation path variables, and pro-rule.mk defines other variables and rules. The final project makefile is made up of split files (include).

Pro-cfg.mk source code

MODULES: = common\ module\ main MOD_CFG: = mod-cfg.mkMOD_RULE: = mod-rule.mkCMD_CFG: = cmd-cfg.mkDIR_BUILD: = buildDIR_COMMON_INC: = common/incAPP: = app.out

Pro-rule.mk source code

.PHONY: all compile link clean rebuildDIR_PROJECT: = $(realpath.) DIR_BUILD_SUB: = $(addprefix $(DIR_BUILD) /, $(MODULES)) MODULE_LIB: = $(addsuffix .a, $(MODULES)) MODULE_LIB: = $(addprefix $(DIR_BUILD) /, $(MODULE_LIB)) APP: = $(addprefix $(DIR_BUILD) /, $(APP)) all: compile $(APP) @ echo "Success! Target = > $(APP) "compile: $(DIR_BUILD) $(DIR_BUILD_SUB) @ echo" Begin to compile... "@ set-e;\ for dir in $(MODULES) \ do\ cd $$dir & &\ $(MAKE) all\ DEBUG:=$ (DEBUG)\ DIR_BUILD:=$ (addprefix $(DIR_PROJECT) /, $(DIR_BUILD))\ DIR_COMMON_INC:=$ (addprefix $(DIR_PROJECT) / (DIR_COMMON_INC))\ CMD_CFG:=$ (addprefix $(DIR_PROJECT) /, $(CMD_CFG))\ MOD_CFG:=$ (addprefix $(DIR_PROJECT) /, $(MOD_CFG))\ MOD_RULE:=$ (addprefix $(DIR_PROJECT) /, $(MOD_RULE)) & &\ cd. ;\ done @ echo "Compile Success!" Link $(APP): $(MODULE_LIB) @ echo "Begin to link..." $(CC)-o $(APP)-Xlinker "- (" $^-Xlinker "-)" $(LFLAGS) @ echo "Link Success!" $(DIR_BUILD) $(DIR_BUILD_SUB): $(MKDIR) $@ clean: @ echo "Begin to clean." $(RM) $(DIR_BUILD) @ echo "Clean Success!" Rebuild: clean all

Engineering makefile source code

Include pro-cfg.mkinclude cmd-cfg.mkinclude pro-rule.mk

Let's take a look at the compilation results.

We see that the result of the compilation has not changed, that is, the change in the code is correct. Through the creation of a professional compilation environment, it is summarized as follows: 1, the compilation environment of a large project is composed of different makefile; 2, the design of the compilation environment needs to be designed according to the overall architecture of the project; 3, the compilation process of the entire project can be divided into different stages; 4, targeted design of makefile according to different stages; 5, makefile also needs to consider basic program features such as reusability and maintainability.

Welcome to learn makefile language, you can add me QQ:243343083.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Servers

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report