作者:朱金灿

来源: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
plain
 copy

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

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

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

[plain] view
plain
 copy

  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
plain
 copy

  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
plain
 copy

  1. all:ConsoleTest.exe

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

[plain] view
plain
 copy

  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
plain
 copy

  1. all: $(OUTDIR) $(OUTDIR)\ConsoleTest.exe

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

[plain] view
plain
 copy

  1. $(OUTDIR) :
  2. if not exist "$(OUTDIR)" mkdir $(OUTDIR)

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

[plain] view
plain
 copy

  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
plain
 copy

  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
plain
 copy

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

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

[plain] view
plain
 copy

  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
plain
 copy

  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,范文庆、周彬彬、安靖编著


【VS开发】Windows平台下Makefile学习笔记的更多相关文章

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

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

  2. <转>Windows平台下Makefile学习笔记(二)

    本文转自:http://blog.csdn.net/clever101/article/details/8286066 上次我们学习了怎么用Makefile编译一个控制台工程.这次我们学习一下如何使用 ...

  3. <转>Windows平台下Makefile学习笔记(一)

    本文转自:http://blog.csdn.net/clever101/article/details/8147352 决心学习Makefile,一方面是为了解决编译开源代码时需要跨编译平台的问题(发 ...

  4. Windows平台下PHP开发环境的配置

    Windows平台下PHP开发环境的配置 一.基本环境 1.Windows XP 32位 2.Apache 2.2.25,下载地址:http://mirror.bit.edu.cn/apache/ht ...

  5. windows平台下VLC2.0.5编译

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

  6. Windows平台下MySQL常用操作与命令

    Windows平台下MySQL常用操作与命令 Windows平台下MySQL常用操作与命令,学习mysql的朋友可以参考下. 1.导出整个数据库 mysqldump -u 用户名 -p --defau ...

  7. 在Windows平台下Qt的exe报错问题排查步骤

    在Windows平台下Qt的exe报错问题排查步骤 工具介绍: 1. Dependency Worker Dependency Worker是一个免费的用具用来扫描任何的32bit 或者64bit 的 ...

  8. windows平台下nginx+PHP环境安装

    因为日常工作在windows下,为方便在window是下进行PHP开发,需要在windows平台下搭建PHP开发环境,web服务器选择nginx,不过windows版本的nginx性能要比Linux/ ...

  9. Windows 平台下 LiteIDE 的安装和使用

    1. 安装 Go 语言并设置环境变量 参考博客<Windows 平台下 Go 语言的安装和环境变量设置>. 2. MinGW 的下载和安装 Windows 下的 Go 调试还需要安装 Mi ...

随机推荐

  1. LeetCode 273. Integer to English Words

    原题链接在这里:https://leetcode.com/problems/integer-to-english-words/description/ 题目: Convert a non-negati ...

  2. php+超大文件上传

    1 背景 用户本地有一份txt或者csv文件,无论是从业务数据库导出.还是其他途径获取,当需要使用蚂蚁的大数据分析工具进行数据加工.挖掘和共创应用的时候,首先要将本地文件上传至ODPS,普通的小文件通 ...

  3. php rsa 非对称加解密类

    <?php header("Content-Type: text/html;charset=utf-8"); /* 生成公钥.私钥对,私钥加密的内容能通过公钥解密(反过来亦可 ...

  4. jQuery Cookie (内附 上百行的中文使用手册,与 所有的注释中文翻译)

    jQuery Cookie (内附 上百行的中文使用手册,与 所有的注释中文翻译) 博主亲自翻译. 大家多多捧场. 更多资源请点击"查看TA 的资源" .全场通通 2积分. htt ...

  5. Java核心复习——J.U.C LinkedBlockingQueue源码分析

    参考文档 LinkedBlockingQueue和ArrayBlockingQueue的异同

  6. 详解JDBC对象

    1. DriverManager (1) 注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); 真正注册驱动的是驱动包下 jdbc 文件夹 ...

  7. [Ubuntu] 14.04版本安装JDK8失败

    系统:Ubuntu 14.04 编译 Android 7.1 系统需要 JDK 8,但是安装时失败, $ -jdk Reading package lists... Done Building dep ...

  8. Java 面向对象(七)

    枚举 枚举的引入(模拟枚举) class Student { private int restDay; public int getRestDay() { return restDay; } publ ...

  9. 【scikit-learn】06:make_blobs聚类数据生成器

      版权声明:本文为博主原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/kevinelstri/article/ ...

  10. sklearn.GridSearchCV选择超参

    from sklearn import datasets from sklearn.model_selection import train_test_split from sklearn.model ...