编写makefile的技巧。

1:

Q:GCC编译过程中出现undefined reference to `__gxx_personality_v0’

A:使用g++编译C++代码,不用gcc编译。

2:

编译中建议开启-Wall查看全部警告。

例如g++ -Wall MyTest.cpp -o MyBin.out

3:

Q:GCC编译过程中出现command-not-found .

A:表示找不到该文件,运行时注意不要直接执行.out .bin文件。注意本文件目录,例如./MyBin.out而非直接MyBin.out

4:

使用AutoConf和AutoMake工具可以减少我们编写make文件代价。

运行Shell(*.sh)访问Config(Configure.in),然后make(makefile.am).

5:

运行Windows下的SH后出现bad interpreter:No such file or directory 错误。

Windows下编写的SH不可被识别,因为Windows中和Linux下的文件格式不同。

可以使用如下命令进行转译

sed ’s/^M//’ Filename > NewFilename

mv –f NewFilename Filename

或者使用UE保存为Unix格式。

6:

编写的SH,运行时一直出现一个问题syntax error near unexpected token `then’

原因是……使用的是标准bashShell,语法要求极严格,竟然强制要求【】两边必须是留有空格的。如果不留,就报这个错误。

同时网上得知一些细节:

1> 定义变量时,=号两边不允许留有空格。A=10可以,A =10或者A= 10, A = 10都是错误。

2> 条件测试语句,[]符号两边必须留有空格。

3> 条件测试时,若进行字符串比较,则比较符号两边必须留空格。If [ $path = “1” ] ; then 可以。If [ $path=”1” ] ; then错误。

4> 条件测试语句中then如果和if在一行,则在then前必须添加分号。若两者不在一行,则then前无需分号。

5> If后面必须跟上then,同理else后必须跟上then,即使不进行任何处理,也必须增加:分割下一个elif。例如if [ $a = 10 ] ;then : elif [ $a = 11 ] ; then : else : fi可以。若将其中的: 忘记,则同样提示syntax error near unexpected token

//--------------
// Shell解释
//--------------

#!/bin/sh

#----------------------------------------------------------------------
#上面的一行作用是通知系统SH脚本执行方式。必须在SH语言文件的第一行。

#常见Shell有BourneShell,参数为/bin/sh (所有Unix都支持)

#                                                               C Shell,参数为/bin/csh                 ( BSD版本都支持)
#                                                               KornShell,参数为/bin/ksh   ( SVR4之后的大部分Unix支持)

#----------------------------------------------------------------------

PREFIX=/usr/local/qqw

#----------------------------------------------------------------------
#上面声明了一个变量PREFIX,同时对变量赋值。
#----------------------------------------------------------------------


cd PirateDBProxy
./autogen.sh
./configure --prefix=$PREFIX/PirateDBProxy --bindir=$PREFIX/PirateDBProxy --datarootdir=$PREFIX/PirateDBProxy
make && make install
cd ..

#----------------------------------------------------------------------

#上面首先进入了一个指定目录PirateDBProxy。
#然后调用了该目录下的一个autogen的shell处理文件。
#然后修改设置了configure.in文件。它是基于config语言写的。
#然后调用了make.in文件。
#最后返回上一级目录

#----------------------------------------------------------------------

cp -r PirateDB $PREFIX/

#----------------------------------------------------------------------
#拷贝文件到指定目录下,其中–r 表示递归文件夹拷贝。
#----------------------------------------------------------------------

//————– //使用Automake和AutoConf生成makefile的步骤。 //————–

附加说明:AutoConf依赖M4,Automake依赖perl。

1:首先进入project目录,运行autoscan命令,可将source code生成出configure.scan文件。

2:修改configure.scan文件后缀为configure.in文件,同时修改其内部数据。

3:在project目录下创建一个makefile.am文件。在存在cpp文件的目录内也创建一个makefile.am文件。修改makefile.am文件。

4:在project目录下创建NEWS,README,ChangeLog,AUTHORS文件。

5:拷贝/usr/share/automake-1.X目录下的depcomp和compile文件拷贝到project目录下。

例如:

# cp usr/share/automake-1.10/depcomp home/free/WorkDir/pirate_backstage/trunk/pirate_group/

# cp usr/share/automake-1.10/compile  home/free/WorkDir/pirate_backstage/trunk/pirate_group/

6:运行aclocal命令。若成功会生成aclocal.m4

7:运行autoconf命令。(这里依赖1,2步骤)正确的话,会生成一个比较大的configure文件。(6.7步骤不可颠倒)

8:运行automake –a命令。(这里依赖3.4步骤)正确的话,会生成一个automake.in文件。(注意。第7.8步骤顺序可颠倒)

其中,-a等于–add-missing。均表示自动增加库内遗失的标准文件。

