Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
cmake: add a verify_sources target to check if source files are decla…
…red as sources
  • Loading branch information
rasky committed Oct 29, 2025
commit c5b565ce7d32fb8a25c2dbb308da8886e4092e7a
10 changes: 10 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ jobs:
run: |
brew uninstall --ignore-dependencies cmake
brew install ${{ matrix.platform.install }}
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: "Build: Windows"
if: runner.os != 'macOS' && runner.os != 'Linux'
run: .github/scripts/build_windows.sh
Expand Down Expand Up @@ -107,6 +111,12 @@ jobs:
TARGET_PRESET: ${{ matrix.platform.target-cmake-preset }}
MACOS_CERTIFICATE_NAME: ${{ secrets.MACOS_CERTIFICATE_NAME }}
MACOS_NOTARIZATION_TEAMID: ${{ secrets.MACOS_NOTARIZATION_TEAMID }}
- name: Verify declared sources (Linux)
if: runner.os == 'Linux'
run: cmake --build build --target verify-sources
- name: Verify declared sources (macOS/Windows)
if: runner.os != 'Linux'
run: cmake --build build --config RelWithDebInfo --target verify-sources
- name: "Compress Build Artifact (macOS)"
if: runner.os == 'macOS'
run: |
Expand Down
8 changes: 8 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ endif()

add_subdirectory(tools/sourcery)

include(cmake/common/verify_sources.cmake)
ares_register_verify(nall ${CMAKE_SOURCE_DIR}/nall)
ares_register_verify(ares ${CMAKE_SOURCE_DIR}/ares)
ares_register_verify(ruby ${CMAKE_SOURCE_DIR}/ruby)
ares_register_verify(hiro ${CMAKE_SOURCE_DIR}/hiro)
ares_register_verify(mia ${CMAKE_SOURCE_DIR}/mia)
ares_define_verify_aggregate()

message_configuration()

add_subdirectory(.github)
Expand Down
68 changes: 68 additions & 0 deletions cmake/common/verify_sources.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
function(ares_register_verify target target_root)
if(NOT TARGET ${target})
return()
endif()

get_target_property(_srcs ${target} SOURCES)
if(NOT _srcs)
set(_srcs)
endif()

# Resolve relative source paths against the target's own SOURCE_DIR
get_target_property(_target_srcdir ${target} SOURCE_DIR)
if(NOT _target_srcdir)
set(_target_srcdir ${CMAKE_CURRENT_SOURCE_DIR})
endif()

file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/verify")
set(_decl "${CMAKE_BINARY_DIR}/verify/${target}-declared.txt")
file(WRITE "${_decl}" "")
foreach(_s IN LISTS _srcs)
if(NOT IS_ABSOLUTE "${_s}")
get_filename_component(_abs "${_s}" REALPATH BASE_DIR "${_target_srcdir}")
else()
set(_abs "${_s}")
endif()
file(APPEND "${_decl}" "${_abs}\n")
endforeach()

find_package(Python3 COMPONENTS Interpreter QUIET)
if(Python3_Interpreter_FOUND)
set(_verify_args "${CMAKE_SOURCE_DIR}/scripts/verify_declared_sources.py"
--build-dir "${CMAKE_BINARY_DIR}"
--target "${target}"
--target-root "${target_root}"
--declared "${_decl}"
--exclude "${CMAKE_SOURCE_DIR}/thirdparty"
--exclude "${CMAKE_SOURCE_DIR}/libco"
--exclude "${CMAKE_SOURCE_DIR}/tools"
--exclude "${CMAKE_SOURCE_DIR}/ares/n64/vulkan/parallel-rdp"
--exclude "${CMAKE_BINARY_DIR}")
if(CMAKE_GENERATOR STREQUAL "Ninja")
list(APPEND _verify_args --ninja "${CMAKE_MAKE_PROGRAM}")
endif()
add_custom_target(verify-${target}-run
COMMAND ${Python3_EXECUTABLE} ${_verify_args}
VERBATIM)
add_dependencies(verify-${target}-run ${target})
else()
add_custom_target(verify-${target}-run
COMMAND ${CMAKE_COMMAND} -E echo "verify-sources: Python3 not found; skipping ${target}."
VERBATIM)
endif()

