diff --git a/cef_api_versions.json b/cef_api_versions.json index de56a761b..4c99122ae 100644 --- a/cef_api_versions.json +++ b/cef_api_versions.json @@ -1,46 +1,40 @@ { "hashes": { "13300": { - "comment": "Added February 17, 2025.", - "linux": "87d31692cf1ff1f0f2445dd026dcc0aeb02c5786", - "mac": "3891d2358697f15e046afc65fefd7019d888817a", - "universal": "c1a7d47be6fc130795366a1627573aa8eba1106f", - "windows": "0efb201c9e981c4513aeea04f9f2f8041ee9ce1f" + "comment": "Added February 21, 2025.", + "linux": "2508f3f0b0e5dfa191036fa6c04f8dcfa18c94b9", + "mac": "80c0b59ba9dd783aa71fae0aa5f7dad64620e8c9", + "windows": "45d39c3669ba75467e3e609f626c31506c0eae22" }, "13301": { - "comment": "Added February 17, 2025.", - "linux": "747f624e5059839e6a7d385de7997d45bbd42184", - "mac": "d3ec7d79adbde067590ab1bfff1f30dd8b06546a", - "universal": "892e654249acc7360f8aca4ee77da896753b87b8", - "windows": "cf9fbb7aa6778fedce78b8557044f219f2b5ba0b" + "comment": "Added February 21, 2025.", + "linux": "aa073dd1c586812503ca293c718358460d8c2dd6", + "mac": "fda40a5df44628cac50a589ff979c0746011591e", + "windows": "7109702038d51512d35dd2ed77231f9100e38214" }, "13302": { - "comment": "Added February 17, 2025.", - "linux": "ae4ab1c52ad951d37e6397db9f4e0ab0ce5044cf", - "mac": "cfe09351db37aa275613c27145fe622d9c28d4cc", - "universal": "acab8df1244923f1f80b18214e73c276a553ded6", - "windows": "c4b5a65478bc1a679f4a47ba5ff6899e5c3d8761" + "comment": "Added February 21, 2025.", + "linux": "d5597ebfa30081953425e897209a8387b9584205", + "mac": "4aa24470ba3a4bd9c06bc0e4a201b896394a86b5", + "windows": "18799961f4461a9cbae2aed89ac04b73ab7c37f3" }, "13303": { - "comment": "Added February 17, 2025.", - "linux": "2683698b779f11bba19f4051e3760fe327a1c8b2", - "mac": "8085a0f62f15946343c63788300e5c2b422b3301", - "universal": "128210b71e3f621cb6d2c4b05c6d0408a4547f2f", - "windows": "5a22d42c3e6294eb4ee2424053eebdeae3027899" + "comment": "Added February 21, 2025.", + "linux": "f3a696ee30ce1e00490a58df017393c126c89709", + "mac": "f2cdce2b9a4b635c28b5b92c42c35625a937380c", + "windows": "20016fd6a9b87ef4c539cd1f42bf1ca09b6903ca" }, "13304": { - "comment": "Added February 17, 2025.", - "linux": "c709233cd071e0d715eb10ceec4c5aea805df997", - "mac": "32af7e8f8828b04cf2908de9857940faa54deb8a", - "universal": "131b720865dd5c54c72041c0fbfb7c9bbfee8e0b", - "windows": "979b48138012378192dac7ee972fa731e9c0dae9" + "comment": "Added February 21, 2025.", + "linux": "f1ababb4ff51ecbf77c481cee3721ef0eca9c8ca", + "mac": "98964c37b8917d83da4b173e22905503d38ad08f", + "windows": "19c014af0082aa901398e006381b6980e4f806e9" }, "13400": { - "comment": "Added February 19, 2025.", - "linux": "342696aa818bc9a512cc965ed4c19ba705d2c839", - "mac": "7a20d0f7dc388406111461d6b1addd329b4be567", - "universal": "65de718ba0e0693dfc3b26e7e6a4814bbeeb10be", - "windows": "afe73f64f156154ff5b099c36186cbc03136172d" + "comment": "Added February 21, 2025.", + "linux": "ea2106b5bc012c25d735521e0c7fb719d433ea4a", + "mac": "ba5ab71db4f9447f19eb7b1943024981c88064dd", + "windows": "6ab74b90e88b7397aab9911baac5484f12466eef" } }, "last": "13400", diff --git a/include/cef_api_hash.h b/include/cef_api_hash.h index af903d4b9..96efd08aa 100644 --- a/include/cef_api_hash.h +++ b/include/cef_api_hash.h @@ -77,19 +77,15 @@ #endif #endif -#define _CEF_AH_PASTE(a, b, c) a##_##b##_##c -#define _CEF_AH_EVAL(a, b, c) _CEF_AH_PASTE(a, b, c) -#define _CEF_AH_DECLARE(version, suffix) \ - _CEF_AH_EVAL(CEF_API_HASH, version, suffix) +#define _CEF_AH_PASTE(a, b) a##_##b +#define _CEF_AH_EVAL(a, b) _CEF_AH_PASTE(a, b) +#define _CEF_AH_DECLARE(version) _CEF_AH_EVAL(CEF_API_HASH, version) // API hashes for the selected CEF_API_VERSION. API hashes are created for // each version by analyzing CEF header files for C API type definitions. The // hash value will change when header files are modified in a way that may -// cause binary incompatibility with other builds. The universal hash value -// will change if any platform is affected whereas the platform hash values -// will change only if that particular platform is affected. -#define CEF_API_HASH_UNIVERSAL _CEF_AH_DECLARE(CEF_API_VERSION, UNIVERSAL) -#define CEF_API_HASH_PLATFORM _CEF_AH_DECLARE(CEF_API_VERSION, PLATFORM) +// cause binary incompatibility with other builds. +#define CEF_API_HASH_PLATFORM _CEF_AH_DECLARE(CEF_API_VERSION) #if defined(BUILDING_CEF_SHARED) @@ -132,7 +128,7 @@ extern "C" { /// parameter describes which hash value will be returned: /// /// 0 - CEF_API_HASH_PLATFORM -/// 1 - CEF_API_HASH_UNIVERSAL +/// 1 - CEF_API_HASH_UNIVERSAL (deprecated, same as CEF_API_HASH_PLATFORM) /// 2 - CEF_COMMIT_HASH (from cef_version.h) /// CEF_EXPORT const char* cef_api_hash(int version, int entry); diff --git a/include/cef_base.h b/include/cef_base.h index 03221d111..729150085 100644 --- a/include/cef_base.h +++ b/include/cef_base.h @@ -31,8 +31,11 @@ #define CEF_INCLUDE_CEF_BASE_H_ #pragma once -#include "include/base/cef_atomic_ref_count.h" +#if !defined(GENERATING_CEF_API_HASH) #include "include/base/cef_build.h" +#endif + +#include "include/base/cef_atomic_ref_count.h" #include "include/base/cef_macros.h" // Bring in common C++ type definitions used by CEF consumers. diff --git a/include/cef_sandbox_mac.h b/include/cef_sandbox_mac.h index cec624eef..42d1b4457 100644 --- a/include/cef_sandbox_mac.h +++ b/include/cef_sandbox_mac.h @@ -31,7 +31,10 @@ #define CEF_INCLUDE_CEF_SANDBOX_MAC_H_ #pragma once +#if !defined(GENERATING_CEF_API_HASH) #include "include/base/cef_build.h" +#endif + #include "include/internal/cef_export.h" #if defined(OS_MAC) diff --git a/include/cef_sandbox_win.h b/include/cef_sandbox_win.h index a98d64c6c..bcef1fb3d 100644 --- a/include/cef_sandbox_win.h +++ b/include/cef_sandbox_win.h @@ -31,7 +31,9 @@ #define CEF_INCLUDE_CEF_SANDBOX_WIN_H_ #pragma once +#if !defined(GENERATING_CEF_API_HASH) #include "include/base/cef_build.h" +#endif #if defined(OS_WIN) diff --git a/include/internal/cef_app_win.h b/include/internal/cef_app_win.h index a28d90596..6bc968156 100644 --- a/include/internal/cef_app_win.h +++ b/include/internal/cef_app_win.h @@ -31,7 +31,9 @@ #define CEF_INCLUDE_INTERNAL_CEF_APP_WIN_H_ #pragma once +#if !defined(GENERATING_CEF_API_HASH) #include "include/base/cef_build.h" +#endif #if defined(OS_WIN) diff --git a/include/internal/cef_export.h b/include/internal/cef_export.h index e268b290b..6430833c4 100644 --- a/include/internal/cef_export.h +++ b/include/internal/cef_export.h @@ -32,7 +32,9 @@ #define CEF_INCLUDE_INTERNAL_CEF_EXPORT_H_ #pragma once +#if !defined(GENERATING_CEF_API_HASH) #include "include/base/cef_build.h" +#endif #if defined(COMPILER_MSVC) diff --git a/include/internal/cef_thread_internal.h b/include/internal/cef_thread_internal.h index 7df3be86c..3e9ac5381 100644 --- a/include/internal/cef_thread_internal.h +++ b/include/internal/cef_thread_internal.h @@ -31,12 +31,14 @@ #define CEF_INCLUDE_INTERNAL_CEF_THREAD_INTERNAL_H_ #pragma once +#if !defined(GENERATING_CEF_API_HASH) #if defined(OS_WIN) #include #elif defined(OS_POSIX) #include #include #endif +#endif #include "include/internal/cef_export.h" diff --git a/include/internal/cef_time.h b/include/internal/cef_time.h index 1a3d6c31a..a05507aaf 100644 --- a/include/internal/cef_time.h +++ b/include/internal/cef_time.h @@ -35,8 +35,11 @@ extern "C" { #endif -#include +#if !defined(GENERATING_CEF_API_HASH) #include +#endif + +#include #include "include/internal/cef_export.h" diff --git a/include/internal/cef_types_linux.h b/include/internal/cef_types_linux.h index eed86dcd2..16b5c2f58 100644 --- a/include/internal/cef_types_linux.h +++ b/include/internal/cef_types_linux.h @@ -31,15 +31,12 @@ #define CEF_INCLUDE_INTERNAL_CEF_TYPES_LINUX_H_ #pragma once +#if !defined(GENERATING_CEF_API_HASH) #include "include/base/cef_build.h" +#endif #if defined(OS_LINUX) -#if defined(CEF_X11) -typedef union _XEvent XEvent; -typedef struct _XDisplay XDisplay; -#endif - #include "include/internal/cef_export.h" #include "include/internal/cef_string.h" #include "include/internal/cef_types_color.h" @@ -47,17 +44,6 @@ typedef struct _XDisplay XDisplay; #include "include/internal/cef_types_osr.h" #include "include/internal/cef_types_runtime.h" -// Handle types. -#if defined(CEF_X11) -#define cef_cursor_handle_t unsigned long -#define cef_event_handle_t XEvent* -#else -#define cef_cursor_handle_t void* -#define cef_event_handle_t void* -#endif - -#define cef_window_handle_t unsigned long - #define kNullCursorHandle 0 #define kNullEventHandle NULL #define kNullWindowHandle 0 @@ -66,6 +52,20 @@ typedef struct _XDisplay XDisplay; extern "C" { #endif +#if defined(CEF_X11) +typedef union _XEvent XEvent; +typedef struct _XDisplay XDisplay; + +// Handle types. +typedef unsigned long cef_cursor_handle_t; +typedef XEvent* cef_event_handle_t; +#else +typedef void* cef_cursor_handle_t; +typedef void* cef_event_handle_t; +#endif + +typedef unsigned long cef_window_handle_t; + /// /// Return the singleton X11 display shared with Chromium. The display is not /// thread-safe and must only be accessed on the browser process UI thread. diff --git a/include/internal/cef_types_mac.h b/include/internal/cef_types_mac.h index 1228a1ff5..b168a74c3 100644 --- a/include/internal/cef_types_mac.h +++ b/include/internal/cef_types_mac.h @@ -31,7 +31,9 @@ #define CEF_INCLUDE_INTERNAL_CEF_TYPES_MAC_H_ #pragma once +#if !defined(GENERATING_CEF_API_HASH) #include "include/base/cef_build.h" +#endif #if defined(OS_MAC) #include "include/internal/cef_string.h" @@ -40,16 +42,6 @@ #include "include/internal/cef_types_osr.h" #include "include/internal/cef_types_runtime.h" -// Handle types. -// Actually NSCursor* -#define cef_cursor_handle_t void* -// Acutally NSEvent* -#define cef_event_handle_t void* -// Actually NSView* -#define cef_window_handle_t void* -// Actually IOSurface* -#define cef_shared_texture_handle_t void* - #define kNullCursorHandle NULL #define kNullEventHandle NULL #define kNullWindowHandle NULL @@ -78,6 +70,16 @@ extern "C" { #endif +// Handle types. +// Actually NSCursor* +typedef void* cef_cursor_handle_t; +// Actually NSEvent* +typedef void* cef_event_handle_t; +// Actually NSView* +typedef void* cef_window_handle_t; +// Actually IOSurface* +typedef void* cef_shared_texture_handle_t; + /// /// Structure representing CefExecuteProcess arguments. /// diff --git a/include/internal/cef_types_win.h b/include/internal/cef_types_win.h index d29e140e0..7de70e299 100644 --- a/include/internal/cef_types_win.h +++ b/include/internal/cef_types_win.h @@ -31,10 +31,15 @@ #define CEF_INCLUDE_INTERNAL_CEF_TYPES_WIN_H_ #pragma once +#if !defined(GENERATING_CEF_API_HASH) #include "include/base/cef_build.h" +#endif #if defined(OS_WIN) + +#if !defined(GENERATING_CEF_API_HASH) #include +#endif #include "include/internal/cef_string.h" #include "include/internal/cef_types_color.h" @@ -42,12 +47,6 @@ #include "include/internal/cef_types_osr.h" #include "include/internal/cef_types_runtime.h" -// Handle types. -#define cef_cursor_handle_t HCURSOR -#define cef_event_handle_t MSG* -#define cef_window_handle_t HWND -#define cef_shared_texture_handle_t HANDLE - #define kNullCursorHandle NULL #define kNullEventHandle NULL #define kNullWindowHandle NULL @@ -56,6 +55,12 @@ extern "C" { #endif +// Handle types. +typedef HCURSOR cef_cursor_handle_t; +typedef MSG* cef_event_handle_t; +typedef HWND cef_window_handle_t; +typedef HANDLE cef_shared_texture_handle_t; + /// /// Structure representing CefExecuteProcess arguments. /// diff --git a/libcef_dll/libcef_dll2.cc b/libcef_dll/libcef_dll2.cc index 39b7755e0..9555ecacd 100644 --- a/libcef_dll/libcef_dll2.cc +++ b/libcef_dll/libcef_dll2.cc @@ -48,23 +48,23 @@ CEF_EXPORT int cef_version_info(int entry) { #include "cef/libcef_dll/cef_api_versions.inc" CEF_EXPORT const char* cef_api_hash(int version, int entry) { - static const ApiVersionHash* hash = nullptr; + static const ApiVersionHash* current_version_hash = nullptr; // Initialize on the first successful lookup. - if (!hash) { - for (size_t i = 0; i < kApiVersionHashesSize; ++i) { - if (version == kApiVersionHashes[i].version) { - hash = &kApiVersionHashes[i]; + if (!current_version_hash) { + for (const auto& version_hash : kApiVersionHashes) { + if (version == version_hash.version) { + current_version_hash = &version_hash; break; } } - if (hash) { + if (current_version_hash) { g_version = version; } } - if (!hash) { + if (!current_version_hash) { LOG(ERROR) << "Request for unsupported CEF API version " << version; return nullptr; } @@ -76,9 +76,8 @@ CEF_EXPORT const char* cef_api_hash(int version, int entry) { switch (entry) { case 0: - return hash->platform; case 1: - return hash->universal; + return current_version_hash->hash; case 2: return CEF_COMMIT_HASH; default: @@ -97,9 +96,8 @@ CEF_EXPORT int cef_id_for_pack_resource_name(const char* name) { // Initialize on the first call. if (string_to_id_map->empty()) { - for (size_t i = 0; i < kIdNamesPackResourcesSize; ++i) { - (*string_to_id_map)[kIdNamesPackResources[i].name] = - kIdNamesPackResources[i].id; + for (const auto& pack_resource : kIdNamesPackResources) { + (*string_to_id_map)[pack_resource.name] = pack_resource.id; } } @@ -119,9 +117,8 @@ CEF_EXPORT int cef_id_for_pack_string_name(const char* name) { // Initialize on the first call. if (string_to_id_map->empty()) { - for (size_t i = 0; i < kIdNamesPackStringsSize; ++i) { - (*string_to_id_map)[kIdNamesPackStrings[i].name] = - kIdNamesPackStrings[i].id; + for (const auto& pack_string : kIdNamesPackStrings) { + (*string_to_id_map)[pack_string.name] = pack_string.id; } } @@ -141,9 +138,8 @@ CEF_EXPORT int cef_id_for_command_id_name(const char* name) { // Initialize on the first call. if (string_to_id_map->empty()) { - for (size_t i = 0; i < kIdNamesCommandIdsSize; ++i) { - (*string_to_id_map)[kIdNamesCommandIds[i].name] = - kIdNamesCommandIds[i].id; + for (const auto& command : kIdNamesCommandIds) { + (*string_to_id_map)[command.name] = command.id; } } diff --git a/tests/ceftests/version_unittest.cc b/tests/ceftests/version_unittest.cc index 50621e17c..40cee2c7e 100644 --- a/tests/ceftests/version_unittest.cc +++ b/tests/ceftests/version_unittest.cc @@ -19,6 +19,6 @@ TEST(VersionTest, VersionInfo) { TEST(VersionTest, ApiHash) { EXPECT_STREQ(CEF_API_HASH_PLATFORM, cef_api_hash(CEF_API_VERSION, 0)); - EXPECT_STREQ(CEF_API_HASH_UNIVERSAL, cef_api_hash(CEF_API_VERSION, 1)); + EXPECT_STREQ(CEF_API_HASH_PLATFORM, cef_api_hash(CEF_API_VERSION, 1)); EXPECT_STREQ(CEF_COMMIT_HASH, cef_api_hash(CEF_API_VERSION, 2)); } diff --git a/tools/cef_api_hash.py b/tools/cef_api_hash.py index ee6fc7c05..a59c55888 100644 --- a/tools/cef_api_hash.py +++ b/tools/cef_api_hash.py @@ -4,343 +4,380 @@ from __future__ import absolute_import from __future__ import print_function +from typing import Dict, List from clang_util import clang_eval from file_util import * import hashlib -import itertools import os import re -import string import sys -import time from version_util import EXP_VERSION -# Determines string type for python 2 and python 3. -if sys.version_info[0] == 3: - string_type = str -else: - string_type = basestring +API_TOKEN = '#if CEF_API' +OS_TOKEN = '#if defined(OS_' +INCLUDE_START_MARKER = 'int begin_includes_tag;\n' +INCLUDE_DIRS = [ + '.', # Includes relative to the 'src/cef' directory. + '..', # Includes relative to the 'src' directory. +] +PLATFORM_DEFINES = { + 'windows': ['OS_WIN', 'ARCH_CPU_X86_64'], + 'mac': ['OS_MAC', 'OS_POSIX', 'ARCH_CPU_ARM64'], + 'linux': ['OS_LINUX', 'OS_POSIX', 'CEF_X11', 'ARCH_CPU_X86_64'], +} + +SYSTEM_INCLUDES_PATTERN = re.compile(r'(#include <[^>]+>)') +FUNCTION_DECLARATION_PATTERN = re.compile( + r'\nCEF_EXPORT\s+?.*?\s+?(\w+)\s*?\(.*?\)\s*?;', re.DOTALL) +STRUCT_PATTERN = re.compile( + r'\ntypedef\s+?struct\s+?(\w+)\s+?\{.*?\}\s+?(\w+)\s*?;', re.DOTALL) +ENUM_PATTERN = re.compile(r'\ntypedef\s+?enum\s+?\{.*?\}\s+?(\w+)\s*?;', + re.DOTALL) +TYPEDEF_PATTERN = re.compile(r'\ntypedef\s+?.*?\s+(\w+);') +WHITESPACE_PATTERN = re.compile(r'\s+') +PARENTHESIS_PATTERN = re.compile(r'\(\s+') +SINGLE_LINE_COMMENT_PATTERN = re.compile(r'//.*\n') +CEF_STRING_TYPE_PATTERN = re.compile( + r'\n\s*?#\s*?define\s+?(CEF_STRING_TYPE_\w+)\s+?.*?\n') +DEFINE_PATTERN = re.compile(r'#define\s+?.*?') -def _run_clang_eval(filename, content, api_version, added_defines, verbose): - # Add a tag so we know where the header-specific output begins. - tag = 'int begin_includes_tag;\n' +def _prepare_content(content: str) -> str: + """ + Add a marker to the content to indicate where the header-specific output + begins and replace system includes with a placeholder + """ find = '#ifdef __cplusplus\nextern "C" {' pos = content.find(find) - assert pos > 0, filename - content = content[0:pos] + tag + content[pos:] + assert pos > 0, f'Cannot find "{find}" in {content}' + content = content[0:pos] + INCLUDE_START_MARKER + content[pos:] - defines = [ - # Makes sure CEF_EXPORT is defined. - 'USING_CEF_SHARED', + content = DEFINE_PATTERN.sub('//DEFINE_PLACEHOLDER//', content) + return SYSTEM_INCLUDES_PATTERN.sub('//INCLUDE_PLACEHOLDER//', content) - # Avoid include of generated headers. - 'GENERATING_CEF_API_HASH', - ] - if filename.find('test/') >= 0: - # Avoids errors parsing test includes. - defines.append('UNIT_TEST') - - # Not the experimental version. - api_version = int(api_version) - if api_version != EXP_VERSION: - # Specify the exact version. - defines.append('CEF_API_VERSION=%d' % api_version) - - if not added_defines is None: - defines.extend(added_defines) - - includes = [ - # Includes relative to the 'src/cef' directory. - '.', - # Includes relative to the 'src' directory. - '..', - ] - - result = clang_eval( - filename, - content, - defines=defines, - includes=includes, - as_cpp=False, - verbose=verbose) - if result is None: - return None - - pos = result.find(tag) - assert pos > 0, filename - result = result[pos + len(tag):] +def _process_result(result: str) -> str: + """ + Remove the non header-specific output and undo substitutions from cef_export.h + """ + pos = result.find(INCLUDE_START_MARKER) + if pos == -1: + return result + result = result[pos + len(INCLUDE_START_MARKER):] replacements = [ - # Undo substitutions from cef_export.h ['__declspec(dllimport)', 'CEF_EXPORT'], ['__attribute__((visibility("default")))', 'CEF_EXPORT'], ['__stdcall', ''], ] - for find, replace in replacements: result = result.replace(find, replace) - - return result + # Must always start with newline as required by _parse_objects() + return '\n' + result -class cef_api_hash: +def _get_defines(filename: str, added_defines: list) -> List[str]: + defines = [ + # Makes sure CEF_EXPORT is defined. + 'USING_CEF_SHARED', + # Avoid include of generated headers. + 'GENERATING_CEF_API_HASH', + ] + + # Avoids errors parsing test includes. + if filename.find('test/') >= 0: + defines.append('UNIT_TEST') + + defines.extend(added_defines) + return defines + + +def parse_versioned_content(filename: str, + content: str, + api_version: str, + added_defines: list, + debug_dir, + verbose) -> list: + """ + Parse the header file content using clang with the specified API version + Used for files that are version-specific but not platform-specific + """ + content = _prepare_content(content) + defines = _get_defines(filename, added_defines) + + if api_version != EXP_VERSION: + # Specify the exact version. + defines.append(f'CEF_API_VERSION={api_version}') + + result = clang_eval(filename, content, defines, INCLUDE_DIRS, verbose) + assert result, f'clang failed to eval {filename} with {api_version=}' + + result = _process_result(result) + + if debug_dir: + _write_debug_file(debug_dir, 'clang-' + filename.replace('/', '-'), result) + + return _parse_objects(filename, result, 'universal') + + +def parse_platformed_content(filename: str, + content: str, + debug_dir, + verbose, + api_version, + added_defines: list) -> list: + """ + Parse the header file content using clang for every supported platform + with the specified API version. Used for files that are both version-specific + and platform-specific. + """ + content = _prepare_content(content) + defines = _get_defines(filename, added_defines) + + if api_version and api_version != EXP_VERSION: + # Specify the exact version. + defines.append(f'CEF_API_VERSION={api_version}') + + content_objects = [] + for platform, platform_defines in PLATFORM_DEFINES.items(): + result = clang_eval(filename, content, defines + platform_defines, + INCLUDE_DIRS, verbose) + assert result, f'clang failed to eval {filename} on {platform=}' + + result = _process_result(result) + if debug_dir: + _write_debug_file( + debug_dir, f'clang-{platform}-' + filename.replace('/', '-'), result) + + objects = _parse_objects(filename, result, platform) + content_objects.extend(objects) + + return content_objects + + +def _write_debug_file(debug_dir, filename, content) -> None: + make_dir(debug_dir) + outfile = os.path.join(debug_dir, filename) + dir = os.path.dirname(outfile) + make_dir(dir) + if not isinstance(content, str): + content = '\n'.join(content) + write_file(outfile, content) + + +def _parse_objects(filename: str, content: str, platform: str) -> list: + objects = [] + content = SINGLE_LINE_COMMENT_PATTERN.sub('', content) + + for m in FUNCTION_DECLARATION_PATTERN.finditer(content): + objects.append({ + 'filename': filename, + 'name': m.group(1), + 'platform': platform, + 'text': _prepare_text(m.group(0)) + }) + + for m in STRUCT_PATTERN.finditer(content): + # Remove 'CEF_CALLBACK' to normalize cross-platform clang output + objects.append({ + 'filename': filename, + 'name': m.group(2), + 'platform': platform, + 'text': _prepare_text(m.group(0).replace('CEF_CALLBACK', '')) + }) + + for m in ENUM_PATTERN.finditer(content): + objects.append({ + 'filename': filename, + 'name': m.group(1), + 'platform': platform, + 'text': _prepare_text(m.group(0)) + }) + + for m in TYPEDEF_PATTERN.finditer(content): + if m.group(1) == 'char16_t': + # Skip char16_t typedefs to avoid platform-specific differences. + continue + + objects.append({ + 'filename': filename, + 'name': m.group(1), + 'platform': platform, + 'text': _prepare_text(m.group(0)) + }) + + return objects + + +def _prepare_text(text: str) -> str: + """ Normalize text for hashing. """ + text = WHITESPACE_PATTERN.sub(' ', text.strip()) + return PARENTHESIS_PATTERN.sub('(', text) + + +def _parse_string_type(filename: str, content: str) -> list: + """ Grab defined CEF_STRING_TYPE_xxx """ + objects = [] + for m in CEF_STRING_TYPE_PATTERN.finditer(content): + objects.append({ + 'filename': filename, + 'name': m.group(1), + 'text': _prepare_text(m.group(0)), + 'platform': 'universal' + }) + return objects + + +def _get_final_sig(objects, platform) -> str: + return '\n'.join(o['text'] for o in objects + if o['platform'] == 'universal' or o['platform'] == platform) + + +def _get_filenames(header_dir) -> List[str]: + """ Return file names to be processed, relative to header_dir """ + cef_dir = os.path.abspath(os.path.join(header_dir, os.pardir)) + + # Read the variables list from the autogenerated cef_paths.gypi file. + cef_paths = eval_file(os.path.join(cef_dir, 'cef_paths.gypi')) + cef_paths = cef_paths['variables'] + # Read the variables list from the manually edited cef_paths2.gypi file. + cef_paths2 = eval_file(os.path.join(cef_dir, 'cef_paths2.gypi')) + cef_paths2 = cef_paths2['variables'] + + # List of all C API include/ files. + paths = cef_paths2['includes_capi'] + cef_paths2['includes_common_capi'] + \ + cef_paths2['includes_linux_capi'] + cef_paths2['includes_mac_capi'] + \ + cef_paths2['includes_win_capi'] + cef_paths['autogen_capi_includes'] + + return [ + os.path.relpath(os.path.join(cef_dir, filename), header_dir).replace( + '\\', '/').lower() for filename in paths + ] + + +def _save_objects_to_file(debug_dir: str, objects: list) -> None: + name_len = max([len(o['name']) for o in objects]) + filename_len = max([len(o['filename']) for o in objects]) + platform_len = max([len(o['platform']) for o in objects]) + dump_sig = [ + f"{o['name']:<{name_len}}|{o['filename']:<{filename_len}}|{o['platform']:<{platform_len}}|{o['text']}" + for o in objects + ] + _write_debug_file(debug_dir, 'objects.txt', dump_sig) + + +class CefApiHasher: """ CEF API hash calculator """ - def __init__(self, headerdir, verbose=False): - if headerdir is None or len(headerdir) == 0: - raise AssertionError("headerdir is not specified") + def __init__(self, header_dir, debug_dir, verbose=False): + if header_dir is None or len(header_dir) == 0: + raise AssertionError('header_dir is not specified') - self.__headerdir = headerdir - self.__verbose = verbose - - self.platforms = ["windows", "mac", "linux"] - - cef_dir = os.path.abspath(os.path.join(self.__headerdir, os.pardir)) - - # Read the variables list from the autogenerated cef_paths.gypi file. - cef_paths = eval_file(os.path.join(cef_dir, 'cef_paths.gypi')) - cef_paths = cef_paths['variables'] - - # Read the variables list from the manually edited cef_paths2.gypi file. - cef_paths2 = eval_file(os.path.join(cef_dir, 'cef_paths2.gypi')) - cef_paths2 = cef_paths2['variables'] - - # Excluded files (paths relative to the include/ directory). - excluded_files = [] - - # List of platform-specific C API include/ files. - self.platform_files = { - "windows": - self.__get_filenames(cef_dir, cef_paths2['includes_win_capi'], - excluded_files), - "mac": - self.__get_filenames(cef_dir, cef_paths2['includes_mac_capi'], - excluded_files), - "linux": - self.__get_filenames(cef_dir, cef_paths2['includes_linux_capi'], - excluded_files) - } - - # List of all C API include/ files. - paths = cef_paths2['includes_capi'] + cef_paths2['includes_common_capi'] + \ - cef_paths2['includes_linux_capi'] + cef_paths2['includes_mac_capi'] + \ - cef_paths2['includes_win_capi'] + cef_paths['autogen_capi_includes'] - self.filenames = self.__get_filenames(cef_dir, paths, excluded_files) - - self.filecontents = {} - self.filecontentobjs = {} + self.filenames = _get_filenames(header_dir) + self.debug_dir = debug_dir + self.verbose = verbose + self.versioned_contents = {} + self.platformed_contents = {} + self.file_content_objs = {} # Cache values that will not change between calls to calculate(). for filename in self.filenames: - if self.__verbose: - print("Processing " + filename + "...") + self.print(f'Processing {filename} ...') - assert not filename in self.filecontents, filename - assert not filename in self.filecontentobjs, filename + if filename in self.versioned_contents \ + or filename in self.platformed_contents \ + or filename in self.file_content_objs: + self.print(f'{filename} already processed, skipping...') + continue - content = read_file(os.path.join(self.__headerdir, filename), True) + content = read_file(os.path.join(header_dir, filename), normalize=True) content_objects = None - # Parse cef_string.h happens in special case: grab only defined CEF_STRING_TYPE_xxx declaration - if filename == "internal/cef_string.h": - content_objects = self.__parse_string_type(content) - elif content.find('#if CEF_API') >= 0: - # Needs to be passed to clang with version-specific defines. - self.filecontents[filename] = content + is_platform_specific = OS_TOKEN in content + is_version_specific = API_TOKEN in content + + # Special case for parsing cef_string.h: + # Only extract declarations of the form #define CEF_STRING_TYPE_xxx + if filename == 'internal/cef_string.h': + content_objects = _parse_string_type(filename, content) + elif is_platform_specific and not is_version_specific: + # Parse once and cache for all platforms + content_objects = parse_platformed_content( + filename, + content, + debug_dir, + verbose, + api_version=None, + added_defines=[]) + elif is_platform_specific and is_version_specific: + # Needs to be passed to clang with version and platform defines. + self.platformed_contents[filename] = content + elif is_version_specific: + # Needs to be passed to clang with version defines. + self.versioned_contents[filename] = content else: - content_objects = self.__parse_objects(content) + content_objects = _parse_objects(filename, content, 'universal') - if not content_objects is None: - self.__prepare_objects(filename, content_objects) - self.filecontentobjs[filename] = content_objects + if content_objects is not None: + self.file_content_objs[filename] = content_objects - def calculate(self, api_version, debug_dir=None, added_defines=None): - debug_enabled = not (debug_dir is None) and len(debug_dir) > 0 + def calculate(self, api_version: str, added_defines: list) -> Dict[str, str]: + """ Calculate the API hash per platform for the specified API version """ + debug_dir = os.path.join(self.debug_dir, + api_version) if self.debug_dir else None objects = [] for filename in self.filenames: - if self.__verbose: - print("Processing " + filename + "...") + self.print(f'Processing {filename}...') - content = self.filecontents.get(filename, None) - if not content is None: - assert content.find('#if CEF_API') >= 0, filename - content = _run_clang_eval(filename, content, api_version, added_defines, - self.__verbose) - if content is None: - sys.stderr.write( - 'ERROR: Failed to compute API hash for %s\n' % filename) - return False - if debug_enabled: - self.__write_debug_file( - debug_dir, 'clang-' + filename.replace('/', '-'), content) - - # content must always start with newline as required by __parse_objects() - content_objects = self.__parse_objects('\n' + content) - self.__prepare_objects(filename, content_objects) + if filename in self.versioned_contents: + content = self.versioned_contents.get(filename, None) + content_objects = parse_versioned_content(filename, content, + api_version, added_defines, + debug_dir, self.verbose) + elif filename in self.platformed_contents: + content = self.platformed_contents.get(filename, None) + content_objects = parse_platformed_content(filename, content, debug_dir, + self.verbose, api_version, + added_defines) else: - content_objects = self.filecontentobjs.get(filename, None) + content_objects = self.file_content_objs.get(filename, None) - assert not content_objects is None, filename + assert content_objects, f'content_objects is None for {filename}' objects.extend(content_objects) - # objects will be sorted including filename, to make stable universal hashes - objects = sorted(objects, key=lambda o: o["name"] + "@" + o["filename"]) + # objects will be sorted including filename to make stable hashes + objects = sorted(objects, key=lambda o: f"{o['name']}@{o['filename']}") - if debug_enabled: - namelen = max([len(o["name"]) for o in objects]) - filenamelen = max([len(o["filename"]) for o in objects]) - dumpsig = [] - for o in objects: - dumpsig.append( - format(o["name"], str(namelen) + "s") + "|" + format( - o["filename"], "" + str(filenamelen) + "s") + "|" + o["text"]) - self.__write_debug_file(debug_dir, "objects.txt", dumpsig) + if debug_dir: + _save_objects_to_file(debug_dir, objects) - revisions = {} + hashes = {} + for platform in PLATFORM_DEFINES.keys(): + sig = _get_final_sig(objects, platform) + if debug_dir: + _write_debug_file(debug_dir, f'{platform}.sig', sig) + hashes[platform] = hashlib.sha1(sig.encode('utf-8')).hexdigest() - for platform in itertools.chain(["universal"], self.platforms): - sig = self.__get_final_sig(objects, platform) - if debug_enabled: - self.__write_debug_file(debug_dir, platform + ".sig", sig) - revstr = hashlib.sha1(sig.encode('utf-8')).hexdigest() - revisions[platform] = revstr + return hashes - return revisions - - def __parse_objects(self, content): - """ Returns array of objects in content file. """ - objects = [] - content = re.sub(r"//.*\n", "", content) - - # function declarations - for m in re.finditer( - r"\nCEF_EXPORT\s+?.*?\s+?(\w+)\s*?\(.*?\)\s*?;", - content, - flags=re.DOTALL): - object = {"name": m.group(1), "text": m.group(0).strip()} - objects.append(object) - - # structs - for m in re.finditer( - r"\ntypedef\s+?struct\s+?(\w+)\s+?\{.*?\}\s+?(\w+)\s*?;", - content, - flags=re.DOTALL): - text = m.group(0).strip() - # remove 'CEF_CALLBACK' to normalize cross-platform clang output - text = text.replace('CEF_CALLBACK', '') - object = {"name": m.group(2), "text": text} - objects.append(object) - - # enums - for m in re.finditer( - r"\ntypedef\s+?enum\s+?\{.*?\}\s+?(\w+)\s*?;", content, - flags=re.DOTALL): - object = {"name": m.group(1), "text": m.group(0).strip()} - objects.append(object) - - # typedefs - for m in re.finditer(r"\ntypedef\s+?.*?\s+(\w+);", content, flags=0): - object = {"name": m.group(1), "text": m.group(0).strip()} - objects.append(object) - - return objects - - def __prepare_objects(self, filename, objects): - platforms = list( - [p for p in self.platforms if self.__is_platform_filename(filename, p)]) - for o in objects: - o["text"] = self.__prepare_text(o["text"]) - o["platforms"] = platforms - o["filename"] = filename - - def __parse_string_type(self, content): - """ Grab defined CEF_STRING_TYPE_xxx """ - objects = [] - for m in re.finditer( - r"\n\s*?#\s*?define\s+?(CEF_STRING_TYPE_\w+)\s+?.*?\n", - content, - flags=0): - object = { - "name": m.group(1), - "text": m.group(0), - } - objects.append(object) - return objects - - def __prepare_text(self, text): - text = text.strip() - text = re.sub(r"\s+", " ", text) - text = re.sub(r"\(\s+", "(", text) - return text - - def __get_final_sig(self, objects, platform): - sig = [] - - for o in objects: - if platform == "universal" or platform in o["platforms"]: - sig.append(o["text"]) - - return "\n".join(sig) - - def __get_filenames(self, cef_dir, paths, excluded_files): - """ Returns file names to be processed, relative to headerdir """ - filenames = [ - os.path.relpath(os.path.join(cef_dir, filename), - self.__headerdir).replace('\\', '/').lower() - for filename in paths - ] - - if len(excluded_files) == 0: - return filenames - - return [ - filename for filename in filenames if not filename in excluded_files - ] - - def __is_platform_filename(self, filename, platform): - if platform == "universal": - return True - if not platform in self.platform_files: - return False - listed = False - for p in self.platforms: - if filename in self.platform_files[p]: - if p == platform: - return True - else: - listed = True - return not listed - - def __write_debug_file(self, debug_dir, filename, content): - make_dir(debug_dir) - outfile = os.path.join(debug_dir, filename) - dir = os.path.dirname(outfile) - make_dir(dir) - if not isinstance(content, string_type): - content = "\n".join(content) - write_file(outfile, content) + def print(self, msg): + if self.verbose: + print(msg) -if __name__ == "__main__": +if __name__ == '__main__': from optparse import OptionParser - import time - disc = """ - This utility calculates CEF API hash. - """ - - parser = OptionParser(description=disc) + parser = OptionParser(description='This utility calculates CEF API hash') parser.add_option( '--cpp-header-dir', - dest='cppheaderdir', + dest='cpp_header_dir', metavar='DIR', help='input directory for C++ header files [required]') parser.add_option( '--debug-dir', - dest='debugdir', + dest='debug_dir', metavar='DIR', help='intermediate directory for easy debugging') parser.add_option( @@ -352,28 +389,16 @@ if __name__ == "__main__": help='output detailed status information') (options, args) = parser.parse_args() - # the cppheader option is required - if options.cppheaderdir is None: + # the cpp_header_dir option is required + if options.cpp_header_dir is None: parser.print_help(sys.stdout) sys.exit() - # calculate - c_start_time = time.time() + calc = CefApiHasher(options.cpp_header_dir, options.debug_dir, + options.verbose) + revisions = calc.calculate(EXP_VERSION, []) - calc = cef_api_hash(options.cppheaderdir, options.debugdir, options.verbose) - revisions = calc.calculate(api_version=EXP_VERSION) - - c_completed_in = time.time() - c_start_time - - if bool(revisions): - print("{") - for k in sorted(revisions.keys()): - print(format("\"" + k + "\"", ">12s") + ": \"" + revisions[k] + "\"") - print("}") - - # print - # print 'Completed in: ' + str(c_completed_in) - # print - - # print "Press any key to continue..."; - # sys.stdin.readline(); + print('{') + for k in sorted(revisions.keys()): + print(format('"' + k + '"', '>12s') + ': "' + revisions[k] + '"') + print('}') diff --git a/tools/cef_api_hash_test.py b/tools/cef_api_hash_test.py new file mode 100644 index 000000000..cddc7a1ef --- /dev/null +++ b/tools/cef_api_hash_test.py @@ -0,0 +1,375 @@ +# Copyright (c) 2025 The Chromium Embedded Framework Authors. All rights +# reserved. Use of this source code is governed by a BSD-style license that +# can be found in the LICENSE file. + +# Execute the following command to run unit tests: +# python3 -m unittest discover -s tools -p *_test.py + +from cef_api_hash import * +from collections import Counter +import unittest + +# Test constants: +FILENAME = 'test.h' +NONE_DEBUG_DIR = None +VERBOSE = False +ADDED_DEFINES = [] + +CEF_TYPES_WIN = """#ifndef CEF_INCLUDE_INTERNAL_CEF_TYPES_WIN_H_ +#define CEF_INCLUDE_INTERNAL_CEF_TYPES_WIN_H_ +#pragma once + +#if !defined(GENERATING_CEF_API_HASH) +#include "include/base/cef_build.h" +#endif + +#if defined(OS_WIN) + +#if !defined(GENERATING_CEF_API_HASH) +#include +#endif + +#include "include/cef_api_hash.h" +#include "include/internal/cef_types_runtime.h" + +#define kNullCursorHandle NULL +#define kNullEventHandle NULL +#define kNullWindowHandle NULL + +#ifdef __cplusplus +extern "C" { +#endif + +// Handle types. +typedef HWND cef_window_handle_t; + +typedef struct _cef_window_info_t { + size_t size; + DWORD ex_style; + HMENU menu; + cef_window_handle_t parent_window; + cef_runtime_style_t runtime_style; +#if CEF_API_ADDED(13304) + int api_version_test; +#endif +} cef_window_info_t; + + +#ifdef __cplusplus +} +#endif +#endif // OS_WIN +#endif // CEF_INCLUDE_INTERNAL_CEF_TYPES_WIN_H_ +""" + +CEF_TYPES_MAC = """#ifndef CEF_INCLUDE_INTERNAL_CEF_TYPES_MAC_H_ +#define CEF_INCLUDE_INTERNAL_CEF_TYPES_MAC_H_ +#pragma once + +#if !defined(GENERATING_CEF_API_HASH) +#include "include/base/cef_build.h" +#endif + +#include + +#if defined(OS_MAC) +#include "include/internal/cef_string.h" +#include "include/internal/cef_types_geometry.h" +#include "include/internal/cef_types_runtime.h" + +#define kNullCursorHandle NULL + +#ifdef __OBJC__ +#if __has_feature(objc_arc) +#define CAST_CEF_CURSOR_HANDLE_TO_NSCURSOR(handle) ((__bridge NSCursor*)handle) +#define CAST_CEF_WINDOW_HANDLE_TO_NSVIEW(handle) ((__bridge NSView*)handle) + +#define CAST_NSCURSOR_TO_CEF_CURSOR_HANDLE(cursor) ((__bridge void*)cursor) +#define CAST_NSVIEW_TO_CEF_WINDOW_HANDLE(view) ((__bridge void*)view) +#else // __has_feature(objc_arc) +#define CAST_CEF_CURSOR_HANDLE_TO_NSCURSOR(handle) ((NSCursor*)handle) +#define CAST_CEF_WINDOW_HANDLE_TO_NSVIEW(handle) ((NSView*)handle) + +#define CAST_NSCURSOR_TO_CEF_CURSOR_HANDLE(cursor) ((void*)cursor) +#define CAST_NSVIEW_TO_CEF_WINDOW_HANDLE(view) ((void*)view) +#endif // __has_feature(objc_arc) +#endif // __OBJC__ + +#ifdef __cplusplus +extern "C" { +#endif + +// Actually NSView* +typedef void* cef_window_handle_t; + +/// +/// Class representing window information. +/// +typedef struct _cef_window_info_t { + size_t size; + cef_string_t window_name; + cef_rect_t bounds; + cef_window_handle_t view; + cef_runtime_style_t runtime_style; +} cef_window_info_t; + +#ifdef __cplusplus +} +#endif + +#endif // OS_MAC +#endif // CEF_INCLUDE_INTERNAL_CEF_TYPES_MAC_H_ +""" + +CEF_TYPES_LINUX = """#ifndef CEF_INCLUDE_INTERNAL_CEF_TYPES_LINUX_H_ +#define CEF_INCLUDE_INTERNAL_CEF_TYPES_LINUX_H_ +#pragma once + +#if !defined(GENERATING_CEF_API_HASH) +#include "include/base/cef_build.h" +#endif + +#if defined(OS_LINUX) + +#include "include/internal/cef_export.h" +#include "include/internal/cef_string.h" +#include "include/internal/cef_types_color.h" +#include "include/internal/cef_types_geometry.h" +#include "include/internal/cef_types_osr.h" +#include "include/internal/cef_types_runtime.h" + +#define kNullCursorHandle 0 +#define kNullEventHandle NULL +#define kNullWindowHandle 0 + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(CEF_X11) +typedef union _XEvent XEvent; +typedef struct _XDisplay XDisplay; + +// Handle types. +typedef XEvent* cef_event_handle_t; +#else +typedef void* cef_event_handle_t; +#endif + +typedef unsigned long cef_window_handle_t; + +/// +/// Return the singleton X11 display shared with Chromium. The display is not +/// thread-safe and must only be accessed on the browser process UI thread. +/// +#if defined(CEF_X11) +CEF_EXPORT XDisplay* cef_get_xdisplay(void); +#endif + +typedef struct _cef_window_info_t { + size_t size; + cef_string_t window_name; + cef_rect_t bounds; + cef_window_handle_t parent_window; + cef_window_handle_t window; + cef_runtime_style_t runtime_style; +} cef_window_info_t; + +#ifdef __cplusplus +} +#endif + +#endif // OS_LINUX + +#endif // CEF_INCLUDE_INTERNAL_CEF_TYPES_LINUX_H_ +""" + +CEF_TYPES = """#ifndef CEF_INCLUDE_INTERNAL_CEF_TYPES_H_ +#define CEF_INCLUDE_INTERNAL_CEF_TYPES_H_ +#pragma once + +#include "include/cef_api_hash.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + CEF_CPAIT_OPTIMIZATION_GUIDE, +#if CEF_API_ADDED(13304) + CEF_CPAIT_COLLABORATION_MESSAGING, +#endif + CEF_CPAIT_NUM_VALUES, +} cef_chrome_page_action_icon_type_t; + +#ifdef __cplusplus +} +#endif + +#endif // CEF_INCLUDE_INTERNAL_CEF_TYPES_H_ +""" + + +class TestRunClangEval(unittest.TestCase): + + def test_parse_platformed_content_version_13304(self): + expected_objects = [{ + 'filename': FILENAME, + 'name': 'cef_window_handle_t', + 'platform': 'windows', + 'text': 'typedef HWND cef_window_handle_t;' + }, { + 'filename': + FILENAME, + 'name': + 'cef_window_info_t', + 'platform': + 'windows', + 'text': + 'typedef struct _cef_window_info_t { size_t size; DWORD ex_style; HMENU menu; cef_window_handle_t parent_window; cef_runtime_style_t runtime_style; int api_version_test; } cef_window_info_t;' + }] + + objects = parse_platformed_content(FILENAME, CEF_TYPES_WIN, NONE_DEBUG_DIR, + VERBOSE, '13304', ADDED_DEFINES) + + self.__assert_equal_objects(objects, expected_objects) + + def test_parse_platformed_content_version_13300(self): + expected_objects = [{ + 'filename': FILENAME, + 'name': 'cef_window_handle_t', + 'platform': 'windows', + 'text': 'typedef HWND cef_window_handle_t;' + }, { + 'filename': + FILENAME, + 'name': + 'cef_window_info_t', + 'platform': + 'windows', + 'text': + 'typedef struct _cef_window_info_t { size_t size; DWORD ex_style; HMENU menu; cef_window_handle_t parent_window; cef_runtime_style_t runtime_style; } cef_window_info_t;' + }] + + objects = parse_platformed_content(FILENAME, CEF_TYPES_WIN, NONE_DEBUG_DIR, + VERBOSE, '13300', ADDED_DEFINES) + + self.__assert_equal_objects(objects, expected_objects) + + def test_parse_platformed_content_mac(self): + expected_objects = [{ + 'filename': FILENAME, + 'name': 'cef_window_handle_t', + 'platform': 'mac', + 'text': 'typedef void* cef_window_handle_t;' + }, { + 'filename': + FILENAME, + 'name': + 'cef_window_info_t', + 'platform': + 'mac', + 'text': + 'typedef struct _cef_window_info_t { size_t size; cef_string_t window_name; cef_rect_t bounds; cef_window_handle_t view; cef_runtime_style_t runtime_style; } cef_window_info_t;' + }] + + objects = parse_platformed_content( + FILENAME, + CEF_TYPES_MAC, + NONE_DEBUG_DIR, + VERBOSE, + api_version=None, + added_defines=[]) + + self.__assert_equal_objects(objects, expected_objects) + + def test_parse_platformed_content_linux(self): + expected_objects = [{ + 'filename': FILENAME, + 'name': 'XEvent', + 'platform': 'linux', + 'text': 'typedef union _XEvent XEvent;' + }, { + 'filename': FILENAME, + 'name': 'XDisplay', + 'platform': 'linux', + 'text': 'typedef struct _XDisplay XDisplay;' + }, { + 'filename': FILENAME, + 'name': 'cef_event_handle_t', + 'platform': 'linux', + 'text': 'typedef XEvent* cef_event_handle_t;' + }, { + 'filename': FILENAME, + 'name': 'cef_window_handle_t', + 'platform': 'linux', + 'text': 'typedef unsigned long cef_window_handle_t;' + }, { + 'filename': FILENAME, + 'name': 'cef_get_xdisplay', + 'platform': 'linux', + 'text': 'CEF_EXPORT XDisplay* cef_get_xdisplay(void);' + }, { + 'filename': + FILENAME, + 'name': + 'cef_window_info_t', + 'platform': + 'linux', + 'text': + 'typedef struct _cef_window_info_t { size_t size; cef_string_t window_name; cef_rect_t bounds; cef_window_handle_t parent_window; cef_window_handle_t window; cef_runtime_style_t runtime_style; } cef_window_info_t;' + }] + + objects = parse_platformed_content( + FILENAME, + CEF_TYPES_LINUX, + NONE_DEBUG_DIR, + VERBOSE, + api_version=None, + added_defines=[]) + + self.__assert_equal_objects(objects, expected_objects) + + def test_parse_versioned_content_version_13304(self): + expected_objects = [{ + 'filename': + FILENAME, + 'name': + 'cef_chrome_page_action_icon_type_t', + 'platform': + 'universal', + 'text': + 'typedef enum { CEF_CPAIT_OPTIMIZATION_GUIDE, CEF_CPAIT_COLLABORATION_MESSAGING, CEF_CPAIT_NUM_VALUES, } cef_chrome_page_action_icon_type_t;' + }] + + objects = parse_versioned_content(FILENAME, CEF_TYPES, '13304', + ADDED_DEFINES, NONE_DEBUG_DIR, VERBOSE) + + self.assertEqual(objects, expected_objects) + + def test_parse_versioned_content_version_13303(self): + expected_objects = [{ + 'filename': + FILENAME, + 'name': + 'cef_chrome_page_action_icon_type_t', + 'platform': + 'universal', + 'text': + 'typedef enum { CEF_CPAIT_OPTIMIZATION_GUIDE, CEF_CPAIT_NUM_VALUES, } cef_chrome_page_action_icon_type_t;' + }] + + objects = parse_versioned_content(FILENAME, CEF_TYPES, '13303', + ADDED_DEFINES, NONE_DEBUG_DIR, VERBOSE) + + self.assertEqual(objects, expected_objects) + + def __assert_equal_objects(self, actual, expected): + # Compare the objects as sets since the order is not guaranteed + expected = Counter(tuple(sorted(d.items())) for d in expected) + actual = Counter(tuple(sorted(d.items())) for d in actual) + self.assertEqual(expected, actual) + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/cef_version.py b/tools/cef_version.py index 4e8d143c0..1e38ac7ab 100644 --- a/tools/cef_version.py +++ b/tools/cef_version.py @@ -161,7 +161,7 @@ class VersionFormatter: # - "X" is the Chromium major version (e.g. 74). # - "Y" is an incremental number that starts at 0 when a release branch is # created and changes only when the CEF C/C++ API changes (similar to how - # the CEF_API_HASH_UNIVERSAL value behaves in cef_api_hash.h) (release + # the CEF_API_HASH_PLATFORM value behaves in cef_api_hash.h) (release # branch only). # - "Z" is an incremental number that starts at 0 when a release branch is # created and changes on each commit, with reset to 0 when "Y" changes diff --git a/tools/clang_util.py b/tools/clang_util.py index cf9eb82be..528019e52 100644 --- a/tools/clang_util.py +++ b/tools/clang_util.py @@ -27,7 +27,7 @@ else: def clang_format(file_name, file_contents): # -assume-filename is necessary to find the .clang-format file and determine # the language when specifying contents via stdin. - result = exec_cmd("%s -assume-filename=%s" % (clang_format_exe, file_name), \ + result = exec_cmd("%s -assume-filename=%s" % (clang_format_exe, file_name), cef_dir, file_contents.encode('utf-8')) if result['err'] != '': sys.stderr.write("clang-format error: %s\n" % result['err']) @@ -48,40 +48,28 @@ def clang_format_inplace(file_name): return True -def clang_eval(file_name, - file_contents, - defines=[], - includes=[], - as_cpp=True, - verbose=False): - lang = 'c++' if as_cpp else 'c' +def clang_eval(file_name, file_contents, defines, includes, verbose): + lang = 'c' if file_name.lower().endswith('.h'): lang += '-header' # The -P option removes unnecessary line markers and whitespace. format = '/EP' if sys.platform == 'win32' else '-E -P' - sdkroot = '' - if sys.platform == 'darwin': - result = exec_cmd('xcrun --show-sdk-path', '.') - if result['ret'] == 0: - sdkroot = " -isysroot %s" % result['out'].strip() - - cmd = "%s -x %s %s %s %s %s -" % (clang_exe, lang, format, - ' '.join(['-D' + v for v in defines]), - ' '.join(['-I' + v - for v in includes]), sdkroot) + cmd = "%s -x %s %s %s %s -" % (clang_exe, lang, format, + ' '.join(['-D' + v for v in defines]), + ' '.join(['-I' + v for v in includes])) if verbose: - print('--- Running "%s" in "%s"' % (cmd, cef_dir)) + print(f'--- Running "{cmd}" in "{cef_dir}"') result = exec_cmd(cmd, cef_dir, file_contents.encode('utf-8')) - if result['err'] != '': - err = result['err'].replace('', file_name) - sys.stderr.write("clang error: %s\n" % err) + if result['err'] != '' or result['ret'] != 0: + error = result['err'].replace('', file_name) + return_code = result['ret'] + sys.stderr.write(f'clang {return_code=} {error=}\n') return None - if result['out'] != '': - output = result['out'] - if sys.platform == 'win32': - # Convert to Unix line endings. - output = output.replace("\r", "") - return output - return None + + output = result['out'] + if output and sys.platform == 'win32': + # Convert to Unix line endings. + output = output.replace("\r", "") + return output diff --git a/tools/exec_util.py b/tools/exec_util.py index a6c23479b..4ad9bda3f 100644 --- a/tools/exec_util.py +++ b/tools/exec_util.py @@ -13,29 +13,25 @@ def exec_cmd(cmd, path, input_string=None): err = '' ret = -1 parts = cmd.split() - try: - if input_string is None: - process = Popen( - parts, - cwd=path, - stdout=PIPE, - stderr=PIPE, - shell=(sys.platform == 'win32')) - out, err = process.communicate() - ret = process.returncode - else: - process = Popen( - parts, - cwd=path, - stdin=PIPE, - stdout=PIPE, - stderr=PIPE, - shell=(sys.platform == 'win32')) - out, err = process.communicate(input=input_string) - ret = process.returncode - except IOError as e: - (errno, strerror) = e.args - raise - except: - raise + + if input_string is None: + process = Popen( + parts, + cwd=path, + stdout=PIPE, + stderr=PIPE, + shell=(sys.platform == 'win32')) + out, err = process.communicate() + ret = process.returncode + else: + process = Popen( + parts, + cwd=path, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + shell=(sys.platform == 'win32')) + out, err = process.communicate(input=input_string) + ret = process.returncode + return {'out': out.decode('utf-8'), 'err': err.decode('utf-8'), 'ret': ret} diff --git a/tools/make_api_versions_header.py b/tools/make_api_versions_header.py index 529c08798..8a4282b49 100644 --- a/tools/make_api_versions_header.py +++ b/tools/make_api_versions_header.py @@ -11,8 +11,8 @@ import sys def make_api_versions_header(json): - result = get_copyright(full=True, translator=False) + \ -"""// + result = get_copyright( + full=True, translator=False) + """// // --------------------------------------------------------------------------- // // This file was generated by the make_api_versions_header.py tool. Versions @@ -27,42 +27,40 @@ def make_api_versions_header(json): """ for version, hashes in json['hashes'].items(): - version_part = """ + version_part = f""" // $COMMENT$ -#define CEF_API_VERSION_$VER$ $VER$ -#define CEF_API_HASH_$VER$_UNIVERSAL "$UNIVERSAL$" +#define CEF_API_VERSION_{version} {version} #if defined(OS_WIN) -#define CEF_API_HASH_$VER$_PLATFORM "$WINDOWS$" +#define CEF_API_HASH_{version} "$WINDOWS$" #elif defined(OS_MAC) -#define CEF_API_HASH_$VER$_PLATFORM "$MAC$" +#define CEF_API_HASH_{version} "$MAC$" #elif defined(OS_LINUX) -#define CEF_API_HASH_$VER$_PLATFORM "$LINUX$" +#define CEF_API_HASH_{version} "$LINUX$" #endif -""".replace('$VER$', version) +""" # Substitute hash values for placeholders. for key, value in hashes.items(): - version_part = version_part.replace('$%s$' % key.upper(), value) + version_part = version_part.replace(f"${key.upper()}$", value) result += version_part - result += \ -""" + result += f""" // Oldest supported CEF version. -#define CEF_API_VERSION_MIN CEF_API_VERSION_$MIN$ +#define CEF_API_VERSION_MIN CEF_API_VERSION_{json['min']} // Newest supported CEF version. -#define CEF_API_VERSION_LAST CEF_API_VERSION_$LAST$ +#define CEF_API_VERSION_LAST CEF_API_VERSION_{json['last']} #endif // CEF_INCLUDE_CEF_API_VERSIONS_H_ -""".replace('$LAST$', json['last']).replace('$MIN$', json['min']) +""" return result def make_api_versions_inc(json): - result = get_copyright(full=False, translator=False) + \ -"""// + result = get_copyright( + full=False, translator=False) + """// // --------------------------------------------------------------------------- // // This file was generated by the make_api_versions_header.py tool. @@ -72,56 +70,53 @@ namespace { struct ApiVersionHash { int version; - const char* const universal; - const char* const platform; + const char* const hash; }; const ApiVersionHash kApiVersionHashes[] = {""" - for version, hashes in json['hashes'].items(): - result += """ - {$VER$, CEF_API_HASH_$VER$_UNIVERSAL, CEF_API_HASH_$VER$_PLATFORM},""".replace( - '$VER$', version) + for version in json['hashes'].keys(): + result += f"\n{{{version}, CEF_API_HASH_{version}}}," - result += \ -""" + result += """ }; -const size_t kApiVersionHashesSize = std::size(kApiVersionHashes); - } // namespace """ return result -def write_api_versions(out_header_file, out_inc_file, json): +def write_api_versions(out_header_file, out_inc_file, json) -> bool: + """ + Return True if the files were written, False if no changes were made. + """ out_file = os.path.abspath(out_header_file) result = make_api_versions_header(json) - if not bool(result): - sys.stderr.write('Failed to create %s\n' % out_file) + if not result: + sys.stderr.write(f'Failed to create {out_file}\n') sys.exit(1) - retval1 = write_file_if_changed(out_file, result) + header_write_result = write_file_if_changed(out_file, result) out_file = os.path.abspath(out_inc_file) result = make_api_versions_inc(json) - if not bool(result): - sys.stderr.write('Failed to create %s\n' % out_file) + if not result: + sys.stderr.write(f'Failed to create {out_file}\n') sys.exit(1) - retval2 = write_file_if_changed(out_file, result) + inc_write_result = write_file_if_changed(out_file, result) - return retval1 or retval2 + return header_write_result or inc_write_result def main(argv): if len(argv) < 5: print( - "Usage:\n %s " - % argv[0]) + f"Usage:\n {argv[0]} " + ) sys.exit(-1) - json, initialized = \ - read_version_files(argv[3], argv[4], True, combine=True) + json = read_version_files(argv[3], argv[4], initialize=True, combine=True)[0] + if not write_api_versions(argv[1], argv[2], json): print('Nothing done') diff --git a/tools/version_manager.py b/tools/version_manager.py index fb3f47b29..07d5cdb73 100644 --- a/tools/version_manager.py +++ b/tools/version_manager.py @@ -4,7 +4,8 @@ from __future__ import absolute_import from __future__ import print_function -from cef_api_hash import cef_api_hash +from typing import Dict +from cef_api_hash import CefApiHasher from cef_version import VersionFormatter from date_util import get_date from file_util import read_file, read_json_file, write_file, write_json_file @@ -41,49 +42,37 @@ def get_next_api_revision(api_versions_file, major_version): return 0 -_CALC = None - - -def compute_api_hashes(cpp_header_dir, api_version, next_allowed, debug_dir, - verbose): - """ Computes API hashes for the specified |api_version|. - """ - if not debug_dir is None: - debug_dir = os.path.join(debug_dir, api_version) - +def compute_api_hashes(api_version: str, + hasher: CefApiHasher, + next_allowed: bool) -> Dict[str, str]: + """ Computes API hashes for the specified |api_version|. """ if not next_allowed: # Next usage is banned with explicit API versions. - assert not api_version in UNTRACKED_VERSIONS, api_version + assert api_version not in UNTRACKED_VERSIONS, api_version added_defines = [ # Using CEF_API_VERSION_NEXT is an error. 'CEF_API_VERSION_NEXT="Please_specify_an_exact_CEF_version"', ] else: - added_defines = None + added_defines = [] - global _CALC - if _CALC is None: - _CALC = cef_api_hash(cpp_header_dir, verbose=verbose) - - hashes = _CALC.calculate(api_version, debug_dir, added_defines) - if bool(hashes): + hashes = hasher.calculate(api_version, added_defines) + if hashes: if api_version in UNTRACKED_VERSIONS: label = version_label(api_version) label = label[0:1].upper() + label[1:] - hashes['comment'] = '%s last updated %s.' % (label, get_date()) + hashes['comment'] = f'{label} last updated {get_date()}.' else: - hashes['comment'] = 'Added %s.' % get_date() + hashes['comment'] = f'Added {get_date()}.' return hashes def same_api_hashes(hashes1, hashes2): - for key in ('universal', 'linux', 'mac', 'windows'): - if hashes1[key] != hashes2[key]: - return False - return True + return all(hashes1[key] == hashes2[key] + for key in ['linux', 'mac', 'windows']) -def compute_next_api_verson(api_versions_file): +def compute_next_api_version(api_versions_file): """ Computes the next available API version number. """ major_version = int(VersionFormatter().get_chrome_major_version()) @@ -207,8 +196,11 @@ def find_replace_next_usage(cpp_header_dir, next_version): return 0 -def exec_apply(cpp_header_dir, api_versions_file, api_untracked_file, - next_version, debug_dir, apply_next, verbose): +def exec_apply(api_versions_file, + api_untracked_file, + next_version, + apply_next, + hasher: CefApiHasher) -> int: """ Updates untracked API hashes if necessary. Saves the hash for the next API version if |apply_next| is true. """ @@ -222,9 +214,8 @@ def exec_apply(cpp_header_dir, api_versions_file, api_untracked_file, untracked_changed = False for version in UNTRACKED_VERSIONS: label = version_label(version) - hashes = compute_api_hashes(cpp_header_dir, version, True, debug_dir, - verbose) - if not bool(hashes): + hashes = compute_api_hashes(version, hasher, next_allowed=True) + if not hashes: sys.stderr.write('ERROR: Failed to process %s\n' % label) return 1 @@ -240,9 +231,8 @@ def exec_apply(cpp_header_dir, api_versions_file, api_untracked_file, if apply_next: next_label = version_label(next_version) - hashes = compute_api_hashes(cpp_header_dir, next_version, False, debug_dir, - verbose) - if not bool(hashes): + hashes = compute_api_hashes(next_version, hasher, next_allowed=False) + if not hashes: sys.stderr.write('ERROR: Failed to process %s\n' % next_label) return 1 @@ -278,29 +268,33 @@ def exec_apply(cpp_header_dir, api_versions_file, api_untracked_file, return 0 -def exec_check(cpp_header_dir, api_versions_file, api_untracked_file, debug_dir, - fast_check, force_update, skip_untracked, verbose): +def exec_check(api_versions_file, + api_untracked_file, + fast_check, + force_update, + skip_untracked, + hasher: CefApiHasher) -> int: """ Checks existing API version hashes. Resaves all API hashes if |force_update| is true. Otherwise, hash changes are considered an error. """ assert not (fast_check and force_update) - json_versions, json_untracked, initialized = \ - read_version_files(api_versions_file, api_untracked_file, False) + json_versions, json_untracked, initialized = read_version_files( + api_versions_file, api_untracked_file, initialize=False) assert not initialized versions = [] len_versioned_existing = len_versioned_checked = len_versioned_failed = 0 len_untracked_existing = len_untracked_checked = len_untracked_failed = 0 - if not json_versions is None: + if json_versions is not None: keys = json_versions['hashes'].keys() len_versioned_existing = len(keys) if len_versioned_existing > 0: if fast_check: # Only checking a subset of versions. - for key in ('last', 'min'): + for key in ['last', 'min']: if key in json_versions: version = json_versions[key] assert version in json_versions['hashes'], version @@ -310,14 +304,14 @@ def exec_check(cpp_header_dir, api_versions_file, api_untracked_file, debug_dir, versions.extend(keys) len_versioned_checked = len_versioned_existing - if not json_untracked is None: + if json_untracked is not None: keys = json_untracked['hashes'].keys() len_untracked_existing = len(keys) if len_untracked_existing > 0 and not skip_untracked: versions.extend(keys) len_untracked_checked = len_untracked_existing - if len(versions) == 0: + if not versions: print('No hashes to check.') return 0 @@ -331,8 +325,7 @@ def exec_check(cpp_header_dir, api_versions_file, api_untracked_file, debug_dir, else: stored_hashes = json_versions['hashes'][version] label = version_label(version) - computed_hashes = compute_api_hashes(cpp_header_dir, version, True, - debug_dir, verbose) + computed_hashes = compute_api_hashes(version, hasher, next_allowed=True) if not bool(computed_hashes): sys.stderr.write('ERROR: Failed to process %s\n' % label) return 1 @@ -417,7 +410,7 @@ https://bitbucket.org/chromiumembedded/cef/wiki/ApiVersioning.md parser = CustomParser(description=desc, epilog=epilog) parser.add_option( '--debug-dir', - dest='debugdir', + dest='debug_dir', metavar='DIR', help='intermediate directory for easy debugging') parser.add_option( @@ -477,7 +470,7 @@ https://bitbucket.org/chromiumembedded/cef/wiki/ApiVersioning.md parser.add_option( '--force-update', action='store_true', - dest='forceupdate', + dest='force_update', default=False, help='force update all API hashes (use with -c)') (options, args) = parser.parse_args() @@ -501,7 +494,7 @@ https://bitbucket.org/chromiumembedded/cef/wiki/ApiVersioning.md parser.print_help(sys.stdout) sys.exit(1) - next_version = compute_next_api_verson(api_versions_file) + next_version = compute_next_api_version(api_versions_file) if next_version is None: sys.exit(1) @@ -527,17 +520,13 @@ https://bitbucket.org/chromiumembedded/cef/wiki/ApiVersioning.md changed = translate(cef_dir, verbose=options.verbose) > 0 skip_untracked = False + hasher = CefApiHasher(cpp_header_dir, options.debug_dir, options.verbose) + if options.update or will_apply_next or changed or not os.path.isfile( api_untracked_file): skip_untracked = True - if exec_apply( - cpp_header_dir, - api_versions_file, - api_untracked_file, - next_version, - options.debugdir, - apply_next=options.apply, - verbose=options.verbose) > 0: + if exec_apply(api_versions_file, api_untracked_file, next_version, + options.apply, hasher) > 0: # Apply failed. sys.exit(1) elif not options.check: @@ -545,12 +534,6 @@ https://bitbucket.org/chromiumembedded/cef/wiki/ApiVersioning.md sys.exit(0) sys.exit( - exec_check( - cpp_header_dir, - api_versions_file, - api_untracked_file, - options.debugdir, - options.fastcheck and not options.forceupdate, - options.check and options.forceupdate, - skip_untracked, - verbose=options.verbose)) + exec_check(api_versions_file, api_untracked_file, options.fastcheck and + not options.force_update, options.check and + options.force_update, skip_untracked, hasher)) diff --git a/tools/version_util.py b/tools/version_util.py index 9ad17f567..ef6f4063a 100644 --- a/tools/version_util.py +++ b/tools/version_util.py @@ -92,7 +92,7 @@ def read_version_files(api_versions_file, } initialized = True else: - json_version = None + json_versions = None else: assert 'hashes' in json_versions, api_versions_file assert 'last' in json_versions, api_versions_file