Commit a3021ad0 authored by Igor Zhirkov's avatar Igor Zhirkov
Browse files

Merge branch 'feature/build-system' into 'master'

Update build processes

See merge request !6
parents f1ff6f83 7ce37b99
......@@ -13,4 +13,5 @@ linter:
test:
stage: test
script:
- ./run-tests
- make -sj SANITIZER=all
- make -sk test SANITIZER=all
NAME := image-transformer
##### Compiler / analyzer common configuration.
CC = clang
LINKER = $(CC)
RM = rm -rf
MKDIR = mkdir -p
# Clang-tidy
CLANG_TIDY = clang-tidy
_noop =
_space = $(noop) $(noop)
_comma = ,
# Using `+=` to let user define their own checks in command line
CLANG_TIDY_CHECKS += $(strip $(file < clang-tidy-checks.txt))
CLANG_TIDY_ARGS = \
-warnings-as-errors=* -header-filter="$(abspath $(INCDIR.main))/.*" \
-checks="$(subst $(_space),$(_comma),$(CLANG_TIDY_CHECKS))" \
# Sanitizers
CFLAGS.none :=
CFLAGS.asan := -fsanitize=address
CFLAGS.lsan := -fsanitize=leak
CFLAGS.msan := -fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -fno-optimize-sibling-calls
CFLAGS.usan := -fsanitize=undefined
SANITIZER ?= none
ifeq ($(SANITIZER),)
override SANITIZER := none
endif
ifeq ($(words $(SANITIZER)),1)
ifeq ($(filter $(SANITIZER),all asan lsan msan usan none),)
$(error Please provide correct argument value for SANITIZER: all, asan, lsan, msan, usan or none)
endif
endif
# Using `+=` to let user define their own flags in command line
CFLAGS += $(CFLAGS.$(SANITIZER))
LDFLAGS += $(CFLAGS.$(SANITIZER))
ifeq ($(SANITIZER),none)
OBJDIR = obj
BUILDDIR = build
else
OBJDIR = obj/$(SANITIZER)
BUILDDIR = build/$(SANITIZER)
endif
##### Configuration for `main` target.
SOLUTION_DIR = solution
SRCDIR.main = $(SOLUTION_DIR)/src
INCDIR.main = $(SOLUTION_DIR)/include
OBJDIR.main = $(OBJDIR)/$(SOLUTION_DIR)
SOURCES.main += $(wildcard $(SRCDIR.main)/*.c) $(wildcard $(SRCDIR.main)/*/*.c)
TARGET.main := $(BUILDDIR)/$(NAME)
CFLAGS.main += $(strip $(file < $(SOLUTION_DIR)/compile_flags.txt)) $(CFLAGS) -I$(INCDIR.main)
##### Configuration for `tester` target.
TESTER_DIR = tester
TESTER_SCRIPT = $(TESTER_DIR)/tester.sh
SRCDIR.tester = $(TESTER_DIR)/src
INCDIR.tester = $(TESTER_DIR)/include
OBJDIR.tester = $(OBJDIR)/$(TESTER_DIR)
SOURCES.tester += $(wildcard $(SRCDIR.tester)/*.c)
TARGET.tester := $(BUILDDIR)/image-tester
CFLAGS.tester += $(strip $(file < $(TESTER_DIR)/compile_flags.txt)) $(CFLAGS) -I$(INCDIR.tester)
##### Rule templates. Should be instantiated with $(eval $(call template, ...))
# I use $$(var) in some variables to avoid variable expanding too early.
# I do not remember when $(var) is expanded in `define` rules, but $$(var)
# is expanded exactly at $(eval ...) call.
# Template for running submake on each SANITIZER value when SANITIZER=all is used.
# $(1) - SANITIZER value (none/asan/lsan/msan/usan)
define make-sanitizer-rule
GOALS.$(1) := $$(patsubst %,%.$(1),$$(MAKECMDGOALS))
$$(MAKECMDGOALS): $$(GOALS.$(1))
.PHONY: $$(GOALS.$(1))
$$(GOALS.$(1)):
@echo Running 'make $$(patsubst %.$(1),%,$$@)' with SANITIZER=$(1)
@$(MAKE) $$(patsubst %.$(1),%,$$@) SANITIZER=$(1)
endef
# Template for compilation and linking rules + depfile generation and inclusion
# $(1) - target name (main/tester)
define make-compilation-rule
OBJECTS.$(1) := $$(SOURCES.$(1):$$(SRCDIR.$(1))/%.c=$$(OBJDIR.$(1))/%.o)
SRCDEPS.$(1) := $$(OBJECTS.$(1):%.o=%.o.d)
DIRS.$(1) := $$(sort $$(dir $$(OBJECTS.$(1)) $$(TARGET.$(1))))
DIRS += $$(DIRS.$(1))
.PHONY: build-$(1)
build-$(1): $$(TARGET.$(1))
$$(TARGET.$(1)): $$(OBJECTS.$(1)) | $$(DIRS.$(1))
$(LINKER) $(LDFLAGS) $$(OBJECTS.$(1)) -o $$@
$$(OBJDIR.$(1))/%.o: $$(SRCDIR.$(1))/%.c | $$(DIRS.$(1))
$(CC) $(CFLAGS.$(1)) -M -MP $$< >$$@.d
$(CC) $(CFLAGS.$(1)) -c $$< -o $$@
-include $$(SRCDEPS.$(1))
endef
# Template for testing rules.
# $(1) - directory with test
define make-test-rule
TST_NAME.$(1) := $$(notdir $(1))
TST_INPUT.$(1) := $(1)/input.bmp
TST_OUTPUT.$(1) := $(OBJDIR.tester)/$$(TST_NAME.$(1)).bmp
TST_EXPECTED.$(1) := $(1)/output_expected.bmp
TST_LOG_OUT.$(1) := $(OBJDIR.tester)/$$(TST_NAME.$(1))_out.log
TST_LOG_ERR.$(1) := $(OBJDIR.tester)/$$(TST_NAME.$(1))_err.log
.PHONY: $$(TST_OUTPUT.$(1)) test-$$(TST_NAME.$(1))
test: test-$$(TST_NAME.$(1))
test-$$(TST_NAME.$(1)): build-main build-tester
$(TESTER_SCRIPT) $$(TST_NAME.$(1)) \
--main-cmd '$(TARGET.main) $$(TST_INPUT.$(1)) $$(TST_OUTPUT.$(1))' \
--tester-cmd '$(TARGET.tester) $$(TST_OUTPUT.$(1)) $$(TST_EXPECTED.$(1))' \
--log-dir '$(OBJDIR.tester)'
endef
##### Rules and targets.
.PHONY: all test clean check
ifeq ($(MAKECMDGOALS),)
MAKECMDGOALS := all
endif
ifeq ($(SANITIZER),all)
# Do all the work in sub-makes
$(foreach sanitizer,none asan lsan msan usan,$(eval $(call make-sanitizer-rule,$(sanitizer))))
else
all: build-main
check:
$(CLANG_TIDY) $(CLANG_TIDY_ARGS) $(SOURCES.main)
$(foreach target,main tester,$(eval $(call make-compilation-rule,$(target))))
$(foreach test,$(sort $(wildcard $(TESTER_DIR)/tests/*)),$(eval $(call make-test-rule,$(test))))
clean:
$(RM) $(OBJDIR) $(BUILDDIR)
$(sort $(DIRS)):
$(MKDIR) $@
endif
......@@ -225,15 +225,50 @@ bmp-заголовка.
```
- Архитектура приложения описана в предыдущем разделе.
- Мы предоставляем вам `Makefile`, который нужно использовать. Заголовочные файлы ищутся в `include`.
Ваша программа будет тестироваться с помощью `clang` и проверяться
статическим анализатором `clang-tidy`. Флаги и формат его запуска можно
посмотреть
[тут](https://gitlab.se.ifmo.ru/low-level-programming/docker-c-test-machine/-/blob/master/to-docker-image/run-checks.sh).
Тесты запускаются в
- Код размещается в директории `solution/src`, заголовочные файлы ищутся в `solution/include`.
## Система сборки и тестирования
- Используйте команду `make` для того, чтобы собрать ваш код в исполняемый файл
`build/image-transformer`. Проверьте, что в вашей системе установлены программа
`make` версии как минимум 4.0 и компилятор `clang`. Флаги для компиляции берутся
из `compile_flags.txt` &mdash; вы можете добавить свои флаги в конец
этого файла или в саму команду `make`. Например, чтобы собрать код с оптимизациями
и протестировать скорость выполнения, вы можете использовать `make CFLAGS=-O3`.
С помощью `LDFLAGS` можно передать дополнительные параметры линковщику.
- Используйте `make check`, чтобы проверить вашу программу через статический анализатор
`clang-tidy`. Список проверок описан в файле `clang-tidy-checks.txt`. Вы можете добавить
свои проверки в конец этого файла или в параметр `CLANG_TIDY_CHECKS` для `make`.
- Вы можете собрать код с поддержкой определенных динамических анализаторов (санитайзеров).
Санитайзеры могут дать подробную информацию о возможных и реальных ошибках в программе вместо
классического сообщения о segmentation fault. Исполняемые файлы будут также размещены в директории `build`, но для санитайзеров выделены отдельные
поддиректории с их названием.
Поддерживаются следующие санитайзеры:
- `make SANITIZER=asan` &mdash; [AddressSanitizer](https://clang.llvm.org/docs/AddressSanitizer.html),
набор проверок на некорректное использование адресов памяти. Примеры:
use-after-free, double-free, выход за пределы стека, кучи или статического блока.
- `make SANITIZER=lsan` &mdash; [LeakSanitizer](https://clang.llvm.org/docs/LeakSanitizer.html),
проверки на утечки памяти.
- `make SANITIZER=msan` &mdash; [MemorySanitizer](https://clang.llvm.org/docs/MemorySanitizer.html),
проверяет, что любая используемая ячейка памяти проинициализирована на момент чтения из нее.
- `make SANITIZER=usan` &mdash; [UndefinedBehaviourSanitizer](https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html),
набор базовых проверок на неопределенное поведение. Примеры: переполнение численного типа,
null-pointer dereference.
- `make SANITIZER=none` &mdash; собрать код без санитайзеров (по умолчанию).
- `make SANITIZER=all` &mdash; собрать 5 версий кода, каждая с одной из предыдущих опций.
- Используйте `make clean` для очистки временных файлов и директории `build`.
- Директория `tester` содержит код и изображения для тестирования вашей программы. Используйте
`make -k test`, чтобы запустить тесты и вывести отчет по их выполнению. Параметры `CFLAGS`,
`LDFLAGS` и `SANITIZER` также могут быть переданы программам, используемым в тестах.
- Чтобы запустить конкретный тест, посмотрите его название в отчете всех тестов (оно же название
директории с изображениями в `testers/tests`) и запустите `make -k test-<name>`. Например,
чтобы запустить тест №1 ([изначальная картинка](testers/tests/1/input.bmp) ->
[ожидаемый результат](testers/tests/1/output_expected.bmp)), используйте `make -k test-1`.
- Ваша программа будет тестироваться в
[Docker-контейнере](https://gitlab.se.ifmo.ru/low-level-programming/docker-c-test-machine/),
вы можете использовать его для тестирования вашего кода локально.
Скрипт `run-tests` запускает тесты.
вы можете использовать его для тестирования вашего кода локально. В контейнере будут запущены:
- Статический анализатор - аналогичен команде `make check`
- Тесты с санитайзерами - аналогичен команде `make -k test SANITIZER=all`
# Для самопроверки
......
clang-analyzer-*
llvm-include-order
misc-*
performance-*
readability-redundant-*
readability-simplify-*
readability-const-*
readability-implicit-bool-*
readability-identifier-naming
readability-inconsistent-declaration-parameter-name
readability-misleading-indentation
readability-named-parameter
bugprone-argument-comment
bugprone-assert-side-effect
bugprone-bad-signal-to-kill-thread
bugprone-bool-pointer-implicit-conversion
bugprone-branch-clone
bugprone-copy-constructor-init
bugprone-dangling-handle
bugprone-dynamic-static-initializers
bugprone-exception-escape
bugprone-fold-init-type
bugprone-forward-declaration-namespace
bugprone-forwarding-reference-overload
bugprone-inaccurate-erase
bugprone-incorrect-roundings
bugprone-infinite-loop
bugprone-integer-division
bugprone-lambda-function-name
bugprone-macro-parentheses
bugprone-macro-repeated-side-effects
bugprone-misplaced-operator-in-strlen-in-alloc
bugprone-misplaced-pointer-arithmetic-in-alloc
bugprone-misplaced-widening-cast
bugprone-move-forwarding-reference
bugprone-multiple-statement-macro
bugprone-narrowing-conversions
bugprone-no-escape
bugprone-not-null-terminated-result
bugprone-parent-virtual-call
bugprone-posix-return
bugprone-signed-char-misuse
bugprone-sizeof-container
bugprone-sizeof-expression
bugprone-spuriously-wake-up-functions
bugprone-string-constructor
bugprone-string-integer-assignment
bugprone-string-literal-with-embedded-nul
bugprone-suspicious-enum-usage
bugprone-suspicious-include
bugprone-suspicious-memset-usage
bugprone-suspicious-missing-comma
bugprone-suspicious-semicolon
bugprone-suspicious-string-compare
bugprone-swapped-arguments
bugprone-terminating-continue
bugprone-throw-keyword-missing
bugprone-too-small-loop-variable
bugprone-undefined-memory-manipulation
bugprone-undelegated-constructor
bugprone-unhandled-self-assignment
bugprone-unused-raii
bugprone-unused-return-value
bugprone-use-after-move
bugprone-virtual-near-miss
echo "--------------------"
echo "Building tester"
echo "--------------------"
make -C tester
echo "Compiling and testing with sanitizers"
sanitizers=( '-fsanitize=leak -fsanitize=address'
'-fsanitize=memory -fsanitize-memory-track-origins=2'
'-fsanitize=undefined'
)
COMPARE_TEST_RESULTS=./tester/build/bmp-compare
for sanitizer in "${sanitizers[@]}"
do
echo "--------------------"
echo "Starting tests with the following sanitizer enabled: $sanitizer"
echo "--------------------"
make -C solution clean && make -C solution with="$sanitizer" || { echo "Unable to compile; see log" ; exit 1 ; }
# Here we launch tests
for test in `ls tester/tests/* -d` ;
do
fst="$test/input.bmp"
snd="$test/output.bmp"
expected="${snd/.bmp/_expected.bmp}"
./solution/build/image-transformer "$fst" "$snd"
echo -e "\ncomparing: $snd and $expected"
$COMPARE_TEST_RESULTS "$snd" "$expected" && echo -e "OK\n" || exit 1 ;
done
# tests end
echo ""
done
CC = clang
LINKER = clang
BUILDDIR = build
OBJDIR = obj
SRCDIR = src
TESTDIR = tests
INCLUDEDIR = include
CFLAGS = -c -std=c17 -I$(INCLUDEDIR) -ggdb -Wall -Werror -pedantic -Wno-attributes
LDFLAGS =
SOURCES = $(wildcard $(SRCDIR)/*.c) $(wildcard $(SRCDIR)/transform/*.c)
OBJECTS = $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o)
TARGET = image-transformer
ifneq ($(with), )
CFLAGS += $(with)
LDFLAGS += $(with)
endif
all: $(BUILDDIR)/$(TARGET)
$(BUILDDIR)/$(TARGET): $(OBJECTS) | $(BUILDDIR) $(BUILDDIR)/transform
$(LINKER) $(LDFLAGS) $(OBJECTS) -o $@
$(OBJECTS): $(OBJDIR)/%.o:$(SRCDIR)/%.c | $(OBJDIR) $(OBJDIR)/transform
$(CC) $(CFLAGS) -c $< -o $@
$(BUILDDIR) $(BUILDDIR)/transform $(OBJDIR) $(OBJDIR)/transform:
mkdir -p $@
clean:
rm -rf $(BUILDDIR) $(OBJDIR)
.PHONY: clean
CC = clang
LINKER = clang
BUILDDIR = build
OBJDIR = obj
SRCDIR = src
TESTDIR = tests
INCLUDEDIR = include
CFLAGS = -c -std=c17 -I$(INCLUDEDIR) -ggdb -Wall -Werror -pedantic -Wno-attributes
LDFLAGS =
SOURCES = $(wildcard $(SRCDIR)/*.c)
OBJECTS = $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o)
TARGET = bmp-compare
all: $(BUILDDIR)/$(TARGET)
$(BUILDDIR)/$(TARGET): $(OBJECTS) | $(BUILDDIR)
$(LINKER) $(LDFLAGS) $(OBJECTS) -o $@
$(OBJECTS): $(OBJDIR)/%.o:$(SRCDIR)/%.c | $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@
$(BUILDDIR) $(OBJDIR) :
mkdir -p $@
clean:
rm -rf $(BUILDDIR) $(OBJDIR)
.PHONY: clean
#!/bin/bash --
usage="tester.sh --main-cmd '<cmd>' --tester-cmd '<cmd>' <test_name> [--log-dir '<dir>']"
POSITIONAL=()
while [ $# -gt 0 ]; do
key="$1"; shift
case $key in
--main-cmd)
main_cmd="$1"; shift
;;
--tester-cmd)
tester_cmd="$1"; shift
;;
--log-dir)
log_dir="$1"; shift
;;
-h|--help)
echo $usage
exit
;;
*)
POSITIONAL+=("$key")
;;
esac
done
set -- "${POSITIONAL[@]}"
test_name=$1
# Ignoring everything that is left
if [ -z "$main_cmd" ] || [ -z "$tester_cmd" ]; then
echo "Error: --main-cmd and --tester-cmd are required." 1>&2
echo $usage 1>&2
exit 1
fi
if [ -z "$test_name" ]; then
echo "Error: at least one positional argument is required." 1>&2
echo $usage 1>&2
exit 1
fi
if [ -z "$log_dir" ]; then
log_out=/dev/stdout
log_err=/dev/stderr
else
mkdir -p "$log_dir"
log_out="$log_dir/${test_name}_out.log"
log_err="$log_dir/${test_name}_err.log"
fi
echo "********************************************************************************"
echo "$test_name: Started"
$main_cmd >"$log_out" 2>"$log_err"
rc=$?
if [ "$rc" -ne "0" ]; then
echo
echo "Failed at creating output. Command: $main_cmd"
if [ ! -z "$log_dir" ]; then
[ ! -e "$log_out" ] && echo "*** stdout log: $log_out ***" && cat $log_out
[ ! -e "$log_err" ] && echo "*** stderr log: $log_err ***" && cat $log_err
fi
echo
echo "$test_name: Failed with exit code $rc"
echo "********************************************************************************"
exit $rc
fi
$tester_cmd >$log_out 2>$log_err
rc=$?
if [ "$rc" -ne "0" ]; then
echo
echo "Actual and expected results differ. Command: $tester_cmd"
if [ ! -z "$log_dir" ]; then
test -s "$log_out" && echo "*** stdout log: $log_out ***" && cat $log_out
test -s "$log_err" && echo "*** stderr log: $log_err ***" && cat $log_err
fi
echo
echo "$test_name: Failed with exit code $rc"
echo "********************************************************************************"
exit $rc
fi
echo "$test_name: Finished successfully"
echo "********************************************************************************"
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment