makefile

文章目录

  • 编译原理
    • 预处理
    • 编译
    • 汇编
    • 链接
  • 静态库
  • 动态库
  • 二者区别
  • makefile
    • 规则
      • 示例
    • 如何工作
    • 变量的使用
    • 环境变量
    • 预定义变量
    • 自动变量
    • 函数
    • 实际操作Linux
    • make的工作流程
  • Androiod.mk
    • 简介
    • 展示
    • 同时编译多个共享库
    • 静态库的编译

编译原理

编译就是将高级语言编写的程序转换为二进制代码,可执行性目标程序的过程。编译分为四大过程,第一步,预处理。第二步,编译。第三步汇编。最后一步链接。

预处理

预处理中完成了宏替换文件引入以及去除空行注释等工作,它为下一步的编译做准备。预处理其实就是对各种预处理命令进行处理,包括头文件的包含宏务定义的扩展。条件编译的选项等。

编译

makefile_第1张图片

汇编

汇编就是把编译阶段生成的点s汇编代码文件。转成二进制目标代码,也就是机器代码01序列。

链接

链接就是将多个目标文件以及所需的库文件链接生成可执行的目标文件的过程。

静态库

makefile_第2张图片

动态库

makefile_第3张图片

二者区别

makefile_第4张图片

makefile

makefile_第5张图片
第一个显示规则。显示规则说明了如何生成一个或多个目标文件。

第二个隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较简略的来书写make fun。这是由make所支持的。

第三个变量的定义。在makefile中,我们可以定义一系列的变量。变量一般都是字符串。这个有点像C语言中的宏。当makefile被执行时,其中的变量都会被扩展到相应的引用位置上。

第四个文件指示。它包括了三个部分。一个是在一个makefile引用,另一个makefile有点像C语言中的include一样。另一个是指根据某些情况,指定makefile中的有效部分。就像C语言中的域变异井号f一样。还有就是定义一个多行的命令。

最后一个注释。makefile中只有行注释,它的注释是用井号字符。如果你要在你的makefile中使用井号字符的话,可以使用反斜杠加井号来进行转译。

规则

makefile_第6张图片
target是我们的目标文件名,可以是object file,也可以是执行文件,还可以是标签label。一般来说,我们的目标基本上是一个文件,但也有可能是多个文件。如果是多个文件的话,可以空格隔开。

另外target可以使用通配符。就是我们的生成目标文件所依赖的文件,或者依赖的目标。command就是make需要执行的命令。

如果命令跟target和requests在同一行的话,可以使用分号作为分割。如果不在同一行,必须要以Tab键开头。另外,如果命令太长,我们可使用反斜杠作为换行符。

makefile的规则其实就是告诉make两件事情文件的依赖关系以及如何生成目标文件。 也就是说target这一个或多个的目标文件依赖于prerequisites中的文件。它的生成规则定义在command中。说白了就是prerequisites中,如果有一个以上的文件比target要新的话。target就会被认为是过时的,需要重新生成,那么command命令就会被执行,从而生成新的target。那么command命令就会被执行,从而生成新的target。这就是makefile的规则,也是makefile中最核心的内容

示例

makefile_第7张图片
当前目录存在三个文件,mian.c, tool.c, tool.h。下面就是makefile文件中的内容,里面有五行代码。第一行main冒号main.o。点o它表示,要生成一个名叫main的目标文件,这里是一个可执行文件。它依赖两个目标文件main.o和tool.o。

第二行表示,生成目标的命令。gcc main.o tool.o -o main。注意命令行,前面有一个Tab键。第三行点phony显示,指明clean是一个伪目标。第四行定义了一个clean标签。它是一个伪目标。第五行表示clean为目标的命令,这里就是清除编译生成的点o文件和可执行文件。我们读取到第一个target为main的时候。那么,这个main就是我们的终极目标。生成这个终极目标main,需要main.o tool.o。

虽然这里没有指定目标main.o和tool.o如何生成,因为make会自动推导生成main.o和tool.o。下面的clean标签,它并不生成clean这个文件,这样的target就是伪目标啊,伪目标的名字不能和文件名重复,所以当前目录下有一个文件名或者文件夹的名字为clean的话。我们运行make clean就会报一个错误,make clean is up to date。为了避免这种情况,可以使用一个特殊的标记点phony来显示的指明一个目标是伪目标。clean里面的rm命令,前面加了一个小减号,它的意思就是也许某些文件出现了问题。但是不用管,继续做后面的事情。clean为目标需要显示的,让make来执行。所以我们要在make后加上clean及make clean。一般我们用于清除目标文件,以便于重新编译。

如何工作

makefile_第8张图片
也就是说make。它会一层又一层的去找文件的依赖关系。直到最终,编译出第一个目标文件,在找的过程中,如果出现错误,比如最后被依赖的文件找不到。那么make就会直接退出,并且报错。而对于所定义的命令的错误或者编译不成功make,是不会理会的make,只管文件的依赖性。也就是说,如果找了依赖关系之后,冒号后面的文件还是不存在,那么。对不起,make就不工作了。如果这个工程已经被编译过,当我们修改其中一个原文件,比如tool.c。那么,根据我们的依赖关系,我们的目标tool.o会被重新编译,于是tool.o文件修改的时间要比main文件要新。所以我们的目标文件main也会被重新链接。

变量的使用

makefile_第9张图片

下面我们来看一下makefile中的变量的使用。如果我们的工程需要加入一个新的点o文件,那么我们需要修改三个地方,当然我们这里的makefile并不复杂。所以修改起来不麻烦,但是如果makefile变得复杂,那么我们就可能会忘掉一个需要加入的地方。导致编译失败。为了makefile的易维护,在makefile中,我们可以使用变量。

makefile的变量也就是一个字符串,可以理解成C语言中的宏。比如我们可以声明一个变量objects,于是我们可以很方便的在我们的makefile中以美元符号括弧。加objects的方式来使用这个变量。下面我来看一下makefile中如何引入其他的makefile。

在我们实际项目工程中,可能会把原文件按类型功能分成很多个模块,每个模块有一份单独的makefile文件。那么,我们编译的时候就需要引入其他模块的makefile了。在makefile中可以使用include关键字来包含其他的makefile。

include语法格式为include加文件名。举个例子,这样几个麦克风a.mk b.mk c.mk,还有一个文件叫foo.make。以及一个变量bar,它包含了e.mk f.mk。那么,我们使用include foo.mk,加上*.mk,加上 $(bar)。这样就等价于我们include foo.make a.mk b.mk c.mk e.mk f.mk。如果我们文件找不到,而你希望make不要理会这些无法读取的文件而继续执行后面。那么,可以在include前面加一个小减号,比如减号include加上我们的文件名。

环境变量

makefile_第10张图片

预定义变量

makefile_第11张图片

自动变量

makefile_第12张图片

函数

我们定义一个函数,可以使用define关键字,define它本质是定义一个多行的变量。没有办法直接调用,但是可以在call的作用下当做函数来使用。比如我们定义一个不带参数的define funk。里面做了一个打印hello。然后and。调用的话,使用美元符号括号括号funk方法名。
makefile_第13张图片
带参数的函数同样的可以使用define。我们调用的时候可以call方法名逗号,你的参数这里我们传了两个参数hello和。和word。然后我们在函数里面可以取出这个参数,比如每元符号括弧一。表示取出传过来的第一个参数没符号,括弧二。表示传进来的第二个参数,以此类推

实际操作Linux

我们还是使用上节课的三个文件main.c tool.c和tool.h。首先,我们不使用makefile,直接使用gcc命令来编译可执行文件main。同时,我们也复习一下上节课的内容。第一步,我们要生成目标文件。使用gcc -c tool.c,我们生成一个tool.o文件。生成完tool.o文件后,我们再来生成main.o。使用命令gcc -c main.c 生成main.o文件。生成了两个目标文件之后,第二步我们来生成可执行文件main。

