From 333ccc7385db0eb6151c1a163e5ea2ac2702012e Mon Sep 17 00:00:00 2001 From: Egor Tensin Date: Thu, 25 Apr 2024 04:28:29 +0200 Subject: Makefile: separate shortcuts for debug & release builds --- .github/workflows/ci.yml | 28 ++++++------ DEVELOPMENT.md | 42 +++++++++-------- Dockerfile | 25 ++++------ Makefile | 117 +++++++++++++++++++++++------------------------ 4 files changed, 103 insertions(+), 109 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 613124d..205a033 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: strategy: matrix: compiler: [gcc, clang] - configuration: [Debug, Release] + configuration: [debug, release] runs-on: ubuntu-latest name: 'Build: ${{ matrix.compiler }} / ${{ matrix.configuration }}' env: @@ -45,24 +45,24 @@ jobs: sudo DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends $DEPS valgrind pip install -q -r ./test/requirements.txt - name: Build - run: make install + run: make '${{ matrix.configuration }}/install' - name: Upload binaries uses: actions/upload-artifact@v4 with: name: 'cimple-${{ matrix.compiler }}-${{ matrix.configuration }}' - path: './build/install/' + path: './build/${{ matrix.configuration }}/install/' if-no-files-found: error - name: Run tests - run: make test/report + run: make '${{ matrix.configuration }}/report' - name: Upload test report uses: actions/upload-artifact@v4 with: name: 'test-report-${{ matrix.compiler }}-${{ matrix.configuration }}' - path: './build/test_report/' + path: './build/${{ matrix.configuration }}/test_report/' if-no-files-found: error if: always() - name: Run Valgrind tests - run: make test/valgrind + run: make '${{ matrix.configuration }}/valgrind' coverage: runs-on: ubuntu-latest @@ -80,7 +80,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: coverage - path: ./build/coverage/ + path: ./build/coverage/html/ if-no-files-found: error flame_graphs: @@ -101,18 +101,18 @@ jobs: run: | echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid - name: Build - run: make install + run: make debug/install - name: Make flame graphs run: | # sudo is used to resolve kernel symbols. Plus, it would be required # if we didn't fix perf_event_paranoid. PATH needs to be preserved # for FlameGraph. - sudo --preserve-env=PATH make flame_graphs + sudo --preserve-env=PATH make debug/flame_graphs - name: Upload flame graphs uses: actions/upload-artifact@v4 with: name: flame_graphs - path: ./build/flame_graphs/ + path: ./build/debug/flame_graphs/ if-no-files-found: error publish_reports: @@ -122,15 +122,15 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - name: Download test report - Clang/Debug + - name: Download test report - clang/debug uses: actions/download-artifact@v4 with: - name: test-report-clang-Debug + name: test-report-clang-debug path: /tmp/reports/test_report_clang_debug/ - - name: Download test report - Clang/Release + - name: Download test report - clang/release uses: actions/download-artifact@v4 with: - name: test-report-clang-Release + name: test-report-clang-release path: /tmp/reports/test_report_clang_release/ - name: Download coverage report uses: actions/download-artifact@v4 diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index f128c5b..59c5e9e 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -1,48 +1,54 @@ There's a Makefile with useful shortcuts to build the project in the "build/" directory: - make build + make debug # Debug build in build/debug/cmake + make release # Release build in build/release/cmake -This command makes a CMake build directory in build/cmake/ and executes `make` -there. +This command makes a CMake build directory in build/{debug,release}/cmake/ and +executes `make` there. -The default is to build using clang in `Debug` configuration. -You can choose a different compiler and configuration like so: +The default is to build using Clang. +You can choose a different compiler like so: - make build COMPILER=gcc CONFIGURATION=Release + make {debug,release} COMPILER=gcc ### Testing After building, you can run the "test suite" (depends on Pytest). - make test + make debug/test + make release/test To only run a subset of basic sanity tests: - make test/sanity + make debug/sanity + make release/sanity -To generate an HTML report in build/test_report/, run (requires pytest-html): +To generate an HTML report in build/{debug,release}/test_report/, run (requires +pytest-html): - make test/report + make debug/report + make release/report Reports for the latest successful Clang builds can be found below: -* [Debug], -* [Release]. +* [debug], +* [release]. -[Debug]: https://egor-tensin.github.io/cimple/test_report_clang_debug/ -[Release]: https://egor-tensin.github.io/cimple/test_report_clang_release/ +[debug]: https://egor-tensin.github.io/cimple/test_report_clang_debug/ +[release]: https://egor-tensin.github.io/cimple/test_report_clang_release/ ### Valgrind You can run a suite of basic sanity tests under Valgrind: - make test/valgrind + make debug/valgrind + make release/valgrind ### Code coverage You can generate a code coverage report (depends on `gcovr`) in -build/coverage/: +build/coverage/html: make coverage @@ -54,11 +60,11 @@ https://egor-tensin.github.io/cimple/coverage/. Some performance analysis can be done by looking at flame graphs. Generate them after building the project (depends on `perf` & [FlameGraph]): - make flame_graphs + make debug/flame_graphs [FlameGraph]: https://github.com/brendangregg/FlameGraph -This will generate two flame graphs in build/flame_graphs/; they stress +This will generate two flame graphs in build/debug/flame_graphs/; they stress slightly different parts of the system: * [flame_graph_output_simple.svg] for a CI script with short output, diff --git a/Dockerfile b/Dockerfile index 60f1f31..1a472b5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,31 +1,23 @@ FROM alpine:3.19 AS base -ARG install_dir="/app/install" - FROM base AS builder RUN build_deps='bash bsd-compat-headers build-base clang cmake coreutils git json-c-dev libgit2-dev libsodium-dev py3-pytest sqlite-dev valgrind' && \ apk add -q --no-cache $build_deps ARG COMPILER=clang -ARG CONFIGURATION=Release ARG DEFAULT_HOST=127.0.0.1 ARG DEFAULT_PORT=5556 -ARG src_dir="/app/src" -ARG install_dir - -COPY [".", "$src_dir"] +COPY [".", "/app"] -RUN cd -- "$src_dir" && \ - make install \ +RUN cd -- "/app" && \ + make release/install \ "COMPILER=$COMPILER" \ - "CONFIGURATION=$CONFIGURATION" \ "DEFAULT_HOST=$DEFAULT_HOST" \ - "DEFAULT_PORT=$DEFAULT_PORT" \ - "INSTALL_PREFIX=$install_dir" && \ + "DEFAULT_PORT=$DEFAULT_PORT" && \ ulimit -n 1024 && \ - make test/docker + make release/sanity FROM base @@ -34,11 +26,10 @@ LABEL maintainer="Egor Tensin " RUN runtime_deps='json-c libgit2 libsodium sqlite tini' && \ apk add -q --no-cache $runtime_deps -ARG install_dir -COPY --from=builder ["$install_dir", "$install_dir"] +COPY --from=builder ["/app/build/release/install", "/app"] -ENV PATH="$install_dir/bin:${PATH}" -WORKDIR "$install_dir/bin" +ENV PATH="/app/bin:${PATH}" +WORKDIR "/app/bin" ENTRYPOINT ["/sbin/tini", "--"] CMD ["cimple-server"] diff --git a/Makefile b/Makefile index cb27efa..342b3ef 100644 --- a/Makefile +++ b/Makefile @@ -1,30 +1,25 @@ include prelude.mk -src_dir := $(abspath .) -build_dir := $(src_dir)/build -cmake_dir := $(build_dir)/cmake -install_dir := $(build_dir)/install - -test_report_dir := $(build_dir)/test_report -coverage_dir := $(build_dir)/coverage -flame_graphs_dir := $(build_dir)/flame_graphs - -COMPILER ?= clang -CONFIGURATION ?= Debug -DEFAULT_HOST ?= 127.0.0.1 -DEFAULT_PORT ?= 5556 -INSTALL_PREFIX ?= $(install_dir) -COVERAGE ?= +COMPILER ?= clang +CONFIGURATION ?= debug +DEFAULT_HOST ?= 127.0.0.1 +DEFAULT_PORT ?= 5556 +COVERAGE ?= $(eval $(call noexpand,COMPILER)) $(eval $(call noexpand,CONFIGURATION)) $(eval $(call noexpand,DEFAULT_HOST)) $(eval $(call noexpand,DEFAULT_PORT)) -$(eval $(call noexpand,INSTALL_PREFIX)) $(eval $(call noexpand,COVERAGE)) +src_dir := $(abspath .) +build_dir := $(src_dir)/build + .PHONY: all -all: build +all: debug + +.PHONY: DO +DO: .PHONY: clean clean: @@ -33,89 +28,91 @@ clean: .PHONY: build build: @echo ----------------------------------------------------------------- - @echo Building + @echo 'Building: $(call escape,$(CONFIGURATION))' @echo ----------------------------------------------------------------- - @mkdir -p -- '$(call escape,$(cmake_dir))' + @mkdir -p -- '$(call escape,$(build_dir)/cmake)' cmake \ -G 'Unix Makefiles' \ -D 'CMAKE_C_COMPILER=$(call escape,$(COMPILER))' \ -D 'CMAKE_BUILD_TYPE=$(call escape,$(CONFIGURATION))' \ - -D 'CMAKE_INSTALL_PREFIX=$(call escape,$(INSTALL_PREFIX))' \ + -D 'CMAKE_INSTALL_PREFIX=$(call escape,$(build_dir)/install)' \ -D 'DEFAULT_HOST=$(call escape,$(DEFAULT_HOST))' \ -D 'DEFAULT_PORT=$(call escape,$(DEFAULT_PORT))' \ - -D 'TEST_REPORT_DIR=$(call escape,$(test_report_dir))' \ + -D 'TEST_REPORT_DIR=$(call escape,$(build_dir)/test_report)' \ -D 'COVERAGE=$(call escape,$(COVERAGE))' \ - -D 'FLAME_GRAPHS_DIR=$(call escape,$(flame_graphs_dir))' \ + -D 'FLAME_GRAPHS_DIR=$(call escape,$(build_dir)/flame_graphs)' \ -S '$(call escape,$(src_dir))' \ - -B '$(call escape,$(cmake_dir))' - cmake --build '$(call escape,$(cmake_dir))' -- -j + -B '$(call escape,$(build_dir)/cmake)' + cmake --build '$(call escape,$(build_dir)/cmake)' -- -j + +.PHONY: debug +debug debug/%: CONFIGURATION := debug +debug debug/%: build_dir := $(build_dir)/debug +debug: build .PHONY: release -release: CONFIGURATION := Release +release release/%: CONFIGURATION := release +release release/%: build_dir := $(build_dir)/release release: build -.PHONY: install -install: build - cmake --install '$(call escape,$(cmake_dir))' +# Coverage report depends on GCC debug data. +.PHONY: coverage +coverage coverage/%: CONFIGURATION := debug +coverage coverage/%: COMPILER := gcc +coverage coverage/%: COVERAGE := 1 +coverage coverage/%: build_dir := $(build_dir)/coverage +coverage: build coverage/test + @echo ----------------------------------------------------------------- + @echo Generating code coverage report + @echo ----------------------------------------------------------------- + @mkdir -p -- '$(call escape,$(build_dir))/html' + gcovr --html-details '$(call escape,$(build_dir))/html/index.html' --print-summary +ifndef CI + xdg-open '$(call escape,$(build_dir))/html/index.html' &> /dev/null +endif + +%/install: % DO + cmake --install '$(call escape,$(build_dir))/cmake' -.PHONY: test -test: +%/test: DO @echo ----------------------------------------------------------------- @echo Running tests @echo ----------------------------------------------------------------- - ctest --test-dir '$(call escape,$(cmake_dir))' \ + ctest --test-dir '$(call escape,$(build_dir))/cmake' \ --verbose --tests-regex python_tests_default -.PHONY: test/report -test/report: +%/report: DO @echo ----------------------------------------------------------------- @echo Generating test report @echo ----------------------------------------------------------------- - ctest --test-dir '$(call escape,$(cmake_dir))' \ + ctest --test-dir '$(call escape,$(build_dir))/cmake' \ --verbose --tests-regex python_tests_report +ifndef CI + xdg-open '$(call escape,$(build_dir))/test_report/index.html' &> /dev/null +endif # A subset of tests, excluding long-running stress tests. -.PHONY: test/sanity -test/sanity: +%/sanity: DO @echo ----------------------------------------------------------------- @echo Running sanity tests @echo ----------------------------------------------------------------- - ctest --test-dir '$(call escape,$(cmake_dir))' \ + ctest --test-dir '$(call escape,$(build_dir))/cmake' \ --verbose --tests-regex python_tests_sanity # Same, but run under Valgrind. -.PHONY: test/valgrind -test/valgrind: +%/valgrind: DO @echo ----------------------------------------------------------------- @echo Running sanity tests w/ Valgrind @echo ----------------------------------------------------------------- - ctest --test-dir '$(call escape,$(cmake_dir))' \ + ctest --test-dir '$(call escape,$(build_dir))/cmake' \ --verbose --tests-regex python_tests_valgrind # When building a Docker image for a different platform, exclude stress tests: # they simply take way too long. -.PHONY: test/docker -test/docker: test/sanity - -# Force a rebuild for a coverage report, since it depends on GCC debug data. -.PHONY: coverage -coverage: COMPILER := gcc -coverage: CONFIGURATION := Debug -coverage: COVERAGE := 1 -coverage: clean build test - @echo ----------------------------------------------------------------- - @echo Generating code coverage report - @echo ----------------------------------------------------------------- - @mkdir -p -- '$(call escape,$(coverage_dir))' - gcovr --html-details '$(call escape,$(coverage_dir))/index.html' --print-summary -ifndef CI - xdg-open '$(call escape,$(coverage_dir))/index.html' &> /dev/null -endif -.PHONY: flame_graphs -flame_graphs: +%/flame_graphs: DO @echo ----------------------------------------------------------------- @echo Generating flame graphs @echo ----------------------------------------------------------------- - ctest --test-dir '$(call escape,$(cmake_dir))' \ + ctest --test-dir '$(call escape,$(build_dir))/cmake' \ --verbose --tests-regex python_tests_flame_graphs -- cgit v1.2.3