来源:http://blog.csdn.net/clever101

决心学习Makefile,一方面是为了解决编译开源代码时需要跨编译平台的问题(发现一些开源代码已经在使用VS2010开发,但我还没安装VS2010,我想在VS2008下编译这些代码);另一方面源码在服务器端编译的话,使用IDE的方式编译还是不太方便。

本文主要分为三部分:第一部分讲述namke工具使用makefile的用法;第二部分讲述makefile的主要语法;第三部分讲述自己动手实践学习写makefile文件。第四部分是编写一个工具将vc工程文件转化为Makefile文件。

首先要清楚的是在VS环境下使用Makefile的工具是nmake。因此我们需要弄明白nmake的使用Makefile文件常用命名行用法。nmake使用Makefile文件常用命名行用法是:

[plain] view plaincopy
  1. namke /f  makefile /x stderrfile  [macrodefs] [targets]

其中makefile为makefile文件,/x stderrfile为可选参数,即把namke错误存储到文件stderrfile。

接着介绍makefile的主要语法。makefile的注释以#开头,如:

[plain] view plaincopy
  1. # this is my first makefile

Makefile的一个重要组成部分是宏。Makefile中的宏和C语言的中宏类似,其实质就是字符串替换。其语法很简单,如下:

macro name =  macro value

直译就是宏名 =  宏的值

VS预定义了很多宏,如OUTDIR,你可以在你的Makefile重新定义这些宏以覆盖原来的值。

宏可以使用环境变量,如你的系统有一个OPEN_SOURCE的环境变量,然后你可以这样定义宏:

THIRD_PARTY  =  $(OPEN_SOURCE)

宏的引用用法是 $(宏名)。

接着介绍Makefile的第二个重要组成部分预处理指令。Makefile的预处理指令和C语言的预处理指令类似,其常用指令如下:

!ERROR string      ——    显示错误“string”, 然后停止执行,错误代码为U1050

!MESSAGE string  ——   显示字符串,这个一般用于信息显示C语言的#pragma message

!INCLUDE [<]filename[>] —— 包含makefile。

!IF const ——  如果成立(非零),则处理!F和下一个!ELSE或!ENDIF之间的语句

还有诸如!IFDEF macroname、!IFNDEF macroname、!ELSE、!ELSEIF、!ELSEIFDEF、!ELSEIFNDEF、!ENDIF和C语言的#if之类的指令的意义是一致的,这里就不一一详述了。

Makefile的第三个主要组成部分是描述块。描述块的结构如下:

目标:依赖项

命令

这里略微解释下什么叫目标、依赖项和命令。所谓目标就是用户最终希望得到的结果,也就是nmake需要生成的结果。目标可以是一个文件、目录,也可以什么都不是。如果目标不存在或者目标的时间戳(文件的最后修改时间)比依赖项早,或者目标类型不是文件,nmake将运行描述块中的“命令”。

依赖项是指在生成目标所需要使用到的对象。一个目标可以有一个或多个依赖项,也可以没有依赖项。多个依赖项以空格分隔。如果指定的依赖项不存在,则在其他描述块的目标中寻找,但首先需要生成这个目标。

命令是nmake在生成目标时所调用的命令。与用户自己在命令行中执行效果是一样的。

在使用namke进行程序构建时,nmake采用了时间戳判断机制。在生成一个目标时,会判断目标文件是否存在或目标的最后修改时间是否晚于所有依赖项的最后修改时间。如果所有依赖项的最后修改时间都比目标的最后修改时间晚,则说明当前的目标文件是使用现有的依赖项生成,是最新的,没有必要再进行生成。

介绍到这里,可能你对Mdakefile的语法细节有了大致的了解,但估计你对Makefile的常用文件结构还不了解。如果缺少对这一层的理解,你还是对如何编写Makefile文件一头雾水。下面介绍一下常用的Makefile文件结构。Makefile文件结构可以是如下的结构:

# 宏定义

……

# 描述块

学了这么多,我们来实践一下。首先我们来一个简单的控制台工程——ConsoleTest。一切根据工程向导采用默认设置即可。然后在main函数中添加几句简单代码(这个用于判断我们生成的程序是否成功),具体如下:

[plain] view plaincopy
  1. int _tmain(int argc, _TCHAR* argv[])
  2. {
  3. printf("Hello World! \n");
  4. getchar();
  5. return 0;
  6. }

