用【Makefile】或【Cmake】编译【C/Fortran】程序文件

一、vimrc文件设置

set tabstop=4       "tab空格4
set expandtab       "将tab扩展成空格
set softtabstop=4   "表示在编辑模式下按退格键时候退回缩进的长度
set shiftwidth=4    "每一级缩进是4个空格
set number          "显示行号
filetype plugin on  "Makefile空格

set fileencodings=utf-8,ucs-bom,gbk,cp936,gb2312,gb18030 "文件编码
"将.cuf文件用fortran的语法高亮配置进行高亮显示
au BufRead,BufNewFile *.cuf set filetype=fortran

if has("persistent_undo")
    let target_path = expand('~/.vim/undodir')

    if !isdirectory(target_path)  "如果位置不存在,则创建目录
        call mkdir(target_path, "p", 0700)
    endif

    let &undodir=target_path "变量赋值
    set undofile  "开启撤销永久化
endif

二、用Makefile编译文件

1.1 C语言程序

创建Makefile文件(vim Makefile)

CC = sw9gcc
FC = sw9gfortran
MPI_C = /usr/sw/mpi/mpi_20210219_SEA/bin/mpicc
EXE = main_dnn
LIB = ./utils/libs/libswdnn_xu.a
OBJDIR = ./obj

CFLAGS = -msimd -I./utils/include/ -mieee -g -O3 -funroll-nest-loops -mfma -fipa-type-escape -fipa-struct-peeling -fprefetch-loop-arrays
#CFLAGS += -DDEBUG
LIBFLAGS = -fcache-hint -mhybrid

