Commit c74346c6 authored by Nikita Akatiev's avatar Nikita Akatiev
Browse files

CMake build system

parent a1a3560f
*.o
build
obj
/build
/out
*.html
......@@ -13,5 +13,9 @@ linter:
test:
stage: test
script:
- make -sj SANITIZER=all
- make -sk test SANITIZER=all
- cmake -B ./build/ -G "Ninja Multi-Config" -DCMAKE_C_COMPILER=clang
- cmake --build ./build/ --config Debug --target check
- cmake --build ./build/ --config ASan --target check
- cmake --build ./build/ --config LSan --target check
- cmake --build ./build/ --config MSan --target check
- cmake --build ./build/ --config UBSan --target check
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeSharedSettings">
<configurations>
<configuration PROFILE_NAME="Debug" ENABLED="true" GENERATION_DIR="out/build/debug" CONFIG_NAME="Debug" GENERATION_OPTIONS="-G Ninja -DCMAKE_BUILD_TYPE=Debug" />
<configuration PROFILE_NAME="Release" ENABLED="true" GENERATION_DIR="out/build/release" CONFIG_NAME="Release" GENERATION_OPTIONS="-G Ninja -DCMAKE_BUILD_TYPE=Release" />
<configuration PROFILE_NAME="ASan" ENABLED="true" GENERATION_DIR="out/build/asan" CONFIG_NAME="ASan" GENERATION_OPTIONS="-G Ninja -DCMAKE_BUILD_TYPE=ASan" />
<configuration PROFILE_NAME="LSan" ENABLED="true" GENERATION_DIR="out/build/lsan" CONFIG_NAME="LSan" GENERATION_OPTIONS="-G Ninja -DCMAKE_BUILD_TYPE=LSan" />
<configuration PROFILE_NAME="MSan" ENABLED="true" GENERATION_DIR="out/build/msan" CONFIG_NAME="MSan" GENERATION_OPTIONS="-G Ninja -DCMAKE_BUILD_TYPE=MSan" />
<configuration PROFILE_NAME="UBSan" ENABLED="true" GENERATION_DIR="out/build/ubsan" CONFIG_NAME="UBSan" GENERATION_OPTIONS="-G Ninja -DCMAKE_BUILD_TYPE=UBSan" />
</configurations>
</component>
</project>
\ No newline at end of file
cmake_minimum_required(VERSION 3.12)
project(image-transformer LANGUAGES C)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
if(CMAKE_C_COMPILER_ID STREQUAL GNU)
set(CMAKE_CONFIGURATION_TYPES Debug Release ASan LSan UBSan)
elseif(CMAKE_C_COMPILER_ID MATCHES Clang)
set(CMAKE_CONFIGURATION_TYPES Debug Release ASan LSan MSan UBSan)
elseif(MSVC)
set(CMAKE_CONFIGURATION_TYPES Debug Release ASan)
endif()
if(CMAKE_BUILD_TYPE AND NOT CMAKE_BUILD_TYPE IN_LIST CMAKE_CONFIGURATION_TYPES)
message(FATAL_ERROR "Unexpected build type ${CMAKE_BUILD_TYPE}, possible values: ${CMAKE_CONFIGURATION_TYPES}")
endif()
if(CMAKE_C_COMPILER_ID STREQUAL GNU OR CMAKE_C_COMPILER_ID MATCHES Clang)
set(CMAKE_C_FLAGS "-std=c17 -pedantic -Wall -Werror -ggdb")
set(CMAKE_C_FLAGS_ASAN "${CMAKE_C_FLAGS_DEBUG} -fsanitize=address -fno-optimize-sibling-calls -fno-omit-frame-pointer")
set(CMAKE_C_FLAGS_LSAN "${CMAKE_C_FLAGS_DEBUG} -fsanitize=leak")
set(CMAKE_C_FLAGS_MSAN "${CMAKE_C_FLAGS_DEBUG} -fsanitize=memory -fno-optimize-sibling-calls -fno-omit-frame-pointer")
set(CMAKE_C_FLAGS_UBSAN "${CMAKE_C_FLAGS_DEBUG} -fsanitize=undefined")
elseif(MSVC)
set(CMAKE_C_FLAGS "/std:c17 /W4 /WX")
set(CMAKE_C_FLAGS_ASAN "${CMAKE_C_FLAGS_DEBUG} /fsanitize=address")
set(CMAKE_EXE_LINKER_FLAGS_ASAN "/debug /INCREMENTAL:NO")
endif()
if(WIN32)
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
endif()
find_program(CLANG_TIDY clang-tidy)
message(STATUS "Clang-tidy: ${CLANG_TIDY}")
if(CLANG_TIDY)
file(STRINGS clang-tidy-checks.txt clang_tidy_checks)
list(JOIN clang_tidy_checks "," clang_tidy_checks_str)
set(CMAKE_C_CLANG_TIDY
${CLANG_TIDY}
-header-filter=${CMAKE_SOURCE_DIR}
-checks=${clang_tidy_checks_str}
-warnings-as-errors=*
)
endif()
add_subdirectory(solution)
option(BUILD_TESTING "Enable tests" ON)
if(BUILD_TESTING)
enable_testing()
add_subdirectory(tester)
endif()
{
"configurations": [
{
"name": "x64-Debug",
"generator": "Ninja",
"configurationType": "Debug",
"inheritEnvironments": [ "msvc_x64_x64" ],
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeCommandArgs": "",
"buildCommandArgs": "",
"ctestCommandArgs": ""
},
{
"name": "x64-Release",
"generator": "Ninja",
"configurationType": "Release",
"inheritEnvironments": [ "msvc_x64_x64" ],
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeCommandArgs": "",
"buildCommandArgs": "",
"ctestCommandArgs": ""
},
{
"name": "x64-Asan",
"generator": "Ninja",
"configurationType": "ASan",
"inheritEnvironments": [ "msvc_x64_x64" ],
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeCommandArgs": "",
"buildCommandArgs": "",
"ctestCommandArgs": ""
}
]
}
\ No newline at end of file
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)) -MD -MF $$@.d -MP -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
buildType:
default: debug
description: Build Type
choices:
debug:
short: Debug
long: Build with debugging information
buildType: Debug
release:
short: Release
long: Optimize the resulting binaries
buildType: Release
asan:
short: ASan
long: Instrument with AddressSanitizer
buildType: ASan
lsan:
short: LSan
long: Instrument with LeakSanitizer
buildType: LSan
msan:
short: MSan
long: Instrument with MemorySanitizer
buildType: MSan
ubsan:
short: UBSan
long: Instrument with UndefinedBehaviourSanitizer
buildType: UBSan
file(GLOB_RECURSE sources CONFIGURE_DEPENDS
src/*.c
src/*.h
include/*.h
)
add_executable(image-transformer ${sources})
target_include_directories(image-transformer PRIVATE src include)
file(GLOB_RECURSE sources CONFIGURE_DEPENDS
src/*.c
src/*.h
include/*.h
)
add_executable(image-matcher ${sources})
target_include_directories(image-matcher PRIVATE src include)
file(GLOB test_directories CONFIGURE_DEPENDS tests/*)
list(FILTER test_directories EXCLUDE REGEX ".*/\.gitignore")
foreach(test_dir IN LISTS test_directories)
string(REPLACE "/" ";" name_components ${test_dir})
list(GET name_components -1 name)
add_test(NAME test-${name}
COMMAND ${CMAKE_COMMAND}
-DTEST_DIR=${test_dir}
-DIMAGE_TRANSFORMER=$<TARGET_FILE:image-transformer>
-DIMAGE_MATCHER=$<TARGET_FILE:image-matcher>
-P ${CMAKE_CURRENT_SOURCE_DIR}/tester.cmake
)
endforeach()
set(CMAKE_CTEST_ARGUMENTS --output-on-failure -C $<CONFIG>)
add_custom_target(check
COMMAND ${CMAKE_CTEST_COMMAND} ${CMAKE_CTEST_ARGUMENTS}
DEPENDS image-transformer image-matcher)
#pragma once
#include <stddef.h>
#include <malloc.h>
#include <inttypes.h>
#include <malloc.h>
#include <stddef.h>
#include "dimensions.h"
......
......@@ -27,9 +27,11 @@
#define DECLARE_FIELD(t, n) t n;
struct __attribute__((packed)) header {
#pragma pack(push, 1)
struct header {
FOR_HEADER(DECLARE_FIELD)
};
#pragma pack(pop)
#undef FOR_HEADER
#undef DECLARE_FIELD
......@@ -66,8 +68,8 @@ enum bmp_compare_status bmp_cmp(FILE *f1, FILE *f2) {
case CMP_DIFF: return BMP_CMP_DIFF;
case CMP_ERROR: return BMP_CMP_FILE_ERROR;
case CMP_EQ:
if (fseek(f1, padding, SEEK_CUR) != 0 ||
fseek(f2, padding, SEEK_CUR) != 0)
if (fseek(f1, (long) padding, SEEK_CUR) != 0 ||
fseek(f2, (long) padding, SEEK_CUR) != 0)
return BMP_CMP_FILE_ERROR;
break;
default: err("Implementation error"); break;
......
# CMP0007: list command no longer ignores empty elements.
if(POLICY CMP0007)
cmake_policy(SET CMP0007 NEW)
endif()
function(exec_check)
execute_process(COMMAND ${ARGV}
OUTPUT_VARIABLE out
ERROR_VARIABLE err
RESULT_VARIABLE result)
if(result)
string(REPLACE "/" ";" name_components ${ARGV0})
list(GET name_components -1 name)
if(NOT out)
set(out "<empty>")
endif()
if(NOT err)
set(err "<empty>")
endif()
message(FATAL_ERROR "\nError running \"${name}\"\n*** Output: ***\n${out}\n*** Error: ***\n${err}\n")
endif()
endfunction()
exec_check(${IMAGE_TRANSFORMER} ${TEST_DIR}/input.bmp ${TEST_DIR}/output.bmp)
exec_check(${IMAGE_MATCHER} ${TEST_DIR}/output.bmp ${TEST_DIR}/output_expected.bmp)
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