然后我们在ConsoleTest文件夹下新建一个makefile.vc。我们开始正式编写一个makefile文件了。这时我们的大脑可能会一片空白,虽然你学了很多makefile语法,但迈出第一步依然是困难,这是正常的反应。好吧,让我们一步步来吧。首先要告诉你makefile的一个基本原则:以终为始,这个似乎和我们平时进行的过程式编程的原则相悖。所谓以终为始,就是你通过makefile文件首先告诉编译器这个工程是想生成一个exe还是一个dll还是一个静态库。然后告诉编译器要生成这个exe之类需要生成哪些obj文件。在这个例子中,我们要生成一个exe,所以我们在makefile文件的第一行就是:

[plain] view plaincopy
  1. all:ConsoleTest.exe

接下来就是编译器的一般生成过程:编译加链接命令,具体是:

[plain] view plaincopy
  1. # compile
  2. stdafx.obj: stdafx.cpp
  3. cl -c -D_X86=1 -DWIN32 -D_DEBUG -D_CONSOLE -Istdafx.h stdafx.cpp
  4. ConsoleTest.obj: ConsoleTest.cpp stdafx.obj
  5. cl -c -D_X86=1 -DWIN32 -D_DEBUG -D_CONSOLE -Istdafx.h ConsoleTest.cpp
  6. # link
  7. ConsoleTest.exe: ConsoleTest.obj
  8. link /INCREMENTAL:YES /NOLOGO /subsystem:console /out:ConsoleTest.exe ConsoleTest.obj kernel32.lib

其中cl语句是VC编译器的编译器的命令行编译,link语句是VC链接器的命令行用法,这里只简单叙述cl和link的用法。

cl的一些常用选项:

-c: 编译但不链接

-D: 定义预处理器,如-D_X86=1:指定在x86平台上编译,-D_DEBUG:定义预处理器_DEBUG,

-I:包含的头文件

cl的最后一个参数是所编译的文件。

link的一些常用选项:

/INCREMENTAL:是否启用增量链接,YES为启用,NO为不启用,

/NOLOGO: 取消显示启动版权标志

/SUBSYSTEM:指定子系统,在PC桌面程序上一般是两个选项:console(控制台程序)和WINDOWS(非控制台程序)。

/out: 指定输出的文件。

link最后的参数是需要链接的obj文件和库文件。

cl和link的详细用法请参考MSDN和参考文献2《VC命令行编译C++》。

我们看到生成的obj文件和ConsoleTest.exe是放到当前的源码文件夹下。一般我们想把它放到debug文件夹下。那么我们该怎么做呢?这时就可以用到makefile中的一个常用部分——宏。我们可以这样定义一个宏,然后创建debug文件夹,具体代码是:

OUTDIR = .\Debug

#这里增加了一个输出:$(OUTDIR)

[plain] view plaincopy
  1. all: $(OUTDIR) $(OUTDIR)\ConsoleTest.exe

#假如不存在$(OUTDIR)文件夹,就创建它

[plain] view plaincopy
  1. $(OUTDIR) :
  2. if not exist "$(OUTDIR)" mkdir $(OUTDIR)

相应地,生成的obj文件和exe文件都需要加上输出文件的路径,具体如下:

[plain] view plaincopy
  1. # compile
  2. $(OUTDIR)\stdafx.obj: stdafx.cpp
  3. cl -c -D_X86=1 -DWIN32 -D_DEBUG -D_CONSOLE -Istdafx.h /Fo"$(OUTDIR)\\" /Fd"$(OUTDIR)\\" stdafx.cpp
  4. $(OUTDIR)/ConsoleTest.obj: ConsoleTest.cpp $(OUTDIR)\stdafx.obj
  5. cl -c -D_X86=1 -DWIN32 -D_DEBUG -D_CONSOLE -Istdafx.h /Fo"$(OUTDIR)\\" /Fd"$(OUTDIR)\\" ConsoleTest.cpp
  6. # link
  7. $(OUTDIR)\ConsoleTest.exe: $(OUTDIR)\ConsoleTest.obj
  8. link /INCREMENTAL:YES /NOLOGO /subsystem:console /out:$(OUTDIR)\ConsoleTest.exe $(OUTDIR)\ConsoleTest.obj kernel32.lib

这里cl工具增加了两个选项

/Fo:指定obj文件的放置路径

/Fd:指定pdb文件的放置路径

这里需要值得注意的,Windows平台下文件反斜杠应该采用\,而不是跨平台的/,因为我曾把OUTDIR = .\Debug写成OUTDIR = ./Debug,结果造成if not exist不识别$(OUTDIR)而造成语法错误。/在windows平台下的makefile中大多地方可以识别,但在一些地方不能识别(例如if not exist语句),而\在任何地方都能识别的。

还有就是命令语句必须至少空出一格,而不能顶格写。如果if not exist"$(OUTDIR)" mkdir $(OUTDIR)顶格,就会出现错误:

makefile.vc(5) : fatal error U1034: 语法错误 : 缺少分隔符

