Nikita Shirikov Ed blog

Make, CMake and others

В этой лекции речь пойдет о системах сборки.

Допустим мы написаили какой-то код. Мы умеем его компилировать, проставлять флаги и т.д.

#include "hello.h"

int main() {
    hello();
}

Задача:

мы хотим распространять программу, то есть каждый должен знать, как собирать программу

Можем сделать сборочный скрипт, в который запишем команду для компиляции.

#!/bin/sh -ex

CC=gcc
LD=gcc
CFLAGS="-Wall -Werror"

$CC hello.c -c $CFLAGS
$CC main.c -c $CFLAGS
$LD hello.o main.o -o hello

Но есть проблема:

Make

Классическая система сборки. Умеет читать правила из конфига. Умеет понимать, что и когда надо перекомпилировать, перекомпоновывать.

  1. Пример Makefile.1:
hello: main.o hello.o
	gcc main.o hello.o -o hello


main.o: main.c
	gcc -c main.c -o main.o -Wall -Werror


hello.o: hello.c
	gcc -c hello.c -o hello.o -Wall -Werror

Пробелы очень важны, а также нужно использовать именно табы, а не 4 пробела.

Make сам может понять, какие файлы меняются и что надо компилировать.

Попросить Make собрать цель:

make -f Makefile.1 hello
make -f Makefile.1 main.o
  1. У Make множество внутренних правил, поэтому мы можем не обязательно явно указывать все параметры. Еще можем просить запускать утилиты.
hello: main.o hello.o


main.o: main.c
	gcc -c main.c -o main.o -Wall -Werror


hello.o: hello.c
	gcc -c hello.c -o hello.o -Wall -Werror


clean:
	rm -f *.o hello
  1. Можем пользоваться переменными для более точного контроля. Причем некоторые переменные (например CC - c compiler) используются самим Make:
CC=gcc
CFLAGS=-Wall -Werror


hello: main.o hello.o


main.o: main.c
	$(CC) -c main.c -o main.o $(CFLAGS)


hello.o: hello.c
	$(CC) -c hello.c -o hello.o $(CFLAGS)


clean:
	rm -f *.o hello
  1. Есть специальные переменные для цели и всех зависимостей:
main.o: main.c
	$(CC) -c $^ -o $@ $(CFLAGS)
  1. Шаблонные правила:
CC=gcc
CFLAGS=-Wall -Werror


hello: main.o hello.o


%.o: %.c
	$(CC) -c $^ -o $@ $(CFLAGS)


clean:
	rm -f *.o hello
  1. Шаблонные правила, тоже могут быть встроены уже в Make:
CC=gcc
CFLAGS=-Wall -Werror


all: hello


hello: main.o hello.o


clean:
	rm -f *.o hello

Проблема make, он не может отследить изменения в header’ах. И в целом он не особо умнее, он просто смотрим, изменились ли сами файлы или нет.

Мы можем попросить компилятор для нас явно выписать абсолютно все зависимости.

[!WARNING] Но если мы хотим нормально распространять свою программу на разные системы разным разработчиком, то работа с make превращается в полный кошмар.

CMake

Приходит нам на помощь и помогает для нам генерировать Make файлы.

В нем собраны различные заготовки на различные случаи в жизни.

Чтобы управлять CMake мы пишем CMakeList.txt

cmake_minimum_required(VERSION 3.20)
project(Hello)
add_executable(hello hello.c main.c)

Сборку лучше делать в директории /build, чтобы поддерживать чистоту

Чтобы попросить CMake сделать свою работу можем:

cmake .. #из папки /build

CMake может позаботиться об очень многом, сделает кучу полезных файлов.

Дальше из директории /build можем просто вызвать и получить исполняемый файлик:

make

Можем генерировать сборочные файлы и для других систем сборок, например ninja. Будет побыстрее.

Что мы хотим дальше? - xотим работать с библиотеками

Через make это супер неудобно + сложно.

cmake может проверять сам проверять какие библиотеки есть, в зависимости от этого собирать разные билды

А еще cmake может для нас установить нужную библиотеку.

Еще cmake может решать проблемы с версиями и совместимостью.

Но в целом, мы хотим гермитичности наших сборок, все должно собираться отдельно, чтоб через кучу лет, мы могли все сорать и все будет всегда работать одинаково.

cmake нам с этим может помочь только частично

#Os