输入命令gcc -o main main.o tool.o。OK,我们生成了一个名叫main的可执行文件,下面我们来执行它。执行完之后,输出max等于八。接下来,我们来编写makefile。使用vim命令makefile我们来进入vim编辑器。然后我们输入I进入编辑模式,接下来我们在makefile文件中输入我们的脚本内容。首先定义我们最终的目标文件,我们这里叫main可执行文件。然后加上它的依赖文件,我们这里是main.o和tool.o两个目标文件。然后按下Tab键,加上我们的GCC命令。输入gcc main.o tool.o -o main。最后,我们来加上clean为目标,我们使用点phony来显示的指明clean为一个为目标。到此编写完。

退出后输入make,他报一个main is up to date,因为我们之前用GCC的命令一次性已经生成了一个main的可执行文件。我们先把前面生成的可执行文件main把它删除掉,我们这里可以直接使用make clean。输入make clean后回车执行完。输入ls可以看到当前目录生成的点o目标文件以及。可执行文件main已经删除了。面我们再来执行mac命令,执行完后我们可以看到输出了三行命令。最后一行就是我们在makefile里面,我们自己写的前面两行是make自动推导给我们生成的。下面我们来看一下当前目录文件情况,可以看到现在已经生成了一个main执行文件。下面我们来执行它OK,我们输入点斜杠慢之后输出了max等于八跟我们之前的。输出结果一样。
makefile_第14张图片
makefile_第15张图片

如果我们这里需要在makefile加入一个新的目标文件,那么我们需要修改三个地方。main依赖文件,还有我们的GCC的命令,还有我们clean命令也要修改,修改起来比较麻烦。这里我们的makefile文件比较简单。

makefile_第16张图片
首先,我们定义一个变量objects等于main.o和tool.o,然后我们需要修改GCC命令以及clean中的rm命令中来使用这个变量
我们回到命令行先把之前的clean掉,输入make clean。然后我们重新来make。make之后,我们可以看到效果,跟之前的一样。下面我们再来执行一下,看一下我们的输出结果是否一样,同样的,我们输入点斜杠慢输出的结果也是max等于八。
makefile_第17张图片
下面我们来看一下在makefile中如何定义以及使用自定义函数

我们定义一个名叫funk的函数,里面打印一个hello,下面我们来调用一下这个函数。输入美元符号括符call,加上我们的函数名funk。然后退出编辑模式,保存退出。OK,我们再来执行make命令,可以看到输出了hello。
makefile_第18张图片
makefile_第19张图片
下面我们再来看一下带参数的函数的定义和使用。我们再来进入编辑器。输入I进入编辑模式,我们再来定义一个函数。

这里我们定义一个名叫func1的函数。美元符号括弧一表示的是函数的第一个参数,美元符号括弧二表示是第二个参数。下面我们来调用一下这个函数。我们输入美元符号括弧括,加上我们的方法名func1。逗号加上我们两个参数hello以及word。
makefile_第20张图片
makefile_第21张图片

make的工作流程

makefile_第22张图片
第一到第五步为第一个阶段,最后两步是第二个阶段。在第一个阶段中,如果定义了变量被使用了,那么make会把变量展开。在使用的位置。但是make并不会完全的马上展开。如果变量出现在依赖关系的规则中,那么只有当这条依赖被决定要使用的时候,变量才会被展开。

Androiod.mk

简介

makefile_第23张图片

展示

makefile_第24张图片

首先,android点MK文件必须以local下划线PS变量的定义开头。android编译系统利用local pass来定位源文件,

这里的my-dir。是由编译系统提供的一个宏函数,返回的是当前MK文件的路径。下面一个CLEAR_VARS是由编译系统提供的一个变量指向一个特定的格鲁makefile片段。可以清除除了local pass以外的以local下划线开头的变量,比如local module。local src files,等等。这样做是因为编译系统在单次执行中会解析多个构建文件和模块定义。而local下划线开头的变量是全局变量,所以描述每个模块之前。我们都会声明clear杠was变量,可以避免冲突。

下面一个local module变量。它是用来定义当前模块名。需要注意的是,模块名必须唯一,而且不能包含空格。这里我们的模块名为hello-jni。就会生成一个libhello-jni.so。注意,如果我们的模块名定义为libhello-jni的话,它生成的还是libhello-jni.so。它不会再在前面加lib前缀。