Stop.

除开命令语句,其它语句都应该顶格写。

我们继续完善这个makefile。我们想增加一个清理输出文件的指令,就是常用的clean指令。我们可以在描述块all后面加一个描述块:clean,clean描述块的代码如下:

[plain] view plaincopy
  1. clean:
  2. if exist $(OUTDIR) del $(OUTDIR)\*.ilk
  3. if exist $(OUTDIR) del $(OUTDIR)\*.obj
  4. if exist $(OUTDIR) del $(OUTDIR)\*.exe

如果makefile文件中不存在clean这个描述块,而你运行下面的命令:

nmake /f makefile.vc clean

会出现下面的错误提示:

NMAKE : fatal error U1052: 未找到文件“clean”

Stop.

我们继续完善这个makefile。因为现在只能编译debug版本,我们想用户能指定编译debug版本或release版本,用户只需要输入“debug”或“release”来指定。我们想到可以设定一个宏标记来指定,当用户输入正确时就编译相应的版本,错误时就提示使用方法。同时我们想到前面提到nmake工具的命令行用法是:

[plain] view plaincopy
  1. namke /f  makefile /x stderrfile  [macrodefs] [targets]

其中macrodefs就是允许我们定义一些自定义宏来控制编译输出的。这次我们可以定义两个宏debug和release。具体不再详述,下面列出代码:

[plain] view plaincopy
  1. #设置编译标记,初始化为FALSE
  2. CFGSET     =  FALSE
  3. #定义debug版本的预处理器
  4. CCDEBUG    = -DWIN32 -D_DEBUG -D_CONSOLE
  5. #定义release版本的预处理器
  6. CCNODBG    = -DWIN32 -D_NDEBUG -D_CONSOLE
  7. !IFDEF debug
  8. CC         = $(CCDEBUG)
  9. OUTDIR = .\Debug
  10. CFGSET     =  TRUE
  11. !ELSE IFDEF release
  12. CC         = $(CCNODBG)
  13. OUTDIR = .\Release
  14. CFGSET     =  TRUE
  15. !ENDIF
  16. # 提示用法
  17. #
  18. !IF "$(CFGSET)"== "FALSE"
  19. !MESSAGE Usage: nmake /f Makefile.vc [<config>] [<target>]
  20. !MESSAGE
  21. !MESSAGE where <config> is one of:
  22. !MESSAGE -  release=1               - build release version
  23. !MESSAGE -  debug=1                 - build debug version
  24. !MESSAGE
  25. !MESSAGE <target> may be:
  26. !MESSAGE -  clean                 - clear output file
  27. !MESSAGE
  28. !MESSAGE
  29. !ERROR please choose a valid configuration instead"
  30. !ENDIF
  31. #这里增加了一个输出:$(OUTDIR)
  32. all: $(OUTDIR) $(OUTDIR)\ConsoleTest.exe
  33. #假如不存在$(OUTDIR)文件夹,就创建它
  34. $(OUTDIR) :
  35. if not exist "$(OUTDIR)" mkdir $(OUTDIR)
  36. clean:
  37. if exist $(OUTDIR) del $(OUTDIR)\*.ilk
  38. if exist $(OUTDIR) del $(OUTDIR)\*.obj
  39. if exist $(OUTDIR) del $(OUTDIR)\*.exe
  40. # compile
  41. $(OUTDIR)\stdafx.obj: stdafx.cpp
  42. cl -c  $(CC) -Istdafx.h /Fo"$(OUTDIR)\\" /Fd"$(OUTDIR)\\" stdafx.cpp
  43. $(OUTDIR)\ConsoleTest.obj: ConsoleTest.cpp $(OUTDIR)\stdafx.obj
  44. cl -c  $(CC) -Istdafx.h /Fo"$(OUTDIR)\\" /Fd"$(OUTDIR)\\" ConsoleTest.cpp
  45. # link
  46. $(OUTDIR)\ConsoleTest.exe: $(OUTDIR)\ConsoleTest.obj
  47. link /machine:x86 /INCREMENTAL:YES /NOLOGO /subsystem:console /out:$(OUTDIR)\ConsoleTest.exe $(OUTDIR)\ConsoleTest.obj kernel32.lib

该makefile的用法是:

[plain] view plaincopy
  1. #编译debug版本
  2. nmake /f makefile.vc debug=1
  3. #编译release版本
  4. nmake /f makefile.vc release=1
  5. #清除debug版本
  6. nmake /f makefile.vc debug=1 clean
  7. #清除release版本
  8. nmake /f makefile.vc release=1 clean

参考文献:

1. MSDN 2008,Microsoft Corporation

2. VC命令行编译C++

