makefile流程搭建学习——基础知识和练习

Makefile流程搭建学习——第1讲

注意:以下内容不能保证都正确,可能存在理解偏差。若有问题,希望大家指出。

1. Makefile 属于工程管理文件。一个工程,也就是一个可运行的软件,包括源文件、头文件、库文件、二进制文件以及工程管理文件。

我使用Makefile是搭建生信分析流程,我以问题为导向,生成自己想要的makefile。

确定目标
输入文件:test.fa。完成三个目标:cutadapt(去除两端adapt序列),gc.py(统计GC含量),stat.pl(统计序列长度)
输出文件:new_test.fa,stat文件(包括gc含量,和序列长度信息)
设计目录
请添加图片描述
因此,我根据需求,从Makefile的书写、注意事项、执行进行学习,以实现我的要求。

2. Makefile文件的书写规则

** 重点强调。Makefile不是shell脚本 **,因此第一行不需要bash解析器路径。不要前面刚学了shell编程,看到什么都写#!/bin/bash。

  1. Makefile核心:目标+依赖。目标: 依赖1 依赖2 依赖3……。目标文件一定要存在,依赖文件可以不要
  2. <Tab键规则>
  3. $^:代表所有的依赖文件; $@:代表目标文件
  4. 一个好的习惯,Makefile文件的首字母大写。似乎是与Linux系统和别的系统间的兼容问题,我就记住了
  5. make命令执行。make执行后,会将下面的执行语句,返回到终端
  6. 若未对内容进行修改,多次make,便会出现make: “XXXX”已是最新。
  7. 如果你的Makefile文件中书写了多个目标文件,那么当你在执行make命令时,不指定目标文件,它就默认只生成第一个目标文件。若你想执行下面的目标文件,必须指定目标文件。

下面是一些例子。(有一些C语言的知识,不懂可以查其他资料,主要是理解Makefile工作原理)
** 例1:最简单的Makefile文件打印helloworld **

#zhaohuiyao@lenovo-ThinkStation-P920:~/test/makefile$ cat ./Makefile
target:
	@echo "helloworld"                        
#zhaohuiyao@lenovo-ThinkStation-P920:~/test/makefile$ make
helloworld

** 例2:Makefile文件进行gcc编译,实现C语言的两个数加 **

#首先分别编辑文件add.c、main.c、add.h以及Makefile
zhaohuiyao@lenovo-ThinkStation-P920:~/test/makefile/add$ cat ./add.c
#include "add.h"
int add(int a,int b)
{
	printf("%d+%d=%d\n",a,b,a+b);
	return 0;
}
zhaohuiyao@lenovo-ThinkStation-P920:~/test/makefile/add$ cat ./main.c
#include "add.h"
int main(void)
{
	add(12,13);
	return 0;
}
zhaohuiyao@lenovo-ThinkStation-P920:~/test/makefile/add$ cat ./add.h
#ifndef _ADD_H_
#define _ADD_H_
#include <stdio.h>
int add(int a,int b);
#endif
zhaohuiyao@lenovo-ThinkStation-P920:~/test/makefile/add$ cat ./Makefile
main:main.c add.c
	gcc main.c add.c -o main   #也可以写成gcc $^ -o $@                #用到了前面提到的$^:代表所有的依赖文件; $@:代表目标文件

#执行:make,生成main,一个可执行文件;执行:main,得到12+13=25

** 例3:Makefile文件进行gcc编译,实现C语言的三个数比较,使用三元运算符 **