再下面一个local_src_files变量定义的是当前模块包含的源文件。这里只有一个,如果有多个可以用空格隔开。最后一个include $(build_shared_library) build_shared_library,表示的是当前模块,将被编译成一个共享库。

这就是一个简单的android.mk文件。这里把hello_jni.c编译成了一个libhello-jni.so的动态库。

同时编译多个共享库

makefile_第25张图片
在一个android点MK文件可能编译产生多个共享控模块。比如右边,这里会产生一个libmodule1.so和libmodule2.so两个动态库。

静态库的编译

虽然android应用程序不能直接使用静态库。我们可以通过静态库来编译动态库。比如,将第三方代码添加到原生项目中时,可以不用直接将第三方源码。包括在原生项目中。而是将第三方源码编译成静态库,然后并入共享库,供我们的android应用程序使用。比如我们这里编译一个第三方的AVI静态库。将第三方代码模块生成静态库以后,共享库就可以通过它的变量名添加到local_static_libraries变量中来使用该模块。
makefile_第26张图片
如果我们有多个共享库,都需要AVI这个库呢?那么AVI库就是一个通用模块。这种情况我们就不能编译成静态库了,那这种情况我们就需要使用共享库来共享通用模块。

下面我们来看一下如何使用共享库,共享通用模块。
makefile_第27张图片
比如我们有两个原生模块。module 1和module 2都依赖了第三方AVI库,那么我们就可以把AVI库编译成共享库。作为一个通用模块来使用, 我们这里同时使用静态库和动态库的时候,可以在模块间共享通用模块。但是所有这些模块必须属于同一个ndk项目。那么,如果是多个ndk项目,如何处理呢?

从ndk二五版本开始,也允许在ndk项目间共享和重用模块。比如前面的例子中的AVI lab模块,我们也可以实现在多个ndk项目间来共享。
makefile_第28张图片

首先,将AVI lab源代码移动到ndk项目以外的位置,比如C盘下面的某一路径注意这个路径,不能有空格。作为共享模块AVI lab,必须要有自己的android.mk文件。这里import杠module函数需要先定位共享模块,然后将它导入到ndk项目中。默认情况下import杠module函数宏只搜索android ndk下面的sources目录。为了能搜索到C盘android下面的shared module目录,我们可以定义一个ndk module杠pass的新环境变量。并且帮他设置成共享模块的根目录。

下面我们来看一下如何使用预编译库,比如我们想提供我们的模块给别人用。但是不想提供源代码,或者想使用一个已编译好的库,这种情况我们就可以使用预编译库。

makefile_第29张图片
虽然库已经被编译了,但是我们还是需要一个android点MK文件。比如我们已经有一个labavilab点so这么一个动态库。那么我们就可以这样来使用这里的local src杠files变量指向的不再是源文件。而是我们实际预编译库相对于local path的位置,接下来我们再来看一下如何编译独立的可执行文件。在android平台上。使用原生组件的推荐方法是将它们打包成动态库,但为了方便测试和进行快速原型设计。ndk也支持编译独立的可执行文件,这些独立的可执行文件可以不用打包成APK就可以复制到我们的安卓设备上的插件。常规linux应用程序,而且可以直接执行,不用通过JAVA应用程序的加载,这里右边就是一个编译独立可执行模块的android点MK文件。
makefile_第30张图片
注意最后一行,我们导入的是build executable变量,而不是build share的library或者build。library这样,我们编译出来的就是一个可执行文件。最后有一个需要同学们注意的地方,假如本地库lab hello杠ji点so依赖于lab test点so。我们可以使用ndk下的ndk杠dependence来查看so的依赖关系。在android六点零版本之前,需要在加载本地库前先加载被依赖的so。比如我们system点load library hello杠ji,这是我们的本地库。在加载hello杠gni之前,我们需要调用system点load library test。那在android六点零版本以后,我们不能使用预编译的动态库了,但是使用静态库没有问题。

你可能感兴趣的:(#,Linux,github)