温馨提示:阅读本文的同学最好能了解makefile和python的编写规则。不懂的同学可以先保存在收藏夹,以便日后查看。
转载请以超链接标明:http://www.guimigame.com/thread-460-1-1.html
其实之前我一直很懒,我不想了解makefile规则,因为在linux下开发我一直使用Qt creator。(很多时候正是一些“懒人”的创造力,解放了我们的双手,显然现在我们还需要用双手写makefile)。Qt creator是一个很好的IDE,而且可以跨平台开发。但是相比VS,显然还是不够优秀。因此很多开发者都会选择在Windows下开发C/C++程序,然后部署在Linux下执行。当然我也不例外。所以最近花了几个晚上了解makefile的编写规则。
开始的时候,我参照网上一些makefile的例子,写了一个初版的makefile。然而这个makefile在编译我的工程的时候报错。
主要出错体现在:
%.o: %.cpp
$(CXX) -fpic -c $(INCPATH) $< -o $@
当然可以写成
$(objdir)/%.o:$(srcdir)/%.cpp
$(CXX) -fpic -c $(INCPATH) $< -o $@
原因在于:
1、.o文件与.cpp文件处于不同的目录下。
2、不同的.o文件或不同的.cpp文件处于不同的目录下。
这时我找到两种解决方法:
1、就是用VPATH这个特殊变量,但是我不可能将所有要包含的目录都一一手动包含进来,于是我放弃。
2、就是把所有的编译规则列举出来。
我最终选择第二种解决方法。因为之前遇到这个困难时,我特意去了解Qt生成的makefile(其实这个makefile是依据.pro工程文件生成的)。而这个makefile正是将所有的编译规则都列举出来。于是就有下面这个python脚本。其实开始的时候我想用shell来做这一步工作的,但是我看到sed和awk,我头都晕,之前还一直抵触学习sed和awk。因此最后选择了python。
#encoding: utf-8 import os import os.path import sys #sys.exit(0); ######################################################################################################################### # # 本脚本的作用是:通过配置必要的信息,用python来生成makefile。(技术支持:www.guimigame.com) # @FILENAME 执行脚本输出makefile文件名 # @BIN 生成可执行文件名 # @SUFFIX 源文件后缀 # @ROOTPATH “根”目录路径(脚本工作目录的上一层) # @PWD 当前工作目录 # @WD 工作目录,如果程序有多个工作目录请一一用append加上 # @BINDIR 可执行文件输出目录 # @OBJDIR 中间文件输出目录 # @INCROOTPATH 头文件包含路径的“根路径”,方便INCPATH的编写 # @LIBROOTPATH 包含库的“根”路径,方便LIBS的编写 # @INCPATH 头文件包含路径 # @SYSLIBS 包含的系统库 # @LIBS 编译程序需要包含的库 # @CXX 一般填写gcc/g++ # @FLAGS gcc/g++的编译标志 # ######################################################################################################################### FILENAME = 'makefile'; BIN = "DatabaseServer"; SUFFIX = ".cpp"; ROOTPATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))); PWD = os.getcwd(); WD = []; WD.append(PWD); WD.append(ROOTPATH + "/common"); BINDIR = PWD + "/Bin/"; OBJDIR = BINDIR + "obj/"; INCROOTPATH = "-I " + ROOTPATH; LIBROOTPATH = "-L " + ROOTPATH; INCPATH = INCROOTPATH + "/common" + " " + INCROOTPATH + "/lib/include"; SYSLIBS = " -lmysqlclient -lpthread" LIBS = LIBROOTPATH + "/lib/linux " + "-lTimeManager -lServerConfig -lGameSocket -lCommon -lTinyxml" + SYSLIBS; CXX = "g++"; FLAGS = '-g -Wall'; ######################################################################################################################### # # 以下不需要再配置 # ######################################################################################################################### OBJFILE = ''; OBJ2SRC = []; SOURCES = ""; def SearchFiles(path): global OBJFILE; global OBJ2SRC; global SOURCES; global SUFFIX; listFile = os.listdir(path); for file in listFile: if os.path.isdir(os.path.join(path, file)): SearchFiles(os.path.join(path, file)); elif file.find(SUFFIX) > 0: if file.find(SUFFIX + "~") > 0: continue; OBJFILE = file; OBJFILE = OBJFILE.replace(SUFFIX,'.o'); OBJ2SRC.append([OBJDIR + OBJFILE,path + "/" + file]); SOURCES += path + "/" + file + " "; for dir in WD: SearchFiles(dir); if os.path.exists(FILENAME): os.remove(FILENAME); f = open(FILENAME,'w'); f.write("PWD = " + PWD + "\n"); f.write("CXX = " + CXX + "\n"); f.write("INCPATH = " + INCPATH + "\n"); f.write("LIBS = " + LIBS + "\n"); f.write("BINDIR = " + BINDIR +"\n"); f.write("OBJDIR = " + OBJDIR + "\n"); f.write("BIN = " + BIN + "\n"); f.write("SOURCES = " + SOURCES+ "\n"); f.write("SOURCEFILES = $(notdir $(SOURCES))\n"); f.write("OBJECTS = $(addprefix $(OBJDIR), $(patsubst %.cpp,%.o,$(SOURCEFILES)))\n"); f.write("FLAGS = " + FLAGS + "\n"); f.write("\n"); f.write("all:makedir $(OBJECTS)\n"); f.write(" $(CXX) $(FLAGS) $(INCPATH) -o $(BIN) $(OBJECTS) $(LIBS);\n"); f.write("\n"); f.write("makedir:\n"); f.write(' $(shell if [ -n "$(OBJDIR)" -a ! -e "$(OBJDIR)" ];then mkdir -p $(OBJDIR); fi)\n'); f.write(' $(shell if [ -n "$(BINDIR)" -a ! -e "$(BINDIR)" ];then mkdir -p $(BINDIR); fi)\n'); f.write("\n"); for val in OBJ2SRC: f.write(val[0] + ":" + val[1] +"\n"); f.write(" rm -f $@\n"); f.write(" $(CXX) -fpic -c $(INCPATH) $< -o $@\n"); f.write("\n"); f.close(); os.system("make"); os.system("mv " + BIN + " " + BINDIR); os.system("cd " + OBJDIR + ";rm -f *.o");
如何编写makefile和python,这里不作说明。因为这篇文章不是makefile和python的教程。以下要说明的是SearchFiles函数。
通过遍历之前设定的工程工作目录,调用SearchFiles遍历该目录下所有的源文件(.cpp),及设定目标文件(.o)的绝对路径,最终是tuple的形式保存到OBJ2SRC数组中;还有的是将所有源文件保存在SOURCES中。当然这个过程中会递归遍历所有子目录,查找到所有的源文件。最终在for val in OBJ2SRC:遍历所有的数据;列出所有的源文件(.cpp)生成所对应的目标文件(.o),将编译规则写进makefile。
这是我要编译的工程,当然截图只是其中一部分。这个工程需要包含的文件除了在DatabaseServer下,还要包含在../common当中(脚本中代码WD.append(ROOTPATH + "/common");)。我截图是为了证明,这个脚本是可行的。有人可能会说为什么不写一个测试例子。其实我想说,很多时候要弄懂一些技术,动手去做也许是最好的方法。如果你有什么问题,欢迎与我讨论!