#首先分别编辑文件max.c、main.c、max.h以及Makefile
zhaohuiyao@lenovo-ThinkStation-P920:~/test/makefile/add$ cat ./max.c
#include "max.h"
int max(int a,int b,int c)
{
	int max=c>((a>b)?a:b)?c:((a>b)?a:b);
	printf("The max is %d in %d %d %d.\n",max,a,b,c);
	return 0;
}
zhaohuiyao@lenovo-ThinkStation-P920:~/test/makefile/add$ cat ./main.c
#include "max.h"
int main(int argc,char **argv)        #这里用到命令行传参
{
	max(atoi(argv[1]),atoi(argv[2]),atoi(argv[3]));
	return 0;
}
zhaohuiyao@lenovo-ThinkStation-P920:~/test/makefile/add$ cat ./max.h
#ifndef _MAX_H_
#define _MAX_H_
#include <stdio.h>
#include <stdlib.h>
int max(int a,int b,int c);
#endif
zhaohuiyao@lenovo-ThinkStation-P920:~/test/makefile/add$ cat ./Makefile
main:main.c max.c
	gcc $^ -o $@

#执行:make,生成main,一个可执行文件;执行:./main 13 57 45,得到The max is 57 in 13 57 45.

** 例4:将例2和例3的两个放在一个project下,各自生成不同的目标文件 **

#分别将mian.c/add.c/add.h和mian.c/max.c/max.h拷贝到project目录下。并改成mian1.c和mian2.c
#重新编辑Makefile文件
add:main1.c add.c
	gcc main1.c add.c -o add
max:main2.c max.c
	gcc main2.c max.c -o max
clean:
	rm add max               #删除可执行文件add和max
#执行make。只会生成add可执行文件。若要生成max可执行文件,需要执行make max
#执行clean。删除可执行文件add和max

3. Makefile中变量的种类

  1. 自定义变量。默认字符串类型。规则:①命名可以包括数据、字母、下划线,但是数字不能作为开头,其它两者可以;②大小写敏感;③不使用#;④变量赋值等号两边可以有也可以没有空格。(shell编程中等号两边不能有空格);⑤变量的引用:$变量名(有可能会访问不成功,那么你可以在使用 ${变量名});⑥下面列出了很多变量使用习惯,看自己,保持统一。我习惯在引用时用 ${变量名}。
  2. 系统变量。可直接使用。常用的有:①CC:编译器的名字,默认CC=gcc。若想使用自己的,你可以先对CC赋值,再使用。如:CC = arm-linux-gcc。使用时:${CC}②RM:删除文件。相当于rm -f。使用 ${RM}
  3. 自动化变量。①$^:代表所有的依赖文件; ② $@:代表目标文件
  4. 复杂的Makefile文件就是里面存了很多自定义变量和使用系统变量,用变量代替依赖、目标等文件命名。这样就显得厉害了呀!!!
#关于Makefile中变量的命名,赋值,合并,引用
A = hello
B=hello
C = hello world
D = $B everyone
E = ${B} everyone
F = $(B) everyone
all:
	@echo $A			#打印hello
	@echo ${A}			#打印hello
	@echo $B            #打印hello
	@echo $C            #打印hello world
	@echo $D            #打印hello everyone
	@echo $E            #打印hello everyone
	@echo $F            #打印hello everyone

#执行:make all

用各种变量,使你的Makefile文件看起来与众不同,遥不可及!!!!

#这是我们例4里面的Makefile文件
add:main1.c add.c
	gcc main1.c add.c -o add
max:main2.c max.c
	gcc main2.c max.c -o max
clean:
	rm add max               #删除可执行文件add和max

#变量替换
C_SOURCE1 = main1.c add.c
C_SOURCE2 = main2.c max.c
C_BIN1 = add
C_BIN2 = max
C_CLEAN = clean
${C_BIN1}:${C_SOURCE1}
	${CC} $^ -o $@
${C_BIN2}:${C_SOURCE2}
	${CC} $^ -o $@
${C_CLEAN}:
	${RM} ${C_BIN1} ${C_BIN2}
是不是,是不是,感觉下面的代码就有点东西了。哈哈哈哈,就是简单的变量定义和引用

4. 伪指令

当使用RM,进行删除时(执行make clean报错:make: ‘clean’ is up to date.)。这一般不会出现,因为你想,怎么可能删除不了呢。但是也有可能出错。那么你可以在的Makefile文件中加一个** 伪指令:.PHONY:clean (如果你前面对clean进行了命名,这里就需要 .PHONY:${clean} **)。加的位置就是你的clean:这一行的上面一行。