9:运行./confiugre文件。(第9步必须最后)生成真正的makefile.in

流程图如下:

其中步骤2中,我们将configure.scan文件修改为configure.in文件后,进行的内部数据调整,详细流程请参见下文中的 config解释 部分。

其中步骤3中,我们将修改makefile.am文件。请参考下文中的 Make解释部分。

//————– // Config解释 //————–

所有的config文件都是以AC_INIT开头,AC_OUTPUT结尾。一般布局为

AC_INIT 测试程序 测试函数库 测试头文件 测试类型定义 测试结构 测试编译器特性 测试库函数 测试系统调用 AC_OUTPUT

建议对该布局不要进行修改。

例子如下:

# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_PREREQ([2.63])
AC_INIT([MyPackageName], [1.0], [duzhi5368@163.com])
AC_CONFIG_SRCDIR([main.cpp])
AM_CONFIG_HEADERS([config.h])
AM_INIT_AUTOMAKE( MyFileName, 1.0 )

#检查程序编译器
AC_PROG_CC                    :选择C++编译器,不额外设置则检测G++
AC_PROG_CXX                  :选择C编译器,不额外设置则检测GCC

#检查函数库

LIBS="-L/usr/local/mysql/lib/mysql/ -L/usr/local/apr/lib"

#检查库中是否有指定的函数

#该命令意义为AC_CHECK_LIB( [库名称], [函数名], [如果函数被找到后的操作],函数未被找到后的操作)
AC_CHECK_LIB(pthread, pthread_create,[LIBS="$LIBS -pthread"],exit 1)
AC_CHECK_LIB(event, event_set, [LIBS="$LIBS -levent"], exit 1)
AC_CHECK_LIB([log4cplus], [main], [LIBS="$LIBS -llog4cplus"], exit 1)
AC_CHECK_LIB([protobuf], [main], [LIBS="$LIBS -lprotobuf"], exit 1)
AC_CHECK_LIB([iconv], [libiconv_open], [LIBS="$LIBS -liconv"], exit 1)
AC_CHECK_LIB([apr-1], [main], [LIBS="$LIBS -lapr-1"], exit 1)
AC_CHECK_LIB([aprutil-1], [main], [LIBS="$LIBS -laprutil-1"], exit 1)
AC_CHECK_LIB(mysqlpp, [main], [LIBS="$LIBS -lmysqlpp"], exit 1)
AC_CHECK_LIB([pthread], [pthread_rwlock_init])

#因为程序是多线程的,所以要求加入AC_PROG_RANLIB
AC_PROG_RANLIB

#检查头文件
AC_CHECK_HEADERS([arpa/inet.h fcntl.h netinet/in.h stdlib.h string.h sys/socket.h sys/time.h unistd.h])

#检查类型定义,检查结构,检查编译器特性
AC_HEADER_STDBOOL
AC_C_INLINE
AC_TYPE_INT16_T
AC_TYPE_INT32_T
AC_TYPE_INT64_T
AC_TYPE_SIZE_T
AC_TYPE_UINT16_T
AC_TYPE_UINT32_T
AC_TYPE_UINT64_T

#检查库函数
AC_CHECK_FUNCS([inet_ntoa memmove memset socket strerror strtoull])
AC_CONFIG_FILES([comm/Makefile
                 group/Makefile
                 makefile
                 net/Makefile
                 protocol/Makefile])
AC_OUTPUT

通常我们需要修改Configure.scan内容包括:

1:增加AM_INIT_AUTOMAKE( myProjectName, version ) 2:最后AC_OUTPUT(最后要生成的makefile,包括子目录中的,中间用空格隔开) 例如:AC_OUTPUT( Makefile subdir/MakeFile subdir2/Makefile ) 3:增加AC_PROG_RANLIB如果我们需要自己生成Lib,最终link到最终的可执行文件中,则需要增加该宏,否则可以不加。 4:如果MakeFile忘记声明AUTOMAKE_OPTIONS = foreign,则在这里可以增加touchREADME NEWS AUTHORS ChangeLog来创建这些文件。

//————– // Make解释 //————–

Makefile.am文件表明了我们要求生成什么文件,由什么文件生成,安装到什么目录的信息。

其中,常见的两类makefile.am格式如下:

1:生成可执行文件

     bin_PROGRAMS =可执行文件名(这里假设可执行文件叫”exe” )
     exe_SOURCES  =  入口main.cpp源文件名
     exe_LDADD =连接的库文件名
     exe_LDFLAGS =连接的库文件标示选项
     exe_DEPENDENCIES =
     SUBDIRS =项目依赖目录
     INCLUDES =项目依赖头文件目录

2:生成静态库

     Lib_LIBRARIES =库名(这里假设静态库名为”lib.a” )
     lib_a_SOURCES =
     lib_a_LDADD =
    lib_a_LIBADD =
    lib_a_LDFALGS =

