Makefile 语法 和 CmakeLists 使用

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/liudao7994/article/details/82226730

一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。

linux 下 编译c 文件 并运行

  1. 利用vim 创建一个c文件
#include <stdio.h>
#include <stdlib.h>
void main(){
        int count =  5 + 5 ;
        printf("结果: %d",count);
}

  1. 使用gcc 编译c 文件生成.o后缀的文件(gcc类似javac工具 生成class文件)
gcc -c test.c 

linux 使用gcc 提示bash:gcc:command not found
出现这个问题时,首先用命令:

whereis gcc

查找看看gcc,gcc是否安装好。如果此时显示
gcc:/usr/bin/gcc
则说明已装好了,只需要配置到PATH路径下面就行了。
如果只是显示
gcc:
则说明gcc没有下载。
Centos(Rehat系列)系统下载gcc命令:yum install gcc
Ubuntu系统下载gcc命令:apt-get inatall gcc

  1. .o文件是中间文件,但是不能够执行, 再执行一个命令
gcc -o main test.o

main 是自己定义的让linux 执行的文件名字
4. 然后再执行 ./main 就可以了

结果

结果: 10[root@host makefileTest]# vim test.c

实际上makefile 包含gcc 里面所做的操作实际上就是类似上面的操作, Cmakelist 又包含了makefile 简化了makefile 的操作.

linux下编译多个c文件并运行

现在有多个文件

mark

宏定义 避免重复编译
在c语言中,对同一个变量或者函数进行多次声明是不会报错的。所以如果h文件里只是进行了声明工作,即使不使用# ifndef宏定义,一个c文件多次包含同一个h文件也不会报错。 使用#ifndef可以避免下面这种错误:如果在h文件中定义了全局变量,一个c文件包含同一个h文件多次,如果不加#ifndef宏定义,会出现变量重复定义的错误;如果加了#ifndef,则不会出现这种错.
https://www.cnblogs.com/challenger-vip/p/3386819.html

include 中有个commom.h 的头文件

#ifndef COMMON_H_
#define COMMON_H_

int plus(int a, int b);
int minus(int a, int b);
int multi(int a, int b);
int divi(int a, int b);

#endif


分别定义了加减乘除的函数 然后在外面分别实现了加减乘除的c文件
plus.c

#include <stdlib.h>
#include <stdio.h>

int plus(int a, int b){
	return a + b;
}


还有减,乘,除的c,剩下的就不写了

main.c文件

#include <stdlib.h>
#include <stdio.h>
#include "include/common.h"
void main(){
	int ret = minus(20,10);
	printf("ret:%d\n",ret);
}

这个时候 如果我想在linux 下 运行main.c 文件 应该把所有的.c文件都编译一次.
和上面编译单个c文件类似

[root@host makefile]# ls
divi.c  include  main.c  minus.c  multi.c  plus.c
[root@host makefile]# gcc -c divi.c 
[root@host makefile]# gcc -c main.c 
[root@host makefile]# gcc -c minus.c 
[root@host makefile]# gcc -c multi.c 
[root@host makefile]# gcc -c plus.c 
[root@host makefile]# ls
divi.c  divi.o  include  main.c  main.o  minus.c  minus.o  multi.c  multi.o  plus.c  plus.o
[root@host makefile]# 

可以看到已经把.o的中间文件生成了. 下面生成可执行的文件,可执行文件是依赖于.o文件的,需要把这些.o文件都打包到可执行文件里面去. 所以命令是

gcc divi.o main.o minus.o multi.o plus.o -o myapp

最后执行 ./myapp

图:
mark

最后结果 ret : 10
因为我在main里面调用了相减的函数

	int ret = minus(20,10);
	printf("ret:%d\n",ret);

这里就模拟了 一个c文件里面调用另外一个c文件里面的函数

很麻烦多个c文件, 所以使用makefile.

makefile 中文手册.

使用Makefile代替手动编译c文件

创建Makefile文件 , 如果不指定 使用make的时候默认会去找Makefile文件