.PHONY:clean
clean:
	rm mian

5. Makefile函数

这里举两个最常见的函数${wildcard}和 ${patsubst},主要是针对.c和.o文件使用,我其实不是用的到,因此了解即可。

  1. ${wildcard}作用:在某一路径下寻找对应的匹配文件。用法: ${wildcard arg1,arg2,arg3}。例如寻找当前目录下的所有.c文件。 ${wildcard *.c}
  2. patsubst 作用:将所有的.c文件编译,生成.o文件。用法:${patsubst %c,%o,依赖文件}。这里的依赖文件就是需要编译的.c文件。但是这种编译需要将.c编译需要的.h和.so文件在一个目录下。

6. 工程目录设置,针对C开发,记得类比就好

  1. 原文件目录 src 放置*.c文件
  2. 头文件目录 include 放置*.h文件
  3. 库文件目录 lib 放置*.so文件
  4. 可执行文件 bin 放置生成的二进制文件
  5. -I(i的大写字符):头文件所在位置 -L:库文件所在位置 -l(L的小写字母):对特定库文件访问

这里设计一个pro工程,src目录下放置:add.c、max.c、sq.c和mian.c。include目录下放置:add.h、max.h和sq.h,bin下面生成可执行文件./main。

#src目录下放置:add.c、max.c和上面一样。
#sq.c文件
#include "sq.h"
void sqrts(int a)
{
	double num;
	printf("a is %d\n",a);
	num=sqrt(a);
	printf("num is %lf\n",num);
}

#main.c文件
#include "add.h"
#include "max.h"
#include "sq.h"
int main(int argc,char **argv)
{
	add(atoi(argv[1]),atoi(argv[2]));
	max(atoi(argv[1]),atoi(argv[2]),atoi(argv[3]));;
	sqrts(atoi(argv[3]));
	return 0;
}

#include目录下放置:add.h、max.h和前面一样
#sq.h文件
#ifndef _SQ_H_
#define _SQ_H_

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
void sqrts(int a);
#endif