set_property(GLOBAL APPEND PROPERTY ARES_VERIFY_TARGETS "verify-${target}-run")
endfunction()

function(ares_define_verify_aggregate)
get_property(_tgts GLOBAL PROPERTY ARES_VERIFY_TARGETS)
if(NOT _tgts)
add_custom_target(verify-sources COMMAND ${CMAKE_COMMAND} -E echo "No verify targets.")
return()
endif()

add_custom_target(verify-sources)
add_dependencies(verify-sources ${_tgts})
endfunction()


133 changes: 133 additions & 0 deletions scripts/verify_declared_sources.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#!/usr/bin/env python3
import argparse, glob, os, re, sys, subprocess
from pathlib import Path


def parse_depfile(p):
s = Path(p).read_text(errors='ignore').replace('\\\n', ' ')
i = s.find(':')
if i < 0:
return []
rhs = s[i + 1 :]
out, cur, esc = [], '', False
for ch in rhs:
if esc:
cur += ch
esc = False
continue
if ch == '\\':
esc = True
continue
if ch in ' \n\r\t':
if cur:
out.append(cur)
cur = ''
else:
cur += ch
if cur:
out.append(cur)
return [str(Path(x).resolve()) for x in out]


def parse_dependinfo(p):
txt = Path(p).read_text(errors='ignore')
deps = []
for m in re.finditer(r'set\(CMAKE_DEPENDS_DEPENDENCY_FILES\s*"([^"]*)"\)', txt):
deps.extend(m.group(1).split(';'))
for m in re.finditer(r'set\([A-Z_]+_DEPENDS_DEPENDENCY_FILES\s*"([^"]*)"\)', txt):
deps.extend(m.group(1).split(';'))
return [str(Path(x).resolve()) for x in deps if x]


def main():
ap = argparse.ArgumentParser()
ap.add_argument('--build-dir', required=True)
ap.add_argument('--target', required=True)
ap.add_argument('--target-root', required=True)
ap.add_argument('--declared', required=True)
ap.add_argument('--exclude', action='append', default=[])
ap.add_argument('--ninja', default=None, help='Path to ninja executable (optional)')
args = ap.parse_args()

root = str(Path(args.target_root).resolve()) + os.sep
excludes = [str(Path(x).resolve()) + os.sep for x in args.exclude]
declared = set(
x.strip() for x in Path(args.declared).read_text().splitlines() if x.strip()
)

used = set()
for d in glob.glob(
os.path.join(args.build_dir, f'**/CMakeFiles/**/*.d'), recursive=True
):
for dep in parse_depfile(d):
used.add(dep)
for d in glob.glob(
os.path.join(args.build_dir, f'**/CMakeFiles/**/DependInfo.cmake'),
recursive=True,
):
for dep in parse_dependinfo(d):
used.add(dep)

if args.ninja:
try:
proc = subprocess.run(
[args.ninja, '-C', args.build_dir, '-t', 'deps'],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
check=False,
)
lines = proc.stdout.splitlines()
n = len(lines)
i = 0
while i < n:
line = lines[i]
if ':' not in line:
i += 1
continue
_, rhs = line.split(':', 1)
rhs = rhs.split('#', 1)[0].strip()
tokens = rhs.split()
j = i + 1
while j < n:
cont = lines[j]
if not cont or (cont[0] not in ' \t'):
break
cont = cont.split('#', 1)[0].strip()
if cont:
tokens.extend(cont.split())
j += 1
for tok in tokens:
p = Path(tok)
if not p.is_absolute():
p = (Path(args.build_dir) / p).resolve()
used.add(str(p))
i = j
except Exception:
pass

filtered = set()
for dep in used:
if not dep.startswith(root):
continue
if any(dep.startswith(ex) for ex in excludes):
continue
if not (dep.endswith('.hpp') or dep.endswith('.cpp') or dep.endswith('.h')):
continue
filtered.add(dep)

missing = sorted(x for x in filtered if x not in declared)
if missing:
sys.stderr.write(
f"\n[verify:{args.target}] files used but not in sources.cmake (count={len(missing)}):\n"
)
for m in missing:
sys.stderr.write(f" {m}\n")
return 1
return 0


if __name__ == '__main__':
raise SystemExit(main())


Loading