diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 47d2e2e..cec0447 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,8 +4,6 @@ on: push: pull_request: -# TODO: Test statically linked - jobs: test-linux: runs-on: ubuntu-22.04 @@ -140,34 +138,15 @@ jobs: unittest-linux: runs-on: ubuntu-22.04 - strategy: - fail-fast: false - matrix: - compiler: [g++-10, clang++-14] - shared: [OFF, ON] - build_type: [Debug, RelWithDebInfo] - has_dl_find_object: [OFF, ON] steps: - uses: actions/checkout@v4 - name: dependencies run: | - sudo apt install gcc-10 g++-10 libgcc-10-dev ninja-build + cd .. + cpptrace/ci/setup-prerequisites-unittest.sh - name: build and test run: | - mkdir build - cd build - cmake .. \ - -GNinja \ - -DCMAKE_CXX_COMPILER=${{matrix.compiler}} \ - -DCMAKE_C_COMPILER=${{matrix.compiler == 'g++-10' && 'gcc-10' || 'clang-14'}} \ - -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ - -DBUILD_SHARED_LIBS=${{matrix.shared}} \ - -DHAS_DL_FIND_OBJECT=${{matrix.has_dl_find_object}} \ - -DCPPTRACE_WERROR_BUILD=On \ - -DCPPTRACE_BUILD_TESTING=On - ninja - ./unittest - bash -c "exec -a u ./unittest" + python3 ci/unittest.py unittest-windows: runs-on: windows-2022 strategy: diff --git a/CMakeLists.txt b/CMakeLists.txt index 23d81c0..45d5afc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -385,9 +385,9 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) cmake_policy(SET CMP0074 NEW) FetchContent_Declare( zstd - GIT_REPOSITORY https://github.com/facebook/zstd.git - GIT_TAG 63779c798237346c2b245c546c40b72a5a5913fe # v1.5.5 - GIT_SHALLOW 1 + GIT_REPOSITORY ${CPPTRACE_ZSTD_REPO} + GIT_TAG ${CPPTRACE_ZSTD_TAG} + GIT_SHALLOW ${CPPTRACE_ZSTD_SHALLOW} SOURCE_SUBDIR build/cmake ) # FetchContent_MakeAvailable(zstd) @@ -409,18 +409,9 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) # set(BUILD_DWARFDUMP FALSE) FetchContent_Declare( libdwarf - # GIT_REPOSITORY https://github.com/davea42/libdwarf-code.git - # GIT_TAG 6216e185863f41d6f19ab850caabfff7326020d7 # v0.8.0 - # GIT_TAG 8b0bd09d8c77d45a68cb1bb00a54186a92b683d9 # v0.9.0 - # GIT_TAG 8cdcc531f310d1c5ae61da469d8056bdd36b77e7 # v0.9.1 + some cmake changes - # Using a lightweight mirror that's optimized for clone + configure speed - # GIT_TAG ee53f0b6c99fc8cdaa3ae77af0196fb20e16177a # main 5.10.24 - GIT_REPOSITORY https://github.com/jeremy-rifkin/libdwarf-lite.git - # GIT_TAG c78e984f3abbd20f6e01d6f51819e826b1691f65 # v0.8.0 - # GIT_TAG 71090c680b4c943448ba87a0f1f864f174e4edda # v0.9.0 - # GIT_TAG 5c0cb251f94b27e90184e6b2d9a0c9c62593babc # v0.9.1 + some cmake changes - GIT_TAG 87401f22cd05628d23059cb29ee6448a55c3a88a # v0.9.2 - GIT_SHALLOW 1 + GIT_REPOSITORY ${CPPTRACE_LIBDWARF_REPO} + GIT_TAG ${CPPTRACE_LIBDWARF_TAG} + GIT_SHALLOW ${CPPTRACE_LIBDWARF_SHALLOW} ) # FetchContent_MakeAvailable(libdwarf) FetchContent_GetProperties(libdwarf) diff --git a/README.md b/README.md index bdf932f..2cf9aca 100644 --- a/README.md +++ b/README.md @@ -746,7 +746,7 @@ make install cd ~/scratch/cpptrace-test git clone https://github.com/jeremy-rifkin/libdwarf-lite.git cd libdwarf-lite -git checkout 5c0cb251f94b27e90184e6b2d9a0c9c62593babc +git checkout 6dbcc23dba6ffd230063bda4b9d7298bf88d9d41 mkdir build cd build cmake .. -DPIC_ALWAYS=On -DBUILD_DWARFDUMP=Off -DCMAKE_PREFIX_PATH=~/scratch/cpptrace-test/resources -DCMAKE_INSTALL_PREFIX=~/scratch/cpptrace-test/resources diff --git a/ci/build-in-all-configs.py b/ci/build-in-all-configs.py index c382eb3..f44f011 100644 --- a/ci/build-in-all-configs.py +++ b/ci/build-in-all-configs.py @@ -11,28 +11,8 @@ from util import * sys.stdout.reconfigure(encoding='utf-8') # for windows gh runner -failed = False - -def run_command(*args: List[str]): - print(f"{Fore.CYAN}{Style.BRIGHT}Running Command \"{' '.join(args)}\"{Style.RESET_ALL}") - p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, stderr = p.communicate() - print(Style.RESET_ALL, end="") # makefile in parallel sometimes messes up colors - if p.returncode != 0: - print(f"{Fore.RED}{Style.BRIGHT}Command failed{Style.RESET_ALL}") - print("stdout:") - print(stdout.decode("utf-8"), end="") - print("stderr:") - print(stderr.decode("utf-8"), end="") - global failed - failed = True - return False - else: - print(f"{Fore.GREEN}{Style.BRIGHT}Command succeeded{Style.RESET_ALL}") - return True - -def build(matrix): - print(f"{Fore.BLUE}{Style.BRIGHT}{'=' * 10} Running build with config {', '.join(matrix.values())} {'=' * 10}{Style.RESET_ALL}") +def build(runner: MatrixRunner): + matrix = runner.current_config() if os.path.exists("build"): shutil.rmtree("build", ignore_errors=True) @@ -41,7 +21,7 @@ def build(matrix): os.chdir("build") if platform.system() != "Windows": - succeeded = run_command( + succeeded = runner.run_command( "cmake", "..", f"-DCMAKE_BUILD_TYPE={matrix['target']}", @@ -56,7 +36,7 @@ def build(matrix): "-DCPPTRACE_BACKTRACE_PATH=/usr/lib/gcc/x86_64-linux-gnu/10/include/backtrace.h", ) if succeeded: - succeeded = run_command("make", "-j", "VERBOSE=1") + succeeded = runner.run_command("make", "-j", "VERBOSE=1") else: args = [ "cmake", @@ -73,20 +53,20 @@ def build(matrix): ] if matrix["compiler"] == "g++": args.append("-GUnix Makefiles") - succeeded = run_command(*args) + succeeded = runner.run_command(*args) if succeeded: if matrix["compiler"] == "g++": - succeeded = run_command("make", "-j", "VERBOSE=1") + succeeded = runner.run_command("make", "-j", "VERBOSE=1") else: - succeeded = run_command("msbuild", "cpptrace.sln") + succeeded = runner.run_command("msbuild", "cpptrace.sln") os.chdir("..") print() return succeeded -def build_full_or_auto(matrix): - print(f"{Fore.BLUE}{Style.BRIGHT}{'=' * 10} Running build with config {'' if matrix['config'] == '' else ', '.join(matrix.values())} {'=' * 10}{Style.RESET_ALL}") +def build_full_or_auto(runner: MatrixRunner): + matrix = runner.current_config() if os.path.exists("build"): shutil.rmtree("build", ignore_errors=True) @@ -108,9 +88,9 @@ def build_full_or_auto(matrix): ] if matrix["config"] != "": args.append(f"{matrix['config']}") - succeeded = run_command(*args) + succeeded = runner.run_command(*args) if succeeded: - succeeded = run_command("make", "-j") + succeeded = runner.run_command("make", "-j") else: args = [ "cmake", @@ -126,12 +106,12 @@ def build_full_or_auto(matrix): args.append(f"{matrix['config']}") if matrix["compiler"] == "g++": args.append("-GUnix Makefiles") - succeeded = run_command(*args) + succeeded = runner.run_command(*args) if succeeded: if matrix["compiler"] == "g++": - succeeded = run_command("make", "-j") + succeeded = runner.run_command("make", "-j") else: - succeeded = run_command("msbuild", "cpptrace.sln") + succeeded = runner.run_command("msbuild", "cpptrace.sln") os.chdir("..") print() @@ -139,143 +119,149 @@ def build_full_or_auto(matrix): return succeeded def run_linux_matrix(compilers: list): - matrix = { - "compiler": compilers, - "target": ["Debug"], - "std": ["11", "20"], - "unwind": [ - "CPPTRACE_UNWIND_WITH_UNWIND", - "CPPTRACE_UNWIND_WITH_EXECINFO", - "CPPTRACE_UNWIND_WITH_LIBUNWIND", - "CPPTRACE_UNWIND_WITH_NOTHING", - ], - "symbols": [ - "CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE", - "CPPTRACE_GET_SYMBOLS_WITH_LIBDL", - "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF", - "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE", - "CPPTRACE_GET_SYMBOLS_WITH_NOTHING", - ], - "demangle": [ - "CPPTRACE_DEMANGLE_WITH_CXXABI", - "CPPTRACE_DEMANGLE_WITH_NOTHING", - ], - } - exclude = [] - run_matrix(matrix, exclude, build) + MatrixRunner( + matrix = { + "compiler": compilers, + "target": ["Debug"], + "std": ["11", "20"], + "unwind": [ + "CPPTRACE_UNWIND_WITH_UNWIND", + "CPPTRACE_UNWIND_WITH_EXECINFO", + "CPPTRACE_UNWIND_WITH_LIBUNWIND", + "CPPTRACE_UNWIND_WITH_NOTHING", + ], + "symbols": [ + "CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE", + "CPPTRACE_GET_SYMBOLS_WITH_LIBDL", + "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF", + "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE", + "CPPTRACE_GET_SYMBOLS_WITH_NOTHING", + ], + "demangle": [ + "CPPTRACE_DEMANGLE_WITH_CXXABI", + "CPPTRACE_DEMANGLE_WITH_NOTHING", + ], + }, + exclude = [] + ).run(build) def run_linux_default(compilers: list): - matrix = { - "compiler": compilers, - "target": ["Debug"], - "std": ["11", "20"], - "config": [""] - } - exclude = [] - run_matrix(matrix, exclude, build_full_or_auto) + MatrixRunner( + matrix = { + "compiler": compilers, + "target": ["Debug"], + "std": ["11", "20"], + "config": [""] + }, + exclude = [] + ).run(build_full_or_auto) def run_macos_matrix(compilers: list): - matrix = { - "compiler": compilers, - "target": ["Debug"], - "std": ["11", "20"], - "unwind": [ - "CPPTRACE_UNWIND_WITH_UNWIND", - "CPPTRACE_UNWIND_WITH_EXECINFO", - "CPPTRACE_UNWIND_WITH_NOTHING", - ], - "symbols": [ - #"CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE", - "CPPTRACE_GET_SYMBOLS_WITH_LIBDL", - "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF", - "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE", - "CPPTRACE_GET_SYMBOLS_WITH_NOTHING", - ], - "demangle": [ - "CPPTRACE_DEMANGLE_WITH_CXXABI", - "CPPTRACE_DEMANGLE_WITH_NOTHING", - ] - } - exclude = [] - run_matrix(matrix, exclude, build) + MatrixRunner( + matrix = { + "compiler": compilers, + "target": ["Debug"], + "std": ["11", "20"], + "unwind": [ + "CPPTRACE_UNWIND_WITH_UNWIND", + "CPPTRACE_UNWIND_WITH_EXECINFO", + "CPPTRACE_UNWIND_WITH_NOTHING", + ], + "symbols": [ + #"CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE", + "CPPTRACE_GET_SYMBOLS_WITH_LIBDL", + "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF", + "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE", + "CPPTRACE_GET_SYMBOLS_WITH_NOTHING", + ], + "demangle": [ + "CPPTRACE_DEMANGLE_WITH_CXXABI", + "CPPTRACE_DEMANGLE_WITH_NOTHING", + ] + }, + exclude = [] + ).run(build) def run_macos_default(compilers: list): - matrix = { - "compiler": compilers, - "target": ["Debug"], - "std": ["11", "20"], - "config": [""] - } - exclude = [] - run_matrix(matrix, exclude, build_full_or_auto) + MatrixRunner( + matrix = { + "compiler": compilers, + "target": ["Debug"], + "std": ["11", "20"], + "config": [""] + }, + exclude = [] + ).run(build_full_or_auto) def run_windows_matrix(compilers: list): - matrix = { - "compiler": compilers, - "target": ["Debug"], - "std": ["11", "20"], - "unwind": [ - "CPPTRACE_UNWIND_WITH_WINAPI", - "CPPTRACE_UNWIND_WITH_DBGHELP", - "CPPTRACE_UNWIND_WITH_UNWIND", - "CPPTRACE_UNWIND_WITH_NOTHING", - ], - "symbols": [ - "CPPTRACE_GET_SYMBOLS_WITH_DBGHELP", - "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF", - "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE", - "CPPTRACE_GET_SYMBOLS_WITH_NOTHING", - ], - "demangle": [ - #"CPPTRACE_DEMANGLE_WITH_CXXABI", - "CPPTRACE_DEMANGLE_WITH_NOTHING", + MatrixRunner( + matrix = { + "compiler": compilers, + "target": ["Debug"], + "std": ["11", "20"], + "unwind": [ + "CPPTRACE_UNWIND_WITH_WINAPI", + "CPPTRACE_UNWIND_WITH_DBGHELP", + "CPPTRACE_UNWIND_WITH_UNWIND", + "CPPTRACE_UNWIND_WITH_NOTHING", + ], + "symbols": [ + "CPPTRACE_GET_SYMBOLS_WITH_DBGHELP", + "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF", + "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE", + "CPPTRACE_GET_SYMBOLS_WITH_NOTHING", + ], + "demangle": [ + #"CPPTRACE_DEMANGLE_WITH_CXXABI", + "CPPTRACE_DEMANGLE_WITH_NOTHING", + ] + }, + exclude = [ + { + "demangle": "CPPTRACE_DEMANGLE_WITH_CXXABI", + "compiler": "cl" + }, + { + "unwind": "CPPTRACE_UNWIND_WITH_UNWIND", + "compiler": "cl" + }, + { + "unwind": "CPPTRACE_UNWIND_WITH_UNWIND", + "compiler": "clang++" + }, + { + "symbols": "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE", + "compiler": "cl" + }, + { + "symbols": "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE", + "compiler": "clang++" + }, + { + "symbols": "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF", + "compiler": "cl" + }, + { + "symbols": "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF", + "compiler": "clang++" + }, + { + "symbols": "CPPTRACE_GET_SYMBOLS_WITH_DBGHELP", + "compiler": "g++" + }, ] - } - exclude = [ - { - "demangle": "CPPTRACE_DEMANGLE_WITH_CXXABI", - "compiler": "cl" - }, - { - "unwind": "CPPTRACE_UNWIND_WITH_UNWIND", - "compiler": "cl" - }, - { - "unwind": "CPPTRACE_UNWIND_WITH_UNWIND", - "compiler": "clang++" - }, - { - "symbols": "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE", - "compiler": "cl" - }, - { - "symbols": "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE", - "compiler": "clang++" - }, - { - "symbols": "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF", - "compiler": "cl" - }, - { - "symbols": "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF", - "compiler": "clang++" - }, - { - "symbols": "CPPTRACE_GET_SYMBOLS_WITH_DBGHELP", - "compiler": "g++" - }, - ] - run_matrix(matrix, exclude, build) + ).run(build) def run_windows_default(compilers: list): - matrix = { - "compiler": compilers, - "target": ["Debug"], - "std": ["11", "20"], - "config": [""] - } - exclude = [] - run_matrix(matrix, exclude, build_full_or_auto) + MatrixRunner( + matrix = { + "compiler": compilers, + "target": ["Debug"], + "std": ["11", "20"], + "config": [""] + }, + exclude = [] + ).run(build_full_or_auto) def main(): parser = argparse.ArgumentParser( @@ -337,9 +323,4 @@ def main(): else: run_windows_matrix(compilers) - global failed - if failed: - print("🔴 Some checks failed") - sys.exit(1) - main() diff --git a/ci/setup-prerequisites-mingw.ps1 b/ci/setup-prerequisites-mingw.ps1 index 8cbfb73..319aeb8 100644 --- a/ci/setup-prerequisites-mingw.ps1 +++ b/ci/setup-prerequisites-mingw.ps1 @@ -17,7 +17,7 @@ mkdir libdwarf cd libdwarf git init git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git -git fetch --depth 1 origin 5c0cb251f94b27e90184e6b2d9a0c9c62593babc +git fetch --depth 1 origin 6dbcc23dba6ffd230063bda4b9d7298bf88d9d41 git checkout FETCH_HEAD mkdir build cd build diff --git a/ci/setup-prerequisites-unittest.sh b/ci/setup-prerequisites-unittest.sh new file mode 100755 index 0000000..551552f --- /dev/null +++ b/ci/setup-prerequisites-unittest.sh @@ -0,0 +1,37 @@ +#!/bin/bash +sudo apt install gcc-10 g++-10 libgcc-10-dev ninja-build + +mkdir zstd +cd zstd +git init +git remote add origin https://github.com/facebook/zstd.git +git fetch --depth 1 origin 63779c798237346c2b245c546c40b72a5a5913fe # 1.5.5 +git checkout FETCH_HEAD +make -j +sudo make install + +cd .. + +mkdir libdwarf +cd libdwarf +git init +git remote add origin https://github.com/davea42/libdwarf-code.git +git fetch --depth 1 origin e2ab28a547ed8a53f2c96a825247a7cc8f7e40bb +git checkout FETCH_HEAD +mkdir build +cd build +cmake .. -GNinja -DPIC_ALWAYS=TRUE -DBUILD_DWARFDUMP=FALSE +sudo ninja install + +cd ../.. + +mkdir googletest +cd googletest +git init +git remote add origin https://github.com/google/googletest.git +git fetch --depth 1 origin f8d7d77c06936315286eb55f8de22cd23c188571 +git checkout FETCH_HEAD +mkdir build +cd build +cmake .. -GNinja +sudo ninja install diff --git a/ci/setup-prerequisites.sh b/ci/setup-prerequisites.sh index 107642a..bf001ca 100755 --- a/ci/setup-prerequisites.sh +++ b/ci/setup-prerequisites.sh @@ -16,7 +16,7 @@ mkdir libdwarf cd libdwarf git init git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git -git fetch --depth 1 origin 5c0cb251f94b27e90184e6b2d9a0c9c62593babc +git fetch --depth 1 origin 6dbcc23dba6ffd230063bda4b9d7298bf88d9d41 git checkout FETCH_HEAD mkdir build cd build diff --git a/ci/test-all-configs.py b/ci/test-all-configs.py index 70021f8..0c86bac 100644 --- a/ci/test-all-configs.py +++ b/ci/test-all-configs.py @@ -11,8 +11,6 @@ from util import * sys.stdout.reconfigure(encoding='utf-8') # for windows gh runner -failed = False - expected_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../test/expected/") def get_c_compiler_counterpart(compiler: str) -> str: @@ -110,57 +108,13 @@ def output_matches(raw_output: str, params: Tuple[str]): return not errored -def run_command(*args: List[str], always_output=False): - global failed - print(f"{Fore.CYAN}{Style.BRIGHT}Running Command \"{' '.join(args)}\"{Style.RESET_ALL}") - p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, stderr = p.communicate() - print(Style.RESET_ALL, end="") # makefile in parallel sometimes messes up colors - if p.returncode != 0: - print(f"{Fore.RED}{Style.BRIGHT}Command failed{Style.RESET_ALL}") - print("stdout:") - print(stdout.decode("utf-8"), end="") - print("stderr:") - print(stderr.decode("utf-8"), end="") - failed = True - return False - else: - print(f"{Fore.GREEN}{Style.BRIGHT}Command succeeded{Style.RESET_ALL}") - if always_output: - print("stdout:") - print(stdout.decode("utf-8"), end="") - print("stderr:") - print(stderr.decode("utf-8"), end="") - return True +def run_test(runner: MatrixRunner, test_binary, params: Tuple[str]): + def output_matcher(output: str): + return output_matches(output, params) + return runner.run_command(test_binary, output_matcher=output_matcher) -def run_test(test_binary, params: Tuple[str]): - global failed - print(f"{Fore.CYAN}{Style.BRIGHT}Running test{Style.RESET_ALL}") - test = subprocess.Popen([test_binary], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - test_stdout, test_stderr = test.communicate() - print(Style.RESET_ALL, end="") # makefile in parallel sometimes messes up colors - - if test.returncode != 0: - print(f"[🔴 Test command failed with code {test.returncode}]") - print("stderr:") - print(test_stderr.decode("utf-8"), end="") - print("stdout:") - print(test_stdout.decode("utf-8"), end="") - failed = True - return False - else: - if len(test_stderr) != 0: - print("stderr:") - print(test_stderr.decode("utf-8"), end="") - if output_matches(test_stdout.decode("utf-8"), params): - print(f"{Fore.GREEN}{Style.BRIGHT}Test succeeded{Style.RESET_ALL}") - return True - else: - print(f"{Fore.RED}{Style.BRIGHT}Test failed{Style.RESET_ALL}") - failed = True - return False - -def build(matrix): +def build(runner: MatrixRunner): + matrix = runner.current_config() if platform.system() != "Windows": args = [ "cmake", @@ -182,9 +136,9 @@ def build(matrix): ] if matrix['symbols'] == "CPPTRACE_GET_SYMBOLS_WITH_LIBDL": args.append("-DCPPTRACE_BUILD_TEST_RDYNAMIC=On") - succeeded = run_command(*args) + succeeded = runner.run_command(*args) if succeeded: - return run_command("make", "-j") + return runner.run_command("make", "-j") else: args = [ "cmake", @@ -205,15 +159,16 @@ def build(matrix): ] if matrix["compiler"] == "g++": args.append("-GUnix Makefiles") - succeeded = run_command(*args) + succeeded = runner.run_command(*args) if succeeded: if matrix["compiler"] == "g++": - return run_command("make", "-j") + return runner.run_command("make", "-j") else: - return run_command("msbuild", "cpptrace.sln") + return runner.run_command("msbuild", "cpptrace.sln") return False -def build_full_or_auto(matrix): +def build_full_or_auto(runner: MatrixRunner): + matrix = runner.current_config() if platform.system() != "Windows": args = [ "cmake", @@ -232,9 +187,9 @@ def build_full_or_auto(matrix): ] if matrix["config"] != "": args.append(f"{matrix['config']}") - succeeded = run_command(*args) + succeeded = runner.run_command(*args) if succeeded: - return run_command("make", "-j") + return runner.run_command("make", "-j") else: args = [ "cmake", @@ -254,52 +209,60 @@ def build_full_or_auto(matrix): args.append(f"{matrix['config']}") if matrix["compiler"] == "g++": args.append("-GUnix Makefiles") - succeeded = run_command(*args) + succeeded = runner.run_command(*args) if succeeded: if matrix["compiler"] == "g++": - return run_command("make", "-j") + return runner.run_command("make", "-j") else: - return run_command("msbuild", "cpptrace.sln") + return runner.run_command("msbuild", "cpptrace.sln") return False -def test(matrix): +def test(runner: MatrixRunner): + matrix = runner.current_config() if platform.system() != "Windows": return run_test( + runner, "./integration", (matrix["compiler"], matrix["unwind"], matrix["symbols"], matrix["demangle"]) ) else: if matrix["compiler"] == "g++": return run_test( + runner, f".\\integration.exe", (matrix["compiler"], matrix["unwind"], matrix["symbols"], matrix["demangle"]) ) else: return run_test( + runner, f".\\{matrix['target']}\\integration.exe", (matrix["compiler"], matrix["unwind"], matrix["symbols"], matrix["demangle"]) ) -def test_full_or_auto(matrix): +def test_full_or_auto(runner: MatrixRunner): + matrix = runner.current_config() if platform.system() != "Windows": return run_test( + runner, "./integration", (matrix["compiler"],) ) else: if matrix["compiler"] == "g++": return run_test( + runner, f".\\integration.exe", (matrix["compiler"],) ) else: return run_test( + runner, f".\\{matrix['target']}\\integration.exe", (matrix["compiler"],) ) -def build_and_test(matrix): - print(f"{Fore.BLUE}{Style.BRIGHT}{'=' * 10} Running build and test with config {', '.join(matrix.values())} {'=' * 10}{Style.RESET_ALL}") +def build_and_test(runner: MatrixRunner): + matrix = runner.current_config() if os.path.exists("build"): shutil.rmtree("build", ignore_errors=True) @@ -309,16 +272,16 @@ def build_and_test(matrix): os.chdir("build") good = False - if build(matrix): - good = test(matrix) + if build(runner): + good = test(runner) os.chdir("..") print() return good -def build_and_test_full_or_auto(matrix): - print(f"{Fore.BLUE}{Style.BRIGHT}{'=' * 10} Running build and test with config {'' if matrix['config'] == '' else ', '.join(matrix.values())} {'=' * 10}{Style.RESET_ALL}") +def build_and_test_full_or_auto(runner: MatrixRunner): + matrix = runner.current_config() if os.path.exists("build"): shutil.rmtree("build", ignore_errors=True) @@ -328,8 +291,8 @@ def build_and_test_full_or_auto(matrix): os.chdir("build") good = False - if build_full_or_auto(matrix): - good = test_full_or_auto(matrix) + if build_full_or_auto(runner): + good = test_full_or_auto(runner) os.chdir("..") print() @@ -337,162 +300,168 @@ def build_and_test_full_or_auto(matrix): return good def run_linux_matrix(compilers: list, shared: bool): - matrix = { - "compiler": compilers, - "target": ["Debug"], - "std": ["11", "20"], - "unwind": [ - "CPPTRACE_UNWIND_WITH_EXECINFO", - "CPPTRACE_UNWIND_WITH_UNWIND", - "CPPTRACE_UNWIND_WITH_LIBUNWIND", - #"CPPTRACE_UNWIND_WITH_NOTHING", - ], - "symbols": [ - # Disabled due to libbacktrace bug - # "CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE", - "CPPTRACE_GET_SYMBOLS_WITH_LIBDL", - "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE", - "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF", - #"CPPTRACE_GET_SYMBOLS_WITH_NOTHING", - ], - "demangle": [ - "CPPTRACE_DEMANGLE_WITH_CXXABI", - #"CPPTRACE_DEMANGLE_WITH_NOTHING", - ], - "shared": ["On" if shared else "Off"] - } - exclude = [] - run_matrix(matrix, exclude, build_and_test) + MatrixRunner( + matrix = { + "compiler": compilers, + "target": ["Debug"], + "std": ["11", "20"], + "unwind": [ + "CPPTRACE_UNWIND_WITH_EXECINFO", + "CPPTRACE_UNWIND_WITH_UNWIND", + "CPPTRACE_UNWIND_WITH_LIBUNWIND", + #"CPPTRACE_UNWIND_WITH_NOTHING", + ], + "symbols": [ + # Disabled due to libbacktrace bug + # "CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE", + "CPPTRACE_GET_SYMBOLS_WITH_LIBDL", + "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE", + "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF", + #"CPPTRACE_GET_SYMBOLS_WITH_NOTHING", + ], + "demangle": [ + "CPPTRACE_DEMANGLE_WITH_CXXABI", + #"CPPTRACE_DEMANGLE_WITH_NOTHING", + ], + "shared": ["On" if shared else "Off"] + }, + exclude = [] + ).run(build_and_test) def run_linux_default(compilers: list, shared: bool): - matrix = { - "compiler": compilers, - "target": ["Debug"], - "std": ["11", "20"], - "config": [""], - "shared": ["On" if shared else "Off"] - } - exclude = [] - run_matrix(matrix, exclude, build_and_test_full_or_auto) + MatrixRunner( + matrix = { + "compiler": compilers, + "target": ["Debug"], + "std": ["11", "20"], + "config": [""], + "shared": ["On" if shared else "Off"] + }, + exclude = [] + ).run(build_and_test_full_or_auto) def run_macos_matrix(compilers: list, shared: bool): - matrix = { - "compiler": compilers, - "target": ["Debug"], - "std": ["11", "20"], - "unwind": [ - "CPPTRACE_UNWIND_WITH_EXECINFO", - "CPPTRACE_UNWIND_WITH_UNWIND", - #"CPPTRACE_UNWIND_WITH_NOTHING", - ], - "symbols": [ - #"CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE", - "CPPTRACE_GET_SYMBOLS_WITH_LIBDL", - "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE", - "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF", - #"CPPTRACE_GET_SYMBOLS_WITH_NOTHING", - ], - "demangle": [ - "CPPTRACE_DEMANGLE_WITH_CXXABI", - #"CPPTRACE_DEMANGLE_WITH_NOTHING", - ], - "shared": ["On" if shared else "Off"] - } - exclude = [] - run_matrix(matrix, exclude, build_and_test) + MatrixRunner( + matrix = { + "compiler": compilers, + "target": ["Debug"], + "std": ["11", "20"], + "unwind": [ + "CPPTRACE_UNWIND_WITH_EXECINFO", + "CPPTRACE_UNWIND_WITH_UNWIND", + #"CPPTRACE_UNWIND_WITH_NOTHING", + ], + "symbols": [ + #"CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE", + "CPPTRACE_GET_SYMBOLS_WITH_LIBDL", + "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE", + "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF", + #"CPPTRACE_GET_SYMBOLS_WITH_NOTHING", + ], + "demangle": [ + "CPPTRACE_DEMANGLE_WITH_CXXABI", + #"CPPTRACE_DEMANGLE_WITH_NOTHING", + ], + "shared": ["On" if shared else "Off"] + }, + exclude = [] + ).run(build_and_test) def run_macos_default(compilers: list, shared: bool): - matrix = { - "compiler": compilers, - "target": ["Debug"], - "std": ["11", "20"], - "config": [""], - "shared": ["On" if shared else "Off"] - } - exclude = [] - run_matrix(matrix, exclude, build_and_test_full_or_auto) + MatrixRunner( + matrix = { + "compiler": compilers, + "target": ["Debug"], + "std": ["11", "20"], + "config": [""], + "shared": ["On" if shared else "Off"] + }, + exclude = [] + ).run(build_and_test_full_or_auto) def run_windows_matrix(compilers: list, shared: bool): - matrix = { - "compiler": compilers, - "target": ["Debug"], - "std": ["11", "20"], - "unwind": [ - "CPPTRACE_UNWIND_WITH_WINAPI", - "CPPTRACE_UNWIND_WITH_DBGHELP", - "CPPTRACE_UNWIND_WITH_UNWIND", # Broken on github actions for some reason - #"CPPTRACE_UNWIND_WITH_NOTHING", - ], - "symbols": [ - "CPPTRACE_GET_SYMBOLS_WITH_DBGHELP", - "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE", - "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF", - #"CPPTRACE_GET_SYMBOLS_WITH_NOTHING", - ], - "demangle": [ - "CPPTRACE_DEMANGLE_WITH_CXXABI", - "CPPTRACE_DEMANGLE_WITH_NOTHING", - ], - "shared": ["On" if shared else "Off"] - } - exclude = [ - { - "demangle": "CPPTRACE_DEMANGLE_WITH_CXXABI", - "compiler": "cl" + MatrixRunner( + matrix = { + "compiler": compilers, + "target": ["Debug"], + "std": ["11", "20"], + "unwind": [ + "CPPTRACE_UNWIND_WITH_WINAPI", + "CPPTRACE_UNWIND_WITH_DBGHELP", + "CPPTRACE_UNWIND_WITH_UNWIND", # Broken on github actions for some reason + #"CPPTRACE_UNWIND_WITH_NOTHING", + ], + "symbols": [ + "CPPTRACE_GET_SYMBOLS_WITH_DBGHELP", + "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE", + "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF", + #"CPPTRACE_GET_SYMBOLS_WITH_NOTHING", + ], + "demangle": [ + "CPPTRACE_DEMANGLE_WITH_CXXABI", + "CPPTRACE_DEMANGLE_WITH_NOTHING", + ], + "shared": ["On" if shared else "Off"] }, - { - "unwind": "CPPTRACE_UNWIND_WITH_UNWIND", - "compiler": "cl" - }, - { - "unwind": "CPPTRACE_UNWIND_WITH_UNWIND", - "compiler": "clang++" - }, - { - "symbols": "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE", - "compiler": "cl" - }, - { - "symbols": "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE", - "compiler": "clang++" - }, - { - "symbols": "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF", - "compiler": "cl" - }, - { - "symbols": "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF", - "compiler": "clang++" - }, - { - "symbols": "CPPTRACE_GET_SYMBOLS_WITH_DBGHELP", - "compiler": "g++" - }, - { - "symbols": "CPPTRACE_GET_SYMBOLS_WITH_DBGHELP", - "demangle": "CPPTRACE_DEMANGLE_WITH_CXXABI" - }, - { - "symbols": "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF", - "demangle": "CPPTRACE_DEMANGLE_WITH_NOTHING" - }, - { - "symbols": "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE", - "demangle": "CPPTRACE_DEMANGLE_WITH_NOTHING" - } - ] - run_matrix(matrix, exclude, build_and_test) + exclude = [ + { + "demangle": "CPPTRACE_DEMANGLE_WITH_CXXABI", + "compiler": "cl" + }, + { + "unwind": "CPPTRACE_UNWIND_WITH_UNWIND", + "compiler": "cl" + }, + { + "unwind": "CPPTRACE_UNWIND_WITH_UNWIND", + "compiler": "clang++" + }, + { + "symbols": "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE", + "compiler": "cl" + }, + { + "symbols": "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE", + "compiler": "clang++" + }, + { + "symbols": "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF", + "compiler": "cl" + }, + { + "symbols": "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF", + "compiler": "clang++" + }, + { + "symbols": "CPPTRACE_GET_SYMBOLS_WITH_DBGHELP", + "compiler": "g++" + }, + { + "symbols": "CPPTRACE_GET_SYMBOLS_WITH_DBGHELP", + "demangle": "CPPTRACE_DEMANGLE_WITH_CXXABI" + }, + { + "symbols": "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF", + "demangle": "CPPTRACE_DEMANGLE_WITH_NOTHING" + }, + { + "symbols": "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE", + "demangle": "CPPTRACE_DEMANGLE_WITH_NOTHING" + } + ] + ).run(build_and_test) def run_windows_default(compilers: list, shared: bool): - matrix = { - "compiler": compilers, - "target": ["Debug"], - "std": ["11", "20"], - "config": [""], - "shared": ["On" if shared else "Off"] - } - exclude = [] - run_matrix(matrix, exclude, build_and_test_full_or_auto) + MatrixRunner( + matrix = { + "compiler": compilers, + "target": ["Debug"], + "std": ["11", "20"], + "config": [""], + "shared": ["On" if shared else "Off"] + }, + exclude = [] + ).run(build_and_test_full_or_auto) def main(): parser = argparse.ArgumentParser( @@ -558,9 +527,4 @@ def main(): else: run_windows_matrix(compilers, args.shared) - global failed - if failed: - print("🔴 Some checks failed") - sys.exit(1) - main() diff --git a/ci/unittest.py b/ci/unittest.py new file mode 100644 index 0000000..2549ee0 --- /dev/null +++ b/ci/unittest.py @@ -0,0 +1,91 @@ +import argparse +import os +import platform +import shutil +import subprocess +import sys +from typing import Tuple +from colorama import Fore, Back, Style + +from util import * + +sys.stdout.reconfigure(encoding='utf-8') # for windows gh runner + +def get_c_compiler_counterpart(compiler: str) -> str: + return compiler.replace("clang++", "clang").replace("g++", "gcc") + +def build(runner: MatrixRunner): + if platform.system() != "Windows": + matrix = runner.current_config() + args = [ + "cmake", + "..", + "-GNinja", + f"-DCMAKE_CXX_COMPILER={matrix['compiler']}", + f"-DCMAKE_C_COMPILER={get_c_compiler_counterpart(matrix['compiler'])}", + f"-DCMAKE_BUILD_TYPE={matrix['build_type']}", + f"-DBUILD_SHARED_LIBS={matrix['shared']}", + f"-DHAS_DL_FIND_OBJECT={matrix['has_dl_find_object']}", + "-DCPPTRACE_WERROR_BUILD=On", + "-DCPPTRACE_BUILD_TESTING=On", + f"-DCPPTRACE_SANITIZER_BUILD={matrix['sanitizers']}", + f"-DCPPTRACE_BUILD_TESTING_SPLIT_DWARF={matrix['split_dwarf']}", + f"-DCPPTRACE_BUILD_TESTING_SPLIT_DWARF={matrix['dwarf_version']}", + f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On", + f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On", + f"-DCPPTRACE_USE_EXTERNAL_GTEST=On", + ] + return runner.run_command(*args) and runner.run_command("ninja") + else: + raise ValueError() + +def test(runner: MatrixRunner): + if platform.system() != "Windows": + return runner.run_command("./unittest") and runner.run_command("bash", "-c", "exec -a u ./unittest") + else: + raise ValueError() + +def build_and_test(runner: MatrixRunner): + # the build directory has to be purged on compiler or shared change + last = runner.last_config() + current = runner.current_config() + if last is None or last["compiler"] != current["compiler"] or last["shared"] != current["shared"]: + if os.path.exists("build"): + shutil.rmtree("build", ignore_errors=True) + + if not os.path.exists("build"): + os.mkdir("build") + os.chdir("build") + + good = False + if build(runner): + good = test(runner) + + os.chdir("..") + print(flush=True) + + return good + +def run_linux_matrix(): + MatrixRunner( + matrix = { + "compiler": ["g++-10", "clang++-14"], + "shared": ["OFF", "ON"], + "build_type": ["Debug", "RelWithDebInfo"], + "sanitizers": ["OFF", "ON"], + "has_dl_find_object": ["OFF", "ON"], + "split_dwarf": ["OFF", "ON"], + "dwarf_version": ["4", "5"], + }, + exclude = [] + ).run(build_and_test) + +def main(): + if platform.system() == "Linux": + run_linux_matrix() + if platform.system() == "Darwin": + raise ValueError() # run_macos_matrix() + if platform.system() == "Windows": + raise ValueError() # run_windows_matrix() + +main() diff --git a/ci/util.py b/ci/util.py index 4933e6e..4e0662b 100644 --- a/ci/util.py +++ b/ci/util.py @@ -4,6 +4,7 @@ import itertools from typing import List from colorama import Fore, Back, Style import re +import time # https://stackoverflow.com/a/14693789/15675011 ansi_escape = re.compile(r''' @@ -18,95 +19,121 @@ ansi_escape = re.compile(r''' ) ''', re.VERBOSE) -def adj_width(text): - return len(text) - len(ansi_escape.sub("", text)) +class MatrixRunner: + def __init__(self, matrix, exclude): + self.matrix = matrix + self.exclude = exclude + self.keys = [*matrix.keys()] + self.values = [*matrix.values()] + self.results = {} # insertion-ordered + self.failed = False + self.work = self.get_work() -def do_exclude(matrix_config, exclude): - return all(map(lambda k: matrix_config[k] == exclude[k], exclude.keys())) + self.last_matrix_config = None + self.current_matrix_config = None -def print_table(table): - columns = len(table[0]) - column_widths = [1 for _ in range(columns)] - for row in table: - for i, cell in enumerate(row): - column_widths[i] = max(column_widths[i], len(ansi_escape.sub("", cell))) - for j, cell in enumerate(table[0]): - print("| {cell:{width}} ".format(cell=cell, width=column_widths[j] + adj_width(cell)), end="") - print("|") - for i, row in enumerate(table[1:]): - for j, cell in enumerate(row): - print("| {cell:{width}} ".format(cell=cell, width=column_widths[j] + adj_width(cell)), end="") - print("|") - -def run_matrix(matrix, exclude, fn): - keys = [*matrix.keys()] - values = [*matrix.values()] - #print("Values:", values) - results = {} # insertion-ordered - for config in itertools.product(*matrix.values()): - #print(config) - matrix_config = {} - for k, v in zip(matrix.keys(), config): - matrix_config[k] = v - #print(matrix_config) - if any(map(lambda ex: do_exclude(matrix_config, ex), exclude)): - continue + def run_command(self, *args: List[str], always_output=False, output_matcher=None) -> bool: + self.log(f"{Fore.CYAN}{Style.BRIGHT}Running Command \"{' '.join(args)}\"{Style.RESET_ALL}") + start_time = time.time() + p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + runtime = time.time() - start_time + self.log(Style.RESET_ALL, end="") # makefile in parallel sometimes messes up colors + if p.returncode != 0: + self.log(f"{Fore.RED}{Style.BRIGHT}Command failed{Style.RESET_ALL} {Fore.MAGENTA}(time: {runtime:.2f}s){Style.RESET_ALL}") + self.log("stdout:") + self.log(stdout.decode("utf-8"), end="") + self.log("stderr:") + self.log(stderr.decode("utf-8"), end="") + self.failed = True + return False else: - config_tuple = tuple(values[i].index(p) for i, p in enumerate(config)) - results[config_tuple] = fn(matrix_config) - # Fudged data for testing - #print(config_tuple) - #if "symbols" not in matrix_config: - # results[config_tuple] = matrix_config["compiler"] != "g++-10" - #else: - # results[config_tuple] = not (matrix_config["compiler"] == "clang++-14" and matrix_config["symbols"] == "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE") - # I had an idea for printing 2d slices of the n-dimensional matrix, but it didn't pan out as much as I'd hoped - dimensions = len(values) - # # Output diagnostic tables - # print("Results:", results) - # if dimensions >= 2: - # for iteraxes in itertools.combinations(range(dimensions), dimensions - 2): - # # iteraxes are the axes we iterate over to slice, these fixed axes are the axes of the table - # # just the complement of axes, these are the two fixed axes - # fixed = [x for x in range(dimensions) if x not in iteraxes] - # assert(len(fixed) == 2) - # if any([len(values[i]) == 1 for i in fixed]): - # continue - # print("Fixed:", fixed) - # for iteraxesvalues in itertools.product( - # *[range(len(values[i])) if i in iteraxes else [-1] for i in range(dimensions)] - # ): - # print(">>", iteraxesvalues) - # # Now that we have our iteraxes values we have a unique plane - # table = [ - # ["", *[value for value in values[fixed[0]]]] - # ] - # #print(values[fixed[1]]) - # for row_i, row_value in enumerate(values[fixed[1]]): - # row = [row_value] - # for col_i in range(len(values[fixed[0]])): - # iteraxesvaluescopy = [x for x in iteraxesvalues] - # iteraxesvaluescopy[fixed[1]] = row_i - # iteraxesvaluescopy[fixed[0]] = col_i - # #print("----->", iteraxesvaluescopy) - # row.append( - # f"{Fore.GREEN}{Style.BRIGHT}Good{Style.RESET_ALL}" - # if results[tuple(iteraxesvaluescopy)] - # else f"{Fore.RED}{Style.BRIGHT}Bad{Style.RESET_ALL}" - # if tuple(iteraxesvaluescopy) in results else "" - # ) - # table.append(row) - # print_table(table) + self.log(f"{Fore.GREEN}{Style.BRIGHT}Command succeeded{Style.RESET_ALL} {Fore.MAGENTA}(time: {runtime:.2f}s){Style.RESET_ALL}") + if always_output: + self.log("stdout:") + self.log(stdout.decode("utf-8"), end="") + self.log("stderr:") + self.log(stderr.decode("utf-8"), end="") + elif len(stderr) != 0: + self.log("stderr:") + self.log(stderr.decode("utf-8"), end="") + if output_matcher is not None: + if not output_matcher(stdout.decode("utf-8")): + self.failed = True + return False + return True - # Better idea would be looking for m