diff --git a/.github/scripts/install_deps.sh b/.github/scripts/install_deps.sh index fa5e74b11..e864e45fa 100755 --- a/.github/scripts/install_deps.sh +++ b/.github/scripts/install_deps.sh @@ -2,10 +2,32 @@ # This script requires `sh` instead of `bash` because the latter is not always installed on FreeBSD. set -eu -case "${1%%-*}" in +case $# in + 1) OS="$1"; TOOLSET= ;; + 2) OS="$1"; TOOLSET="$2";; + *) echo >&2 "Usage: $0 [toolset]" && exit 1;; +esac + +case "${OS%%-*}" in ubuntu|debian) - sudo apt-get -qq update - sudo apt-get install -yq bison libpng-dev pkgconf + pkgs=bison + case "$TOOLSET" in + mingw32) + pkgs="$pkgs libz-mingw-w64-dev g++-mingw-w64-i686-win32" + TOOLSET= + ;; + mingw64) + pkgs="$pkgs libz-mingw-w64-dev g++-mingw-w64-x86-64-win32" + TOOLSET= + ;; + '' | lcov) + pkgs="$pkgs libpng-dev pkgconf $TOOLSET" + TOOLSET= + ;; + esac + sudo apt-get update -qq + # shellcheck disable=SC2086 # (This word splitting is intentional.) + sudo apt-get install -yq $pkgs ;; macos) # macOS bundles GNU Make 3.81, which doesn't support synced output. @@ -32,6 +54,11 @@ case "${1%%-*}" in ;; esac +if [ -n "$TOOLSET" ]; then + printf >&2 'Unknown toolset `%s` for OS `%s`\n' "$TOOLSET" "$OS" + exit 1 +fi + # Print some system info, for easier debugging. # https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands#grouping-log-lines diff --git a/.github/scripts/mingw-w64-libpng-dev.sh b/.github/scripts/mingw-w64-libpng-dev.sh deleted file mode 100755 index 77842a75e..000000000 --- a/.github/scripts/mingw-w64-libpng-dev.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash -set -euo pipefail - -pngver=1.6.56 -arch="$1" - -## Grab sources and check them - -wget http://downloads.sourceforge.net/project/libpng/libpng16/$pngver/libpng-$pngver.tar.xz -echo f7d8bf1601b7804f583a254ab343a6549ca6cf27d255c302c47af2d9d36a6f18 \*libpng-$pngver.tar.xz | \ - sha256sum -c - - -## Extract sources and patch them - -tar -xf libpng-$pngver.tar.xz - -## Start building! - -mkdir -p build -cd build -../libpng-$pngver/configure \ - --host="$arch" --target="$arch" \ - --prefix="/usr/$arch" \ - --enable-shared --disable-static \ - CPPFLAGS="-D_FORTIFY_SOURCE=2" \ - CFLAGS="-O2 -pipe -fno-plt -fno-exceptions --param=ssp-buffer-size=4" \ - LDFLAGS="-Wl,-O1,--sort-common,--as-needed -fstack-protector" -make -kj -sudo make install diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index ed08bf691..8451017ba 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -11,16 +11,13 @@ env: jobs: coverage: - runs-on: ubuntu-24.04 + runs-on: ubuntu-latest steps: - name: Checkout repo uses: actions/checkout@v6 - name: Install deps run: | - ./.github/scripts/install_deps.sh ubuntu - - name: Install LCOV - run: | - sudo apt-get install lcov + .github/scripts/install_deps.sh ubuntu-latest lcov - name: Install test dependency dependencies run: | test/fetch-test-deps.sh --get-deps ubuntu diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 3712812ec..fa1db43e8 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -49,7 +49,7 @@ jobs: uses: actions/checkout@v6 - name: Install deps run: | - ./.github/scripts/install_deps.sh ${{ matrix.os }} + .github/scripts/install_deps.sh ${{ matrix.os }} - name: Build & install using Make if: matrix.buildsys == 'make' run: | @@ -113,7 +113,7 @@ jobs: uses: actions/checkout@v6 - name: Install deps run: | - ./.github/scripts/install_deps.sh macos + .github/scripts/install_deps.sh macos - name: Cache library deps uses: actions/cache@v5 with: @@ -222,45 +222,29 @@ jobs: strategy: matrix: bits: [32, 64] - include: - - bits: 32 - arch: i686 - triplet: i686-w64-mingw32 - - bits: 64 - arch: x86-64 - triplet: x86_64-w64-mingw32 fail-fast: false - runs-on: ubuntu-22.04 - env: - DIST_DIR: win${{ matrix.bits }} + runs-on: ubuntu-latest steps: - name: Checkout repo uses: actions/checkout@v6 - name: Install deps run: | - ./.github/scripts/install_deps.sh ubuntu - - name: Install MinGW - run: | # dpkg-dev is apparently required for pkg-config for cross-building - sudo apt-get install g++-mingw-w64-${{ matrix.arch }}-win32 mingw-w64-tools libz-mingw-w64-dev dpkg-dev - - name: Install libpng dev headers for MinGW - run: | - ./.github/scripts/mingw-w64-libpng-dev.sh ${{ matrix.triplet }} + .github/scripts/install_deps.sh ubuntu mingw${{ matrix.bits }} - name: Cross-build Windows binaries - run: | - make mingw${{ matrix.bits }} -kj Q= + run: | # MinGW does not support `--preset develop` sanitizers ASan or UBSan. + dll_search_dir=$(printf '%s\n' /usr/lib/gcc/*-w64-mingw32/*-win32 | tee -a /dev/stderr) + cmake -B build --preset develop -DSANITIZERS=OFF --toolchain cmake/toolchain-mingw${{ matrix.bits }}.cmake \ + -DFETCHCONTENT_BASE_DIR="${{ env.DEPS_ROOT_DIR }}" -DDLL_SEARCH_DIRS="$dll_search_dir" + cmake --build build - name: Package binaries - run: | # DLL dependencies can be figured out using e.g. Dependency Walker or objdump -p - mkdir bins - mv -v rgb{asm,link,fix,gfx}.exe bins/ - cp -v /usr/${{ matrix.triplet }}/lib/zlib1.dll bins - cp -v /usr/${{ matrix.triplet }}/bin/libpng16-16.dll bins - cp -v /usr/lib/gcc/${{ matrix.triplet }}/10-win32/lib{ssp-0,stdc++-6}.dll bins - [ "${{ matrix.bits }}" -ne 32 ] || cp -v /usr/lib/gcc/${{ matrix.triplet }}/10-win32/libgcc_s_dw2-1.dll bins + run: | + cmake --install build --prefix . --verbose --component binaries + cmake --install build --prefix . --verbose --component shared-libs - name: Upload Windows binaries uses: actions/upload-artifact@v7 with: name: rgbds-canary-mingw-win${{ matrix.bits }} - path: bins + path: bin if-no-files-found: error - name: Upload Windows test binaries uses: actions/upload-artifact@v7 diff --git a/.gitignore b/.gitignore index e78169b0c..be15d21b7 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,6 @@ CMakeCache.txt CMakeFiles/ cmake_install.cmake CMakeUserPresets.json -build/ +build*/ *.dSYM/ callgrind.out.* diff --git a/CMakeLists.txt b/CMakeLists.txt index 320a76c51..c46928da9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,31 +33,18 @@ endif() include(CTest) # Note: CTest only functions properly if included from the top-level CMakeLists. include(GNUInstallDirs) +include(CMakeDependentOption) -## Compiler switches. +## Compiler switches that should apply to our deps too. -include(CMakeDependentOption) option(SANITIZERS "Build with sanitizers enabled" OFF) cmake_dependent_option(MORE_WARNINGS "Turn on more warnings" OFF "NOT MSVC" OFF) -if(MSVC) - add_compile_options( - /wd5030 # Warning C5030 is about unknown attributes (`[[gnu::ATTR]]`), none of ours being load-bearing. - /wd4996 # Warning C4996 is about using POSIX names, which we want to do for portability. - /Zc:preprocessor # Opt into the C++20-conformant preprocessor. - ) - add_definitions(/D_CRT_SECURE_NO_WARNINGS) - - if(SANITIZERS) +if(SANITIZERS) + if(MSVC) message(STATUS "ASan enabled") add_compile_options(/fsanitize=address) # Note that this shouldn't be passed to the linker. - endif() -else() - add_compile_options(-Wall -pedantic -fno-exceptions $<$:-fno-rtti> -Wno-unknown-warning-option - # C++20 allows macros to take zero variadic arguments. - # Some versions of Clang don't recognize this, and treat them as a GNU extension. - -Wno-gnu-zero-variadic-macro-arguments) - if(SANITIZERS) + else() # We assume a GNU-like compiler. message(STATUS "ASan and UBSan enabled") set(SAN_FLAGS -fsanitize=address -fsanitize=undefined -fsanitize=float-divide-by-zero) @@ -69,18 +56,6 @@ else() # be able to override these easily so we put them first. string(PREPEND CMAKE_CXX_FLAGS_DEBUG "-Og -fno-omit-frame-pointer -fno-optimize-sibling-calls ") endif() - - if(MORE_WARNINGS) - add_compile_options(-Werror -Wextra - -Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond - -Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2 - -Wstringop-overflow=4 -Wtrampolines -Wundef -Wuninitialized -Wunused -Wshadow - -Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 - -Wno-format-nonliteral -Wno-strict-overflow - -Wno-unused-but-set-variable # bison's `yynerrs_` is incremented but unused - -Wno-type-limits -Wno-tautological-constant-out-of-range-compare - -Wvla) # MSVC does not support VLAs - endif() endif() message(CHECK_START "Checking if LTO is supported") @@ -164,6 +139,40 @@ include_directories("include") set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED True) +if(MSVC) + add_compile_options( + /wd5030 # Warning C5030 is about unknown attributes (`[[gnu::ATTR]]`), none of ours being load-bearing. + /wd4996 # Warning C4996 is about using POSIX names, which we want to do for portability. + /Zc:preprocessor # Opt into the C++20-conformant preprocessor. + ) + add_definitions(/D_CRT_SECURE_NO_WARNINGS) +else() + add_compile_options(-Wall -pedantic -fno-exceptions -fno-rtti -Wno-unknown-warning-option + # C++20 allows macros to take zero variadic arguments. + # Some versions of Clang don't recognize this, and treat them as a GNU extension. + -Wno-gnu-zero-variadic-macro-arguments) + + if(MORE_WARNINGS) + add_compile_options(-Wextra + -Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond + -Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2 + -Wstringop-overflow=4 -Wtrampolines -Wundef -Wuninitialized -Wunused -Wshadow + -Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 + -Wno-format-nonliteral -Wno-strict-overflow + -Wno-unused-but-set-variable # bison's `yynerrs_` is incremented but unused + -Wno-type-limits -Wno-tautological-constant-out-of-range-compare + -Wvla) # MSVC does not support VLAs + endif() + + if(MINGW) + # MinGW warns about format specifiers like `%z` and `%j`, which are missing on Windows XP and earlier. + # We rely on runtimes more modern than that, so we can ignore those warnings. + # If this is a problem for you: use Microsoft's equivalents, or define `__USE_MINGW_ANSI_STDIO=1`. + # https://sourceforge.net/p/mingw-w64/wiki2/gnu%20printf/ + add_compile_options(-Wno-error=format -Wno-error=format-extra-args) + endif() +endif() + add_subdirectory(src) if(BUILD_TESTING) # This option is defined implicitly by `include(CTest)`. add_subdirectory(test) @@ -202,4 +211,5 @@ set(CPACK_STRIP_FILES ON) # Only applies to binary packages, not sources. set(CPACK_VERBATIM_VARIABLES ON) set(CPACK_THREADS 0) # Use all available CPU cores. set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY OFF) + include(CPack) diff --git a/CMakePresets.json b/CMakePresets.json index 255c4db74..74b4d18fb 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -7,6 +7,8 @@ "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", "CMAKE_EXPORT_COMPILE_COMMANDS": true, + "CMAKE_COMPILE_WARNING_AS_ERROR": true, + "CMAKE_LINK_WARNING_AS_ERROR": true, "MORE_WARNINGS": true, "SANITIZERS": true }, diff --git a/Makefile b/Makefile index 3908ee032..b8235290d 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ .SUFFIXES: .SUFFIXES: .cpp .y .o -.PHONY: all clean install checkdiff develop debug profile coverage format tidy iwyu mingw32 mingw64 wine-shim dist +.PHONY: all clean install checkdiff develop debug profile coverage format tidy iwyu wine-shim dist # User-defined variables @@ -256,22 +256,6 @@ iwyu: CXX="include-what-you-use" \ REALCXXFLAGS="-std=c++20 -I include" -# Targets for the project maintainer to easily create Windows exes. -# This is not for Windows users! -# If you're building on Windows with Cygwin or MinGW, just follow the Unix -# install instructions instead. - -mingw32: - $Q${MAKE} all test/gfx/randtilegen test/gfx/rgbgfx_test \ - CXX=i686-w64-mingw32-g++ \ - CXXFLAGS="-O3 -flto -DNDEBUG -static-libgcc -static-libstdc++" \ - PKG_CONFIG="PKG_CONFIG_SYSROOT_DIR=/usr/i686-w64-mingw32 pkg-config" - -mingw64: - $Q${MAKE} all test/gfx/randtilegen test/gfx/rgbgfx_test \ - CXX=x86_64-w64-mingw32-g++ \ - PKG_CONFIG="PKG_CONFIG_SYSROOT_DIR=/usr/x86_64-w64-mingw32 pkg-config" - wine-shim: $Qecho '#!/usr/bin/env bash' > rgbshim.sh $Qecho 'WINEDEBUG=-all wine $$0.exe "$${@:1}"' >> rgbshim.sh diff --git a/cmake/toolchain-mingw32.cmake b/cmake/toolchain-mingw32.cmake new file mode 100644 index 000000000..13f67e58e --- /dev/null +++ b/cmake/toolchain-mingw32.cmake @@ -0,0 +1,22 @@ +# From https://www.mingw-w64.org/build-systems/cmake/ + +set(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_SYSTEM_PROCESSOR i686) + +# Specify the cross-compiler. +set(CMAKE_C_COMPILER i686-w64-mingw32-gcc) +set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++) +set(CMAKE_RC_COMPILER i686-w64-mingw32-windres) + +# Specify the target environment. +set(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32) + +# Search for programs in the build host directories. +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +# Search for libraries and headers in the target directories. +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + +# CMake determines how to examine dependencies based on the *host* system, leading to +# a "file unknown error" unless the target platform is explicitly specified. +set(CMAKE_GET_RUNTIME_DEPENDENCIES_PLATFORM "windows+pe") diff --git a/cmake/toolchain-mingw64.cmake b/cmake/toolchain-mingw64.cmake new file mode 100644 index 000000000..76d8a70d1 --- /dev/null +++ b/cmake/toolchain-mingw64.cmake @@ -0,0 +1,22 @@ +# From https://www.mingw-w64.org/build-systems/cmake/ + +set(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_SYSTEM_PROCESSOR x86_64) + +# Specify the cross-compiler. +set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) +set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++) +set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres) + +# Specify the target environment. +set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32) + +# Search for programs in the build host directories. +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +# Search for libraries and headers in the target directories. +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + +# CMake determines how to examine dependencies based on the *host* system, leading to +# a "file unknown error" unless the target platform is explicitly specified. +set(CMAKE_GET_RUNTIME_DEPENDENCIES_PLATFORM "windows+pe") diff --git a/src/asm/lexer.cpp b/src/asm/lexer.cpp index 6739e90ab..8801093dc 100644 --- a/src/asm/lexer.cpp +++ b/src/asm/lexer.cpp @@ -358,7 +358,7 @@ void LexerState::setFileAsNextState(std::string const &filePath, bool updateStat fatal("Failed to read file \"%s\": %s", path.c_str(), strerror(errno)); // LCOV_EXCL_STOP } - content.emplace(ptr, size); + content.emplace(ptr, static_cast(size)); // LCOV_EXCL_START verbosePrint(VERB_INFO, "File \"%s\" is fully read\n", path.c_str()); diff --git a/src/asm/section.cpp b/src/asm/section.cpp index ffc296094..24dd943d1 100644 --- a/src/asm/section.cpp +++ b/src/asm/section.cpp @@ -934,8 +934,14 @@ bool sect_BinaryFile(std::string const &name, uint32_t startPos) { Defer closeFile{[&] { fclose(file); }}; if (fseek(file, 0, SEEK_END) == 0) { - if (startPos > ftell(file)) { - error("Specified start position is greater than length of file \"%s\"", name.c_str()); + if (unsigned long fsize = ftell(file); + startPos > fsize) { // `ftell` cannot fail here, since `fseek` succeeded. + error( + "Specified start position (%" PRIu32 ") is greater than length of \"%s\" (%lu)", + startPos, + name.c_str(), + fsize + ); return false; } // The file is seekable; skip to the specified start position @@ -989,8 +995,14 @@ bool sect_BinaryFileSlice(std::string const &name, uint32_t startPos, uint32_t l Defer closeFile{[&] { fclose(file); }}; if (fseek(file, 0, SEEK_END) == 0) { - if (long fsize = ftell(file); startPos > fsize) { - error("Specified start position is greater than length of file \"%s\"", name.c_str()); + if (unsigned long fsize = ftell(file); + startPos > fsize) { // `ftell` cannot fail here, since `fseek` succeeded. + error( + "Specified start position (%" PRIu32 ") is greater than length of \"%s\" (%lu)", + startPos, + name.c_str(), + fsize + ); return false; } else if (startPos + length > fsize) { error( diff --git a/src/link/sdas_obj.cpp b/src/link/sdas_obj.cpp index 35dd5b25e..134ff7c38 100644 --- a/src/link/sdas_obj.cpp +++ b/src/link/sdas_obj.cpp @@ -661,8 +661,8 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector &f patch.rpnExpression[0] = RPN_BANK_SYM; patch.rpnExpression[1] = idx; patch.rpnExpression[2] = idx >> 8; - patch.rpnExpression[3] = idx >> 16; - patch.rpnExpression[4] = idx >> 24; + patch.rpnExpression[3] = 0; + patch.rpnExpression[4] = 0; } else if (sym.name.starts_with("l_")) { patch.rpnExpression.resize(1 + sym.name.length() - 2 + 1); patch.rpnExpression[0] = RPN_SIZEOF_SECT; @@ -684,8 +684,8 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector &f patch.rpnExpression[0] = RPN_SYM; patch.rpnExpression[1] = idx; patch.rpnExpression[2] = idx >> 8; - patch.rpnExpression[3] = idx >> 16; - patch.rpnExpression[4] = idx >> 24; + patch.rpnExpression[3] = 0; + patch.rpnExpression[4] = 0; } } else { if (idx >= fileSections.size()) { diff --git a/test/asm/incbin-start-bad.err b/test/asm/incbin-start-bad.err index f5e8c5997..b32c937dd 100644 --- a/test/asm/incbin-start-bad.err +++ b/test/asm/incbin-start-bad.err @@ -1,5 +1,5 @@ -error: Specified start position is greater than length of file "data.bin" +error: Specified start position (999) is greater than length of "data.bin" (123) at incbin-start-bad.asm(3) -error: Specified start position is greater than length of file "data.bin" +error: Specified start position (999) is greater than length of "data.bin" (123) at incbin-start-bad.asm(4) Assembly aborted with 2 errors diff --git a/test/gfx/rgbgfx_test.cpp b/test/gfx/rgbgfx_test.cpp index f1f1a1779..8691d80e3 100644 --- a/test/gfx/rgbgfx_test.cpp +++ b/test/gfx/rgbgfx_test.cpp @@ -320,7 +320,7 @@ static char *execProg(char const *name, char * const *argv) { nullptr, errnum, 0, - (LPTSTR)&buf, + reinterpret_cast(&buf), 0, nullptr ) @@ -341,7 +341,8 @@ static char *execProg(char const *name, char * const *argv) { STARTUPINFOA startupInfo; GetStartupInfoA(&startupInfo); - STARTUPINFOA childStartupInfo = {sizeof(startupInfo)}; + STARTUPINFOA childStartupInfo = {}; + childStartupInfo.cb = sizeof(startupInfo); PROCESS_INFORMATION child; if (CreateProcessA(