刚才手动编译的过程 原始的c文件 > 创建.o文件 > 生成可执行文件myapp
在makefile里面 是倒过来的
使用tab键后面跟上规则的命令行. 必须是tab键跟上命令才会执行.要不然不认识.

rm *.o 吧刚才生成的.o文件全部删除 还有myapp也删除

为什么myapp是可执行文件,因为有main函数.

冒号: 代表依赖
main.o:main.c, main.o依赖于main.c

Makefile 文件

myapp:main.o plus.o minus.o multi.o divi.o
        gcc main.o plus.o minus.o multi.o divi.o -o myapp
main.o:main.c
        gcc -c main.c
plus.o:plus.c
        gcc -c plus.c
minus.o:minus.c
        gcc -c minus.c
multi.o:multi.c
        gcc -c multi.c
divi.o:divi.c
        gcc -c divi.c

保存以后返回 执行make 命令

[root@host makefile]# vim Makefile 
[root@host makefile]# make
gcc -c main.c
gcc -c plus.c
gcc -c minus.c
gcc -c multi.c
gcc -c divi.c
gcc main.o plus.o minus.o multi.o divi.o -o myapp
[root@host makefile]# ./myapp
ret:10
[root@host makefile]# 

这样的结果和刚才单个执行的结果是一样的.

  • 但是如果再次使用make命令就编译不了了. 和最后修改时间有关系. 如果最后源文件修改的时间比如main.c没有修改,那么生成的可执行文件myapp也不会重新生成. 只有当源文件的时间大于生成的可执行文件的时间才会重新编译.(android studio 也是用的这种机制)

  • make 也可以执行shell命令

使用Makefile单独执行某个命令

目标“clean”不是一个文件,它仅仅代表执行一个动作的标识。正常情况下,不
需要执行这个规则所定义的动作,因此目标“clean”没有出现在其它任何规则的依赖
列表中。因此在执行make时,它所指定的动作不会被执行。除非在执行make时明确地
指定它。而且目标“clean”没有任何依赖文件,它只有一个目的,就是通过这个目标
名来执行它所定义的命令。Makefile中把那些没有任何依赖只有执行动作的目标称为
“伪目标”(
中把那些没有任何依赖只有执行动作的目标称为
“伪目标”(phony targets) )。参考4.6 Makefile伪目标 一节。需要执行“clean”目标
所定义的命令,可在shell下输入:make clean。

4.6 Makefile伪目标
本节我们讨论 Makefile 的一个重要的特殊目标:伪目标。伪目标是这样一个目标:
它不代表一个真正的文件名,在执行 make 时可以指定这个目标来执行其所在规则定义
的命令,有时也可以将一个伪目标称为标签。使用伪目标有两点原因:

  1. 避免在我们
    的 Makefile 中定义的只执行命令的目标(此目标的目的为了执行执行一些列命令,而
    不需要创建这个目标)和工作目录下的实际文件出现名字冲突。

  2. 提高执行 make 时
    的效率,特别是对于一个大型的工程来说,编译的效率也许你同样关心。以下就这两个
    问题我们进行分析讨论:

  3. 如果我们需要书写这样一个规则:规则所定义的命令不是去创建目标文件,而
    是通过 make 命令行明确指定它来执一些特定的命令。像常见的 clean 目标:
    clean:
    rm *.o temp
    规则中“rm”不是创建文件“clean”的命令,而是删除当前目录下的所有.o 文件和 temp
    文件。当工作目录下不存在“clean”这个文件时,我们输入“make clean”,“rm *.o
    temp”总会被执行。这是我们的初衷。
    但是如果在当前工作目录下存在文件“clean”,情况就不一样了,同样我们输入
    “make clean”, 由于这个规则没有任何依赖文件,所以目标被认为是最新的而不去执
    行规则所定义的命令,因此命令“rm”将不会被执行。这并不是我们的初衷。为了解
    决这个问题,我们需要将目标“clean”声明为伪目标。将一个目标声明为伪目标的方
    法是将它作为特殊目标.PHONY”的依赖。如下:

.PHONY : clean

