├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── Makefile ├── README.rst ├── requirements.txt └── source ├── archives.rst ├── conditionals.rst ├── conf.py ├── functions.rst ├── implicit_rules.rst ├── index.rst ├── introduction.rst ├── invoke.rst ├── overview.rst ├── postscript.rst ├── recipes.rst ├── rules.rst └── variables.rst /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Build and deploy the documentation 3 | # 4 | name: Deploy 5 | 6 | on: 7 | pull_request: 8 | push: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | deploy: 14 | runs-on: ubuntu-latest 15 | defaults: 16 | run: 17 | shell: bash -l {0} 18 | steps: 19 | # Cancel previous runs that are not completed 20 | - name: Cancel Previous Runs 21 | uses: styfle/cancel-workflow-action@0.9.1 22 | 23 | - name: Checkout 24 | uses: actions/checkout@v2.4.0 25 | 26 | - name: Set up Python 27 | uses: actions/setup-python@v2 28 | with: 29 | python-version: '3.9' 30 | 31 | - name: Install dependencies 32 | run: pip install -r requirements.txt 33 | 34 | - name: Build HTML documentaiton 35 | run: make html 36 | 37 | - name: Install TinyTeX 38 | uses: r-lib/actions/setup-tinytex@v2 39 | 40 | - name: Install LaTeX packages 41 | run: | 42 | tlmgr update --self 43 | tlmgr install --repository http://mirrors.ustc.edu.cn/CTAN/systems/texlive/tlnet/ \ 44 | tabulary latexmk ulem environ trimspaces titlesec \ 45 | varwidth framed threeparttable wrapfig upquote capt-of \ 46 | multirow eqparbox needspace fncychap letltxmacro parskip \ 47 | fancyhdr ctex colortbl 48 | 49 | - name: Build PDF documentation 50 | run: | 51 | make latexpdf 52 | cp build/latex/Makefile.pdf build/html/ 53 | 54 | - name: Deploy documentation 55 | uses: peaceiris/actions-gh-pages@068dc23d9710f1ba62e86896f84735d869951305 56 | if: ${{ github.ref == 'refs/heads/main' }} 57 | with: 58 | github_token: ${{ secrets.GITHUB_TOKEN }} 59 | publish_dir: ./build/html 60 | # Only keep the latest commit to avoid bloating the repository 61 | force_orphan: true 62 | user_name: 'github-actions[bot]' 63 | user_email: 'github-actions[bot]@users.noreply.github.com' 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = source 8 | BUILDDIR = build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 20 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 跟我一起写Makefile (PDF重制版) 2 | ############################## 3 | 4 | .. image:: https://github.com/seisman/how-to-write-makefile/actions/workflows/deploy.yml/badge.svg 5 | :target: https://github.com/seisman/how-to-write-makefile/actions/workflows/deploy.yml 6 | 7 | 简介 8 | ---- 9 | 10 | 《跟我一起写Makefile》是 `陈皓`_ 发表在其CSDN博客上的系列文章。该系列文章翻译整理自 `GNU Make Manual`_ ,一直受到读者的推荐,是很多人学习Makefile的首选文档。目前网络上流传的PDF版本多为祝冬华整理的版本。这个版本的排版一般,代码部分没有做任何语法高亮。 11 | 12 | 2010年初学Makefile的时候,读了前几章皮毛,一直用到了现在。最近想着重新学习一下Makefile,顺便学习一下Sphinx,重新制作一个更精美的PDF版本。 13 | 14 | 相关 15 | ---- 16 | 17 | - 书的文字部分来自于 `Andriki`_ 提供的Mediawiki源码; 18 | - 使用 `Sphinx`_ 制作文档 19 | - 项目主页: https://github.com/seisman/how-to-write-makefile 20 | - 网页在线版: https://seisman.github.io/how-to-write-makefile/ 21 | - PDF下载: https://seisman.github.io/how-to-write-makefile/Makefile.pdf 22 | 23 | 本地编译 24 | -------- 25 | 26 | #. Clone项目到本地:: 27 | 28 | $ git clone https://github.com/seisman/how-to-write-makefile.git 29 | 30 | #. 安装依赖:: 31 | 32 | $ pip install -r requirements.txt 33 | 34 | #. 编译生成HTML:: 35 | 36 | $ make html 37 | $ firefox build/html/index.html& 38 | 39 | #. 编译生成PDF(要求安装TeXLive):: 40 | 41 | $ make latexpdf 42 | $ evince build/latex/Makefile.pdf& 43 | 44 | .. _`陈皓`: http://coolshell.cn/haoel 45 | .. _`Andriki`: http://andriki.com/mediawiki/index.php?title=Linux:%E8%B7%9F%E6%88%91%E4%B8%80%E8%B5%B7%E5%86%99Makefile 46 | .. _`Sphinx`: http://sphinx-doc.org/ 47 | .. _`GNU Make Manual`: https://www.gnu.org/software/make/manual/ 48 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinx_rtd_theme 3 | sphinx-cjkspace 4 | pygments 5 | -------------------------------------------------------------------------------- /source/archives.rst: -------------------------------------------------------------------------------- 1 | 使用make更新函数库文件 2 | ====================== 3 | 4 | 函数库文件也就是对Object文件(程序编译的中间文件)的打包文件。在Unix下,一般是由 5 | 命令 ``ar`` 来完成打包工作。 6 | 7 | 函数库文件的成员 8 | ---------------- 9 | 10 | 一个函数库文件由多个文件组成。你可以用如下格式指定函数库文件及其组成:: 11 | 12 | archive(member) 13 | 14 | 这个不是一个命令,而一个目标和依赖的定义。一般来说,这种用法基本上就是为了 15 | ``ar`` 命令来服务的。如:: 16 | 17 | foolib(hack.o) : hack.o 18 | ar cr foolib hack.o 19 | 20 | 如果要指定多个member,那就以空格分开,如:: 21 | 22 | foolib(hack.o kludge.o) 23 | 24 | 其等价于:: 25 | 26 | foolib(hack.o) foolib(kludge.o) 27 | 28 | 你还可以使用Shell的文件通配符来定义,如:: 29 | 30 | foolib(*.o) 31 | 32 | 函数库成员的隐含规则 33 | -------------------- 34 | 35 | 当make搜索一个目标的隐含规则时,一个特殊的特性是,如果这个目标是 ``a(m)`` 形式 36 | 的,其会把目标变成 ``(m)`` 。于是,如果我们的成员是 ``%.o`` 的模式定义,并且如果 37 | 我们使用 ``make foo.a(bar.o)`` 的形式调用Makefile时,隐含规则会去找 ``bar.o`` 的 38 | 规则,如果没有定义 ``bar.o`` 的规则,那么内建隐含规则生效,make会去找 ``bar.c`` 39 | 文件来生成 ``bar.o`` ,如果找得到的话,make执行的命令大致如下:: 40 | 41 | cc -c bar.c -o bar.o 42 | ar r foo.a bar.o 43 | rm -f bar.o 44 | 45 | 还有一个变量要注意的是 ``$%`` ,这是专属函数库文件的自动化变量,有关其说明请参 46 | 见“自动化变量”一节。 47 | 48 | 函数库文件的后缀规则 49 | -------------------- 50 | 51 | 你可以使用“后缀规则”和“隐含规则”来生成函数库打包文件,如: 52 | 53 | .. code-block:: makefile 54 | 55 | .c.a: 56 | $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $*.o 57 | $(AR) r $@ $*.o 58 | $(RM) $*.o 59 | 60 | 其等效于: 61 | 62 | .. code-block:: makefile 63 | 64 | (%.o) : %.c 65 | $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $*.o 66 | $(AR) r $@ $*.o 67 | $(RM) $*.o 68 | 69 | 70 | 注意事项 71 | -------- 72 | 73 | 在进行函数库打包文件生成时,请小心使用make的并行机制( ``-j`` 参数)。如果多个 74 | ``ar`` 命令在同一时间运行在同一个函数库打包文件上,就很有可以损坏这个函数库文件 75 | 。所以,在make未来的版本中,应该提供一种机制来避免并行操作发生在函数打包文件上。 76 | 77 | 但就目前而言,你还是应该不要尽量不要使用 ``-j`` 参数。 78 | -------------------------------------------------------------------------------- /source/conditionals.rst: -------------------------------------------------------------------------------- 1 | 使用条件判断 2 | ============ 3 | 4 | 使用条件判断,可以让make根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量的值, 5 | 或是比较变量和常量的值。 6 | 7 | 示例 8 | ---- 9 | 10 | 下面的例子,判断 ``$(CC)`` 变量是否 ``gcc`` ,如果是的话,则使用GNU函数编译目标。 11 | 12 | .. code-block:: makefile 13 | 14 | libs_for_gcc = -lgnu 15 | normal_libs = 16 | 17 | foo: $(objects) 18 | ifeq ($(CC),gcc) 19 | $(CC) -o foo $(objects) $(libs_for_gcc) 20 | else 21 | $(CC) -o foo $(objects) $(normal_libs) 22 | endif 23 | 24 | 可见,在上面示例的这个规则中,目标 ``foo`` 可以根据变量 ``$(CC)`` 值来选取不同的函数库来 25 | 编译程序。 26 | 27 | 我们可以从上面的示例中看到三个关键字: ``ifeq`` 、 ``else`` 和 ``endif`` 。 ``ifeq`` 的 28 | 意思表示条件语句的开始,并指定一个条件表达式,表达式包含两个参数,以逗号分隔,表达式以圆括号括 29 | 起。 ``else`` 表示条件表达式为假的情况。 ``endif`` 表示一个条件语句的结束,任何一个条件表达 30 | 式都应该以 ``endif`` 结束。 31 | 32 | 当我们的变量 ``$(CC)`` 值是 ``gcc`` 时,目标 ``foo`` 的规则是: 33 | 34 | .. code-block:: makefile 35 | 36 | foo: $(objects) 37 | $(CC) -o foo $(objects) $(libs_for_gcc) 38 | 39 | 而当我们的变量 ``$(CC)`` 值不是 ``gcc`` 时(比如 ``cc`` ),目标 ``foo`` 的规则是: 40 | 41 | .. code-block:: makefile 42 | 43 | foo: $(objects) 44 | $(CC) -o foo $(objects) $(normal_libs) 45 | 46 | 当然,我们还可以把上面的那个例子写得更简洁一些: 47 | 48 | .. code-block:: makefile 49 | 50 | libs_for_gcc = -lgnu 51 | normal_libs = 52 | 53 | ifeq ($(CC),gcc) 54 | libs=$(libs_for_gcc) 55 | else 56 | libs=$(normal_libs) 57 | endif 58 | 59 | foo: $(objects) 60 | $(CC) -o foo $(objects) $(libs) 61 | 62 | 语法 63 | ---- 64 | 65 | 条件表达式的语法为:: 66 | 67 | 68 | 69 | endif 70 | 71 | 以及:: 72 | 73 | 74 | 75 | else 76 | 77 | endif 78 | 79 | 其中 ```` 表示条件关键字,如 ``ifeq`` 。这个关键字有四个。 80 | 81 | 第一个是我们前面所见过的 ``ifeq`` 82 | 83 | .. code-block:: makefile 84 | 85 | ifeq (, ) 86 | ifeq '' '' 87 | ifeq "" "" 88 | ifeq "" '' 89 | ifeq '' "" 90 | 91 | 比较参数 ``arg1`` 和 ``arg2`` 的值是否相同。当然,参数中我们还可以使用make的函数。如:: 92 | 93 | ifeq ($(strip $(foo)),) 94 | 95 | endif 96 | 97 | 这个示例中使用了 ``strip`` 函数,如果这个函数的返回值是空(Empty),那么 98 | ```` 就生效。 99 | 100 | 第二个条件关键字是 ``ifneq`` 。语法是: 101 | 102 | .. code-block:: makefile 103 | 104 | ifneq (, ) 105 | ifneq '' '' 106 | ifneq "" "" 107 | ifneq "" '' 108 | ifneq '' "" 109 | 110 | 其比较参数 ``arg1`` 和 ``arg2`` 的值是否相同,如果不同,则为真。和 ``ifeq`` 类似。 111 | 112 | 第三个条件关键字是 ``ifdef`` 。语法是: 113 | 114 | .. code-block:: makefile 115 | 116 | ifdef 117 | 118 | 如果变量 ```` 的值非空,那到表达式为真。否则,表达式为假。当然, 119 | ```` 同样可以是一个函数的返回值。注意, ``ifdef`` 只是测试一个变量 120 | 是否有值,其并不会把变量扩展到当前位置。还是来看两个例子: 121 | 122 | 示例一: 123 | 124 | .. code-block:: makefile 125 | 126 | bar = 127 | foo = $(bar) 128 | ifdef foo 129 | frobozz = yes 130 | else 131 | frobozz = no 132 | endif 133 | 134 | 示例二: 135 | 136 | .. code-block:: makefile 137 | 138 | foo = 139 | ifdef foo 140 | frobozz = yes 141 | else 142 | frobozz = no 143 | endif 144 | 145 | 第一个例子中, ``$(frobozz)`` 值是 ``yes`` ,第二个则是 ``no``。 146 | 147 | 第四个条件关键字是 ``ifndef`` 。其语法是: 148 | 149 | .. code-block:: makefile 150 | 151 | ifndef 152 | 153 | 这个我就不多说了,和 ``ifdef`` 是相反的意思。 154 | 155 | 在 ```` 这一行上,多余的空格是被允许的,但是不能以 ``Tab`` 键 156 | 作为开始(不然就被认为是命令)。而注释符 ``#`` 同样也是安全的。 ``else`` 和 ``endif`` 157 | 也一样,只要不是以 ``Tab`` 键开始就行了。 158 | 159 | 特别注意的是,make是在读取Makefile时就计算条件表达式的值,并根据条件表达式的值来选择语句, 160 | 所以,你最好不要把自动化变量(如 ``$@`` 等)放入条件表达式中,因为自动化变量是在运行时才有的。 161 | 162 | 而且为了避免混乱,make不允许把整个条件语句分成两部分放在不同的文件中。 163 | -------------------------------------------------------------------------------- /source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # 跟我一起写Makefile documentation build configuration file, created by 4 | # sphinx-quickstart on Tue Feb 25 20:24:37 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | # If extensions (or modules to document with autodoc) are in another directory, 16 | # add these directories to sys.path here. If the directory is relative to the 17 | # documentation root, use os.path.abspath to make it absolute, like shown here. 18 | # 19 | import os 20 | import sys 21 | # sys.path.insert(0, os.path.abspath('.')) 22 | 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | # 28 | # needs_sphinx = '1.5' 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = ['sphinx.ext.todo', 34 | 'sphinx.ext.viewcode', 35 | 'sphinx.ext.githubpages', 36 | 'sphinx_cjkspace.cjkspace'] 37 | 38 | # Add any paths that contain templates here, relative to this directory. 39 | templates_path = ['_templates'] 40 | 41 | # The suffix(es) of source filenames. 42 | # You can specify multiple suffix as a list of string: 43 | # 44 | # source_suffix = ['.rst', '.md'] 45 | source_suffix = '.rst' 46 | 47 | # The master toctree document. 48 | master_doc = 'index' 49 | 50 | # General information about the project. 51 | project = u'跟我一起写Makefile' 52 | copyright = u'2014-2019, 作者:陈皓;排版:SeisMan' 53 | author = 'SeisMan' 54 | 55 | # The version info for the project you're documenting, acts as replacement for 56 | # |version| and |release|, also used in various other places throughout the 57 | # built documents. 58 | # 59 | # The short X.Y version. 60 | version = '1.0' 61 | # The full version, including alpha/beta/rc tags. 62 | release = '1.0' 63 | 64 | # The language for content autogenerated by Sphinx. Refer to documentation 65 | # for a list of supported languages. 66 | # 67 | # This is also used if you do content translation via gettext catalogs. 68 | # Usually you set "language" from the command line for these cases. 69 | language = 'zh_CN' 70 | 71 | # List of patterns, relative to source directory, that match files and 72 | # directories to ignore when looking for source files. 73 | # This patterns also effect to html_static_path and html_extra_path 74 | exclude_patterns = [] 75 | 76 | # The name of the Pygments (syntax highlighting) style to use. 77 | pygments_style = 'sphinx' 78 | 79 | # If true, `todo` and `todoList` produce output, else they produce nothing. 80 | todo_include_todos = True 81 | 82 | 83 | # -- Options for HTML output ---------------------------------------------- 84 | on_rtd = os.environ.get('READTHEDOCS', None) == 'True' 85 | 86 | # The theme to use for HTML and HTML Help pages. See the documentation for 87 | # a list of builtin themes. 88 | if on_rtd: 89 | html_theme = 'default' 90 | else: 91 | import sphinx_rtd_theme 92 | html_theme = 'sphinx_rtd_theme' 93 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 94 | 95 | # Theme options are theme-specific and customize the look and feel of a theme 96 | # further. For a list of options available for each theme, see the 97 | # documentation. 98 | # 99 | # html_theme_options = {} 100 | 101 | # Add any paths that contain custom static files (such as style sheets) here, 102 | # relative to this directory. They are copied after the builtin static files, 103 | # so a file named "default.css" will overwrite the builtin "default.css". 104 | html_static_path = ['_static'] 105 | 106 | 107 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 108 | # using the given strftime format. 109 | html_last_updated_fmt = '%x' 110 | 111 | # If true, SmartyPants will be used to convert quotes and dashes to 112 | # typographically correct entities. 113 | #html_use_smartypants = True 114 | 115 | # Custom sidebar templates, maps document names to template names. 116 | #html_sidebars = {} 117 | 118 | # Additional templates that should be rendered to pages, maps page names to 119 | # template names. 120 | #html_additional_pages = {} 121 | 122 | # If false, no module index is generated. 123 | html_domain_indices = False 124 | 125 | # If false, no index is generated. 126 | html_use_index = True 127 | 128 | # If true, the index is split into individual pages for each letter. 129 | html_split_index = False 130 | 131 | # If true, links to the reST sources are added to the pages. 132 | html_show_sourcelink = True 133 | 134 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 135 | #html_show_sphinx = True 136 | 137 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 138 | # html_show_copyright = True 139 | 140 | # If true, an OpenSearch description file will be output, and all pages will 141 | # contain a tag referring to it. The value of this option must be the 142 | # base URL from which the finished HTML is served. 143 | #html_use_opensearch = '' 144 | 145 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 146 | #html_file_suffix = None 147 | 148 | # Output file base name for HTML help builder. 149 | htmlhelp_basename = 'Makefiledoc' 150 | 151 | 152 | # -- Options for LaTeX output --------------------------------------------- 153 | latex_engine="xelatex" 154 | latex_elements = { 155 | 'papersize' : 'a4paper', 156 | 'figure_align' : 'H', 157 | 'fncychap' : '', # use default chapter style from ctex 158 | 'babel' : '', 159 | 'polyglossia': '', 160 | 'fontpkg' : '', 161 | 'cmappkg' : '', 162 | 'fontenc' : '', 163 | 'utf8extra' : '', 164 | 'inputenc' : '', 165 | 'release' : '', 166 | 'hyperref': r''' 167 | \usepackage{hyperref} 168 | \urlstyle{same} 169 | ''', 170 | 'maketitle' : '\\maketitle', 171 | 'preamble' : r''' 172 | \usepackage{ctex} 173 | \parindent 2em 174 | \setcounter{tocdepth}{3} 175 | ''' 176 | } 177 | 178 | # Grouping the document tree into LaTeX files. List of tuples 179 | # (source start file, target name, title, 180 | # author, documentclass [howto, manual, or own class]). 181 | latex_documents = [ 182 | (master_doc, 'Makefile.tex', 183 | u'跟我一起写Makefile (PDF重制版)', 184 | u'作者: 陈皓', 'manual', True), 185 | ] 186 | 187 | # -- Options for manual page output --------------------------------------- 188 | 189 | # One entry per manual page. List of tuples 190 | # (source start file, name, description, authors, manual section). 191 | man_pages = [ 192 | (master_doc, 'makefile', u'跟我一起写Makefile', 193 | [u'陈皓'], 1) 194 | ] 195 | 196 | 197 | # -- Options for Texinfo output ------------------------------------------- 198 | 199 | # Grouping the document tree into Texinfo files. List of tuples 200 | # (source start file, target name, title, author, 201 | # dir menu entry, description, category) 202 | texinfo_documents = [ 203 | (master_doc, 'Makefile', u'跟我一起写Makefile', 204 | u'陈皓', 'Makefile', 'One line description of project.', 205 | 'Miscellaneous'), 206 | ] 207 | -------------------------------------------------------------------------------- /source/functions.rst: -------------------------------------------------------------------------------- 1 | 使用函数 2 | ======== 3 | 4 | 在Makefile中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和具有智能。make 5 | 所支持的函数也不算很多,不过已经足够我们的操作了。函数调用后,函数的返回值可以当做变量来使用。 6 | 7 | 函数的调用语法 8 | -------------- 9 | 10 | 函数调用,很像变量的使用,也是以 ``$`` 来标识的,其语法如下: 11 | 12 | .. code-block:: makefile 13 | 14 | $( ) 15 | 16 | 或是:: 17 | 18 | ${ } 19 | 20 | 这里, ```` 就是函数名,make支持的函数不多。 ```` 为函数的参数, 21 | 参数间以逗号 ``,`` 分隔,而函数名和参数之间以“空格”分隔。函数调用以 ``$`` 开头,以圆括号 22 | 或花括号把函数名和参数括起。感觉很像一个变量,是不是?函数中的参数可以使用变量,为了风格的 23 | 统一,函数和变量的括号最好一样,如使用 ``$(subst a,b,$(x))`` 这样的形式,而不是 24 | ``$(subst a,b, ${x})`` 的形式。因为统一会更清楚,也会减少一些不必要的麻烦。 25 | 26 | 还是来看一个示例: 27 | 28 | .. code-block:: makefile 29 | 30 | comma:= , 31 | empty:= 32 | space:= $(empty) $(empty) 33 | foo:= a b c 34 | bar:= $(subst $(space),$(comma),$(foo)) 35 | 36 | 在这个示例中, ``$(comma)`` 的值是一个逗号。 ``$(space)`` 使用了 ``$(empty)`` 定义了 37 | 一个空格, ``$(foo)`` 的值是 ``a b c`` , ``$(bar)`` 的定义用,调用了函数 ``subst`` , 38 | 这是一个替换函数,这个函数有三个参数,第一个参数是被替换字串,第二个参数是替换字串,第三个参数 39 | 是替换操作作用的字串。这个函数也就是把 ``$(foo)`` 中的空格替换成逗号,所以 ``$(bar)`` 的值 40 | 是 ``a,b,c`` 。 41 | 42 | 字符串处理函数 43 | -------------- 44 | 45 | subst 46 | ~~~~~ 47 | 48 | .. code-block:: makefile 49 | 50 | $(subst ,,) 51 | 52 | - 名称:字符串替换函数 53 | - 功能:把字串 ```` 中的 ```` 字符串替换成 ```` 。 54 | - 返回:函数返回被替换过后的字符串。 55 | 56 | - 示例: 57 | 58 | .. code-block:: makefile 59 | 60 | $(subst ee,EE,feet on the street) 61 | 62 | 把 ``feet on the street`` 中的 ``ee`` 替换成 ``EE`` ,返回结果是 ``fEEt on the strEEt`` 。 63 | 64 | patsubst 65 | ~~~~~~~~ 66 | 67 | .. code-block:: makefile 68 | 69 | $(patsubst ,,) 70 | 71 | - 名称:模式字符串替换函数。 72 | - 功能:查找 ```` 中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式 73 | ```` ,如果匹配的话,则以 ```` 替换。这里, ```` 可以 74 | 包括通配符 ``%`` ,表示任意长度的字串。如果 ```` 中也包含 ``%`` ,那么, 75 | ```` 中的这个 ``%`` 将是 ```` 中的那个 ``%`` 所代表的字串。 76 | (可以用 ``\`` 来转义,以 ``\%`` 来表示真实含义的 ``%`` 字符) 77 | - 返回:函数返回被替换过后的字符串。 78 | - 示例: 79 | 80 | .. code-block:: makefile 81 | 82 | $(patsubst %.c,%.o,x.c.c bar.c) 83 | 84 | 把字串 ``x.c.c bar.c`` 符合模式 ``%.c`` 的单词替换成 ``%.o`` ,返回结果是 85 | ``x.c.o bar.o`` 86 | 87 | - 备注:这和我们前面“变量章节”说过的相关知识有点相似。如 88 | ``$(var:=;)`` 相当于 89 | ``$(patsubst ,,$(var))`` ,而 90 | ``$(var: =)`` 则相当于 91 | ``$(patsubst %,%,$(var))`` 。 92 | 93 | 例如有:: 94 | 95 | objects = foo.o bar.o baz.o, 96 | 97 | 那么, ``$(objects:.o=.c)`` 和 ``$(patsubst %.o,%.c,$(objects))`` 是一样的。 98 | 99 | strip 100 | ~~~~~ 101 | 102 | .. code-block:: makefile 103 | 104 | $(strip ) 105 | 106 | - 名称:去空格函数。 107 | - 功能:去掉 ```` 字串中开头和结尾的空字符。 108 | - 返回:返回被去掉空格的字符串值。 109 | - 示例: 110 | 111 | .. code-block:: makefile 112 | 113 | $(strip a b c ) 114 | 115 | 把字串 |abc| 去掉开头和结尾的空格,结果是 ``a b c``。 116 | 117 | .. |abc| raw:: html 118 | 119 | a b c  120 | 121 | findstring 122 | ~~~~~~~~~~ 123 | 124 | .. code-block:: makefile 125 | 126 | $(findstring ,) 127 | 128 | - 名称:查找字符串函数 129 | - 功能:在字串 ```` 中查找 ```` 字串。 130 | - 返回:如果找到,那么返回 ```` ,否则返回空字符串。 131 | - 示例: 132 | 133 | .. code-block:: makefile 134 | 135 | $(findstring a,a b c) 136 | $(findstring a,b c) 137 | 138 | 第一个函数返回 ``a`` 字符串,第二个返回空字符串 139 | 140 | filter 141 | ~~~~~~ 142 | 143 | .. code-block:: makefile 144 | 145 | $(filter ,) 146 | 147 | - 名称:过滤函数 148 | - 功能:以 ```` 模式过滤 ```` 字符串中的单词,保留符合模式 149 | ```` 的单词。可以有多个模式。 150 | - 返回:返回符合模式 ```` 的字串。 151 | - 示例: 152 | 153 | .. code-block:: makefile 154 | 155 | sources := foo.c bar.c baz.s ugh.h 156 | foo: $(sources) 157 | cc $(filter %.c %.s,$(sources)) -o foo 158 | 159 | ``$(filter %.c %.s,$(sources))`` 返回的值是 ``foo.c bar.c baz.s`` 。 160 | 161 | filter-out 162 | ~~~~~~~~~~ 163 | 164 | .. code-block:: makefile 165 | 166 | $(filter-out ,) 167 | 168 | - 名称:反过滤函数 169 | - 功能:以 ```` 模式过滤 ```` 字符串中的单词,去除符合模式 170 | ```` 的单词。可以有多个模式。 171 | - 返回:返回不符合模式 ```` 的字串。 172 | - 示例: 173 | 174 | .. code-block:: makefile 175 | 176 | objects=main1.o foo.o main2.o bar.o 177 | mains=main1.o main2.o 178 | 179 | ``$(filter-out $(mains),$(objects))`` 返回值是 ``foo.o bar.o`` 。 180 | 181 | sort 182 | ~~~~ 183 | 184 | .. code-block:: makefile 185 | 186 | $(sort ) 187 | 188 | - 名称:排序函数 189 | - 功能:给字符串 ```` 中的单词排序(升序)。 190 | - 返回:返回排序后的字符串。 191 | - 示例: ``$(sort foo bar lose)`` 返回 ``bar foo lose`` 。 192 | - 备注: ``sort`` 函数会去掉 ```` 中相同的单词。 193 | 194 | word 195 | ~~~~ 196 | 197 | .. code-block:: makefile 198 | 199 | $(word ,) 200 | 201 | - 名称:取单词函数 202 | - 功能:取字符串 ```` 中第 ```` 个单词。(从一开始) 203 | - 返回:返回字符串 ```` 中第 ```` 个单词。如果 ```` 比 ```` 中的 204 | 单词数要大,那么返回空字符串。 205 | - 示例: ``$(word 2, foo bar baz)`` 返回值是 ``bar`` 。 206 | 207 | wordlist 208 | ~~~~~~~~ 209 | 210 | .. code-block:: makefile 211 | 212 | $(wordlist ,,) 213 | 214 | - 名称:取单词串函数 215 | - 功能:从字符串 ```` 中取从 ```` 开始到 ```` 的单词串。 ```` 216 | 和 ```` 是一个数字。 217 | - 返回:返回字符串 ```` 中从 ```` 到 ```` 的单词字串。如果 ```` 218 | 比 ```` 中的单词数要大,那么返回空字符串。如果 ```` 大于 ```` 的单词数, 219 | 那么返回从 ```` 开始,到 ```` 结束的单词串。 220 | - 示例: ``$(wordlist 2, 3, foo bar baz)`` 返回值是 ``bar baz`` 。 221 | 222 | words 223 | ~~~~~ 224 | 225 | .. code-block:: makefile 226 | 227 | $(words ) 228 | 229 | - 名称:单词个数统计函数 230 | - 功能:统计 ```` 中字符串中的单词个数。 231 | - 返回:返回 ```` 中的单词数。 232 | - 示例: ``$(words, foo bar baz)`` 返回值是 ``3`` 。 233 | - 备注:如果我们要取 ```` 中最后的一个单词,我们可以这样: 234 | ``$(word $(words ),)`` 。 235 | 236 | firstword 237 | ~~~~~~~~~ 238 | 239 | .. code-block:: makefile 240 | 241 | $(firstword ) 242 | 243 | - 名称:首单词函数——firstword。 244 | - 功能:取字符串 ```` 中的第一个单词。 245 | - 返回:返回字符串 ```` 的第一个单词。 246 | - 示例: ``$(firstword foo bar)`` 返回值是 ``foo``。 247 | - 备注:这个函数可以用 ``word`` 函数来实现: ``$(word 1,)`` 。 248 | 249 | 以上,是所有的字符串操作函数,如果搭配混合使用,可以完成比较复杂的功能。这里,举一个现实中 250 | 应用的例子。我们知道,make使用 ``VPATH`` 变量来指定“依赖文件”的搜索路径。于是,我们可以 251 | 利用这个搜索路径来指定编译器对头文件的搜索路径参数 ``CFLAGS`` ,如: 252 | 253 | .. code-block:: makefile 254 | 255 | override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH))) 256 | 257 | 如果我们的 ``$(VPATH)`` 值是 ``src:../headers`` ,那么 258 | ``$(patsubst %,-I%,$(subst :, ,$(VPATH)))`` 将返回 ``-Isrc -I../headers`` , 259 | 这正是cc或gcc搜索头文件路径的参数。 260 | 261 | 文件名操作函数 262 | -------------- 263 | 264 | 下面我们要介绍的函数主要是处理文件名的。每个函数的参数字符串都会被当做一个或是一系列的文件名 265 | 来对待。 266 | 267 | dir 268 | ~~~ 269 | 270 | .. code-block:: makefile 271 | 272 | $(dir ) 273 | 274 | - 名称:取目录函数——dir。 275 | - 功能:从文件名序列 ```` 中取出目录部分。目录部分是指最后一个反斜杠( ``/`` )之前 276 | 的部分。如果没有反斜杠,那么返回 ``./`` 。 277 | - 返回:返回文件名序列 ```` 的目录部分。 278 | - 示例: ``$(dir src/foo.c hacks)`` 返回值是 ``src/ ./`` 。 279 | 280 | notdir 281 | ~~~~~~ 282 | 283 | .. code-block:: makefile 284 | 285 | $(notdir ) 286 | 287 | - 名称:取文件函数——notdir。 288 | - 功能:从文件名序列 ```` 中取出非目录部分。非目录部分是指最後一个反斜杠( ``/`` ) 289 | 之后的部分。 290 | - 返回:返回文件名序列 ```` 的非目录部分。 291 | - 示例: ``$(notdir src/foo.c hacks)`` 返回值是 ``foo.c hacks`` 。 292 | 293 | suffix 294 | ~~~~~~ 295 | 296 | .. code-block:: makefile 297 | 298 | $(suffix ) 299 | 300 | - 名称:取後缀函数——suffix。 301 | - 功能:从文件名序列 ```` 中取出各个文件名的后缀。 302 | - 返回:返回文件名序列 ```` 的后缀序列,如果文件没有后缀,则返回空字串。 303 | - 示例: ``$(suffix src/foo.c src-1.0/bar.c hacks)`` 返回值是 ``.c .c``。 304 | 305 | basename 306 | ~~~~~~~~ 307 | 308 | .. code-block:: makefile 309 | 310 | $(basename ) 311 | 312 | - 名称:取前缀函数——basename。 313 | - 功能:从文件名序列 ```` 中取出各个文件名的前缀部分。 314 | - 返回:返回文件名序列 ```` 的前缀序列,如果文件没有前缀,则返回空字串。 315 | - 示例: ``$(basename src/foo.c src-1.0/bar.c hacks)`` 返回值是 316 | ``src/foo src-1.0/bar hacks`` 。 317 | 318 | addsuffix 319 | ~~~~~~~~~ 320 | 321 | .. code-block:: makefile 322 | 323 | $(addsuffix ,) 324 | 325 | - 名称:加后缀函数——addsuffix。 326 | - 功能:把后缀 ```` 加到 ```` 中的每个单词后面。 327 | - 返回:返回加过后缀的文件名序列。 328 | - 示例: ``$(addsuffix .c,foo bar)`` 返回值是 ``foo.c bar.c`` 。 329 | 330 | addprefix 331 | ~~~~~~~~~ 332 | 333 | .. code-block:: makefile 334 | 335 | $(addprefix ,) 336 | 337 | - 名称:加前缀函数——addprefix。 338 | - 功能:把前缀 ```` 加到 ```` 中的每个单词前面。 339 | - 返回:返回加过前缀的文件名序列。 340 | - 示例: ``$(addprefix src/,foo bar)`` 返回值是 ``src/foo src/bar`` 。 341 | 342 | join 343 | ~~~~ 344 | 345 | .. code-block:: makefile 346 | 347 | $(join ,) 348 | 349 | - 名称:连接函数——join。 350 | - 功能:把 ```` 中的单词对应地加到 ```` 的单词后面。如果 ```` 的 351 | 单词个数要比 ```` 的多,那么, ```` 中的多出来的单词将保持原样。如果 352 | ```` 的单词个数要比 ```` 多,那么, ```` 多出来的单词将被复制到 353 | ```` 中。 354 | - 返回:返回连接过后的字符串。 355 | - 示例: ``$(join aaa bbb , 111 222 333)`` 返回值是 ``aaa111 bbb222 333`` 。 356 | 357 | foreach 函数 358 | ------------ 359 | 360 | foreach函数和别的函数非常的不一样。因为这个函数是用来做循环用的,Makefile中的foreach函数 361 | 几乎是仿照于Unix标准Shell(/bin/sh)中的for语句,或是C-Shell(/bin/csh)中的foreach语句 362 | 而构建的。它的语法是: 363 | 364 | .. code-block:: makefile 365 | 366 | $(foreach ,,) 367 | 368 | 这个函数的意思是,把参数 ```` 中的单词逐一取出放到参数 ```` 所指定的变量中, 369 | 然后再执行 ```` 所包含的表达式。每一次 ```` 会返回一个字符串,循环过程中, 370 | ```` 的所返回的每个字符串会以空格分隔,最后当整个循环结束时, ```` 所返回的 371 | 每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。 372 | 373 | 所以, ```` 最好是一个变量名, ```` 可以是一个表达式,而 ```` 中一般会 374 | 使用 ```` 这个参数来依次枚举 ```` 中的单词。举个例子: 375 | 376 | .. code-block:: makefile 377 | 378 | names := a b c d 379 | 380 | files := $(foreach n,$(names),$(n).o) 381 | 382 | 上面的例子中, ``$(name)`` 中的单词会被挨个取出,并存到变量 ``n`` 中, ``$(n).o`` 每次 383 | 根据 ``$(n)`` 计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以, ``$(files)`` 384 | 的值是 ``a.o b.o c.o d.o`` 。 385 | 386 | 注意,foreach中的 ```` 参数是一个临时的局部变量,foreach函数执行完后,参数 ```` 387 | 的变量将不在作用,其作用域只在foreach函数当中。 388 | 389 | if 函数 390 | ------- 391 | 392 | if函数很像GNU的make所支持的条件语句——ifeq(参见前面所述的章节),if函数的语法是: 393 | 394 | .. code-block:: makefile 395 | 396 | $(if ,) 397 | 398 | 或是 399 | 400 | .. code-block:: makefile 401 | 402 | $(if ,,) 403 | 404 | 可见,if函数可以包含“else”部分,或是不含。即if函数的参数可以是两个,也可以是三个。 405 | ```` 参数是if的表达式,如果其返回的为非空字符串,那么这个表达式就相当于返回真, 406 | 于是, ```` 会被计算,否则 ```` 会被计算。 407 | 408 | 而if函数的返回值是,如果 ```` 为真(非空字符串),那个 ```` 409 | 会是整个函数的返回值,如果 ```` 为假(空字符串),那么 ```` 会是 410 | 整个函数的返回值,此时如果 ```` 没有被定义,那么,整个函数返回空字串。 411 | 412 | 所以, ```` 和 ```` 只会有一个被计算。 413 | 414 | call函数 415 | -------- 416 | 417 | call函数是唯一一个可以用来创建新的参数化的函数。你可以写一个非常复杂的表达式,这个表达式中, 418 | 你可以定义许多参数,然后你可以call函数来向这个表达式传递参数。其语法是: 419 | 420 | .. code-block:: makefile 421 | 422 | $(call ,,,...,) 423 | 424 | 当make执行这个函数时, ```` 参数中的变量,如 ``$(1)`` 、 ``$(2)`` 等,会 425 | 被参数 ```` 、 ```` 、 ```` 依次取代。而 ```` 的 426 | 返回值就是 call 函数的返回值。例如: 427 | 428 | .. code-block:: makefile 429 | 430 | reverse = $(1) $(2) 431 | 432 | foo = $(call reverse,a,b) 433 | 434 | 那么, ``foo`` 的值就是 ``a b`` 。当然,参数的次序是可以自定义的,不一定是顺序的,如: 435 | 436 | .. code-block:: makefile 437 | 438 | reverse = $(2) $(1) 439 | 440 | foo = $(call reverse,a,b) 441 | 442 | 此时的 ``foo`` 的值就是 ``b a`` 。 443 | 444 | 需要注意:在向 call 函数传递参数时要尤其注意空格的使用。call 函数在处理参数时,第2个及其之后的 445 | 参数中的空格会被保留,因而可能造成一些奇怪的效果。因而在向call函数提供参数时,最安全的做法是 446 | 去除所有多余的空格。 447 | 448 | origin函数 449 | ---------- 450 | 451 | origin函数不像其它的函数,他并不操作变量的值,他只是告诉你你的这个变量是哪里来的?其语法是: 452 | 453 | .. code-block:: makefile 454 | 455 | $(origin ) 456 | 457 | 注意, ```` 是变量的名字,不应该是引用。所以你最好不要在 ```` 中使用 458 | ``$`` 字符。Origin函数会以其返回值来告诉你这个变量的“出生情况”,下面,是origin函数的返回值: 459 | 460 | ``undefined`` 461 | 如果 ```` 从来没有定义过,origin函数返回这个值 ``undefined`` 462 | ``default`` 463 | 如果 ```` 是一个默认的定义,比如“CC”这个变量,这种变量我们将在后面讲述。 464 | ``environment`` 465 | 如果 ```` 是一个环境变量,并且当Makefile被执行时, ``-e`` 参数没有被打开。 466 | ``file`` 467 | 如果 ```` 这个变量被定义在Makefile中。 468 | ``command line`` 469 | 如果 ```` 这个变量是被命令行定义的。 470 | ``override`` 471 | 如果 ```` 是被override指示符重新定义的。 472 | ``automatic`` 473 | 如果 ```` 是一个命令运行中的自动化变量。关于自动化变量将在后面讲述。 474 | 475 | 这些信息对于我们编写Makefile是非常有用的,例如,假设我们有一个Makefile其包了一个定义文件 476 | Make.def,在 Make.def中定义了一个变量“bletch”,而我们的环境中也有一个环境变量“bletch”, 477 | 此时,我们想判断一下,如果变量来源于环境,那么我们就把之重定义了,如果来源于Make.def或是命令行 478 | 等非环境的,那么我们就不重新定义它。于是,在我们的Makefile中,我们可以这样写: 479 | 480 | .. code-block:: makefile 481 | 482 | ifdef bletch 483 | ifeq "$(origin bletch)" "environment" 484 | bletch = barf, gag, etc. 485 | endif 486 | endif 487 | 488 | 当然,你也许会说,使用 ``override`` 关键字不就可以重新定义环境中的变量了吗?为什么需要使用这样 489 | 的步骤?是的,我们用 ``override`` 是可以达到这样的效果,可是 ``override`` 过于粗暴,它同时 490 | 会把从命令行定义的变量也覆盖了,而我们只想重新定义环境传来的,而不想重新定义命令行传来的。 491 | 492 | shell函数 493 | --------- 494 | 495 | shell函数也不像其它的函数。顾名思义,它的参数应该就是操作系统Shell的命令。它和反引号“`”是 496 | 相同的功能。这就是说,shell函数把执行操作系统命令后的输出作为函数返回。于是,我们可以用操作 497 | 系统命令以及字符串处理命令awk,sed等等命令来生成一个变量,如: 498 | 499 | .. code-block:: makefile 500 | 501 | contents := $(shell cat foo) 502 | files := $(shell echo *.c) 503 | 504 | 注意,这个函数会新生成一个Shell程序来执行命令,所以你要注意其运行性能,如果你的Makefile中 505 | 有一些比较复杂的规则,并大量使用了这个函数,那么对于你的系统性能是有害的。特别是Makefile的 506 | 隐式规则可能会让你的shell函数执行的次数比你想像的多得多。 507 | 508 | 控制make的函数 509 | -------------- 510 | 511 | make提供了一些函数来控制make的运行。通常,你需要检测一些运行Makefile时的运行时信息,并且 512 | 根据这些信息来决定,你是让make继续执行,还是停止。 513 | 514 | .. code-block:: makefile 515 | 516 | $(error ) 517 | 518 | 519 | 产生一个致命的错误, ```` 是错误信息。注意,error函数不会在一被使用就会产生错误 520 | 信息,所以如果你把其定义在某个变量中,并在后续的脚本中使用这个变量,那么也是可以的。例如: 521 | 522 | 示例一: 523 | 524 | .. code-block:: makefile 525 | 526 | ifdef ERROR_001 527 | $(error error is $(ERROR_001)) 528 | endif 529 | 530 | 示例二: 531 | 532 | .. code-block:: makefile 533 | 534 | ERR = $(error found an error!) 535 | 536 | .PHONY: err 537 | 538 | err: $(ERR) 539 | 540 | 示例一会在变量ERROR_001定义了后执行时产生error调用,而示例二则在目录err被执行时才发生error调用。 541 | 542 | .. code-block:: makefile 543 | 544 | $(warning ) 545 | 546 | 这个函数很像error函数,只是它并不会让make退出,只是输出一段警告信息,而make继续执行。 547 | -------------------------------------------------------------------------------- /source/implicit_rules.rst: -------------------------------------------------------------------------------- 1 | 隐含规则 2 | ======== 3 | 4 | 在我们使用Makefile时,有一些我们会经常使用,而且使用频率非常高的东西,比如,我们编译C/C++的 5 | 源程序为中间目标文件(Unix下是 ``.o`` 文件,Windows下是 ``.obj`` 文件)。本章讲述的就是 6 | 一些在Makefile中的“隐含的”,早先约定了的,不需要我们再写出来的规则。 7 | 8 | “隐含规则”也就是一种惯例,make会按照这种“惯例”心照不宣地来运行,那怕我们的Makefile中没有书写 9 | 这样的规则。例如,把 ``.c`` 文件编译成 ``.o`` 文件这一规则,你根本就不用写出来,make会自动 10 | 推导出这种规则,并生成我们需要的 ``.o`` 文件。 11 | 12 | “隐含规则”会使用一些我们系统变量,我们可以改变这些系统变量的值来定制隐含规则的运行时的参数。 13 | 如系统变量 ``CFLAGS`` 可以控制编译时的编译器参数。 14 | 15 | 我们还可以通过“模式规则”的方式写下自己的隐含规则。用“后缀规则”来定义隐含规则会有许多的限制。 16 | 使用“模式规则”会显得更智能和清楚,但“后缀规则”可以用来保证我们Makefile的兼容性。 17 | 我们了解了“隐含规则”,可以让其为我们更好的服务,也会让我们知道一些“约定俗成”了的东西,而不至于 18 | 使得我们在运行Makefile时出现一些我们觉得莫名其妙的东西。当然,任何事物都是矛盾的,水能载舟, 19 | 亦可覆舟,所以,有时候“隐含规则”也会给我们造成不小的麻烦。只有了解了它,我们才能更好地使用它。 20 | 21 | 使用隐含规则 22 | ------------ 23 | 24 | 如果要使用隐含规则生成你需要的目标,你所需要做的就是不要写出这个目标的规则。那么,make会试图去 25 | 自动推导产生这个目标的规则和命令,如果 make可以自动推导生成这个目标的规则和命令,那么这个行为 26 | 就是隐含规则的自动推导。当然,隐含规则是make事先约定好的一些东西。例如,我们有下面的一个Makefile: 27 | 28 | .. code-block:: makefile 29 | 30 | foo : foo.o bar.o 31 | cc –o foo foo.o bar.o $(CFLAGS) $(LDFLAGS) 32 | 33 | 我们可以注意到,这个Makefile中并没有写下如何生成 ``foo.o`` 和 ``bar.o`` 这两目标的规则和命令。 34 | 因为make的“隐含规则”功能会自动为我们自动去推导这两个目标的依赖目标和生成命令。 35 | 36 | make会在自己的“隐含规则”库中寻找可以用的规则,如果找到,那么就会使用。如果找不到,那么就会报错。 37 | 在上面的那个例子中,make调用的隐含规则是,把 ``.o`` 的目标的依赖文件置成 ``.c`` ,并使用C的 38 | 编译命令 ``cc –c $(CFLAGS) foo.c`` 来生成 ``foo.o`` 的目标。也就是说,我们完全没有必要 39 | 写下下面的两条规则: 40 | 41 | .. code-block:: makefile 42 | 43 | foo.o : foo.c 44 | cc –c foo.c $(CFLAGS) 45 | bar.o : bar.c 46 | cc –c bar.c $(CFLAGS) 47 | 48 | 因为,这已经是“约定”好了的事了,make和我们约定好了用C编译器 ``cc`` 生成 ``.o`` 文件的规则, 49 | 这就是隐含规则。 50 | 51 | 当然,如果我们为 ``.o`` 文件书写了自己的规则,那么make就不会自动推导并调用隐含规则,它会按照 52 | 我们写好的规则忠实地执行。 53 | 54 | 还有,在make的“隐含规则库”中,每一条隐含规则都在库中有其顺序,越靠前的则是越被经常使用的,所以, 55 | 这会导致我们有些时候即使我们显示地指定了目标依赖,make也不会管。如下面这条规则(没有命令): 56 | 57 | .. code-block:: makefile 58 | 59 | foo.o : foo.p 60 | 61 | 依赖文件 ``foo.p`` (Pascal程序的源文件)有可能变得没有意义。如果目录下存在了 ``foo.c`` 文件, 62 | 那么我们的隐含规则一样会生效,并会通过 ``foo.c`` 调用C的编译器生成 ``foo.o`` 文件。因为,在 63 | 隐含规则中,Pascal的规则出现在C的规则之后,所以,make找到可以生成 ``foo.o`` 的C的规则就不再 64 | 寻找下一条规则了。如果你确实不希望任何隐含规则推导,那么,你就不要只写出“依赖规则”,而不写命令。 65 | 66 | 隐含规则一览 67 | ------------ 68 | 69 | 这里我们将讲述所有预先设置(也就是make内建)的隐含规则,如果我们不明确地写下规则,那么,make就 70 | 会在这些规则中寻找所需要规则和命令。当然,我们也可以使用make的参数 ``-r`` 或 ``--no-builtin-rules`` 71 | 选项来取消所有的预设置的隐含规则。 72 | 73 | 当然,即使是我们指定了 ``-r`` 参数,某些隐含规则还是会生效,因为有许多的隐含规则都是使用了 74 | “后缀规则”来定义的,所以,只要隐含规则中有 “后缀列表”(也就一系统定义在目标 ``.SUFFIXES`` 75 | 的依赖目标),那么隐含规则就会生效。默认的后缀列表是: 76 | .out, .a, .ln, .o, .c, .cc, .C, .p, .f, .F, .r, .y, .l, .s, .S, .mod, .sym, 77 | .def, .h, .info, .dvi, .tex, .texinfo, .texi, .txinfo, .w, .ch .web, .sh, .elc, .el。 78 | 具体的细节,我们会在后面讲述。 79 | 80 | 还是先来看一看常用的隐含规则吧。 81 | 82 | #. 编译C程序的隐含规则。 83 | 84 | ``.o`` 的目标的依赖目标会自动推导为 ``.c`` ,并且其生成命令是 ``$(CC) –c $(CPPFLAGS) $(CFLAGS)`` 85 | 86 | #. 编译C++程序的隐含规则。 87 | 88 | ``.o`` 的目标的依赖目标会自动推导为 ``.cc`` 或 ``.cpp`` 或是 ``.C`` ,并且其生成命令是 89 | ``$(CXX) –c $(CPPFLAGS) $(CXXFLAGS)`` 。(建议使用 ``.cc`` 或 ``.cpp`` 作为C++源文件的后缀,而不是 ``.C`` ) 90 | 91 | #. 编译Pascal程序的隐含规则。 92 | 93 | ``.o`` 的目标的依赖目标会自动推导为 ``.p`` ,并且其生成命令是 ``$(PC) –c $(PFLAGS)`` 。 94 | 95 | #. 编译Fortran/Ratfor程序的隐含规则。 96 | 97 | ``.o`` 的目标的依赖目标会自动推导为 ``.r`` 或 ``.F`` 或 ``.f`` ,并且其生成命令是: 98 | 99 | - ``.f`` ``$(FC) –c $(FFLAGS)`` 100 | - ``.F`` ``$(FC) –c $(FFLAGS) $(CPPFLAGS)`` 101 | - ``.r`` ``$(FC) –c $(FFLAGS) $(RFLAGS)`` 102 | 103 | #. 预处理Fortran/Ratfor程序的隐含规则。 104 | 105 | ``.f`` 的目标的依赖目标会自动推导为 ``.r`` 或 ``.F`` 。这个规则只是转换Ratfor 106 | 或有预处理的Fortran程序到一个标准的Fortran程序。其使用的命令是: 107 | 108 | - ``.F`` ``$(FC) –F $(CPPFLAGS) $(FFLAGS)`` 109 | - ``.r`` ``$(FC) –F $(FFLAGS) $(RFLAGS)`` 110 | 111 | #. 编译Modula-2程序的隐含规则。 112 | 113 | ``.sym`` 的目标的依赖目标会自动推导为 ``.def`` ,并且其生成命令是: 114 | ``$(M2C) $(M2FLAGS) $(DEFFLAGS)`` 。 ``.o`` 的目标的依赖目标会自动推导为 ``.mod`` , 115 | 并且其生成命令是: ``$(M2C) $(M2FLAGS) $(MODFLAGS)`` 。 116 | 117 | #. 汇编和汇编预处理的隐含规则。 118 | 119 | ``.o`` 的目标的依赖目标会自动推导为 ``.s`` ,默认使用编译器 ``as`` ,并且其生成 120 | 命令是: ``$ (AS) $(ASFLAGS)`` 。 ``.s`` 的目标的依赖目标会自动推导为 ``.S`` , 121 | 默认使用C预编译器 ``cpp`` ,并且其生成命令是: ``$(CPP) $(CPPFLAGS)`` 。 122 | 123 | #. 链接Object文件的隐含规则。 124 | 125 | ```` 目标依赖于 ``.o`` ,通过运行C的编译器来运行链接程序生成(一般是 ``ld`` ), 126 | 其生成命令是: ``$(CC) $(LDFLAGS) .o $(LOADLIBES) $(LDLIBS)`` 。这个规则对于 127 | 只有一个源文件的工程有效,同时也对多个Object文件(由不同的源文件生成)的也有效。例如如下规则:: 128 | 129 | x : y.o z.o 130 | 131 | 并且 ``x.c`` 、 ``y.c`` 和 ``z.c`` 都存在时,隐含规则将执行如下命令:: 132 | 133 | cc -c x.c -o x.o 134 | cc -c y.c -o y.o 135 | cc -c z.c -o z.o 136 | cc x.o y.o z.o -o x 137 | rm -f x.o 138 | rm -f y.o 139 | rm -f z.o 140 | 141 | 如果没有一个源文件(如上例中的x.c)和你的目标名字(如上例中的x)相关联,那么,你最好写出自己 142 | 的生成规则,不然,隐含规则会报错的。 143 | 144 | #. Yacc C程序时的隐含规则。 145 | 146 | ``.c`` 的依赖文件被自动推导为 ``n.y`` (Yacc生成的文件),其生成命令是: ``$(YACC) $(YFALGS)`` 。 147 | (“Yacc”是一个语法分析器,关于其细节请查看相关资料) 148 | 149 | #. Lex C程序时的隐含规则。 150 | 151 | ``.c`` 的依赖文件被自动推导为 ``n.l`` (Lex生成的文件),其生成命令是: ``$(LEX) $(LFALGS)`` 。 152 | (关于“Lex”的细节请查看相关资料) 153 | 154 | #. Lex Ratfor程序时的隐含规则。 155 | 156 | ``.r`` 的依赖文件被自动推导为 ``n.l`` (Lex生成的文件),其生成命令是: ``$(LEX) $(LFALGS)`` 。 157 | 158 | #. 从C程序、Yacc文件或Lex文件创建Lint库的隐含规则。 159 | 160 | ``.ln`` (lint生成的文件)的依赖文件被自动推导为 ``n.c`` ,其生成命令是: 161 | ``$(LINT) $(LINTFALGS) $(CPPFLAGS) -i`` 。对于 ``.y`` 和 ``.l`` 也是同样的规则。 162 | 163 | 隐含规则使用的变量 164 | ------------------ 165 | 166 | 在隐含规则中的命令中,基本上都是使用了一些预先设置的变量。你可以在你的makefile中改变这些变量的值, 167 | 或是在make的命令行中传入这些值,或是在你的环境变量中设置这些值,无论怎么样,只要设置了这些特定的变量, 168 | 那么其就会对隐含规则起作用。当然,你也可以利用make的 ``-R`` 或 ``--no–builtin-variables`` 169 | 参数来取消你所定义的变量对隐含规则的作用。 170 | 171 | 例如,第一条隐含规则——编译C程序的隐含规则的命令是 ``$(CC) –c $(CFLAGS) $(CPPFLAGS)`` 。 172 | Make默认的编译命令是 ``cc`` ,如果你把变量 ``$(CC)`` 重定义成 ``gcc`` ,把变量 ``$(CFLAGS)`` 173 | 重定义成 ``-g`` ,那么,隐含规则中的命令全部会以 ``gcc –c -g $(CPPFLAGS)`` 的样子来执行了。 174 | 175 | 我们可以把隐含规则中使用的变量分成两种:一种是命令相关的,如 ``CC`` ;一种是参数相的关,如 176 | ``CFLAGS`` 。下面是所有隐含规则中会用到的变量: 177 | 178 | 关于命令的变量。 179 | ~~~~~~~~~~~~~~~~ 180 | 181 | - ``AR`` : 函数库打包程序。默认命令是 ``ar`` 182 | - ``AS`` : 汇编语言编译程序。默认命令是 ``as`` 183 | - ``CC`` : C语言编译程序。默认命令是 ``cc`` 184 | - ``CXX`` : C++语言编译程序。默认命令是 ``g++`` 185 | - ``CO`` : 从 RCS文件中扩展文件程序。默认命令是 ``co`` 186 | - ``CPP`` : C程序的预处理器(输出是标准输出设备)。默认命令是 ``$(CC) –E`` 187 | - ``FC`` : Fortran 和 Ratfor 的编译器和预处理程序。默认命令是 ``f77`` 188 | - ``GET`` : 从SCCS文件中扩展文件的程序。默认命令是 ``get`` 189 | - ``LEX`` : Lex方法分析器程序(针对于C或Ratfor)。默认命令是 ``lex`` 190 | - ``PC`` : Pascal语言编译程序。默认命令是 ``pc`` 191 | - ``YACC`` : Yacc文法分析器(针对于C程序)。默认命令是 ``yacc`` 192 | - ``YACCR`` : Yacc文法分析器(针对于Ratfor程序)。默认命令是 ``yacc –r`` 193 | - ``MAKEINFO`` : 转换Texinfo源文件(.texi)到Info文件程序。默认命令是 ``makeinfo`` 194 | - ``TEX`` : 从TeX源文件创建TeX DVI文件的程序。默认命令是 ``tex`` 195 | - ``TEXI2DVI`` : 从Texinfo源文件创建军TeX DVI 文件的程序。默认命令是 ``texi2dvi`` 196 | - ``WEAVE`` : 转换Web到TeX的程序。默认命令是 ``weave`` 197 | - ``CWEAVE`` : 转换C Web 到 TeX的程序。默认命令是 ``cweave`` 198 | - ``TANGLE`` : 转换Web到Pascal语言的程序。默认命令是 ``tangle`` 199 | - ``CTANGLE`` : 转换C Web 到 C。默认命令是 ``ctangle`` 200 | - ``RM`` : 删除文件命令。默认命令是 ``rm –f`` 201 | 202 | 关于命令参数的变量 203 | ~~~~~~~~~~~~~~~~~~ 204 | 205 | 下面的这些变量都是相关上面的命令的参数。如果没有指明其默认值,那么其默认值都是空。 206 | 207 | - ``ARFLAGS`` : 函数库打包程序AR命令的参数。默认值是 ``rv`` 208 | - ``ASFLAGS`` : 汇编语言编译器参数。(当明显地调用 ``.s`` 或 ``.S`` 文件时) 209 | - ``CFLAGS`` : C语言编译器参数。 210 | - ``CXXFLAGS`` : C++语言编译器参数。 211 | - ``COFLAGS`` : RCS命令参数。 212 | - ``CPPFLAGS`` : C预处理器参数。( C 和 Fortran 编译器也会用到)。 213 | - ``FFLAGS`` : Fortran语言编译器参数。 214 | - ``GFLAGS`` : SCCS “get”程序参数。 215 | - ``LDFLAGS`` : 链接器参数。(如: ``ld`` ) 216 | - ``LFLAGS`` : Lex文法分析器参数。 217 | - ``PFLAGS`` : Pascal语言编译器参数。 218 | - ``RFLAGS`` : Ratfor 程序的Fortran 编译器参数。 219 | - ``YFLAGS`` : Yacc文法分析器参数。 220 | 221 | 隐含规则链 222 | ---------- 223 | 224 | 有些时候,一个目标可能被一系列的隐含规则所作用。例如,一个 ``.o`` 的文件生成,可能会是先被 225 | Yacc的[.y]文件先成 ``.c`` ,然后再被C的编译器生成。我们把这一系列的隐含规则叫做“隐含规则链”。 226 | 227 | 在上面的例子中,如果文件 ``.c`` 存在,那么就直接调用C的编译器的隐含规则,如果没有 ``.c`` 文件, 228 | 但有一个 ``.y`` 文件,那么Yacc的隐含规则会被调用,生成 ``.c`` 文件,然后,再调用C编译的隐含 229 | 规则最终由 ``.c`` 生成 ``.o`` 文件,达到目标。 230 | 231 | 我们把这种 ``.c`` 的文件(或是目标),叫做中间目标。不管怎么样,make会努力自动推导生成目标的 232 | 一切方法,不管中间目标有多少,其都会执着地把所有的隐含规则和你书写的规则全部合起来分析,努力达到 233 | 目标,所以,有些时候,可能会让你觉得奇怪,怎么我的目标会这样生成?怎么我的 makefile发疯了? 234 | 235 | 在默认情况下,对于中间目标,它和一般的目标有两个地方所不同:第一个不同是除非中间的目标不存在, 236 | 才会引发中间规则。第二个不同的是,只要目标成功产生,那么,产生最终目标过程中,所产生的中间目标 237 | 文件会被以 ``rm -f`` 删除。 238 | 239 | 通常,一个被makefile指定成目标或是依赖目标的文件不能被当作中介。然而,你可以明显地说明一个 240 | 文件或是目标是中介目标,你可以使用伪目标 ``.INTERMEDIATE`` 来强制声明。 241 | (如: ``.INTERMEDIATE : mid`` ) 242 | 243 | 你也可以阻止make自动删除中间目标,要做到这一点,你可以使用伪目标 ``.SECONDARY`` 来强制声明 244 | (如: ``.SECONDARY : sec`` )。你还可以把你的目标,以模式的方式来指定(如: ``%.o`` )成 245 | 伪目标 ``.PRECIOUS`` 的依赖目标,以保存被隐含规则所生成的中间文件。 246 | 247 | 在“隐含规则链”中,禁止同一个目标出现两次或两次以上,这样一来,就可防止在make自动推导时出现 248 | 无限递归的情况。 249 | 250 | Make会优化一些特殊的隐含规则,而不生成中间文件。如,从文件 ``foo.c`` 生成目标程序 ``foo`` , 251 | 按道理,make会编译生成中间文件 ``foo.o`` ,然后链接成 ``foo`` ,但在实际情况下,这一动作可以 252 | 被一条 ``cc`` 的命令完成( ``cc –o foo foo.c`` ),于是优化过的规则就不会生成中间文件。 253 | 254 | 定义模式规则 255 | ------------ 256 | 257 | 你可以使用模式规则来定义一个隐含规则。一个模式规则就好像一个一般的规则,只是在规则中,目标的定义 258 | 需要有 ``%`` 字符。 ``%`` 的意思是表示一个或多个任意字符。在依赖目标中同样可以使用 ``%`` , 259 | 只是依赖目标中的 ``%`` 的取值,取决于其目标。 260 | 261 | 有一点需要注意的是, ``%`` 的展开发生在变量和函数的展开之后,变量和函数的展开发生在make载入 262 | Makefile时,而模式规则中的 ``%`` 则发生在运行时。 263 | 264 | 模式规则介绍 265 | ~~~~~~~~~~~~ 266 | 267 | 模式规则中,至少在规则的目标定义中要包含 ``%`` ,否则,就是一般的规则。目标中的 ``%`` 定义 268 | 表示对文件名的匹配, ``%`` 表示长度任意的非空字符串。例如: ``%.c`` 表示以 ``.c`` 结尾的 269 | 文件名(文件名的长度至少为3),而 ``s.%.c`` 则表示以 ``s.`` 开头, ``.c`` 结尾的文件名 270 | (文件名的长度至少为5)。 271 | 272 | 如果 ``%`` 定义在目标中,那么,依赖中的 ``%`` 的值决定了目标中的 ``%`` 的值,也就是说, 273 | 依赖中的模式的 ``%`` 决定了目标中 ``%`` 的样子。例如有一个模式规则如下: 274 | 275 | .. code-block:: makefile 276 | 277 | %.o : %.c ; ; 278 | 279 | 其含义是,指出了怎么从所有的 ``.c`` 文件生成相应的 ``.o`` 文件的规则。如果要生成的目标是 280 | ``a.o b.o`` ,那么 ``%c`` 就是 ``a.c b.c`` 。 281 | 282 | 一旦依赖目标中的 ``%`` 模式被确定,那么,make会被要求去匹配当前目录下所有的文件名,一旦找到, 283 | make就会规则下的命令,所以,在模式规则中,目标可能会是多个的,如果有模式匹配出多个目标,make就 284 | 会产生所有的模式目标,此时,make关心的是依赖的文件名和生成目标的命令这两件事。 285 | 286 | 模式规则示例 287 | ~~~~~~~~~~~~ 288 | 289 | 下面这个例子表示了,把所有的 ``.c`` 文件都编译成 ``.o`` 文件. 290 | 291 | .. code-block:: makefile 292 | 293 | %.o : %.c 294 | $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ 295 | 296 | 其中, ``$@`` 表示所有的目标的挨个值, ``$<`` 表示了所有依赖目标的挨个值。这些奇怪的变量我们 297 | 叫“自动化变量”,后面会详细讲述。 298 | 299 | 下面的这个例子中有两个目标是模式的: 300 | 301 | .. code-block:: makefile 302 | 303 | %.tab.c %.tab.h: %.y 304 | bison -d $< 305 | 306 | 这条规则告诉make把所有的 ``.y`` 文件都以 ``bison -d .y`` 执行,然后生成 ``.tab.c`` 307 | 和 ``.tab.h`` 文件。(其中, ```` 表示一个任意字符串)。如果我们的执行程序 ``foo`` 308 | 依赖于文件 ``parse.tab.o`` 和 ``scan.o`` ,并且文件 ``scan.o`` 依赖于文件 ``parse.tab.h`` , 309 | 如果 ``parse.y`` 文件被更新了,那么根据上述的规则, ``bison -d parse.y`` 就会被执行一次, 310 | 于是, ``parse.tab.o`` 和 ``scan.o`` 的依赖文件就齐了。(假设, ``parse.tab.o`` 由 311 | ``parse.tab.c`` 生成,和 ``scan.o`` 由 ``scan.c`` 生成,而 ``foo`` 由 ``parse.tab.o`` 312 | 和 ``scan.o`` 链接生成,而且 ``foo`` 和其 ``.o`` 文件的依赖关系也写好,那么,所有的目标都会得到满足) 313 | 314 | 自动化变量 315 | ~~~~~~~~~~ 316 | 317 | 在上述的模式规则中,目标和依赖文件都是一系例的文件,那么我们如何书写一个命令来完成从不同的依赖 318 | 文件生成相应的目标?因为在每一次的对模式规则的解析时,都会是不同的目标和依赖文件。 319 | 320 | 自动化变量就是完成这个功能的。在前面,我们已经对自动化变量有所提涉,相信你看到这里已对它有一个 321 | 感性认识了。所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的 322 | 符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。 323 | 324 | 下面是所有的自动化变量及其说明: 325 | 326 | - ``$@`` : 表示规则中的目标文件集。在模式规则中,如果有多个目标,那么, ``$@`` 就是匹配于 327 | 目标中模式定义的集合。 328 | - ``$%`` : 仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是 ``foo.a(bar.o)`` , 329 | 那么, ``$%`` 就是 ``bar.o`` , ``$@`` 就是 ``foo.a`` 。如果目标不是函数库文件 330 | (Unix下是 ``.a`` ,Windows下是 ``.lib`` ),那么,其值为空。 331 | - ``$<`` : 依赖目标中的第一个目标名字。如果依赖目标是以模式(即 ``%`` )定义的,那么 ``$<`` 332 | 将是符合模式的一系列的文件集。注意,其是一个一个取出来的。 333 | - ``$?`` : 所有比目标新的依赖目标的集合。以空格分隔。 334 | - ``$^`` : 所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那么这个变量会去除 335 | 重复的依赖目标,只保留一份。 336 | - ``$+`` : 这个变量很像 ``$^`` ,也是所有依赖目标的集合。只是它不去除重复的依赖目标。 337 | - ``$*`` : 这个变量表示目标模式中 ``%`` 及其之前的部分。如果目标是 ``dir/a.foo.b`` ,并且 338 | 目标的模式是 ``a.%.b`` ,那么, ``$*`` 的值就是 ``dir/foo`` 。这个变量对于构造有关联的 339 | 文件名是比较有效。如果目标中没有模式的定义,那么 ``$*`` 也就不能被推导出,但是,如果目标文件的 340 | 后缀是make所识别的,那么 ``$*`` 就是除了后缀的那一部分。例如:如果目标是 ``foo.c`` ,因为 341 | ``.c`` 是make所能识别的后缀名,所以, ``$*`` 的值就是 ``foo`` 。这个特性是GNU make的, 342 | 很有可能不兼容于其它版本的make,所以,你应该尽量避免使用 ``$*`` ,除非是在隐含规则或是静态 343 | 模式中。如果目标中的后缀是make所不能识别的,那么 ``$*`` 就是空值。 344 | 345 | 当你希望只对更新过的依赖文件进行操作时, ``$?`` 在显式规则中很有用,例如,假设有一个函数库文件 346 | 叫 ``lib`` ,其由其它几个object文件更新。那么把object文件打包的比较有效率的Makefile规则是: 347 | 348 | .. code-block:: makefile 349 | 350 | lib : foo.o bar.o lose.o win.o 351 | ar r lib $? 352 | 353 | 在上述所列出来的自动量变量中。四个变量( ``$@`` 、 ``$<`` 、 ``$%`` 、 ``$*`` )在扩展时 354 | 只会有一个文件,而另三个的值是一个文件列表。这七个自动化变量还可以取得文件的目录名或是在当前 355 | 目录下的符合模式的文件名,只需要搭配上 ``D`` 或 ``F`` 字样。这是GNU make中老版本的特性, 356 | 在新版本中,我们使用函数 ``dir`` 或 ``notdir`` 就可以做到了。 ``D`` 的含义就是Directory, 357 | 就是目录, ``F`` 的含义就是File,就是文件。 358 | 359 | 下面是对于上面的七个变量分别加上 ``D`` 或是 ``F`` 的含义: 360 | 361 | ``$(@D)`` 362 | 表示 ``$@`` 的目录部分(不以斜杠作为结尾),如果 ``$@`` 值是 ``dir/foo.o`` ,那么 363 | ``$(@D)`` 就是 ``dir`` ,而如果 ``$@`` 中没有包含斜杠的话,其值就是 ``.`` (当前目录)。 364 | 365 | ``$(@F)`` 366 | 表示 ``$@`` 的文件部分,如果 ``$@`` 值是 ``dir/foo.o`` ,那么 ``$(@F)`` 就是 ``foo.o`` , 367 | ``$(@F)`` 相当于函数 ``$(notdir $@)`` 。 368 | 369 | ``$(*D)``, ``$(*F)`` 370 | 和上面所述的同理,也是取文件的目录部分和文件部分。对于上面的那个例子, ``$(*D)`` 返回 ``dir`` , 371 | 而 ``$(*F)`` 返回 ``foo`` 372 | 373 | ``$(%D)``, ``$(%F)`` 374 | 分别表示了函数包文件成员的目录部分和文件部分。这对于形同 ``archive(member)`` 形式的目标中的 375 | ``member`` 中包含了不同的目录很有用。 376 | 377 | ``$(... 330 | 331 | ```` 可以是当前操作系统Shell的文件模式(可以包含路径和通配符)。 332 | 333 | 在 ``include`` 前面可以有一些空字符,但是绝不能是 ``Tab`` 键开始。 ``include`` 和 334 | ```` 可以用一个或多个空格隔开。举个例子,你有这样几个Makefile: ``a.mk`` 、 335 | ``b.mk`` 、 ``c.mk`` ,还有一个文件叫 ``foo.make`` ,以及一个变量 ``$(bar)`` ,其包含 336 | 了 ``bish`` 和 ``bash`` ,那么,下面的语句: 337 | 338 | .. code-block:: makefile 339 | 340 | include foo.make *.mk $(bar) 341 | 342 | 等价于: 343 | 344 | .. code-block:: makefile 345 | 346 | include foo.make a.mk b.mk c.mk bish bash 347 | 348 | make命令开始时,会找寻 ``include`` 所指出的其它Makefile,并把其内容安置在当前的位置。就好 349 | 像C/C++的 ``#include`` 指令一样。如果文件都没有指定绝对路径或是相对路径的话,make会在当前目 350 | 录下首先寻找,如果当前目录下没有找到,那么,make还会在下面的几个目录下找: 351 | 352 | #. 如果make执行时,有 ``-I`` 或 ``--include-dir`` 参数,那么make就会在这个参数所指定的目 353 | 录下去寻找。 354 | #. 接下来按顺序寻找目录 ``/include`` (一般是 ``/usr/local/bin`` )、 355 | ``/usr/gnu/include`` 、 ``/usr/local/include`` 、 ``/usr/include`` 。 356 | 357 | 环境变量 ``.INCLUDE_DIRS`` 包含当前 make 会寻找的目录列表。你应当避免使用命令行参数 358 | ``-I`` 来寻找以上这些默认目录,否则会使得 ``make`` “忘掉”所有已经设定的包含目录,包括默认 359 | 目录。 360 | 361 | 如果有文件没有找到的话,make会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的 362 | 文件,一旦完成makefile的读取,make会再重试这些没有找到,或是不能读取的文件,如果还是 363 | 不行,make才会出现一条致命信息。如果你想让make不理那些无法读取的文件,而继续执行,你可以 364 | 在include前加一个减号“-”。如: 365 | 366 | .. code-block:: makefile 367 | 368 | -include ... 369 | 370 | 其表示,无论include过程中出现什么错误,都不要报错继续执行。如果要和其它版本 ``make`` 兼容, 371 | 可以使用 ``sinclude`` 代替 ``-include`` 。 372 | 373 | 环境变量MAKEFILES 374 | ----------------- 375 | 376 | 如果你的当前环境中定义了环境变量 ``MAKEFILES`` ,那么make会把这个变量中的值做一个类似于 377 | ``include`` 的动作。这个变量中的值是其它的Makefile,用空格分隔。只是,它和 ``include`` 不 378 | 同的是,从这个环境变量中引入的Makefile的“默认目标”(the default goal)不会起作用,如果环境变量中定义的文件发现 379 | 错误,make也会不理。 380 | 381 | 但是在这里我还是建议不要使用这个环境变量,因为只要这个变量一被定义,那么当你使用make时, 382 | 所有的Makefile都会受到它的影响,这绝不是你想看到的。在这里提这个事,只是为了告诉大家,也许 383 | 有时候你的Makefile出现了怪事,那么你可以看看当前环境中有没有定义这个变量。 384 | 385 | make的工作方式 386 | -------------- 387 | 388 | GNU的make工作时的执行步骤如下:(想来其它的make也是类似) 389 | 390 | #. 读入所有的Makefile。 391 | #. 读入被include的其它Makefile。 392 | #. 初始化文件中的变量。 393 | #. 推导隐式规则,并分析所有规则。 394 | #. 为所有的目标文件创建依赖关系链。 395 | #. 根据依赖关系,决定哪些目标要重新生成。 396 | #. 执行生成命令。 397 | 398 | 1-5步为第一个阶段,6-7为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么,make会把其展 399 | 开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则 400 | 中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。 401 | 402 | 当然,这个工作方式你不一定要清楚,但是知道这个方式你也会对make更为熟悉。有了这个基础,后续部分 403 | 也就容易看懂了。 404 | -------------------------------------------------------------------------------- /source/invoke.rst: -------------------------------------------------------------------------------- 1 | make 的运行 2 | =========== 3 | 4 | 一般来说,最简单的就是直接在命令行下输入make命令,make命令会找当前目录的makefile来执行,一切 5 | 都是自动的。但也有时你也许只想让make重编译某些文件,而不是整个工程,而又有的时候你有几套编译规 6 | 则,你想在不同的时候使用不同的编译规则,等等。本章节就是讲述如何使用make命令的。 7 | 8 | make的退出码 9 | ------------ 10 | 11 | make命令执行后有三个退出码: 12 | 13 | 0 14 | 表示成功执行。 15 | 16 | 1 17 | 如果make运行时出现任何错误,其返回1。 18 | 19 | 2 20 | 如果你使用了make的“-q”选项,并且make使得一些目标不需要更新,那么返回2。 21 | 22 | Make的相关参数我们会在后续章节中讲述。 23 | 24 | 指定Makefile 25 | ------------ 26 | 27 | 前面我们说过,GNU make找寻默认的Makefile的规则是在当前目录下依次找三个文件——“GNUmakefile” 28 | 、“makefile”和“Makefile”。其按顺序找这三个文件,一旦找到,就开始读取这个文件并执行。 29 | 30 | 当前,我们也可以给make命令指定一个特殊名字的Makefile。要达到这个功能,我们要使用make的 31 | ``-f`` 或是 ``--file`` 参数( ``--makefile`` 参数也行)。例如,我们有个makefile的名字 32 | 是“hchen.mk”,那么,我们可以这样来让make来执行这个文件: 33 | 34 | :: 35 | 36 | make –f hchen.mk 37 | 38 | 如果在make的命令行是,你不只一次地使用了 ``-f`` 参数,那么,所有指定的makefile将会被连在 39 | 一起传递给make执行。 40 | 41 | 指定目标 42 | -------- 43 | 44 | 一般来说,make的最终目标是makefile中的第一个目标,而其它目标一般是由这个目标连带出来的。这 45 | 是make的默认行为。当然,一般来说,你的makefile中的第一个目标是由许多个目标组成,你可以指 46 | 示make,让其完成你所指定的目标。要达到这一目的很简单,需在make命令后直接跟目标的名字就可以完成 47 | (如前面提到的“make clean”形式) 48 | 49 | 任何在makefile中的目标都可以被指定成终极目标,但是除了以 ``-`` 打头,或是包含了 ``=`` 的 50 | 目标,因为有这些字符的目标,会被解析成命令行参数或是变量。甚至没有被我们明确写出来的目标也可以 51 | 成为make的终极目标,也就是说,只要make可以找到其隐含规则推导规则,那么这个隐含目标同样可以被指 52 | 定成终极目标。 53 | 54 | 有一个make的环境变量叫 ``MAKECMDGOALS`` ,这个变量中会存放你所指定的终极目标的列表,如果在 55 | 命令行上,你没有指定目标,那么,这个变量是空值。这个变量可以让你使用在一些比较特殊的情形下。比 56 | 如下面的例子: 57 | 58 | .. code-block:: makefile 59 | 60 | sources = foo.c bar.c 61 | ifneq ( $(MAKECMDGOALS),clean) 62 | include $(sources:.c=.d) 63 | endif 64 | 65 | 基于上面的这个例子,只要我们输入的命令不是“make clean”,那么makefile会自动包含“foo.d” 66 | 和“bar.d”这两个makefile。 67 | 68 | 使用指定终极目标的方法可以很方便地让我们编译我们的程序,例如下面这个例子: 69 | 70 | .. code-block:: makefile 71 | 72 | .PHONY: all 73 | all: prog1 prog2 prog3 prog4 74 | 75 | 从这个例子中,我们可以看到,这个makefile中有四个需要编译的程序——“prog1”, “prog2”, 76 | “prog3”和 “prog4”,我们可以使用“make all”命令来编译所有的目标(如果把all置成第一个目标, 77 | 那么只需执行“make”),我们也可以使用 “make prog2”来单独编译目标“prog2”。 78 | 79 | 即然make可以指定所有makefile中的目标,那么也包括“伪目标”,于是我们可以根据这种性质来让我们 80 | 的makefile根据指定的不同的目标来完成不同的事。在Unix世界中,软件发布时,特别是GNU这种开源软 81 | 件的发布时,其makefile都包含了编译、安装、打包等功能。我们可以参照这种规则来书写我们 82 | 的makefile中的目标。 83 | 84 | - all:这个伪目标是所有目标的目标,其功能一般是编译所有的目标。 85 | - clean:这个伪目标功能是删除所有被make创建的文件。 86 | - install:这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去。 87 | - print:这个伪目标的功能是例出改变过的源文件。 88 | - tar:这个伪目标功能是把源程序打包备份。也就是一个tar文件。 89 | - dist:这个伪目标功能是创建一个压缩文件,一般是把tar文件压成Z文件。或是gz文件。 90 | - TAGS:这个伪目标功能是更新所有的目标,以备完整地重编译使用。 91 | - check和test:这两个伪目标一般用来测试makefile的流程。 92 | 93 | 当然一个项目的makefile中也不一定要书写这样的目标,这些东西都是GNU的东西,但是我想,GNU搞出这 94 | 些东西一定有其可取之处(等你的 UNIX下的程序文件一多时你就会发现这些功能很有用了),这里只不过 95 | 是说明了,如果你要书写这种功能,最好使用这种名字命名你的目标,这样规范一些,规范的好处就是——不 96 | 用解释,大家都明白。而且如果你的makefile中有这些功能,一是很实用,二是可以显得你的makefile很 97 | 专业(不是那种初学者的作品)。 98 | 99 | 检查规则 100 | -------- 101 | 102 | 有时候,我们不想让我们的makefile中的规则执行起来,我们只想检查一下我们的命令,或是执行的序列。 103 | 于是我们可以使用make命令的下述参数: 104 | 105 | ``-n``, ``--just-print``, ``--dry-run``, ``--recon`` 106 | 不执行参数,这些参数只是打印命令,不管目标是否更新,把规则和连带规则下的命令打印出来,但不 107 | 执行,这些参数对于我们调试makefile很有用处。 108 | 109 | ``-t``, ``--touch`` 110 | 这个参数的意思就是把目标文件的时间更新,但不更改目标文件。也就是说,make假装编译目标,但不 111 | 是真正的编译目标,只是把目标变成已编译过的状态。 112 | 113 | ``-q``, ``--question`` 114 | 这个参数的行为是找目标的意思,也就是说,如果目标存在,那么其什么也不会输出,当然也不会执行 115 | 编译,如果目标不存在,其会打印出一条出错信息。 116 | 117 | ``-W ``, ``--what-if=``, ``--assume-new=``, ``--new-file=`` 118 | 这个参数需要指定一个文件。一般是是源文件(或依赖文件),Make会根据规则推导来运行依赖于这个 119 | 文件的命令,一般来说,可以和“-n”参数一同使用,来查看这个依赖文件所发生的规则命令。 120 | 121 | 另外一个很有意思的用法是结合 ``-p`` 和 ``-v`` 来输出makefile被执行时的信息(这个将在后面讲述)。 122 | 123 | make的参数 124 | ---------- 125 | 126 | 下面列举了所有GNU make 3.80版的参数定义。其它版本和产商的make大同小异,不过其它产商的make的具体参数还是请参考各自的产品文档。 127 | 128 | ``-b``, ``-m`` 129 | 这两个参数的作用是忽略和其它版本make的兼容性。 130 | 131 | ``-B``, ``--always-make`` 132 | 认为所有的目标都需要更新(重编译)。 133 | 134 | ``-C`` **, ``--directory``\ =\ ** 135 | 指定读取makefile的目录。如果有多个“-C”参数,make的解释是后面的路径以前面的作为相对路径 136 | ,并以最后的目录作为被指定目录。如:“make -C ~hchen/test -C prog”等价于 137 | “make -C ~hchen/test/prog”。 138 | 139 | ``-debug``\ [=\ **] 140 | 输出make的调试信息。它有几种不同的级别可供选择,如果没有参数,那就是输出最简单的调试信息。 141 | 下面是的取值: 142 | 143 | - a: 也就是all,输出所有的调试信息。(会非常的多) 144 | - b: 也就是basic,只输出简单的调试信息。即输出不需要重编译的目标。 145 | - v: 也就是verbose,在b选项的级别之上。输出的信息包括哪个makefile被解析,不需要被重编 146 | 译的依赖文件(或是依赖目标)等。 147 | - i: 也就是implicit,输出所有的隐含规则。 148 | - j: 也就是jobs,输出执行规则中命令的详细信息,如命令的PID、返回码等。 149 | - m: 也就是makefile,输出make读取makefile,更新makefile,执行makefile的信息。 150 | 151 | ``-d`` 152 | 相当于“--debug=a”。 153 | 154 | ``-e``, ``--environment-overrides`` 155 | 指明环境变量的值覆盖makefile中定义的变量的值。 156 | 157 | ``-f``\ =\ **, ``--file``\ =\ **, ``--makefile``\ =\ ** 158 | 指定需要执行的makefile。 159 | 160 | ``-h``, ``--help`` 161 | 显示帮助信息。 162 | 163 | ``-i`` , ``--ignore-errors`` 164 | 在执行时忽略所有的错误。 165 | 166 | ``-I`` **, ``--include-dir``\ =\ ** 167 | 指定一个被包含makefile的搜索目标。可以使用多个“-I”参数来指定多个目录。 168 | 169 | ``-j`` [**], ``--jobs``\ [=\ **] 170 | 指同时运行命令的个数。如果没有这个参数,make运行命令时能运行多少就运行多少。如果有一个以上的“-j”参数,那么仅最后一个“-j”才是有效的。(注意这个参数在MS-DOS中是无用的) 171 | 172 | ``-k``, ``--keep-going`` 173 | 出错也不停止运行。如果生成一个目标失败了,那么依赖于其上的目标就不会被执行了。 174 | 175 | ``-l`` **, ``--load-average``\ [=\ **], ``-max-load``\ [=\ **] 176 | 指定make运行命令的负载。 177 | 178 | ``-n``, ``--just-print``, ``--dry-run``, ``--recon`` 179 | 仅输出执行过程中的命令序列,但并不执行。 180 | 181 | ``-o`` **, ``--old-file``\ =\ **, ``--assume-old``\ =\ ** 182 | 不重新生成的指定的,即使这个目标的依赖文件新于它。 183 | 184 | ``-p``, ``--print-data-base`` 185 | 输出makefile中的所有数据,包括所有的规则和变量。这个参数会让一个简单的makefile都会输出 186 | 一堆信息。如果你只是想输出信息而不想执行makefile,你可以使用“make -qp”命令。如果你想查 187 | 看执行makefile前的预设变量和规则,你可以使用 “make –p –f /dev/null”。这个参数输出的 188 | 信息会包含着你的makefile文件的文件名和行号,所以,用这个参数来调试你的 makefile会是很有 189 | 用的,特别是当你的环境变量很复杂的时候。 190 | 191 | ``-q``, ``--question`` 192 | 不运行命令,也不输出。仅仅是检查所指定的目标是否需要更新。如果是0则说明要更新,如果是2则说 193 | 明有错误发生。 194 | 195 | ``-r``, ``--no-builtin-rules`` 196 | 禁止make使用任何隐含规则。 197 | 198 | ``-R``, ``--no-builtin-variabes`` 199 | 禁止make使用任何作用于变量上的隐含规则。 200 | 201 | ``-s``, ``--silent``, ``--quiet`` 202 | 在命令运行时不输出命令的输出。 203 | 204 | ``-S``, ``--no-keep-going``, ``--stop`` 205 | 取消“-k”选项的作用。因为有些时候,make的选项是从环境变量“MAKEFLAGS”中继承下来的。所以你 206 | 可以在命令行中使用这个参数来让环境变量中的“-k”选项失效。 207 | 208 | ``-t``, ``--touch`` 209 | 相当于UNIX的touch命令,只是把目标的修改日期变成最新的,也就是阻止生成目标的命令运行。 210 | 211 | ``-v``, ``--version`` 212 | 输出make程序的版本、版权等关于make的信息。 213 | 214 | ``-w``, ``--print-directory`` 215 | 输出运行makefile之前和之后的信息。这个参数对于跟踪嵌套式调用make时很有用。 216 | 217 | ``--no-print-directory`` 218 | 禁止“-w”选项。 219 | 220 | ``-W`` **, ``--what-if``\ =\ **, ``--new-file``\ =\ **, ``--assume-file``\ =\ ** 221 | 假定目标;需要更新,如果和“-n”选项使用,那么这个参数会输出该目标更新时的运行动作。 222 | 如果没有“-n”那么就像运行UNIX的“touch”命令一样,使得;的修改时间为当前时间。 223 | 224 | ``--warn-undefined-variables`` 225 | 只要make发现有未定义的变量,那么就输出警告信息。 226 | -------------------------------------------------------------------------------- /source/overview.rst: -------------------------------------------------------------------------------- 1 | 概述 2 | ==== 3 | 4 | 什么是makefile?或许很多Windows的程序员都不知道这个东西,因为那些Windows的集成开发环境 5 | (integrated development environment,IDE)都为你做了这个工作,但我觉得要作一个好的和专 6 | 业的程序员,makefile还是要懂。这就好像现在有这么多的HTML编辑器,但如果你想成为一个专业人士, 7 | 你还是要了解HTML的标签的含义。特别在Unix下的软件编译,你就不能不自己写makefile了,会不会 8 | 写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力。 9 | 10 | 因为,makefile关系到了整个工程的编译规则。一个工程中的源文件不计其数,并且按类型、功能、模块分 11 | 别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译, 12 | 哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell脚本一样,其中也可 13 | 以执行操作系统的命令。 14 | 15 | makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极 16 | 大的提高了软件开发的效率。 make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说, 17 | 大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见 18 | ,makefile都成为了一种在工程方面的编译方法。 19 | 20 | 现在讲述如何写makefile的文章比较少,这是我想写这篇文章的原因。当然,不同产商的make各不相同, 21 | 也有不同的语法,但其本质都是在 “文件依赖性”上做文章,这里,我仅对GNU的make进行讲述,我的环境 22 | 是RedHat Linux 8.0,make的版本是3.80。毕竟,这个make是应用最为广泛的,也是用得最多的。而且 23 | 其还是最遵循于IEEE 1003.2-1992标准的(POSIX.2)。 24 | 25 | 在这篇文档中,将以C/C++的源码作为基础,所以必然涉及一些关于C/C++的编译的知识。关于这方面的内 26 | 容,还请各位查看相关的编译器的文档。这里所默认的编译器是UNIX下的GCC和CC。 27 | 28 | 关于程序的编译和链接 29 | -------------------- 30 | 31 | 在此,我想多说关于程序编译的一些规范和方法。一般来说,无论是C还是C++,首先要把源文件编译成中间 32 | 代码文件,在Windows下也就是 ``.obj`` 文件,UNIX下是 ``.o`` 文件,即Object File,这个动 33 | 作叫做编译(compile)。然后再把大量的Object File合成可执行文件,这个动作叫作链接(link)。 34 | 35 | 编译时,编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头文 36 | 件的所在位置(头文件中应该只是声明,而定义应该放在C/C++文件中),只要所有的语法正确,编译器就 37 | 可以编译出中间目标文件。一般来说,每个源文件都应该对应于一个中间目标文件( ``.o`` 文件或 38 | ``.obj`` 文件)。 39 | 40 | 链接时,主要是链接函数和全局变量。所以,我们可以使用这些中间目标文件( ``.o`` 文件或 41 | ``.obj`` 文件)来链接我们的应用程序。链接器并不管函数所在的源文件,只管函数的中间目标文件 42 | (Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显 43 | 地指出中间目标文件名,这对于编译很不方便。所以,我们要给中间目标文件打个包,在Windows下这种包 44 | 叫“库文件”(Library File),也就是 ``.lib`` 文件,在UNIX下,是Archive File,也就是 45 | ``.a`` 文件。 46 | 47 | 总结一下,源文件首先会生成中间目标文件,再由中间目标文件生成可执行文件。在编译时,编译器只检测程 48 | 序语法和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成Object File。 49 | 而在链接程序时,链接器会在所有的Object File中找寻函数的实现,如果找不到,那到就会报链接错误码 50 | (Linker Error),在VC下,这种错误一般是: ``Link 2001错误`` ,意思说是说,链接器未能找到 51 | 函数的实现。你需要指定函数的Object File。 52 | 53 | 好,言归正传,gnu的make有许多的内容,闲言少叙。 54 | -------------------------------------------------------------------------------- /source/postscript.rst: -------------------------------------------------------------------------------- 1 | 后序 2 | ==== 3 | 4 | 终于到写结束语的时候了,以上基本上就是GNU make的Makefile的所有细节了。其它的厂商的make基本 5 | 上也就是这样的,无论什么样的make,都是以文件的依赖性为基础的,其基本是都是遵循一个标准的。这篇 6 | 文档中80%的技术细节都适用于任何的make,我猜测“函数”那一章的内容可能不是其它make所支持的,而隐 7 | 含规则方面,我想不同的make会有不同的实现,我没有精力来查看GNU的make和VC的nmake、BCB的 make 8 | ,或是别的UNIX下的make有些什么样的差别,一是时间精力不够,二是因为我基本上都是在Unix下使 9 | 用make,以前在SCO Unix和 IBM的AIX,现在在Linux、Solaris、HP-UX、AIX和Alpha下使用 10 | ,Linux和Solaris下更多一点。不过,我可以肯定的是,在Unix下的make,无论是哪种平台,几乎都使 11 | 用了Richard Stallman开发的make和cc/gcc的编译器,而且,基本上都是 GNU的make(公司里所有 12 | 的UNIX机器上都被装上了GNU的东西,所以,使用GNU的程序也就多了一些)。GNU的东西还是很不错的,特 13 | 别是使用得深了以后,越来越觉得GNU的软件的强大,也越来越觉得GNU的在操作系统中(主要是Unix,甚 14 | 至Windows)“杀伤力”。 15 | 16 | 对于上述所有的make的细节,我们不但可以利用make这个工具来编译我们的程序,还可以利用make来完成 17 | 其它的工作,因为规则中的命令可以是任何Shell之下的命令,所以,在Unix下,你不一定只是使用程序语 18 | 言的编译器,你还可以在Makefile中书写其它的命令,如:tar、 awk、mail、sed、cvs、compress 19 | 、ls、rm、yacc、rpm、ftp等等,等等,来完成诸如“程序打包”、“程序备份”、“制作程序安装包”、“提 20 | 交代码”、“使用程序模板”、“合并文件”等等五花八门的功能,文件操作,文件管理,编程开发设计,或是 21 | 其它一些异想天开的东西。比如,以前在书写银行交易程序时,由于银行的交易程序基本一样,就见到有人 22 | 书写了一些交易的通用程序模板,在该模板中把一些网络通讯、数据库操作的、业务操作共性的东西写在一 23 | 个文件中,在这些文件中用些诸如“@@@N、###N”奇怪字串标注一些位置,然后书写交易时,只需按照一种 24 | 特定的规则书写特定的处理,最后在make时,使用awk和sed,把模板中的“@@@N、###N”等字串替代成特 25 | 定的程序,形成C文件,然后再编译。这个动作很像数据库的“扩展C”语言(即在C语言中用“EXEC SQL”的 26 | 样子执行SQL语句,在用cc/gcc编译之前,需要使用“扩展C”的翻译程序,如cpre,把其翻译成标准C)。 27 | 如果你在使用make时有一些更为绝妙的方法,请记得告诉我啊。 28 | 29 | 回头看看整篇文档,不觉记起几年前刚刚开始在Unix下做开发的时候,有人问我会不会写Makefile时,我 30 | 两眼发直,根本不知道在说什么。一开始看到别人在vi中写完程序后输入“!make”时,还以为是vi的功能, 31 | 后来才知道有一个Makefile在作怪,于是上网查啊查,那时又不愿意看英文,发现就根本没有中文的文档介 32 | 绍Makefile,只得看别人写的Makefile,自己瞎碰瞎搞才积累了一点知识,但在很多地方完全是知其然不 33 | 知所以然。后来开始从事UNIX下产品软件的开发,看到一个400人,近200万行代码的大工程,发现要编译 34 | 这样一个庞然大物,如果没有Makefile,那会是多么恐怖的一样事啊。于是横下心来,狠命地读了一堆英文 35 | 文档,才觉得对其掌握了。但发现目前网上对Makefile介绍的文章还是少得那么的可怜,所以想写这样一篇 36 | 文章,共享给大家,希望能对各位有所帮助。 37 | 38 | 现在我终于写完了,看了看文件的创建时间,这篇技术文档也写了两个多月了。发现,自己知道是一回事, 39 | 要写下来,跟别人讲述又是另外一回事,而且,现在越来越没有时间钻研技术细节,所以在写作时,发现在 40 | 阐述一些细节问题时很难做到严谨和精炼,而且对先讲什么后讲什么不是很清楚,所以,还是参考了一些国 41 | 外站点上的资料和提纲,以及一些技术书籍的语言风格,才得以完成。整篇文档的提纲是基于GNU 42 | 的Makefile技术手册的提纲来书写的,并结合了自己的工作经验,以及自己的学习历程。因为从来没有写过 43 | 这么长,这么细的文档,所以一定会有很多地方存在表达问题,语言歧义或是错误。因些,我迫切地等待各 44 | 位给我指正和建议,以及任何的反馈。 45 | 46 | 最后,还是利用这个后序,介绍一下自己。我目前从事于所有Unix平台下的软件研发,主要是做分布式计 47 | 算/网格计算方面的系统产品软件,并且我对于下一代的计算机革命——网格计算非常地感兴趣,对于分布式计 48 | 算、P2P、Web Service、J2EE技术方向也很感兴趣,同时,对于项目实施、团队管理、项目管理也小有心 49 | 得,希望同样和我战斗在“技术和管理并重”的阵线上的年轻一代,能够和我多多地交流。我的MSN是: 50 | haoel@hotmail.com(常用),QQ是:753640(不常用)。(注:请勿给我MSN的邮箱发信,由 51 | 于hotmail的垃圾邮件导致我拒收这个邮箱的所有来信) 52 | 53 | 我欢迎任何形式的交流,无论是讨论技术还是管理,或是其它海阔天空的东西。除了政治和娱乐新闻我不关 54 | 心,其它只要积极向上的东西我都欢迎! 55 | 56 | 最最后,我还想介绍一下make程序的设计开发者。 57 | 58 | 首屈一指的是:Richard Stallman 59 | 60 | 开源软件的领袖和先驱,从来没有领过一天工资,从来没有使用过Windows操作系统。对于他的事迹和他的 61 | 软件以及他的思想,我无需说过多的话,相信大家对这个人并不比我陌生,这是他的主页 62 | :http://www.stallman.org/ 。这里只贴上一张他的近照: 63 | 64 | http://bbs.chinaunix.net/attachments/rms.jpg 65 | 66 | 第二位是:Roland McGrath 67 | 68 | 个人主页是:http://www.frob.com/~roland/ ,下面是他的一些事迹: 69 | 70 | #. 合作编写了并维护GNU make。 71 | #. 和Thomas Bushnell一同编写了GNU Hurd。 72 | #. 编写并维护着GNU C library。 73 | #. 合作编写并维护着部分的GNU Emacs。 74 | 75 | 在此,向这两位开源项目的斗士致以最真切的敬意。 76 | 77 | (全文完) 78 | -------------------------------------------------------------------------------- /source/recipes.rst: -------------------------------------------------------------------------------- 1 | 书写命令 2 | ======== 3 | 4 | 每条规则中的命令和操作系统Shell的命令行是一致的。make会一按顺序一条一条的执行命令,每条命令的 5 | 开头必须以 ``Tab`` 键开头,除非,命令是紧跟在依赖规则后面的分号后的。在命令行之间中的空格或是 6 | 空行会被忽略,但是如果该空格或空行是以Tab键开头的,那么make会认为其是一个空命令。 7 | 8 | 我们在UNIX下可能会使用不同的Shell,但是make的命令默认是被 ``/bin/sh`` ——UNIX的标准Shell 9 | 解释执行的。除非你特别指定一个其它的Shell。Makefile中, ``#`` 是注释符,很像C/C++中的 10 | ``//`` ,其后的本行字符都被注释。 11 | 12 | 显示命令 13 | -------- 14 | 15 | 通常,make会把其要执行的命令行在命令执行前输出到屏幕上。当我们用 ``@`` 字符在命令行前,那么, 16 | 这个命令将不被make显示出来,最具代表性的例子是,我们用这个功能来向屏幕显示一些信息。如:: 17 | 18 | @echo 正在编译XXX模块...... 19 | 20 | 当make执行时,会输出“正在编译XXX模块......”字串,但不会输出命令,如果没有“@”,那么,make将 21 | 输出:: 22 | 23 | echo 正在编译XXX模块...... 24 | 正在编译XXX模块...... 25 | 26 | 如果make执行时,带入make参数 ``-n`` 或 ``--just-print`` ,那么其只是显示命令,但不会执行 27 | 命令,这个功能很有利于我们调试我们的Makefile,看看我们书写的命令是执行起来是什么样子的或是什么 28 | 顺序的。 29 | 30 | 而make参数 ``-s`` 或 ``--silent`` 或 ``--quiet`` 则是全面禁止命令的显示。 31 | 32 | 命令执行 33 | -------- 34 | 35 | 当依赖目标新于目标时,也就是当规则的目标需要被更新时,make会一条一条的执行其后的命令。需要注意 36 | 的是,如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。比如你的第一 37 | 条命令是cd命令,你希望第二条命令得在cd之后的基础上运行,那么你就不能把这两条命令写在两行上,而 38 | 应该把这两条命令写在一行上,用分号分隔。如: 39 | 40 | - 示例一: 41 | 42 | .. code-block:: makefile 43 | 44 | exec: 45 | cd /home/hchen 46 | pwd 47 | 48 | - 示例二: 49 | 50 | .. code-block:: makefile 51 | 52 | exec: 53 | cd /home/hchen; pwd 54 | 55 | 当我们执行 ``make exec`` 时,第一个例子中的cd没有作用,pwd会打印出当前的Makefile目录,而第 56 | 二个例子中,cd就起作用了,pwd会打印出“/home/hchen”。 57 | 58 | make一般是使用环境变量SHELL中所定义的系统Shell来执行命令,默认情况下使用UNIX的标 59 | 准Shell——/bin/sh来执行命令。但在MS-DOS下有点特殊,因为MS-DOS下没有SHELL环境变量,当然你也 60 | 可以指定。如果你指定了UNIX风格的目录形式,首先,make会在SHELL所指定的路径中找寻命令解释器,如 61 | 果找不到,其会在当前盘符中的当前目录中寻找,如果再找不到,其会在PATH环境变量中所定义的所有路径 62 | 中寻找。MS-DOS中,如果你定义的命令解释器没有找到,其会给你的命令解释器加上诸如 ``.exe`` 、 63 | ``.com`` 、 ``.bat`` 、 ``.sh`` 等后缀。 64 | 65 | 命令出错 66 | -------- 67 | 68 | 每当命令运行完后,make会检测每个命令的返回码,如果命令返回成功,那么make会执行下一条命令,当规 69 | 则中所有的命令成功返回后,这个规则就算是成功完成了。如果一个规则中的某个命令出错了(命令退出码 70 | 非零),那么make就会终止执行当前规则,这将有可能终止所有规则的执行。 71 | 72 | 有些时候,命令的出错并不表示就是错误的。例如mkdir命令,我们一定需要建立一个目录,如果目录不存 73 | 在,那么mkdir就成功执行,万事大吉,如果目录存在,那么就出错了。我们之所以使用mkdir的意思就是 74 | 一定要有这样的一个目录,于是我们就不希望mkdir出错而终止规则的运行。 75 | 76 | 为了做到这一点,忽略命令的出错,我们可以在Makefile的命令行前加一个减号 ``-`` (在Tab键之后) 77 | ,标记为不管命令出不出错都认为是成功的。如: 78 | 79 | .. code-block:: bash 80 | 81 | clean: 82 | -rm -f *.o 83 | 84 | 还有一个全局的办法是,给make加上 ``-i`` 或是 ``--ignore-errors`` 参数,那么,Makefile中 85 | 所有命令都会忽略错误。而如果一个规则是以 ``.IGNORE`` 作为目标的,那么这个规则中的所有命令将会 86 | 忽略错误。这些是不同级别的防止命令出错的方法,你可以根据你的不同喜欢设置。 87 | 88 | 还有一个要提一下的make的参数的是 ``-k`` 或是 ``--keep-going`` ,这个参数的意思是,如果某 89 | 规则中的命令出错了,那么就终止该规则的执行,但继续执行其它规则。 90 | 91 | 嵌套执行make 92 | ------------ 93 | 94 | 在一些大的工程中,我们会把我们不同模块或是不同功能的源文件放在不同的目录中,我们可以在每个目录 95 | 中都书写一个该目录的Makefile,这有利于让我们的Makefile变得更加地简洁,而不至于把所有的东西全 96 | 部写在一个Makefile中,这样会很难维护我们的Makefile,这个技术对于我们模块编译和分段编译有着非 97 | 常大的好处。 98 | 99 | 例如,我们有一个子目录叫subdir,这个目录下有个Makefile文件,来指明了这个目录下文件的编译规则 100 | 。那么我们总控的Makefile可以这样书写: 101 | 102 | .. code-block:: makefile 103 | 104 | subsystem: 105 | cd subdir && $(MAKE) 106 | 107 | 其等价于: 108 | 109 | .. code-block:: makefile 110 | 111 | subsystem: 112 | $(MAKE) -C subdir 113 | 114 | 定义$(MAKE)宏变量的意思是,也许我们的make需要一些参数,所以定义成一个变量比较利于维护。这两个 115 | 例子的意思都是先进入“subdir”目录,然后执行make命令。 116 | 117 | 我们把这个Makefile叫做“总控Makefile”,总控Makefile的变量可以传递到下级的Makefile中(如果 118 | 你显示的声明),但是不会覆盖下层的Makefile中所定义的变量,除非指定了 ``-e`` 参数。 119 | 120 | 如果你要传递变量到下级Makefile中,那么你可以使用这样的声明:: 121 | 122 | export ; 123 | 124 | 如果你不想让某些变量传递到下级Makefile中,那么你可以这样声明:: 125 | 126 | unexport ; 127 | 128 | 如: 129 | 130 | 示例一: 131 | 132 | .. code-block:: makefile 133 | 134 | export variable = value 135 | 136 | 其等价于: 137 | 138 | .. code-block:: makefile 139 | 140 | variable = value 141 | export variable 142 | 143 | 其等价于: 144 | 145 | .. code-block:: makefile 146 | 147 | export variable := value 148 | 149 | 其等价于: 150 | 151 | .. code-block:: makefile 152 | 153 | variable := value 154 | export variable 155 | 156 | 示例二: 157 | 158 | .. code-block:: makefile 159 | 160 | export variable += value 161 | 162 | 其等价于: 163 | 164 | .. code-block:: makefile 165 | 166 | variable += value 167 | export variable 168 | 169 | 如果你要传递所有的变量,那么,只要一个export就行了。后面什么也不用跟,表示传递所有的变量。 170 | 171 | 需要注意的是,有两个变量,一个是 ``SHELL`` ,一个是 ``MAKEFLAGS`` ,这两个变量不管你是 172 | 否export,其总是要传递到下层 Makefile中,特别是 ``MAKEFLAGS`` 变量,其中包含了make的参数 173 | 信息,如果我们执行“总控Makefile”时有make参数或是在上层 Makefile中定义了这个变量,那么 174 | ``MAKEFLAGS`` 变量将会是这些参数,并会传递到下层Makefile中,这是一个系统级的环境变量。 175 | 176 | 但是make命令中的有几个参数并不往下传递,它们是 ``-C`` , ``-f`` , ``-h``, ``-o`` 和 177 | ``-W`` (有关Makefile参数的细节将在后面说明),如果你不想往下层传递参数,那么,你可以这样来: 178 | 179 | .. code-block:: makefile 180 | 181 | subsystem: 182 | cd subdir && $(MAKE) MAKEFLAGS= 183 | 184 | 如果你定义了环境变量 ``MAKEFLAGS`` ,那么你得确信其中的选项是大家都会用到的,如果其中有 185 | ``-t`` , ``-n`` 和 ``-q`` 参数,那么将会有让你意想不到的结果,或许会让你异常地恐慌。 186 | 187 | 还有一个在“嵌套执行”中比较有用的参数, ``-w`` 或是 ``--print-directory`` 会在make的过程 188 | 中输出一些信息,让你看到目前的工作目录。比如,如果我们的下级make目录 189 | 是“/home/hchen/gnu/make”,如果我们使用 ``make -w`` 来执行,那么当进入该目录时,我们会看 190 | 到:: 191 | 192 | make: Entering directory `/home/hchen/gnu/make'. 193 | 194 | 而在完成下层make后离开目录时,我们会看到:: 195 | 196 | make: Leaving directory `/home/hchen/gnu/make' 197 | 198 | 当你使用 ``-C`` 参数来指定make下层Makefile时, ``-w`` 会被自动打开的。如果参数中有 199 | ``-s`` ( ``--slient`` )或是 ``--no-print-directory`` ,那么, ``-w`` 总是失效的。 200 | 201 | 定义命令包 202 | ---------- 203 | 204 | 如果Makefile中出现一些相同命令序列,那么我们可以为这些相同的命令序列定义一个变量。定义这种命令 205 | 序列的语法以 ``define`` 开始,以 ``endef`` 结束,如:: 206 | 207 | define run-yacc 208 | yacc $(firstword $^) 209 | mv y.tab.c $@ 210 | endef 211 | 212 | 这里,“run-yacc”是这个命令包的名字,其不要和Makefile中的变量重名。在 ``define`` 和 213 | ``endef`` 中的两行就是命令序列。这个命令包中的第一个命令是运行Yacc程序,因为Yacc程序总是生 214 | 成“y.tab.c”的文件,所以第二行的命令就是把这个文件改改名字。还是把这个命令包放到一个示例中来看 215 | 看吧。 216 | 217 | .. code-block:: makefile 218 | 219 | foo.c : foo.y 220 | $(run-yacc) 221 | 222 | 我们可以看见,要使用这个命令包,我们就好像使用变量一样。在这个命令包的使用中,命令 223 | 包“run-yacc”中的 ``$^`` 就是 ``foo.y`` , ``$@`` 就是 ``foo.c`` (有关这种以 ``$`` 224 | 开头的特殊变量,我们会在后面介绍),make在执行命令包时,命令包中的每个命令会被依次独立执行。 225 | -------------------------------------------------------------------------------- /source/rules.rst: -------------------------------------------------------------------------------- 1 | 书写规则 2 | ======== 3 | 4 | 规则包含两个部分,一个是依赖关系,一个是生成目标的方法。 5 | 6 | 在Makefile中,规则的顺序是很重要的,因为,Makefile中只应该有一个最终目标,其它的目标都是被这 7 | 个目标所连带出来的,所以一定要让make知道你的最终目标是什么。一般来说,定义在Makefile中的目标 8 | 可能会有很多,但是第一条规则中的目标将被确立为最终的目标。如果第一条规则中的目标有很多个,那么 9 | ,第一个目标会成为最终的目标。make所完成的也就是这个目标。 10 | 11 | 好了,还是让我们来看一看如何书写规则。 12 | 13 | 规则举例 14 | -------- 15 | 16 | .. code-block:: makefile 17 | 18 | foo.o: foo.c defs.h # foo模块 19 | cc -c -g foo.c 20 | 21 | 看到这个例子,各位应该不是很陌生了,前面也已说过, ``foo.o`` 是我们的目标, ``foo.c`` 和 22 | ``defs.h`` 是目标所依赖的源文件,而只有一个命令 ``cc -c -g foo.c`` (以Tab键开头)。这个 23 | 规则告诉我们两件事: 24 | 25 | #. 文件的依赖关系, ``foo.o`` 依赖于 ``foo.c`` 和 ``defs.h`` 的文件,如果 ``foo.c`` 26 | 和 ``defs.h`` 的文件日期要比 ``foo.o`` 文件日期要新,或是 ``foo.o`` 不存在,那么依赖 27 | 关系发生。 28 | #. 生成或更新 ``foo.o`` 文件,就是那个cc命令。它说明了如何生成 ``foo.o`` 这个文件。 29 | (当然,foo.c文件include了defs.h文件) 30 | 31 | 规则的语法 32 | ---------- 33 | 34 | .. code-block:: makefile 35 | 36 | targets : prerequisites 37 | command 38 | ... 39 | 40 | 或是这样: 41 | 42 | .. code-block:: makefile 43 | 44 | targets : prerequisites ; command 45 | command 46 | ... 47 | 48 | targets是文件名,以空格分开,可以使用通配符。一般来说,我们的目标基本上是一个文件,但也有可能 49 | 是多个文件。 50 | 51 | command是命令行,如果其不与“target:prerequisites”在一行,那么,必须以 ``Tab`` 键开头,如 52 | 果和prerequisites在一行,那么可以用分号做为分隔。(见上) 53 | 54 | prerequisites也就是目标所依赖的文件(或依赖目标)。如果其中的某个文件要比目标文件要新,那么, 55 | 目标就被认为是“过时的”,被认为是需要重生成的。这个在前面已经讲过了。 56 | 57 | 如果命令太长,你可以使用反斜杠( ``\`` )作为换行符。make对一行上有多少个字符没有限制。规则告 58 | 诉make两件事,文件的依赖关系和如何生成目标文件。 59 | 60 | 一般来说,make会以UNIX的标准Shell,也就是 ``/bin/sh`` 来执行命令。 61 | 62 | 在规则中使用通配符 63 | ------------------ 64 | 65 | 如果我们想定义一系列比较类似的文件,我们很自然地就想起使用通配符。make支持三个通配符: 66 | ``*`` , ``?`` 和 ``~`` 。这是和Unix的B-Shell是相同的。 67 | 68 | 波浪号( ``~`` )字符在文件名中也有比较特殊的用途。如果是 ``~/test`` ,这就表示当前用户 69 | 的 ``$HOME`` 目录下的test目录。而 ``~hchen/test`` 则表示用户hchen的宿主目录下的test 70 | 目录。(这些都是Unix下的小知识了,make也支持)而在Windows或是 MS-DOS下,用户没有宿主目录, 71 | 那么波浪号所指的目录则根据环境变量“HOME”而定。 72 | 73 | 通配符代替了你一系列的文件,如 ``*.c`` 表示所有后缀为c的文件。一个需要我们注意的是,如果我们 74 | 的文件名中有通配符,如: ``*`` ,那么可以用转义字符 ``\`` ,如 ``\*`` 来表示真实的 ``*`` 75 | 字符,而不是任意长度的字符串。 76 | 77 | 好吧,还是先来看几个例子吧: 78 | 79 | .. code-block:: makefile 80 | 81 | clean: 82 | rm -f *.o 83 | 84 | 其实在这个clean:后面可以加上你想做的一些事情,如果你想看到在编译完后看看main.c的源代码,你 85 | 可以在加上cat这个命令,例子如下: 86 | 87 | .. code-block:: makefile 88 | 89 | clean: 90 | cat main.c 91 | rm -f *.o 92 | 93 | 其结果你试一下就知道的。 上面这个例子我不不多说了,这是操作系统Shell所支持的通配符。这是在命令 94 | 中的通配符。 95 | 96 | .. code-block:: makefile 97 | 98 | print: *.c 99 | lpr -p $? 100 | touch print 101 | 102 | 上面这个例子说明了通配符也可以在我们的规则中,目标print依赖于所有的 ``.c`` 文件。其中的 103 | ``$?`` 是一个自动化变量,我会在后面给你讲述。 104 | 105 | .. code-block:: makefile 106 | 107 | objects = *.o 108 | 109 | 上面这个例子,表示了通配符同样可以用在变量中。并不是说 ``*.o`` 会展开,不!objects的值就是 110 | ``*.o`` 。Makefile中的变量其实就是C/C++中的宏。如果你要让通配符在变量中展开,也就是 111 | 让objects的值是所有 ``.o`` 的文件名的集合,那么,你可以这样: 112 | 113 | .. code-block:: makefile 114 | 115 | objects := $(wildcard *.o) 116 | 117 | 另给一个变量使用通配符的例子: 118 | 119 | #. 列出一确定文件夹中的所有 ``.c`` 文件。 120 | 121 | .. code-block:: makefile 122 | 123 | objects := $(wildcard *.c) 124 | 125 | #. 列出(1)中所有文件对应的 ``.o`` 文件,在(3)中我们可以看到它是由make自动编译出的:: 126 | 127 | $(patsubst %.c,%.o,$(wildcard *.c)) 128 | 129 | #. 由(1)(2)两步,可写出编译并链接所有 ``.c`` 和 ``.o`` 文件 130 | 131 | .. code-block:: makefile 132 | 133 | objects := $(patsubst %.c,%.o,$(wildcard *.c)) 134 | foo : $(objects) 135 | cc -o foo $(objects) 136 | 137 | 这种用法由关键字“wildcard”,“patsubst”指出,关于Makefile的关键字,我们将在后面讨论。 138 | 139 | 文件搜寻 140 | -------- 141 | 142 | 在一些大的工程中,有大量的源文件,我们通常的做法是把这许多的源文件分类,并存放在不同的目录中。 143 | 所以,当make需要去找寻文件的依赖关系时,你可以在文件前加上路径,但最好的方法是把一个路径告 144 | 诉make,让make在自动去找。 145 | 146 | Makefile文件中的特殊变量 ``VPATH`` 就是完成这个功能的,如果没有指明这个变量,make只会在当前 147 | 的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make就会在当前目录找不到的情况下 148 | ,到所指定的目录中去找寻文件了。 149 | 150 | .. code-block:: makefile 151 | 152 | VPATH = src:../headers 153 | 154 | 上面的定义指定两个目录,“src”和“../headers”,make会按照这个顺序进行搜索。目录由“冒号”分隔 155 | 。(当然,当前目录永远是最高优先搜索的地方) 156 | 157 | 另一个设置文件搜索路径的方法是使用make的“vpath”关键字(注意,它是全小写的),这不是变量,这是 158 | 一个make的关键字,这和上面提到的那个VPATH变量很类似,但是它更为灵活。它可以指定不同的文件在不 159 | 同的搜索目录中。这是一个很灵活的功能。它的使用方法有三种: 160 | 161 | ``vpath `` 162 | 为符合模式的文件指定搜索目录。 163 | 164 | ``vpath `` 165 | 清除符合模式的文件的搜索目录。 166 | 167 | ``vpath`` 168 | 清除所有已被设置好了的文件搜索目录。 169 | 170 | vpath使用方法中的需要包含 ``%`` 字符。 ``%`` 的意思是匹配零或若干字符,(需引用 171 | ``%`` ,使用 ``\`` )例如, ``%.h`` 表示所有以 ``.h`` 结尾的文件。指定了要搜索 172 | 的文件集,而则指定了< pattern>的文件集的搜索的目录。例如: 173 | 174 | .. code-block:: makefile 175 | 176 | vpath %.h ../headers 177 | 178 | 该语句表示,要求make在“../headers”目录下搜索所有以 ``.h`` 结尾的文件。(如果某文件在当前目 179 | 录没有找到的话) 180 | 181 | 我们可以连续地使用vpath语句,以指定不同搜索策略。如果连续的vpath语句中出现了相同的 182 | ,或是被重复了的,那么,make会按照vpath语句的先后顺序来执行搜索。如: 183 | 184 | .. code-block:: makefile 185 | 186 | vpath %.c foo 187 | vpath % blish 188 | vpath %.c bar 189 | 190 | 其表示 ``.c`` 结尾的文件,先在“foo”目录,然后是“blish”,最后是“bar”目录。 191 | 192 | .. code-block:: makefile 193 | 194 | vpath %.c foo:bar 195 | vpath % blish 196 | 197 | 而上面的语句则表示 ``.c`` 结尾的文件,先在“foo”目录,然后是“bar”目录,最后才是“blish”目录。 198 | 199 | 伪目标 200 | ------ 201 | 202 | 最早先的一个例子中,我们提到过一个“clean”的目标,这是一个“伪目标”, 203 | 204 | .. code-block:: makefile 205 | 206 | clean: 207 | rm *.o temp 208 | 209 | 正像我们前面例子中的“clean”一样,既然我们生成了许多文件编译文件,我们也应该提供一个清除它们的“ 210 | 目标”以备完整地重编译而用。 (以“make clean”来使用该目标) 211 | 212 | 因为,我们并不生成“clean”这个文件。“伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是 213 | 文件,所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显式地指明这个“目标”才能让其 214 | 生效。当然,“伪目标”的取名不能和文件名重名,不然其就失去了“伪目标”的意义了。 215 | 216 | 当然,为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显式地指明一个目标是“ 217 | 伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。 218 | 219 | .. code-block:: makefile 220 | 221 | .PHONY : clean 222 | 223 | 只要有这个声明,不管是否有“clean”文件,要运行“clean”这个目标,只有“make clean”这样。于是整 224 | 个过程可以这样写: 225 | 226 | .. code-block:: makefile 227 | 228 | .PHONY : clean 229 | clean : 230 | rm *.o temp 231 | 232 | 伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为“默认目 233 | 标”,只要将其放在第一个。一个示例就是,如果你的Makefile需要一口气生成若干个可执行文件,但你只 234 | 想简单地敲一个make完事,并且,所有的目标文件都写在一个Makefile中,那么你可以使用“伪目标”这个 235 | 特性: 236 | 237 | .. code-block:: makefile 238 | 239 | all : prog1 prog2 prog3 240 | .PHONY : all 241 | 242 | prog1 : prog1.o utils.o 243 | cc -o prog1 prog1.o utils.o 244 | 245 | prog2 : prog2.o 246 | cc -o prog2 prog2.o 247 | 248 | prog3 : prog3.o sort.o utils.o 249 | cc -o prog3 prog3.o sort.o utils.o 250 | 251 | 我们知道,Makefile中的第一个目标会被作为其默认目标。我们声明了一个“all”的伪目标,其依赖于其它 252 | 三个目标。由于默认目标的特性是,总是被执行的,但由于“all”又是一个伪目标,伪目标只是一个标签不 253 | 会生成文件,所以不会有“all”文件产生。于是,其它三个目标的规则总是会被决议。也就达到了我们一口 254 | 气生成多个目标的目的。 ``.PHONY : all`` 声明了“all”这个目标为“伪目标”。(注:这里的显式 255 | “.PHONY : all” 不写的话一般情况也可以正确的执行,这样make可通过隐式规则推导出, “all” 是一 256 | 个伪目标,执行make不会生成“all”文件,而执行后面的多个目标。建议:显式写出是一个好习惯。) 257 | 258 | 随便提一句,从上面的例子我们可以看出,目标也可以成为依赖。所以,伪目标同样也可成为依赖。看下面 259 | 的例子: 260 | 261 | .. code-block:: makefile 262 | 263 | .PHONY : cleanall cleanobj cleandiff 264 | 265 | cleanall : cleanobj cleandiff 266 | rm program 267 | 268 | cleanobj : 269 | rm *.o 270 | 271 | cleandiff : 272 | rm *.diff 273 | 274 | “make cleanall”将清除所有要被清除的文件。“cleanobj”和“cleandiff”这两个伪目标有点像“子程 275 | 序”的意思。我们可以输入“make cleanall”和“make cleanobj”和“make cleandiff”命令来达到清 276 | 除不同种类文件的目的。 277 | 278 | 多目标 279 | ------ 280 | 281 | Makefile的规则中的目标可以不止一个,其支持多目标,有可能我们的多个目标同时依赖于一个文件,并且 282 | 其生成的命令大体类似。于是我们就能把其合并起来。当然,多个目标的生成规则的执行命令不是同一个, 283 | 这可能会给我们带来麻烦,不过好在我们可以使用一个自动化变量 ``$@`` (关于自动化变量,将在后面讲 284 | 述),这个变量表示着目前规则中所有的目标的集合,这样说可能很抽象,还是看一个例子吧。 285 | 286 | .. code-block:: makefile 287 | 288 | bigoutput littleoutput : text.g 289 | generate text.g -$(subst output,,$@) > $@ 290 | 291 | 上述规则等价于: 292 | 293 | .. code-block:: makefile 294 | 295 | bigoutput : text.g 296 | generate text.g -big > bigoutput 297 | littleoutput : text.g 298 | generate text.g -little > littleoutput 299 | 300 | 其中, ``-$(subst output,,$@)`` 中的 ``$`` 表示执行一个Makefile的函数,函数名为subst, 301 | 后面的为参数。关于函数,将在后面讲述。这里的这个函数是替换字符串的意思, ``$@`` 表示目标的 302 | 集合,就像一个数组, ``$@`` 依次取出目标,并执于命令。 303 | 304 | 静态模式 305 | -------- 306 | 307 | 静态模式可以更加容易地定义多目标的规则,可以让我们的规则变得更加的有弹性和灵活。我们还是先来 308 | 看一下语法: 309 | 310 | .. code-block:: makefile 311 | 312 | : : 313 | 314 | ... 315 | 316 | targets定义了一系列的目标文件,可以有通配符。是目标的一个集合。 317 | 318 | target-pattern是指明了targets的模式,也就是的目标集模式。 319 | 320 | prereq-patterns是目标的依赖模式,它对target-pattern形成的模式再进行一次依赖目标的定义。 321 | 322 | 这样描述这三个东西,可能还是没有说清楚,还是举个例子来说明一下吧。如果我们 323 | 的定义成 ``%.o`` ,意思是我们的;集合中都是以 ``.o`` 结尾的,而 324 | 如果我们的定义成 ``%.c`` ,意思是对所形成的目标集进 325 | 行二次定义,其计算方法是,取模式中的 ``%`` (也就是去掉了 ``.o`` 这个结 326 | 尾),并为其加上 ``.c`` 这个结尾,形成的新集合。 327 | 328 | 所以,我们的“目标模式”或是“依赖模式”中都应该有 ``%`` 这个字符,如果你的文件名中有 ``%`` 那么 329 | 你可以使用反斜杠 ``\`` 进行转义,来标明真实的 ``%`` 字符。 330 | 331 | 看一个例子: 332 | 333 | .. code-block:: makefile 334 | 335 | objects = foo.o bar.o 336 | 337 | all: $(objects) 338 | 339 | $(objects): %.o: %.c 340 | $(CC) -c $(CFLAGS) $< -o $@ 341 | 342 | 上面的例子中,指明了我们的目标从$object中获取, ``%.o`` 表明要所有以 ``.o`` 结尾的目标,也 343 | 就是 ``foo.o bar.o`` ,也就是变量 ``$object`` 集合的模式,而依赖模式 ``%.c`` 则取模式 344 | ``%.o`` 的 ``%`` ,也就是 ``foo bar`` ,并为其加下 ``.c`` 的后缀,于是,我们的依赖目标就 345 | 是 ``foo.c bar.c`` 。而命令中的 ``$<`` 和 ``$@`` 则是自动化变量, ``$<`` 表示第一个依赖文件, 346 | ``$@`` 表示目标集(也就是“foo.o bar.o”)。于是,上面 347 | 的规则展开后等价于下面的规则: 348 | 349 | .. code-block:: makefile 350 | 351 | foo.o : foo.c 352 | $(CC) -c $(CFLAGS) foo.c -o foo.o 353 | bar.o : bar.c 354 | $(CC) -c $(CFLAGS) bar.c -o bar.o 355 | 356 | 试想,如果我们的 ``%.o`` 有几百个,那么我们只要用这种很简单的“静态模式规则”就可以写完一堆 357 | 规则,实在是太有效率了。“静态模式规则”的用法很灵活,如果用得好,那会是一个很强大的功能。再看一 358 | 个例子: 359 | 360 | .. code-block:: makefile 361 | 362 | files = foo.elc bar.o lose.o 363 | 364 | $(filter %.o,$(files)): %.o: %.c 365 | $(CC) -c $(CFLAGS) $< -o $@ 366 | $(filter %.elc,$(files)): %.elc: %.el 367 | emacs -f batch-byte-compile $< 368 | 369 | $(filter %.o,$(files))表示调用Makefile的filter函数,过滤“$files”集,只要其中模式 370 | 为“%.o”的内容。其它的内容,我就不用多说了吧。这个例子展示了Makefile中更大的弹性。 371 | 372 | 自动生成依赖性 373 | -------------- 374 | 375 | 在Makefile中,我们的依赖关系可能会需要包含一系列的头文件,比如,如果我们的main.c中有一句 376 | ``#include "defs.h"`` ,那么我们的依赖关系应该是: 377 | 378 | .. code-block:: makefile 379 | 380 | main.o : main.c defs.h 381 | 382 | 但是,如果是一个比较大型的工程,你必需清楚哪些C文件包含了哪些头文件,并且,你在加入或删除头文件 383 | 时,也需要小心地修改Makefile,这是一个很没有维护性的工作。为了避免这种繁重而又容易出错的事情, 384 | 我们可以使用C/C++编译的一个功能。大多数的C/C++编译器都支持一个“-M”的选项,即自动找寻源文件中 385 | 包含的头文件,并生成一个依赖关系。例如,如果我们执行下面的命令:: 386 | 387 | cc -M main.c 388 | 389 | 其输出是: 390 | 391 | .. code-block:: makefile 392 | 393 | main.o : main.c defs.h 394 | 395 | 于是由编译器自动生成的依赖关系,这样一来,你就不必再手动书写若干文件的依赖关系,而由编译器自动 396 | 生成了。需要提醒一句的是,如果你使用GNU的C/C++编译器,你得用 ``-MM`` 参数,不然, ``-M`` 397 | 参数会把一些标准库的头文件也包含进来。 398 | 399 | gcc -M main.c的输出是:: 400 | 401 | main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h \ 402 | /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \ 403 | /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h \ 404 | /usr/include/bits/types.h /usr/include/bits/pthreadtypes.h \ 405 | /usr/include/bits/sched.h /usr/include/libio.h \ 406 | /usr/include/_G_config.h /usr/include/wchar.h \ 407 | /usr/include/bits/wchar.h /usr/include/gconv.h \ 408 | /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h \ 409 | /usr/include/bits/stdio_lim.h 410 | 411 | gcc -MM main.c的输出则是:: 412 | 413 | main.o: main.c defs.h 414 | 415 | 那么,编译器的这个功能如何与我们的Makefile联系在一起呢。因为这样一来,我们的Makefile也要根据 416 | 这些源文件重新生成,让 Makefile 自己依赖于源文件?这个功能并不现实,不过我们可以有其它手段来迂 417 | 回地实现这一功能。GNU组织建议把编译器为每一个源文件的自动生成的依赖关系放到一个文件中,为每一 418 | 个 ``name.c`` 的文件都生成一个 ``name.d`` 的Makefile文件, ``.d`` 文件中就存放对应 419 | ``.c`` 文件的依赖关系。 420 | 421 | 于是,我们可以写出 ``.c`` 文件和 ``.d`` 文件的依赖关系,并让make自动更新或生成 ``.d`` 422 | 文件,并把其包含在我们的主Makefile中,这样,我们就可以自动化地生成每个文件的依赖关系了。 423 | 424 | 这里,我们给出了一个模式规则来产生 ``.d`` 文件: 425 | 426 | .. code-block:: makefile 427 | 428 | %.d: %.c 429 | @set -e; rm -f $@; \ 430 | $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \ 431 | sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ 432 | rm -f $@.$$$$ 433 | 434 | 435 | 这个规则的意思是,所有的 ``.d`` 文件依赖于 ``.c`` 文件, ``rm -f $@`` 的意思是删除所有的 436 | 目标,也就是 ``.d`` 文件,第二行的意思是,为每个依赖文件 ``$<`` ,也就是 ``.c`` 文件生成依 437 | 赖文件, ``$@`` 表示模式 ``%.d`` 文件,如果有一个C文件是name.c,那么 ``%`` 就是 438 | ``name`` , ``$$$$`` 意为一个随机编号,第二行生成的文件有可能是“name.d.12345”,第三行使 439 | 用sed命令做了一个替换,关于sed命令的用法请参看相关的使用文档。第四行就是删除临时文件。 440 | 441 | 总而言之,这个模式要做的事就是在编译器生成的依赖关系中加入 ``.d`` 文件的依赖,即把依赖关系: 442 | 443 | .. code-block:: makefile 444 | 445 | main.o : main.c defs.h 446 | 447 | 转成: 448 | 449 | .. code-block:: makefile 450 | 451 | main.o main.d : main.c defs.h 452 | 453 | 于是,我们的 ``.d`` 文件也会自动更新了,并会自动生成了,当然,你还可以在这个 ``.d`` 文件中 454 | 加入的不只是依赖关系,包括生成的命令也可一并加入,让每个 ``.d`` 文件都包含一个完整的规则。一旦 455 | 我们完成这个工作,接下来,我们就要把这些自动生成的规则放进我们的主Makefile中。我们可以使 456 | 用Makefile的“include”命令,来引入别的Makefile文件(前面讲过),例如: 457 | 458 | .. code-block:: makefile 459 | 460 | sources = foo.c bar.c 461 | 462 | include $(sources:.c=.d) 463 | 464 | 上述语句中的 ``$(sources:.c=.d)`` 中的 ``.c=.d`` 的意思是做一个替换,把变量 465 | ``$(sources)`` 所有 ``.c`` 的字串都替换成 ``.d`` ,关于这个“替换”的内容,在后面我会有更为 466 | 详细的讲述。当然,你得注意次序,因为include是按次序来载入文件,最先载入的 ``.d`` 文件中的目 467 | 标会成为默认目标。 468 | -------------------------------------------------------------------------------- /source/variables.rst: -------------------------------------------------------------------------------- 1 | 使用变量 2 | ======== 3 | 4 | 在Makefile中的定义的变量,就像是C/C++语言中的宏一样,他代表了一个文本字串,在Makefile中 5 | 执行的时候其会自动原模原样地展开在所使用的地方。其与C/C++所不同的是,你可以在Makefile中改变其 6 | 值。在Makefile中,变量可以使用在“目标”,“依赖目标”, “命令”或是Makefile的其它部分中。 7 | 8 | 变量的命名字可以包含字符、数字,下划线(可以是数字开头),但不应该含有 ``:`` 、 ``#`` 、 9 | ``=`` 或是空字符(空格、回车等)。变量是大小写敏感的,“foo”、“Foo”和“FOO”是三个不同的 10 | 变量名。传统的Makefile的变量名是全大写的命名方式,但我推荐使用大小写搭配的变量名,如: 11 | MakeFlags。这样可以避免和系统的变量冲突,而发生意外的事情。 12 | 13 | 有一些变量是很奇怪字串,如 ``$<`` 、 ``$@`` 等,这些是自动化变量,我会在后面介绍。 14 | 15 | 变量的基础 16 | ---------- 17 | 18 | 变量在声明时需要给予初值,而在使用时,需要给在变量名前加上 ``$`` 符号,但最好用小括号 19 | ``()`` 或是大括号 ``{}`` 把变量给包括起来。如果你要使用真实的 ``$`` 字符,那么你需要用 20 | ``$$`` 来表示。 21 | 22 | 变量可以使用在许多地方,如规则中的“目标”、“依赖”、“命令”以及新的变量中。先看一个例子: 23 | 24 | .. code-block:: makefile 25 | 26 | objects = program.o foo.o utils.o 27 | program : $(objects) 28 | cc -o program $(objects) 29 | 30 | $(objects) : defs.h 31 | 32 | 变量会在使用它的地方精确地展开,就像C/C++中的宏一样,例如: 33 | 34 | .. code-block:: makefile 35 | 36 | foo = c 37 | prog.o : prog.$(foo) 38 | $(foo)$(foo) -$(foo) prog.$(foo) 39 | 40 | 展开后得到: 41 | 42 | .. code-block:: makefile 43 | 44 | prog.o : prog.c 45 | cc -c prog.c 46 | 47 | 当然,千万不要在你的Makefile中这样干,这里只是举个例子来表明Makefile中的变量在使用处展开的 48 | 真实样子。可见其就是一个“替代”的原理。 49 | 50 | 另外,给变量加上括号完全是为了更加安全地使用这个变量,在上面的例子中,如果你不想给变量加上 51 | 括号,那也可以,但我还是强烈建议你给变量加上括号。 52 | 53 | 变量中的变量 54 | ------------ 55 | 56 | 在定义变量的值时,我们可以使用其它变量来构造变量的值,在Makefile中有两种方式来在用变量定义 57 | 变量的值。 58 | 59 | 先看第一种方式,也就是简单的使用 ``=`` 号,在 ``=`` 左侧是变量,右侧是变量的值,右侧变量的值 60 | 可以定义在文件的任何一处,也就是说,右侧中的变量不一定非要是已定义好的值,其也可以使用后面定义的值。如: 61 | 62 | .. code-block:: makefile 63 | 64 | foo = $(bar) 65 | bar = $(ugh) 66 | ugh = Huh? 67 | 68 | all: 69 | echo $(foo) 70 | 71 | 我们执行“make all”将会打出变量 ``$(foo)`` 的值是 ``Huh?`` ( ``$(foo)`` 的值是 72 | ``$(bar)`` , ``$(bar)`` 的值是 ``$(ugh)`` , ``$(ugh)`` 的值是 ``Huh?`` )可见,变 73 | 量是可以使用后面的变量来定义的。 74 | 75 | 这个功能有好的地方,也有不好的地方,好的地方是,我们可以把变量的真实值推到后面来定义,如: 76 | 77 | .. code-block:: makefile 78 | 79 | CFLAGS = $(include_dirs) -O 80 | include_dirs = -Ifoo -Ibar 81 | 82 | 当 ``CFLAGS`` 在命令中被展开时,会是 ``-Ifoo -Ibar -O`` 。但这种形式也有不好的地方,那就 83 | 是递归定义,如: 84 | 85 | .. code-block:: makefile 86 | 87 | CFLAGS = $(CFLAGS) -O 88 | 89 | 或: 90 | 91 | .. code-block:: makefile 92 | 93 | A = $(B) 94 | B = $(A) 95 | 96 | 这会让make陷入无限的变量展开过程中去,当然,我们的make是有能力检测这样的定义,并会报错。还有就 97 | 是如果在变量中使用函数,那么,这种方式会让我们的make运行时非常慢,更糟糕的是,他会使用得两 98 | 个make的函数“wildcard”和“shell”发生不可预知的错误。因为你不会知道这两个函数会被调用多少次。 99 | 100 | 为了避免上面的这种方法,我们可以使用make中的另一种用变量来定义变量的方法。这种方法使用的是 ``:=`` 操作符,如: 101 | 102 | .. code-block:: makefile 103 | 104 | x := foo 105 | y := $(x) bar 106 | x := later 107 | 108 | 其等价于: 109 | 110 | .. code-block:: makefile 111 | 112 | y := foo bar 113 | x := later 114 | 115 | 值得一提的是,这种方法,前面的变量不能使用后面的变量,只能使用前面已定义好了的变量。如果是这样: 116 | 117 | .. code-block:: makefile 118 | 119 | y := $(x) bar 120 | x := foo 121 | 122 | 那么,y的值是“bar”,而不是“foo bar”。 123 | 124 | 上面都是一些比较简单的变量使用了,让我们来看一个复杂的例子,其中包括了make的函数、条件表达式和 125 | 一个系统变量“MAKELEVEL”的使用: 126 | 127 | .. code-block:: makefile 128 | 129 | ifeq (0,${MAKELEVEL}) 130 | cur-dir := $(shell pwd) 131 | whoami := $(shell whoami) 132 | host-type := $(shell arch) 133 | MAKE := ${MAKE} host-type=${host-type} whoami=${whoami} 134 | endif 135 | 136 | 关于条件表达式和函数,我们在后面再说,对于系统变量“MAKELEVEL”,其意思是,如果我们的make有一 137 | 个嵌套执行的动作(参见前面的“嵌套使用make”),那么,这个变量会记录了我们的当前Makefile的调用 138 | 层数。 139 | 140 | 下面再介绍两个定义变量时我们需要知道的,请先看一个例子,如果我们要定义一个变量,其值是一个 141 | 空格,那么我们可以这样来: 142 | 143 | .. code-block:: makefile 144 | 145 | nullstring := 146 | space := $(nullstring) # end of the line 147 | 148 | nullstring是一个Empty变量,其中什么也没有,而我们的space的值是一个空格。因为在操作符的右边 149 | 是很难描述一个空格的,这里采用的技术很管用,先用一个Empty变量来标明变量的值开始了,而后面采 150 | 用“#”注释符来表示变量定义的终止,这样,我们可以定义出其值是一个空格的变量。请注意这里关于“#”的 151 | 使用,注释符“#”的这种特性值得我们注意,如果我们这样定义一个变量: 152 | 153 | .. code-block:: makefile 154 | 155 | dir := /foo/bar # directory to put the frobs in 156 | 157 | dir这个变量的值是“/foo/bar”,后面还跟了4个空格,如果我们这样使用这个变量来指定别的目 158 | 录——“$(dir)/file”那么就完蛋了。 159 | 160 | 还有一个比较有用的操作符是 ``?=`` ,先看示例: 161 | 162 | .. code-block:: makefile 163 | 164 | FOO ?= bar 165 | 166 | 其含义是,如果FOO没有被定义过,那么变量FOO的值就是“bar”,如果FOO先前被定义过,那么这条语将 167 | 什么也不做,其等价于: 168 | 169 | .. code-block:: makefile 170 | 171 | ifeq ($(origin FOO), undefined) 172 | FOO = bar 173 | endif 174 | 175 | 变量高级用法 176 | ------------ 177 | 178 | 这里介绍两种变量的高级使用方法,第一种是变量值的替换。 179 | 180 | 我们可以替换变量中的共有的部分,其格式是 ``$(var:a=b)`` 或是 ``${var:a=b}`` ,其意思是, 181 | 把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符”。 182 | 183 | 还是看一个示例吧: 184 | 185 | .. code-block:: makefile 186 | 187 | foo := a.o b.o c.o 188 | bar := $(foo:.o=.c) 189 | 190 | 这个示例中,我们先定义了一个 ``$(foo)`` 变量,而第二行的意思是把 ``$(foo)`` 中所有以 191 | ``.o`` 字串“结尾”全部替换成 ``.c`` ,所以我们的 ``$(bar)`` 的值就是“a.c b.c c.c”。 192 | 193 | 另外一种变量替换的技术是以“静态模式”(参见前面章节)定义的,如: 194 | 195 | .. code-block:: makefile 196 | 197 | foo := a.o b.o c.o 198 | bar := $(foo:%.o=%.c) 199 | 200 | 这依赖于被替换字串中的有相同的模式,模式中必须包含一个 ``%`` 字符,这个例子同样让 201 | ``$(bar)`` 变量的值为“a.c b.c c.c”。 202 | 203 | 第二种高级用法是——“把变量的值再当成变量”。先看一个例子: 204 | 205 | .. code-block:: makefile 206 | 207 | x = y 208 | y = z 209 | a := $($(x)) 210 | 211 | 在这个例子中,$(x)的值是“y”,所以$($(x))就是$(y),于是$(a)的值就是“z”。(注意,是“x=y”, 212 | 而不是“x=$(y)”) 213 | 214 | 我们还可以使用更多的层次: 215 | 216 | .. code-block:: makefile 217 | 218 | x = y 219 | y = z 220 | z = u 221 | a := $($($(x))) 222 | 223 | 这里的 ``$(a)`` 的值是“u”,相关的推导留给读者自己去做吧。 224 | 225 | 让我们再复杂一点,使用上“在变量定义中使用变量”的第一个方式,来看一个例子: 226 | 227 | .. code-block:: makefile 228 | 229 | x = $(y) 230 | y = z 231 | z = Hello 232 | a := $($(x)) 233 | 234 | 这里的 ``$($(x))`` 被替换成了 ``$($(y))`` ,因为 ``$(y)`` 值是“z”,所以,最终结果是: 235 | ``a:=$(z)`` ,也就是“Hello”。 236 | 237 | 再复杂一点,我们再加上函数: 238 | 239 | .. code-block:: makefile 240 | 241 | x = variable1 242 | variable2 := Hello 243 | y = $(subst 1,2,$(x)) 244 | z = y 245 | a := $($($(z))) 246 | 247 | 这个例子中, ``$($($(z)))`` 扩展为 ``$($(y))`` ,而其再次被扩展为 248 | ``$($(subst 1,2,$(x)))`` 。 ``$(x)`` 的值是“variable1”,subst函数把“variable1”中的 249 | 所有“1”字串替换成“2”字串,于是,“variable1”变成 “variable2”,再取其值,所以,最终, 250 | ``$(a)`` 的值就是 ``$(variable2)`` 的值——“Hello”。(喔,好不容易) 251 | 252 | 在这种方式中,或要可以使用多个变量来组成一个变量的名字,然后再取其值: 253 | 254 | .. code-block:: makefile 255 | 256 | first_second = Hello 257 | a = first 258 | b = second 259 | all = $($a_$b) 260 | 261 | 这里的 ``$a_$b`` 组成了“first_second”,于是, ``$(all)`` 的值就是“Hello”。 262 | 263 | 再来看看结合第一种技术的例子: 264 | 265 | .. code-block:: makefile 266 | 267 | a_objects := a.o b.o c.o 268 | 1_objects := 1.o 2.o 3.o 269 | 270 | sources := $($(a1)_objects:.o=.c) 271 | 272 | 这个例子中,如果 ``$(a1)`` 的值是“a”的话,那么, ``$(sources)`` 的值就是“a.c b.c c.c”; 273 | 如果 ``$(a1)`` 的值是“1”,那么 ``$(sources)`` 的值是“1.c 2.c 3.c”。 274 | 275 | 再来看一个这种技术和“函数”与“条件语句”一同使用的例子: 276 | 277 | .. code-block:: makefile 278 | 279 | ifdef do_sort 280 | func := sort 281 | else 282 | func := strip 283 | endif 284 | 285 | bar := a d b g q c 286 | 287 | foo := $($(func) $(bar)) 288 | 289 | 这个示例中,如果定义了“do_sort”,那么: ``foo := $(sort a d b g q c)`` ,于是 290 | ``$(foo)`` 的值就是 “a b c d g q”,而如果没有定义“do_sort”,那么: 291 | ``foo := $(strip a d b g q c)`` ,调用的就是strip函数。 292 | 293 | 当然,“把变量的值再当成变量”这种技术,同样可以用在操作符的左边:: 294 | 295 | dir = foo 296 | $(dir)_sources := $(wildcard $(dir)/*.c) 297 | define $(dir)_print 298 | lpr $($(dir)_sources) 299 | endef 300 | 301 | 这个例子中定义了三个变量:“dir”,“foo_sources”和“foo_print”。 302 | 303 | 追加变量值 304 | ---------- 305 | 306 | 我们可以使用 ``+=`` 操作符给变量追加值,如: 307 | 308 | .. code-block:: makefile 309 | 310 | objects = main.o foo.o bar.o utils.o 311 | objects += another.o 312 | 313 | 于是,我们的 ``$(objects)`` 值变成:“main.o foo.o bar.o utils.o another.o”(another.o被追加进去了) 314 | 315 | 使用 ``+=`` 操作符,可以模拟为下面的这种例子: 316 | 317 | .. code-block:: makefile 318 | 319 | objects = main.o foo.o bar.o utils.o 320 | objects := $(objects) another.o 321 | 322 | 所不同的是,用 ``+=`` 更为简洁。 323 | 324 | 如果变量之前没有定义过,那么, ``+=`` 会自动变成 ``=`` ,如果前面有变量定义,那么 ``+=`` 会 325 | 继承于前次操作的赋值符。如果前一次的是 ``:=`` ,那么 ``+=`` 会以 ``:=`` 作为其赋值符,如: 326 | 327 | .. code-block:: makefile 328 | 329 | variable := value 330 | variable += more 331 | 332 | 等价于: 333 | 334 | .. code-block:: makefile 335 | 336 | variable := value 337 | variable := $(variable) more 338 | 339 | 但如果是这种情况: 340 | 341 | .. code-block:: makefile 342 | 343 | variable = value 344 | variable += more 345 | 346 | 由于前次的赋值符是 ``=`` ,所以 ``+=`` 也会以 ``=`` 来做为赋值,那么岂不会发生变量的递补归 347 | 定义,这是很不好的,所以make会自动为我们解决这个问题,我们不必担心这个问题。 348 | 349 | override 指令 350 | --------------- 351 | 352 | 如果有变量是通过make的命令行参数设置的,那么Makefile文件中对这个变量的赋值会被忽略。如果你想 353 | 在Makefile文件中设置这类参数的值,那么,你可以使用“override”指令。其语法是:: 354 | 355 | override ; = ; 356 | 357 | override ; := ; 358 | 359 | 当然,你还可以追加:: 360 | 361 | override ; += ; 362 | 363 | 对于多行的变量定义,我们用define指令,在define指令前,也同样可以使用override指令, 364 | 如:: 365 | 366 | override define foo 367 | bar 368 | endef 369 | 370 | 多行变量 371 | -------- 372 | 373 | 还有一种设置变量值的方法是使用define关键字。使用define关键字设置变量的值可以有换行,这有利于 374 | 定义一系列的命令(前面我们讲过“命令包”的技术就是利用这个关键字)。 375 | 376 | define指令后面跟的是变量的名字,而重起一行定义变量的值,定义是以endef 关键字结束。其工作方 377 | 式和“=”操作符一样。变量的值可以包含函数、命令、文字,或是其它变量。因为命令需要以[Tab]键开头, 378 | 所以如果你用define定义的命令变量中没有以 ``Tab`` 键开头,那么make 就不会把其认为是命令。 379 | 380 | 下面的这个示例展示了define的用法:: 381 | 382 | define two-lines 383 | echo foo 384 | echo $(bar) 385 | endef 386 | 387 | 环境变量 388 | -------- 389 | 390 | make运行时的系统环境变量可以在make开始运行时被载入到Makefile文件中,但是如果Makefile中已 391 | 定义了这个变量,或是这个变量由make命令行带入,那么系统的环境变量的值将被覆盖。(如果make指定 392 | 了“-e”参数,那么,系统环境变量将覆盖Makefile中定义的变量) 393 | 394 | 因此,如果我们在环境变量中设置了 ``CFLAGS`` 环境变量,那么我们就可以在所有的Makefile中使用 395 | 这个变量了。这对于我们使用统一的编译参数有比较大的好处。如果Makefile中定义了CFLAGS,那么则会 396 | 使用Makefile中的这个变量,如果没有定义则使用系统环境变量的值,一个共性和个性的统一,很像“全局 397 | 变量”和“局部变量”的特性。 398 | 399 | 当make嵌套调用时(参见前面的“嵌套调用”章节),上层Makefile中定义的变量会以系统环境变量的方式 400 | 传递到下层的Makefile 中。当然,默认情况下,只有通过命令行设置的变量会被传递。而定义在文件中的 401 | 变量,如果要向下层Makefile传递,则需要使用export关键字来声明。(参见前面章节) 402 | 403 | 当然,我并不推荐把许多的变量都定义在系统环境中,这样,在我们执行不用的Makefile时,拥有的是同一 404 | 套系统变量,这可能会带来更多的麻烦。 405 | 406 | 目标变量 407 | -------- 408 | 409 | 前面我们所讲的在Makefile中定义的变量都是“全局变量”,在整个文件,我们都可以访问这些变量。 410 | 当然,“自动化变量”除外,如 ``$<`` 等这种类量的自动化变量就属于“规则型变量”,这种变量的值依赖 411 | 于规则的目标和依赖目标的定义。 412 | 413 | 当然,我也同样可以为某个目标设置局部变量,这种变量被称为“Target-specific Variable”,它可以 414 | 和“全局变量”同名,因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效。而 415 | 不会影响规则链以外的全局变量的值。 416 | 417 | 其语法是: 418 | 419 | .. code-block:: makefile 420 | 421 | : ; 422 | 423 | : overide 424 | 425 | ;可以是前面讲过的各种赋值表达式,如 ``=`` 、 ``:=`` 、 ``+=`` 426 | 或是 ``?=`` 。第二个语法是针对于make命令行带入的变量,或是系统环境变量。 427 | 428 | 这个特性非常的有用,当我们设置了这样一个变量,这个变量会作用到由这个目标所引发的所有的规则中 429 | 去。如: 430 | 431 | .. code-block:: makefile 432 | 433 | prog : CFLAGS = -g 434 | prog : prog.o foo.o bar.o 435 | $(CC) $(CFLAGS) prog.o foo.o bar.o 436 | 437 | prog.o : prog.c 438 | $(CC) $(CFLAGS) prog.c 439 | 440 | foo.o : foo.c 441 | $(CC) $(CFLAGS) foo.c 442 | 443 | bar.o : bar.c 444 | $(CC) $(CFLAGS) bar.c 445 | 446 | 在这个示例中,不管全局的 ``$(CFLAGS)`` 的值是什么,在prog目标,以及其所引发的所有规则 447 | 中(prog.o foo.o bar.o的规则), ``$(CFLAGS)`` 的值都是 ``-g`` 448 | 449 | 模式变量 450 | -------- 451 | 452 | 在GNU的make中,还支持模式变量(Pattern-specific Variable),通过上面的目标变量中,我们 453 | 知道,变量可以定义在某个目标上。模式变量的好处就是,我们可以给定一种“模式”,可以把变量定义在符 454 | 合这种模式的所有目标上。 455 | 456 | 我们知道,make的“模式”一般是至少含有一个 ``%`` 的,所以,我们可以以如下方式给所有以 ``.o`` 457 | 结尾的目标定义目标变量: 458 | 459 | .. code-block:: makefile 460 | 461 | %.o : CFLAGS = -O 462 | 463 | 同样,模式变量的语法和“目标变量”一样: 464 | 465 | .. code-block:: makefile 466 | 467 | ; : ; 468 | 469 | ; : override ; 470 | 471 | override同样是针对于系统环境传入的变量,或是make命令行指定的变量。 472 | --------------------------------------------------------------------------------