Compile generated common contract bodies through wrappers for Ninja

This commit is contained in:
Matthew Stanley
2026-10-06 22:18:08 -07:00
parent 20b0ea04bc
commit e5e2dca85c
2 changed files with 61 additions and 29 deletions
+46 -26
View File
@@ -102,6 +102,31 @@ function(psxrecomp_execution_profile target)
VERBATIM)
endfunction()
# A materialized game shard can also be a configure/hash input. Compile an
# include wrapper so Ninja does not give the shard both a generation rule and
# CMake's input-file phony rule. Keep hashing the original body.
function(_psx_execution_compile_source target source output)
get_filename_component(_absolute "${source}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
get_source_file_property(_generated "${_absolute}" GENERATED)
if(NOT _generated)
set(${output} "${source}" PARENT_SCOPE)
return()
endif()
string(SHA256 _path_hash "${_absolute}")
get_filename_component(_ext "${_absolute}" LAST_EXT)
set(_wrapper "${CMAKE_CURRENT_BINARY_DIR}/psx_execution/${target}/sources/${_path_hash}${_ext}")
file(CONFIGURE OUTPUT "${_wrapper}" CONTENT "#include \"@_absolute@\"\n" @ONLY)
foreach(_property LANGUAGE COMPILE_DEFINITIONS COMPILE_OPTIONS COMPILE_FLAGS
INCLUDE_DIRECTORIES OBJECT_DEPENDS SKIP_PRECOMPILE_HEADERS
SKIP_UNITY_BUILD_INCLUSION)
get_source_file_property(_value "${_absolute}" "${_property}")
if(NOT _value STREQUAL "NOTFOUND")
set_source_files_properties("${_wrapper}" PROPERTIES "${_property}" "${_value}")
endif()
endforeach()
set(${output} "${_wrapper}" PARENT_SCOPE)
endfunction()
# Both source lists implement the same caller contract. Exclude these files
# from common target sources; exactly one list is linked. CONTRACT_FILES names
# adapter headers/recipes whose changes also affect the implementation identity.
@@ -134,34 +159,29 @@ function(psxrecomp_add_implementation target)
endif()
list(APPEND _inputs "${_absolute}")
endforeach()
# CONTRACT_FILES can include common generated game bodies already added by
# runtime.cmake, not only selected replacement bodies. Normalize those
# existing source entries before declaring their configure dependency.
get_target_property(_common_sources ${target} SOURCES)
set(_common_compile_sources "")
foreach(_source IN LISTS _common_sources)
if(_source MATCHES "^\\$<")
list(APPEND _common_compile_sources "${_source}")
continue()
endif()
get_filename_component(_absolute "${_source}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
if(_absolute IN_LIST _inputs)
_psx_execution_compile_source(${target} "${_absolute}" _compiled)
list(APPEND _common_compile_sources "${_compiled}")
else()
list(APPEND _common_compile_sources "${_source}")
endif()
endforeach()
set_property(TARGET ${target} PROPERTY SOURCES "${_common_compile_sources}")
set(_compile_sources "")
foreach(_source IN LISTS _selected)
get_filename_component(_absolute "${_source}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
get_source_file_property(_generated "${_absolute}" GENERATED)
if(_generated)
# runtime.cmake marks game shards GENERATED, though they are
# materialized before configure. Giving Ninja that file both as
# a compiled generated source and a configure dependency creates
# two output rules (an empty generation rule and a CMake phony).
# Compile an include wrapper instead. The original remains the
# sole maintained body and configure/hash input; #include keeps
# its relative includes and __FILE__ behavior intact.
string(SHA256 _path_hash "${_absolute}")
get_filename_component(_ext "${_absolute}" LAST_EXT)
set(_wrapper "${CMAKE_CURRENT_BINARY_DIR}/psx_execution/${target}/sources/${_path_hash}${_ext}")
file(CONFIGURE OUTPUT "${_wrapper}" CONTENT "#include \"@_absolute@\"\n" @ONLY)
foreach(_property LANGUAGE COMPILE_DEFINITIONS COMPILE_OPTIONS COMPILE_FLAGS
INCLUDE_DIRECTORIES OBJECT_DEPENDS SKIP_PRECOMPILE_HEADERS
SKIP_UNITY_BUILD_INCLUSION)
get_source_file_property(_value "${_absolute}" "${_property}")
if(NOT _value STREQUAL "NOTFOUND")
set_source_files_properties("${_wrapper}" PROPERTIES "${_property}" "${_value}")
endif()
endforeach()
list(APPEND _compile_sources "${_wrapper}")
else()
list(APPEND _compile_sources "${_absolute}")
endif()
_psx_execution_compile_source(${target} "${_source}" _compiled)
list(APPEND _compile_sources "${_compiled}")
endforeach()
target_sources(${target} PRIVATE ${_compile_sources})
set_property(TARGET ${target} APPEND PROPERTY PSX_IMPLEMENTATION_FAMILIES "${I_NAME}")
+15 -3
View File
@@ -28,7 +28,9 @@ def main():
#include "netplay_content_gate.h"
#include <stdio.h>
extern int operation(int);
extern int common_game_body(void);
int main(int argc, char** argv) {
if (common_game_body() != 5) return 4;
char no_mods[65], modded[65], upper[65];
if (!psx_execution_content_identity("", no_mods) ||
!psx_execution_content_identity("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", modded) ||
@@ -49,6 +51,7 @@ int main(int argc, char** argv) {
}
''')
(source / "hle.c").write_text("int operation(int x) { return x * 3; }\n")
(source / "common.c").write_text("#ifndef COMMON_SOURCE_FLAG\n#error common source property lost\n#endif\nint common_game_body(void) { return 5; }\n")
(source / "local_step.h").write_text("#define LOCAL_STEP 3\n")
(source / "lle.c").write_text('#include "local_step.h"\n#ifndef LLE_SOURCE_FLAG\n#error source compile property lost\n#endif\nint operation(int x) { int s=0; while(x--) s+=LOCAL_STEP; return s; }\n')
(source / "contract.h").write_text("/* caller contract revision 1 */\n")
@@ -60,14 +63,16 @@ include("{root.as_posix()}/cmake/psx_execution_profile.cmake")
# automatic configure/hash inputs without creating duplicate Ninja rules.
set_source_files_properties("${{CMAKE_CURRENT_SOURCE_DIR}}/lle.c" PROPERTIES
GENERATED TRUE COMPILE_DEFINITIONS LLE_SOURCE_FLAG=1)
add_executable(fixture main.c "{root.as_posix()}/runtime/src/psx_sha256.c")
set_source_files_properties("${{CMAKE_CURRENT_SOURCE_DIR}}/common.c" PROPERTIES
GENERATED TRUE COMPILE_DEFINITIONS COMMON_SOURCE_FLAG=1)
add_executable(fixture main.c common.c "{root.as_posix()}/runtime/src/psx_sha256.c")
target_include_directories(fixture PRIVATE "{root.as_posix()}/runtime/include")
add_executable(fixture-pgxp main.c "{root.as_posix()}/runtime/src/psx_sha256.c")
add_executable(fixture-pgxp main.c common.c "{root.as_posix()}/runtime/src/psx_sha256.c")
target_include_directories(fixture-pgxp PRIVATE "{root.as_posix()}/runtime/include")
psxrecomp_execution_profile(fixture CONTRACT_FILES shared.h)
psxrecomp_execution_profile(fixture-pgxp CONTRACT_FILES shared.h)
psxrecomp_add_implementation(fixture NAME triple CONTRACT integer-v1
HLE_SOURCES hle.c LLE_SOURCES lle.c CONTRACT_FILES contract.h)
HLE_SOURCES hle.c LLE_SOURCES lle.c CONTRACT_FILES contract.h common.c)
''')
def configure(profile, name=None, ok=True):
@@ -125,6 +130,13 @@ psxrecomp_add_implementation(fixture NAME triple CONTRACT integer-v1
assert changed_contract["identity"] != changed["identity"]
(source / "shared.h").write_text("/* shared service revision 2 */\n")
assert build_run(enhanced_build)["identity"] != changed_contract["identity"]
# Common generated bodies are compiled in both profiles and hashed as
# caller contracts. Their edits must also reconfigure automatically.
common_before = build_run(enhanced_build)
reference_before = build_run(Path(temp) / "REFERENCE")
(source / "common.c").write_text("int common_game_body(void) { return 2 + 3; }\n")
assert build_run(enhanced_build)["identity"] != common_before["identity"]
assert build_run(Path(temp) / "REFERENCE")["identity"] != reference_before["identity"]
# Private HLE input is not required to build the maintained reference.
(source / "hle.c").unlink()
build_run(configure("REFERENCE", "reference-no-private"))