这样目标“clean”就被声明为一个伪目标,无论在当前目录下是否存在“clean”这个
文件。我们输入“make clean”之后。“rm”命令都会被执行。而且,当一个目标被声
明为伪目标后,make 在执行此规则时不会去试图去查找隐含规则来创建它。这样也提
高了 make 的执行效率,同时也不用担心由于目标和文件名重名而使我们的期望失败。
在书写伪目标规则时,首先需要声明目标是一个伪目标,之后才是伪目标的规则定义。
目标“clean”的完整书写格式应该如下:

.PHONY: clean
clean:
    rm *.o temp

按照上面的例子在makefile里添加clear, 作用是把之前生成的.o文件删除

myapp:main.o plus.o minus.o multi.o divi.o
        gcc main.o plus.o minus.o multi.o divi.o -o myapp
main.o:main.c
        gcc -c main.c
plus.o:plus.c
        gcc -c plus.c
minus.o:minus.c
        gcc -c minus.c
multi.o:multi.c
        gcc -c multi.c
divi.o:divi.c
        gcc -c divi.c
.PHONY: clean
clean:
        rm -f *.o
                   

保存之后 执行 make clean
效果如下图
mark

可以看到最后把.o文件全删了. 把上一次生成的.o文件清空.

成功.


但是上面代码依旧很多.

使用通配符优化

  • $ 后面跟上的就是变量
  • Makefile有三个非常有用的变量。分别是 @ @, ^,$<代表的意义分别是:
    @ @--目标文件, ^–所有的依赖文件,$<–第一个依赖文件。
  • 规则命令行中的自动化变量“$^”代表所有通过目
    录搜索得到的依赖文件的完整路径名(目录 + 一般文件名)列表

Makefile 修改为

OBJECTS=main.o plus.o minus.o multi.o divi.o
myapp:$(OBJECTS)
        gcc $(OBJECTS) -o myapp
#通配符.o依赖于.c
%.o:%.c
        gcc -c $^ -o $@
                        

通配符.o依赖于.c
解释: OBJECTS 是变量 包含了那些.o的名字
用$(OBJECTS) 替换变量

和上面的是一样的效果

结果:

[root@host makefile]# vim Makefile 
[root@host makefile]# make
gcc -c main.c -o main.o
gcc -c plus.c -o plus.o
gcc -c minus.c -o minus.o
gcc -c multi.c -o multi.o
gcc -c divi.c -o divi.o
gcc main.o plus.o minus.o multi.o divi.o -o myapp
[root@host makefile]# ls
divi.c  divi.o  include  main.c  main.o  Makefile  minus.c  minus.o  multi.c  multi.o  myapp  plus.c  plus.o


makefile 函数的使用

wildcard 函数查找
找到所有的.c文件 放到SOURCE变量里面

SOURCE=$(wildcard *.c)

找到所有.o文件, 可以用上面的复制把c换成o,也可以直接把上面的代码替换.
patsubst 函数替换

