Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
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
51 changes: 51 additions & 0 deletions cmake/common/verify_sources.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
function(ares_register_verify target target_root)
get_target_property(_srcs ${target} SOURCES)
get_target_property(_target_srcdir ${target} SOURCE_DIR)

# Create a file with all the declared sources for the target,
# converted to absolute paths.
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/verify")
set(_decl "${CMAKE_BINARY_DIR}/verify/${target}-declared.txt")
file(WRITE "${_decl}" "")
foreach(_s IN LISTS _srcs)
get_filename_component(_abs "${_s}" REALPATH BASE_DIR "${_target_srcdir}")
file(APPEND "${_decl}" "${_abs}\n")
endforeach()

# Run the verify_declared_sources.py script to check if all the sources
# are declared in as target sources.
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)
add_custom_target(verify-sources)
add_dependencies(verify-sources ${_tgts})
endfunction()


116 changes: 116 additions & 0 deletions scripts/verify_declared_sources.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/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 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)

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