若我们生成的执行文件和库文件不想直接安装到系统中,则可以使用noinst_PROGRAMS代替bin_PROGRAMS,使用noinst_LIBRARIES代替lib_LIBRARIES.

其中automake设置了默认的安装路径在/usr/local中,我们可以通过

./configure –prefix = 的方法进行修改覆盖。同时还有其他一些预定义的目录包括bindir = $( prefix )/bin , libdir = $( prefix )/lib , datadir = $( prefix )/share, sysconfdir = $( prefix )/etc等

例子1:

//因为Automake目的是帮助开发GUN人员维护,所以在执行Automake的时候会自动检查目录下是否有标准的GUN文件,例如:NEWS, AUTHOR,ChangeLog。当我们设置Automake参数为foreign的时候,automake就改为非GUN软件检查,无需再检查这三项存在性了。

AUTOMAKE_OPTIONS = foreign
SUBDIRS = ./comm ./net ./group ./protocol
INCLUDES =-I./group -I./comm -I./net -I./protocol -I/usr/local/mysql/include/mysql
bin_PROGRAMS = PirateGroup
data_DATA = conf/config.xml conf/log.properties conf/group.xml conf/groupUpgrade.xml conf/groupActivationValue.xml
PirateGroup_SOURCES = main.cpp
PirateGroup_LDADD = ./comm/libcomm.a ./net/libnet.a\
        ./group/libgroup.a ./protocol/libprotocol.a

对于我们来说,Makefile.am中要求编写的如下

1:首先最上面写明AUTOMAKE_OPTIONS = foreign

当然,如果这个目录没有要编译的文件,只包含了子目录,则写个SUBDIRS = subdir就OK了。

例如:

AUTOMAKE_OPTIONS=foreign SUBDIRS=src

2:如果该目录下有需要编译的文件,那么需要指定。假设我们现在有个项目叫MyProgram,它需要连接其目录下的Sub1这个文件夹下的一个libsub1.a库文件。

则需要在MyProgram中编写如下MakeFile

bin_PROGRAMS= myprogram
 SUBDIRS= sub1
  myprogram_SOURCES= \
     a.cpp\
   b.cpp\
 # 要编译的源文件。这儿的_SOURCES是关键字
 EXTRA_DIST= \
  a.h \
  b.h
 # 不用编成.o,但生成target myprogram也需要给编译器处理的头文件放这里
        myprogram_LDADD = libsub1.a 这个_LDADD是关键字,
 # 最后生成myprogram这个执行文件,还要link src/sub1这个目录中的内容编成的一个lib :libsub1.a,
 myprogram_LDFLAGS = -lpthread -lglib-2.0 -L/usr/bin $(all_libraries)
 # myprogram还要link系统中的动态so,以此类推,需要连自编译的so,也写到这个关键字 _LDFLAGS后面就好了。
 AM_CXXFLAGS = -D_LINUX
 # 传递给g++编译器的一些编译宏定义,选项,
 INCLUDES=-IPassport -Isub1/ -I/usr/include/glib-2.0\
-I/usr/lib/glib-2.0/include $(all_includes)
#  传递给编译器的头文件路径。

然后我们在字目录subdir1内增加如下Makefile.am

noinst_LIBRARIES = libprotocol.a
 # 不是生成可执行文件,而是静态库,target用noinst_LIBRARIES
 libprotocol_a_SOURCES = \
  alib.cpp
 EXTRA_DIST = mylib.h\
   alib.h
 INCLUDES= -I../ $(all_includes)
AM_CXXFLAGS = -D_LINUX -DONLY_EPOLL -D_SERVER

//————– //附加的Shell部分常用命令 //————–

1:echo 和Windows一样,输出部分文字内容到屏幕上。 例如echo “HelloWorld”

2:ls 获取当前目录下的文件和文件夹列表。等同于windows下的dir

3:cd 转到指定目录,和Windows一样。Cd ..为返回上一级目录,直接cd则直接返回根目录。

4:cp 文件拷贝,参数意义为cp SrcFile DestFile 附加参数可参考cp –help

5:mv 文件移动或重新命名,参数意义为mv OldName NewName

6:rm 删除文件,参数意义为rm FileName

7:grep 在文件内搜索字符串。参数意义为grep ‘NeedString’ FileName 例如:grep ‘HelloWorld’ MyFile.txt

8:cat 将文件内容输出到屏幕上,参数意义为cat FileName 例如:cat MyFile.txt将完整输出MyFile.txt的内容。

9:file 获得文件类型,参数意义为file FileName 例如:file MyFile.txt将输出MyFile.txt: ASCII English text, with very long lines

10:流程控制 If [ ],中括号内可进行条件测试。