3. 精通Windows API,范文庆、周彬彬、安靖编著

Windows平台下Makefile学习笔记相关推荐

  1. [转]Windows平台下Makefile学习笔记

    Windows平台下Makefile学习笔记(一) 作者:朱金灿 来源:http://blog.csdn.net/clever101 决心学习Makefile,一方面是为了解决编译开源代码时需要跨编译 ...

  2. windows平台下VLC2.0.5编译

    windows平台下VLC2.0.5编译说明 时隔一年多,又要搞流媒体了,不过这次是要做流媒体服务器. 暂时决定使用vlc+ffmpeg+live555,虽然听有些前辈说这个组合的性能较差,只能作为学 ...

  3. windows平台下vlc编译之十三:vlc1.1.0编译

    请移步https://higoge.github.io/,所有下载资料在那个博客都能找到.谢谢. --------------------------------------------------- ...

  4. windows平台下vlc编译之六:vlc-0.9.8a的编译

    请移步https://higoge.github.io/,所有下载资料在那个博客都能找到.谢谢. --------------------------------------------------- ...

  5. Windows平台下NS2网络仿真环境的搭建

    NS2(Network Simulator 2) 是一种针对网络技术的源代码公开的.免费的软件模拟平台,研究人员使用它可以很容易的进行网络技术的开发,而且发展到今天,它所包含的模块几乎涉及到了网络技术 ...

  6. windows平台下的mysql启动等基本操作

    一.windows下启动和停止mysql ======================= mysql安装好之后,需要启动mysql服务,否则无法访问到. 当我们在windows平台下,且使用二进制分发 ...

  7. Makefile学习笔记 - 我的CPP之路 - C++博客

    Makefile学习笔记 - 我的CPP之路 - C++博客 Makefile学习笔记 Makefile学习笔记 先列出一个很简单的Makefile例子: --------- hd.cpp #incl ...

  8. Windows进程与线程学习笔记(九)—— 线程优先级/进程挂靠/跨进程读写

    Windows进程与线程学习笔记(九)-- 线程优先级/进程挂靠/跨进程读写 要点回顾 线程优先级 调度链表 分析 KiFindReadyThread 分析 KiSwapThread 总结 进程挂靠 ...

  9. Windows进程与线程学习笔记(八)—— 线程切换与TSS/FS

    Windows进程与线程学习笔记(八)-- 线程切换与TSS/FS 要点回顾 线程切换与TSS 内核堆栈 调用API进0环 实验:分析SwapContext 线程切换与FS 段描述符结构 分析Swap ...

最新文章

  1. Seaborn使用lmplot函数可视化散点图并添加回归曲线以及回归线对应的置信区间(Scatter plot with regression line)
  2. Android10.0 Binder通信原理(八)-Framework层分析
  3. 在VS2010平台上创建并使用dll
  4. C语言面试题(四)--------------------网上题目
  5. 创建调用查询删除存储过程语法
  6. 已知两个链表A和B分别表示两个集合,其元素递增排列。请设计算法求出两个集合A和集合B的差集(近由在A中出现而不再B中出现的元素所构成的集合),并以同样的形式存储,同时返回该集合的元素个数。
  7. linux查看命令类型,查看linux命令类型
  8. SequoiaDB 系列之六 :源码分析之coord节点
  9. (5)ISE14.7 开发流程(FPGA不积跬步101)
  10. 4373支队伍报名2020数字中国创新大赛-数字政府赛道 数字战“疫”彰显社会责任...
  11. 修改华为服务器管理口地址,修改华为服务器管理口地址
  12. puppetmaster 、agent 证书管理相关
  13. 天津麒麟正式更名为麒麟软件
  14. 王阳明的心学精髓是什么?
  15. 分享个一拳超人辅助脚本,自动挂机刷金币/经验/副本工具
  16. Edison编译时显示No such file or directory
  17. 关于10的勾股数有哪些_关于“天风证券”“浪潮信息”的配股提示
  18. Android 个人中心界面实现
  19. 商城系统开发,使用微信服务号好?还是小程序?
  20. GAN(1)-生成对抗网络的开山之作

热门文章

  1. 规格参数组查询的代码实现
  2. 图片存储解决方案的分析
  3. 工程搭建:搭建子工程之分布式id生成器
  4. SpringBoot的配置文件-通过@ConfigurationProperties映射数据
  5. Netty--Future和Promise
  6. 【Homework】银行存取款业务
  7. linux 内核dmesg,linux內核調試kmsg,dmesg
  8. 1001. 温度转换
  9. 慕课-北京理工大学 机器学习 大学生上网时间 聚类,小白学习
  10. 数楼梯——恶心的高精斐波那契数列