slavesrc = $(wildcard ./utils/slave/*.c)
mastersrc = $(wildcard ./utils/master/*.c)
utilsrc = $(wildcard ./utils/src/*.c)
LIBOBJ1 += $(patsubst %.c, %.o, $(slavesrc) $(mastersrc) $(utilsrc))
LIBOBJ2 = $(notdir $(LIBOBJ1))
OBJ += $(patsubst %.o, $(OBJDIR)/%.o, $(LIBOBJ2))

all: $(EXE)
ar: $(LIB)
$(EXE): $(OBJDIR)/main_dnn.o $(LIB)
	$(CC) $(LIBFLAGS) -o $@ $^ -lm -lm_slave -L/usr/sw/yyzlib/xMath-SACA -lswblas
$(LIB): $(OBJ)
	sw9ar rcs $@ $^
$(OBJDIR)/main_dnn.o: main_dnn.c
	$(CC) -Dcheck_res $(CFLAGS) -mhost -c $^ -o $@
$(OBJDIR)/%.o: ./utils/master/%.c
	$(CC) $(CFLAGS) -mhost -c $^ -o $@
$(OBJDIR)/%.o: ./utils/src/%.c
	$(CC) $(CFLAGS) -mhost -c $^ -o $@
$(OBJDIR)/%.o: ./utils/slave/%.c
	$(CC) $(CFLAGS) -mpws -mslave -Dperf -c $^ -o $@

.PHONY: clean
clean:
	rm -f ${LIB} $(EXE)  $(OBJDIR)/*.o

~

1.2 Fortran语言程序

创建Makefile文件(vim Makefile)

# 获取文件夹中所有.f90文件列表 notdir把展开的文件去除掉路径信息
SRCS_F90 = $(wildcard *.f90)
SRCS_F = $(wildcard ./*.f)
SRCS_DIR = $(notdir $(SRCS_F))
# 替换.f90后缀为.o后缀 得到.o文件列表 notdir用于去掉文件的绝对路径,只保留文件名
OBJS_F90 = $(patsubst %.f90, %.o, $(SRCS_F90))
OBJS_F = $(patsubst %.f, %.o, $(SRCS_DIR))

# module依赖文件
MOD_OBJS = variable.o mpi_variable.o mod_split_funs.o
MOD_SRCS = variable.f90 mpi_variable.f90 mod_split_funs.f90

# 定义编译器变量
F90= mpif90 
FC = mpif90

FFLAGS = -O2

# 定义目标变量
EXE = main 

all:$(EXE) 
# 	@echo $(SRCS_F)
# 	@echo $(SRCS_DIR)
# 	@echo $(OBJS_F)

# $^表示所有依赖文件 $@--目标文件,$<第一个依赖文件
$(EXE): $(OBJS_F90) $(OBJS_F) 
	$(F90) -o $@ $^
# $(FC) -o $(EXE) $(OBJS)

# %.o %.f90 %.mod 表示任意文件
%.o: %.f 
	$(FC) -c $< -o $@ 
%.o: %.f90 $(MOD_OBJS)
	$(F90) $(CFLAGS) -c $< -o $@ 
$(MOD_OBJS): $(MOD_SRCS)
	$(F90) -c $^

# *.o 表示所有.o文件
.PHONY: clean
clean:
	rm -f *.o *.mod

 ~

 

三、用Cmake编译文件

2.1 C语言程序

1、创建CMakeLists.txt文件(vim CMakeLists.txt)

cmake_minimum_required(VERSION 3.16.2) # 声明使用CMAKE的最低版本要求
project(main_blas LANGUAGES C) # 定义项目的名字
 
#find_package(MPI REQUIRED) # 寻找 MPI
#include_directories(SYSTEM ${PROJECT_SOURCE_DIR}/include) # 引入头文件目录
# 打印信息
message(WARNING  "This application cannot compile without MPI  end end ")
 
# 设置编译目标类型是Debug、Release版还是RelWithDebInfo版本
#set(DEFAULT_BUILD_TYPE "RelWithDebInfo")
set(CMAKE_BUILD_TYPE Release)
 
# 设置编译器选项
set(CMAKE_C_FLAGS "-msimd -mieee")
set(CMAKE_C_FLAGS_RELEASE "-O3 ") # Release版编译器选项
set(CMAKE_C_FLAGS_RELWITHDEBINFO "-g -O3")
# 静态库的输出目录
#set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/utils/libs)
 
# 添加工作目录下子目录src中的源文件到SRC变量
file(GLOB SRC ${PROJECT_SOURCE_DIR}/utils/src/*.c)
file(GLOB MASTER_SRC ${PROJECT_SOURCE_DIR}/utils/master/*.c)
file(GLOB SLAVE_SRC ${PROJECT_SOURCE_DIR}/utils/slave/*.c)
 
# 传递编译器选项
set_source_files_properties(${SRC} PROPERTIES COMPILE_FLAGS "-mhost")
set_source_files_properties(${MASTER_SRC} PROPERTIES COMPILE_FLAGS "-mhost")
set_source_files_properties(${SLAVE_SRC} PROPERTIES COMPILE_FLAGS "-mslave -mfma -mpws -Dperf")
 
# 将SRCS编译为swdnn_xu这个静态链接库
add_library(swdnn_xu STATIC ${SRC} ${MASTER_SRC} ${SLAVE_SRC})
target_include_directories(swdnn_xu PRIVATE ${PROJECT_SOURCE_DIR}/utils/include)
set_target_properties(swdnn_xu PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/utils/libs) # 静态库的输出目录

link_directories("${PROJECT_SOURCE_DIR}/utils/libs" "/usr/sw/yyzlib/xMath-SACA/") # 指定静态库路径
# 生成可执行文件 ${CMAKE_PROJECT_NAME}指项目名 main_blas
add_executable(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}/main_blas.c)
target_link_options(${PROJECT_NAME} PRIVATE "-mhybrid" "-Wl,-zmuldefs")
#set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-mhybrid -Wl,-zmuldefs") # 指定链接选项
target_include_directories(${PROJECT_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/utils/include) # 指定头文件路径
target_link_libraries(${PROJECT_NAME} swdnn_xu -lcblas -lswblas -lgfortran -lm -lm_slave) # 指定要链接的静态库

 2、执行编译文件(./cm.sh)

#!/bin/sh
set -ex

file=build

rm -rf ${file}

DIR=$(dirname $(readlink -f ${BASH_SOURCE[0]}))

#		-DCMAKE_TOOLCHAIN_FILE=${DIR}/cmake/toolchain-sw_64.cmake
cmake . -B ${file} \
		-DCMAKE_C_COMPILER=/usr/sw/mpi/mpi_20210219_SEA/bin/mpicc \
		-DCMAKE_AR=/usr/sw/swgcc/swgcc710-tools-SEA-1307/usr/bin/sw9ar

cd ${file}
make -j

 

3、编译器环境变量设置(不需要)

vim cmake/toolchain-sw_64.cmake

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR sw_64)

set(CMAKE_C_COMPILER /usr/sw/mpi/mpi_20210219_SEA/bin/mpicc)
set(CMAKE_CXX_COMPILER /usr/sw/mpi/mpi_20210219_SEA/bin/mpicxx)
set(CMAKE_AR /usr/sw/swgcc/swgcc710-tools-SEA-1208/usr/bin/sw9ar)
set(MPI_C_COMPILER /usr/sw/mpi/mpi_20210219_SEA/bin/mpicc)
set(MPI_CXX_COMPILER /usr/sw/mpi/mpi_20210219_SEA/bin/mpicxx)
set(SW_C_COMPILER /usr/sw/swgcc/swgcc710-tools-SEA-1208/usr/bin/sw9gcc)
set(SW_CXX_COMPILER /usr/sw/swgcc/swgcc710-tools-SEA-1208/usr/bin/sw9g++)

~

2.2 Fortran语言程序

 创建CMakeLists.txt文件(vim CMakeLists.txt)

简单版Cmake(FC=mpif90 cmake . -B build -DCMAKE_Fortran_COMPILER=/usr/local/bin/mpif90)

cmake_minimum_required(VERSION 3.21.4) # 声明使用CMAKE的最低版本要求
project(xu_main LANGUAGES Fortran) # 定义项目的名字

find_package(MPI REQUIRED) # 寻找 MPI
#include_directories(SYSTEM ${MPI_INCLUDE_PATH}) # 引入头文件目录

# 设置编译目标类型是release版还是debug版本
set(DEFAULT_BUILD_TYPE "Release")

set(CMAKE_Fortran_FLAGS "-O2") # 设置编译器选项

# 添加工作目录下子目录src中的源文件到SRC变量
file(GLOB SRC ./funs9/*.f90 ${PROJECT_SOURCE_DIR}/funs9/*.f)

# 生成可执行文件 ${CMAKE_PROJECT_NAME}指项目名 xu_main
add_executable(${CMAKE_PROJECT_NAME} ${SRC} )

2.3 命令简介

2.3.1 project

project命令用于指定cmake工程的名称,实际上,它还可以指定cmake工程的版本号(VERSION关键字)、简短的描述(DESCRIPTION关键字)、主页URL(HOMEPAGE_URL关键字)和编译工程使用的语言(LANGUAGES关键字)。
(1)参数project(<PROJECT_NAME> [VERSION <major>] [DESCRIPTION <project-description-string>] [HOMEPAGE_URL <url-string>] [LANGUAGES <language-name>])
PROJECT_NAME:将当前工程的名称赋值给PROJECT_NAME,同时${PROJECT_NAME}变量赋值为PROJECT_NAME。
VERSION:指定工程的版本号。
DESCRIPTION:对工程的文本描述。
HOMEPAGE_URL:指定工程的主页URL。
LANGUAGES选项:选择构建工程需要的编程语言。
(2)当定义了project()后,一些cmake自带变量会自动赋值
PROJECT_NAME:将当前工程的名称赋值给PROJECT_NAME。
PROJECT_SOURCE_DIR:当前工程的源码路径。
CMAKE_PROJECT_NAME:顶层工程的名称。例如当前调用的CMakeLists.txt位于顶层目录(可以理解为使用cmake命令首次调用的那个CMakeLists.txt),那么工程名还会赋值给CMAKE_PROJECT_NAME。

2.3.2 add_executable生成可执行文件

1、参数add_executable(<name> <EXCLUDE_FROM_ALL> <source1> <source2 ...>)
name:可执行目标文件的名字,在一个cmake工程中,这个名字必须全局唯一。
EXCLUDE_FROM_ALL:用于指定可执行目标是否会被构建,当该选项使用的时候,可执行目标不会被构建。
[source1] [source2 …]:构建可执行目标文件所需要的源文件。
2、EXCLUDE_FROM_ALL参数用法
project(test)
add_executable(test EXCLUDE_FROM_ALL test.f90)
// test加了EXCLUDE_FROM_ALL属性,在默认编译的时候,不会被编译,
// 如果要编译它,需要手动编译,比如make test指定编译名为test
make test

2.3.3 add_library编译出静态库和动态库

 1、add_library(<name> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] [source1 source2 ...])
STATIC、SHARED 和 MODULE 表示库文件的类型,分别表示静态库、动态库和可加载模块;
# 将SRCS编译为swdnn_xu这个静态链接库
add_library(swdnn_xu STATIC ${SRC} ${MASTER_SRC} ${SLAVE_SRC})

2.3.4 link_libraries 和 target_link_libraries 链接库

1、link_libraries(link_libraries 基本上被遗弃了,尽可能用 target_link_libraries)

link_libraries("/usr/sw/yyzlib/xMath-SACA/libswblas.a")
add_executable(${PROJECT_NAME} main.c) 

2、target_link_libraries(<target> <PRIVATE | PUBLIC | INTERFACE> <item>...)

link_directories("/usr/sw/yyzlib/xMath-SACA") # 指定静态库路径
add_executable(${PROJECT_NAME} main.c)
target_link_libraries(${PROJECT_NAME} swzgemm_xu /usr/sw/yyzlib/xMath-SACA/libswblas.a)

target_link_libraries(${PROJECT_NAME} libswzgemm_xu.a)  #这些库名写法都可以。
target_link_libraries(${PROJECT_NAME} swzgemm_xu)
target_link_libraries(${PROJECT_NAME} -lswzgemm_xu)

【注】:调用link_directories必须在生成可执行文件之前调用,也就是在add_executable之前调用

target_link_libraries(PUBLIC target target1 target2)
target_link_libraries(PUBLIC target3 target target4)
//PUBLIC:表示target能够使用target1&target2库中内容,target3也能使用target1&target2库中内容;默认状态为PUBLIC。
target_link_libraries(PRIVATE target target1 target2)
target_link_libraries(PRIVATE target3 target target4)
//PRIVATE:表示target能够使用target1&target2库中内容,target3不能使用target1&target2中定义的内容,只能使用target中定义的内容。
target_link_libraries(INTERFACE target target1 target2)
target_link_libraries(INTERFACE target3 target target4)
//INTERFACE:表示target无法使用target1&target2库中内容,但是target3能使用target1&target2中内容。

3、link_libraries 和 target_link_libraries 区别
(1)link_libraries用在add_executable之前,target_link_libraries用在add_executable之后
(2)link_libraries用来链接静态库,target_link_libraries用来链接导入库,即按照header file + .lib + .dll方式隐式调用动态库的.lib库
(3)link_libraries 基本上被遗弃了,尽可能用 target_link_libraries

2.3.5 include_directories 和 target_include_directories引用头文件

1、include_directories([AFTER | BEFORE] [SYSTEM]  [<dir1> <dir2> ...])
include_directories(SYSTEM ${PROJECT_SOURCE_DIR}/include) # 引入头文件目录
add_executable(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}/main_blas.c)
默认情况下,指定的目录被追加到当前目录列表中。这个默认行为可以通过将CMAKE_INCLUDE_DIRECTORIES_BEFORE设置为ON来改变。通过显式使用AFTER或BEFORE,可以在appending和prepending之间进行选择,而不依赖于默认值。
如果给出了SYSTEM选项,编译器将被告知在某些平台上目录是系统包含目录。
2、target_include_directories(<target> [SYSTEM][AFTER|BEFORE] <INTERFACE|PUBLIC|PRIVATE> [items1...][<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
(1)SYSTEM:表示这些头文件是系统头文件;BEFORE:表示这些路径将被添加到其他路径之前;AFTER:表示这些路径将被添加到其他路径之后;
(2)INTERFACE、PUBLIC和PRIVATE是指定这些路径的可见性。它们的含义如下:
         i、INTERFACE:这些路径只会被添加到目标的接口中,不会被添加到目标自身的编译选项中。
         ii、PUBLIC:这些路径会被添加到目标自身的编译选项中,同时也会被添加到目标的接口中。
         iii、PRIVATE:这些路径只会被添加到目标自身的编译选项中,不会被添加到目标的接口中。
add_executable(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}/main_blas.c)
target_include_directories(${PROJECT_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/utils/include) # 指定头文件路径
3、区别
(1)include_directories:当前CMakeLists.txt的所有目标,以及之后添加的所有子目录的目标添加头文件搜索路径。因此,慎用include_directories,因为会影响全局target。
(2)target_include_directories:指定目标包含的头文件路径。如果想为不同目标设置不同的搜索路径,那么用target_include_directories更合适。

2.3.6 add_subdirectory添加子目录

1、语法:add_subdirectory (source_dir [binary_dir] [EXCLUDE_FROM_ALL])
2、作用:添加一个子目录并构建该子目录,告诉CMAKE我还有其它子目录的CMakeList.txt需要编译。
3、参数
(1)source_dir:必选参数。该参数指定一个子目录,子目录下应该包含CMakeLists.txt文件和代码文件。子目录可以是相对路径也可以是绝对路径,如果是相对路径,则是相对当前目录的一个相对路径。
(2)binary_dir:可选参数。该参数指定一个目录,用于存放输出文件。可以是相对路径也可以是绝对路径,如果是相对路径,则是相对当前输出目录的一个相对路径。如果该参数没有指定,则默认的输出目录使用source_dir。
(3)EXCLUDE_FROM_ALL:可选参数。当指定了该参数,则子目录下的目标不会被父目录下的目标文件包含进去,父目录的CMakeLists.txt不会构建子目录的目标文件,必须在子目录下显式去构建。例外情况:当父目录的目标依赖于子目录的目标,则子目录的目标仍然会被构建出来以满足依赖关系(例如使用了target_link_libraries)。

2.3.7 target_compile_options设置目标的编译选项

1、target_compile_options(<target> [BEFORE] <INTERFACE|PUBLIC|PRIVATE> [items1 items1 ...])
add_executable(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}/main_blas.c)
target_compile_options(${PROJECT_NAME} PRIVATE "-O3")

2.3.8 option选项

1、option(<variable> "<help_text>" [value])
variable:定义选项名称
help_text:说明选项的含义
value:定义选项默认状态,一般是OFF或者ON,除去ON之外,其他所有值都为认为是OFF。

相当于bool变量,用于控制是否进入if分支。

option(ENABLE_AUTO_INIT "Automatically initialize the thread pool" ON)
if(ENABLE_AUTO_INIT)
    add_definitions(-DCRTS_AUTO_INIT)
endif()
在工程的根目录,编写CMakeLists.txt, 除了直接初始化函数的值外,还可以另外创建一个独立的option.txt,专门管理编译选项,语法如下:
在CMakeLists.txt中加如下语句
include option.txt
在option.txt中添加以下语句
/*USE_MYMATH 为编译开关,中间的字符串为描述信息,ON/OFF 为默认选项*/
option (USE_CRTS “Use SW_CRTS” ON)
/*看完整的make 信息*/
make VERBOSE=1

2.3.9 add_definitions 和 target_compile_definitions添加预处理器宏

1、add_definitions 
基本用法:add_definitions(-DDEBUG)
2、target_compile_definitions(<target>  <INTERFACE|PUBLIC|PRIVATE> [items1...]  [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
PRIVATE:只有目标自己会使用这些编译参数。
PUBLIC:目标自己和其他依赖这个目标的目标都会使用这些编译参数。
INTERFACE:只有其他依赖这个目标的目标会使用这些编译参数

target_compile_definitions(target PRIVATE DEBUG)
target_compile_definitions(target PRIVATE -DDEBUG)  # -D 被移除

int main(int argc, char** argv)
{
#ifdef DEBUG
    std::cout << "main:In debug print mode..." << std::endl;
#endif
 
    return 0;
}

对于大型项目和需要更细致管理的情况,建议使用 target_compile_definitions 来为特定目标设置特定的编译器定义,以保持项目的清晰和模块化。

2.3.10 将目标文件保存到指定目录下

1、设置输出目录

set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)      # 动态库
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/static)   # 静态库
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)      # 执行文件

2、指向性保存

同样是指定输出目录,但是不同的动态库文件指定不同的输出目录。(静态库和二进制执行文件也是同理)。目标文件可以大致分为三种类型:二进制执行文件、动态库、静态库。保存不同目标文件所用到的属性不一样。具体分类如下。

RUNTIME_OUTPUT_DIRECTORY:二进制执行文件
LIBRARY_OUTPUT_DIRECTORY:动态库
ARCHIVE_OUTPUT_DIRECTORY:静态库

add_library(swdnn_xu STATIC ${SRC} ${MASTER_SRC} ${SLAVE_SRC})
set_target_properties(swdnn_xu PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/utils/libs)

2.3.9 设置目标的链接选项

target_compile_options(${PROJECT_NAME} PRIVATE "-mhybrid -Wl,-zmuldefs")
target_link_options(${PROJECT_NAME} PRIVATE "-mhybrid" "-Wl,-zmuldefs")

 

 

 

 

 

aaaa

 

 
posted @ 2023-05-20 18:19  惊小呆  阅读(339)  评论(0编辑  收藏  举报