#make编译
gcc ./src/*.c -o ./bin/main -I ./include/ -lm     #对src所有*.c文件进行编译。生成名为main的可执行文件(放在bin目录下)
                                            #参数-I:提供头文件.h文件所在位置。在./include/
                                            #参数-l:对特定库文件访问。这里是对math库进行访问,这是使用sqrt函数需要的
#这里我们将这个编译下载Makefile文件中(这里你就可以使用前面的函数${wildcard})
#Makefile文件
C_SRC = ./src/*.c			#C_SRC = ${wildcard ./src/*.c}
I_DIR = -I ./include/
L_DIR = -L ./lib/ -lm 
C_BIN = ./bin/main
C_CLEAN = clean
${C_BIN}:${C_SRC}
	${CC} $^ -o $@ ${I_DIR} ${L_DIR}
.PHONY:${C_CLEAN}			#加个伪指令,以保证make clean的执行
${C_CLEAN}:
	${RM} ${C_BIN}
#执行make,返回cc src/main.c src/sq.c src/max.c src/add.c -o bin/main -I ./include/  -lm
#在目录bin/下生成main可执行文件
#执行./bin/main 13 25 57。返回12+35=47\nThe max is 57 in 12 35 57.\na is 57\nnum is 7.549834
#执行make clean。返回rm -f ./bin/main。删除./bin/main文件

7. 库制作

1. 什么是库?

库在Linux环境中以二进制的形式存储,在编译的需要链接库

2. 库的格式

静态库:lib*.a;动态库:lib*.so

3. 库的种类

静态库:lib**.a;特点:1. 静态库就是编译的时候需要静态链接,直接将库里面的函数加入(复制)到你的可执行程序。2. 由于是静态链接,所以在编译之后不需要静态库即可使用。
动态库:lib**.so;特点:1. 程序编译的时候并没有将库的内容编译,只是进行了链接。2. 由于是动态链接,所以在执行文件的时候需要动态库的支持。

3. 静态库的制作步骤

三步法。准备文件:主函数:用于测试数据,main.c。功能函数:实现功能,add.c add.h。将你的功能函数编译为库文件**.a。

  1. 将功能文件.c编译成.o文件:gcc命令
  2. 将.o文件塞入到lib*.a文件中:ar rcs命令
  3. 联合编译:gcc命令
  4. 测试执行主函数:./main
#准备add.c、add.h和main.c文件
#1.对add.c编译成.o文件
gcc add.c -o add.o -c     #这里加参数-c,是因为add.c只是功能函数,没有主函数
#2.将add.o塞入到libadd.a文件中
ar rcs libadd.a add.o     #生成libadd.a文件(一个二进制文件)
#3.联合编译
gcc main.c libadd.a -o mian
#执行主函数
./main

这里你会觉得比较傻,比原来还多了一步,但是这里重点是,如果别人要用你的功能函数,但是你不想给你的.c文件,你就生成一个.a文件丢给他,他可以调用,但是不知道具体内容。大佬就喜欢给你.a或.so文件,告诉你里面有哪些函数,你可以调用,但你不永远不知道具体内容。当然,你可以用反编译,将二进制搞成.c,那也很牛逼。

4. 动态库的制作步骤

三步法。准备文件:主函数:用于测试数据,main.c。功能函数:实现功能,add.c add.h。将你的功能函数编译为库文件**.so。

  1. 将功能文件.c编译成.o文件:gcc命令
  2. 将.o文件塞入到lib*.so文件中:gcc命令
  3. 联合编译需要链接动态库:gcc命令
  4. 测试执行主函数:./main
#准备add.c、add.h和main.c文件
#1.对add.c编译成.o文件
gcc add.c -o add.o -fPIC -c
#2.将add.o塞入到libadd.so文件中
gcc -shared -fPIC -o libadd.so add.o           #生成libadd.so文件(一个二进制文件)
#3.联合编译
gcc main.c -o mian -L ./ -ladd                 #-L :库文件所在位置;-ladd:链接动态库libadd.so
#执行主函数
./main                                         
#这里会报错
./main: error while loading shared libraries: libadd.so: cannot open shared object file: No such file or directory		#这也是你将别人的库拿来用的时候会遇到的问题
建议解决方案(最简单):将对应的动态库,拷贝到Linux系统中的lib文件中。但是你可能会遇到你的权限不够,这就不是我考虑的问题了。
cp libadd.so /lib/
#这样你再次执行,就不会报错
./main
#如果这样你还是不能解决,你就再看看别人的资料,我的C,浅尝辄止。

练习:exercise,三个功能函数,进行封装,制作一个静态库,然后再次使用Makefile文件
sum.c 两个数的和,一个数据的平方
cmp 实现两个数据交换
str 实现字符串大小计算
这个题目就是用到的前面的所有知识,可以练习练习

#进入src目录
zhaohuiyao@lenovo-ThinkStation-P920:~/test/makefile/exercise/src$ cat ./cmp.c
#include "cmp.h"
void cmp(int a,int b)
{
	int c;
	printf("a原来是%d,b原来是%d\n",a,b);
	c=a;a=b;b=c;
	printf("a现在是%d,b现在是%d\n",a,b);
}
zhaohuiyao@lenovo-ThinkStation-P920:~/test/makefile/exercise/src$ cat ./str.c
#include "str.h"
void str(char *string)
{
	int length;
	length=strlen(str)-1;
	printf("%s的长度是%d\n",string,length);
}
zhaohuiyao@lenovo-ThinkStation-P920:~/test/makefile/exercise/src$ cat ./sum.c
#include "sum.h"
int sum(int a,int b)
{
	int add,mult;

	add=a+b;
	mult=a*a;
	printf("%d+%d=%d\n",a,b,add);
	printf("%d的平方是:%d\n",a,mult);
	return 0;
}
zhaohuiyao@lenovo-ThinkStation-P920:~/test/makefile/exercise/src$ cat ./main.c
#include "sum.h"
#include "cmp.h"
#include "str.h"

int main(int argc,char **argv)
{
	sum(atoi(argv[1]),atoi(argv[2]));
	cmp(atoi(argv[1]),atoi(argv[2]));
	str(argv[3]);
	return 0;
}
#进入include目录
zhaohuiyao@lenovo-ThinkStation-P920:~/test/makefile/exercise/include$ cat ./cmp.h
#ifndef _CMP_H_
#define _CMP_H_
#include <stdio.h>
#include <stdlib.h>
void cmp(int a,int b);
#endif
zhaohuiyao@lenovo-ThinkStation-P920:~/test/makefile/exercise/include$ cat ./sum.h
#ifndef _SUM_H_
#define _SUM_H_
#include <stdio.h>
#include <stdlib.h>
int sum(int a,int b);
#endif
zhaohuiyao@lenovo-ThinkStation-P920:~/test/makefile/exercise/include$ cat ./str.h
#ifndef _STR_H_
#define _STR_H_
#include <stdio.h>
#include <string.h>
void str(char *string);
#endif

#进入lib目录下,制作静态库
gcc ../src/sum.c -o sum.o -I ../include/ -c
gcc ../src/str.c -o str.o -I ../include/ -c
gcc ../src/cmp.c -o cmp.o -I ../include/ -c
ar rcs libexercise.a *.o
#联合编译
gcc ../src/main.c libexercise.a -o ../bin/main -I ../include/
#执行mian
./main 11 22 helloworld
11+22=33
11的平方是:121
a原来是11,b原来是22
a现在是22,b现在是11
hello的长度是:5

#将上面的操作写进Makefile中
C_DIR = ./src/			
I_DIR = -I ./include/
L_DIR = ./lib/
C_BIN = ./bin/main
C_CLEAN = clean

lib:${C_DIR}
	${CC} ${C_DIR}sum.c -o ${L_DIR}sum.o ${I_DIR} -c
	${CC} ${C_DIR}str.c -o ${L_DIR}str.o ${I_DIR} -c
	${CC} ${C_DIR}cmp.c -o ${L_DIR}cmp.o ${I_DIR} -c
	ar rcs ${L_DIR}libexercise.a ${L_DIR}*.o
main:${L_DIR}libexercise.a ${C_DIR}main.c
	${CC} ${C_DIR}main.c ${L_DIR}libexercise.a -o ${C_BIN} ${I_DIR}
.PHONY:${C_CLEAN}			
${C_CLEAN}:
	${RM} ${L_DIR}* ${C_BIN} 
#执行make all
cc ./src/sum.c -o ./lib/sum.o -I ./include/ -c
cc ./src/str.c -o ./lib/str.o -I ./include/ -c
cc ./src/cmp.c -o ./lib/cmp.o -I ./include/ -c
#执行make lib
ar rcs ./lib/libexercise.a ./lib/*.o
#执行make main
cc ./src/main.c ./lib/libexercise.a -o ./bin/main -I ./include/
#执行./bin/main 13 25 zhaohuiyao。
12+25=37
12的平方是:144
a原来是12,b原来是25
a现在是25,b现在是12
zhaohuiyao的长度是:10
#执行make clean。
rm -f ./lib/* ./bin/main
#当你再次make时,就会发现报错
make: “lib”已是最新。
#我尝试了一下,原因是你对.c文件没有进行修改,所以这里不能执行。我就将某个.c文件打开保存一下,就可以再次make了

总结:以上是我学习书写Makefile文件的基础知识,其中的例子主要是基于C语言的工程开发。但是我的方向是生信,语言主要是R、Perl和Python,因此还需要多练习。下一篇文章就主要是前面提到的Fasta_stat工程的书写,敬请期待。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值