SOURCES=$(wildcard *.c)
OBJECTS=$(patsubst %.c %.o,$(SOURCES)

后面和上面 的类似. 最后用@echo 打印下

SOURCES=$(wildcard *.c)
OBJECTS=$(patsubst %.c,%.o,$(SOURCES))
myapp:$(OBJECTS)
        gcc $^ -o $@
%o:%c
        gcc -c $^ -o $@
        @echo $(OBJECTS)


结果:

mark

定义自己的函数

在上面的基础上写.


myfun=$2 $1
myfun_ret=$(call myfun,20,10)
test:
        @echo $(myfun_ret)
        @echo "ceshi"


myfun:$2 1 s h e l l m y f u n r e t = 1 和shell类似 传递的参数 第二个在前面 myfun_ret= (call myfun,20,10) 函数执行myfun, call关键字,后面带上参数
最后test里面跟上打印
结果:

[root@host makefile]# make test
10 20
ceshi
[root@host makefile]# 

递归展开式

在后面定义的前面也能用

myfun=$2 $1
myfun_ret=$(call myfun,20,10)
#递归展开式
str2=$(str1)
str1=hello

test:
        @echo $(myfun_ret)
        @echo $(str2)
                     

打印结果:

[root@host makefile]# make test
10 20
hello

继续修改:

#递归展开式
str2=$(str1)
str1=hello
str1=after_test
test:
        @echo $(myfun_ret)
        @echo $(str2)

打印:

[root@host makefile]# make test
10 20
after_test

递归展开式 : 下面改了上面也要改

直接展开

:= 符号

#递归展开式
str2=$(str1)
str1=hello
str1=after_test
#直接展开 放到上面才认识
str4:=android
str3=$(str4)
test:
        @echo $(myfun_ret)
        @echo $(str2)
        @echo $(str3)

打印:

[root@host makefile]# make test
10 20
after_test
android
[root@host makefile]# 

追加:

str4=android
str3=$(str4) addStr
test:
        @echo $(myfun_ret)
        @echo $(str2)
        @echo $(str3)


打印结果:

[root@host makefile]# make test
10 20
after_test
android addStr

判断语句

ifeq 关键字, 分开编译某些模块的时候可以用

Cmake

makefile 只能在linux下编译

linux 下使用cmake编译项目

Cmake linux下编译项目

准备工作 : 三个文件夹.
mark

  • include里面只有一个头文件 add.h
int add(int a,int b);

  • src里面有两个文件 add.c , main.c

add.c

#include "../include/add.h"
int add(int a,int b){
	return a+b;
}

main.c

这里只引入了add.h的头文件 没有引入add.c

#include "../include/add.h"
#include <stdio.h>
//这里只引入了add.h的头文件 没有引入add.c
int main(int argc,char** argv)
{
	int reslut=add(3,4);
	printf("结果 %d ",reslut);
	return 0;

}

  • build 文件夹.
  1. 在项目根目录创建CmakeList.txt

编辑文件


cmake_minimum_required(VERSION 2.6)
PROJECT(myapp)
#导入头文件
INCLUDE_DIRECTORIES(
include
)
#指定源文件的目录
AUX_SOURCE_DIRECTORY(src DIR_SRCS)
#把可执行的文件放到那个目录下 用set  设置变量
SET(TEST_MATH ${DIR_SRCS})
ADD_EXECUTABLE(${PROJECT_NAME} ${TEST_MATH})


顺便说下 不分大小写.

  1. 来到bulid文件夹下 执行
cmake ..

命令

成功打印

[root@host build]# cmake ..
-- Configuring done
-- Generating done
-- Build files have been written to: /home/makefileTest/cmake/build
[root@host build]# 

cmake … 作用 是生成了MakeFile
下面要用makefile 的语法生成可执行文件myapp
3. 在build文件夹下调用make

[root@host build]# 
[root@host build]# cmake ..
-- Configuring done
-- Generating done
-- Build files have been written to: /home/makefileTest/cmake/build
[root@host build]# make
Scanning dependencies of target myapp
[ 50%] Building C object CMakeFiles/myapp.dir/src/main.c.o
[100%] Building C object CMakeFiles/myapp.dir/src/add.c.o
Linking C executable myapp
[100%] Built target myapp
[root@host build]# ls
CMakeCache.txt  CMakeFiles  cmake_install.cmake  Makefile  myapp
[root@host build]# 

可以看到成功打印 myapp 调用一下看看

[root@host build]# ./myapp
结果 7 [root@host build]# 

遇到问题

  • CMake Error: The source directory “/home/makefileTest/cmake” does not appear to contain CMakeLists.txt.
    Specify --help for usage, or press the help button on the CMake GUI.

结果是CMakeLists.txt 文件名写错了. 改过来就行

  • CMake Error: your CXX compiler: “CMAKE_CXX_COMPILER-NOTFOUND” was not found

解决办法: 终端下输入
安装g++

yum install -y gcc
yum install -y gcc-c++

android 官网 NDK 集成

https://developer.android.com/studio/projects/add-native-code

猜你喜欢

转载自blog.csdn.net/liudao7994/article/details/82226730