Add initial support for API versioning (see #3836)

- Generated files are now created when running cef_create_projects or
  the new version_manager.py tool. These files are still created in the
  cef/ source tree (same location as before) but Git ignores them due to
  the generated .gitignore file.
- API hashes are committed to Git as a new cef_api_versions.json file.
  This file is used for both code generation and CEF version calculation
  (replacing the previous usage of cef_api_hash.h for this purpose).
  It will be updated by the CEF admin before merging breaking API
  changes upstream.
- As an added benefit to the above, contributor PRs will no longer
  contain generated code that is susceptible to frequent merge conflicts.
- From a code generation perspective, the main difference is that we now
  use versioned structs (e.g. cef_browser_0_t instead of cef_browser_t)
  on the libcef (dll/framework) side. Most of the make_*.py tool changes
  are related to supporting this.
- From the client perspective, you can now define CEF_API_VERSION in the
  project configuration (or get CEF_EXPERIMENTAL by default). This
  define will change the API exposed in CEF’s include/ and include/capi
  header files. All client-side targets including libcef_dll_wrapper
  will need be recompiled when changing this define.
- Examples of the new API-related define usage are provided in
  cef_api_version_test.h, api_version_test_impl.cc and
  api_version_unittest.cc.

To test:
- Run `ceftests --gtest_filter=ApiVersionTest.*`
- Add `cef_api_version=13300` to GN_DEFINES. Re-run configure, build and
  ceftests steps.
- Repeat with 13301, 13302, 13303 (all supported test versions).
This commit is contained in:
Marshall Greenblatt
2024-12-09 15:20:44 -05:00
parent 219bf3406c
commit dd81904a2f
68 changed files with 7466 additions and 1265 deletions

View File

@ -3,35 +3,45 @@
# can be found in the LICENSE file.
from __future__ import absolute_import
import codecs
import fnmatch
from glob import iglob
from io import open
import json
import os
import fnmatch
import shutil
import sys
import time
def read_file(name, normalize=True):
def read_file(path, normalize=True):
""" Read a file. """
try:
with open(name, 'r', encoding='utf-8') as f:
# read the data
data = f.read()
if normalize:
# normalize line endings
data = data.replace("\r\n", "\n")
return data
except IOError as e:
(errno, strerror) = e.args
sys.stderr.write('Failed to read file ' + name + ': ' + strerror)
raise
if os.path.isfile(path):
try:
with open(path, 'r', encoding='utf-8') as f:
# read the data
data = f.read()
if normalize:
# normalize line endings
data = data.replace("\r\n", "\n")
return data
except IOError as e:
(errno, strerror) = e.args
sys.stderr.write('ERROR: Failed to read file ' + path + ': ' + strerror)
raise
return None
def write_file(name, data):
def write_file(path, data, overwrite=True, quiet=True):
""" Write a file. """
if not overwrite and os.path.exists(path):
return False
if not quiet:
print('Writing file %s' % path)
try:
with open(name, 'w', encoding='utf-8', newline='\n') as f:
with open(path, 'w', encoding='utf-8', newline='\n') as f:
# write the data
if sys.version_info.major == 2:
f.write(data.decode('utf-8'))
@ -39,57 +49,67 @@ def write_file(name, data):
f.write(data)
except IOError as e:
(errno, strerror) = e.args
sys.stderr.write('Failed to write file ' + name + ': ' + strerror)
sys.stderr.write('ERROR: Failed to write file ' + path + ': ' + strerror)
raise
return True
def path_exists(name):
def path_exists(path):
""" Returns true if the path currently exists. """
return os.path.exists(name)
return os.path.exists(path)
def write_file_if_changed(name, data):
def write_file_if_changed(path, data, quiet=True):
""" Write a file if the contents have changed. Returns True if the file was written. """
if path_exists(name):
old_contents = read_file(name)
if path_exists(path):
old_contents = read_file(path)
assert not old_contents is None, path
else:
old_contents = ''
if (data != old_contents):
write_file(name, data)
write_file(path, data, quiet=quiet)
return True
return False
def backup_file(name):
def backup_file(path):
""" Rename the file to a name that includes the current time stamp. """
move_file(name, name + '.' + time.strftime('%Y-%m-%d-%H-%M-%S'))
move_file(path, path + '.' + time.strftime('%Y-%m-%d-%H-%M-%S'))
def copy_file(src, dst, quiet=True):
""" Copy a file. """
if not os.path.isfile(src):
return False
try:
shutil.copy2(src, dst)
if not quiet:
sys.stdout.write('Transferring ' + src + ' file.\n')
shutil.copy2(src, dst)
except IOError as e:
(errno, strerror) = e.args
sys.stderr.write('Failed to copy file from ' + src + ' to ' + dst + ': ' +
strerror)
sys.stderr.write('ERROR: Failed to copy file from ' + src + ' to ' + dst +
': ' + strerror)
raise
return True
def move_file(src, dst, quiet=True):
""" Move a file. """
if not os.path.isfile(src):
return False
try:
shutil.move(src, dst)
if not quiet:
sys.stdout.write('Moving ' + src + ' file.\n')
print('Moving ' + src + ' file.')
shutil.move(src, dst)
except IOError as e:
(errno, strerror) = e.args
sys.stderr.write('Failed to move file from ' + src + ' to ' + dst + ': ' +
strerror)
sys.stderr.write('ERROR: Failed to move file from ' + src + ' to ' + dst +
': ' + strerror)
raise
return True
def copy_files(src_glob, dst_folder, quiet=True):
@ -102,56 +122,62 @@ def copy_files(src_glob, dst_folder, quiet=True):
copy_file(fname, dst, quiet)
def remove_file(name, quiet=True):
def remove_file(path, quiet=True):
""" Remove the specified file. """
try:
if path_exists(name):
os.remove(name)
if path_exists(path):
if not quiet:
sys.stdout.write('Removing ' + name + ' file.\n')
print('Removing ' + path + ' file.')
os.remove(path)
return True
except IOError as e:
(errno, strerror) = e.args
sys.stderr.write('Failed to remove file ' + name + ': ' + strerror)
sys.stderr.write('ERROR: Failed to remove file ' + path + ': ' + strerror)
raise
return False
def copy_dir(src, dst, quiet=True):
""" Copy a directory tree. """
try:
remove_dir(dst, quiet)
shutil.copytree(src, dst)
if not quiet:
sys.stdout.write('Transferring ' + src + ' directory.\n')
print('Transferring ' + src + ' directory.')
shutil.copytree(src, dst)
except IOError as e:
(errno, strerror) = e.args
sys.stderr.write('Failed to copy directory from ' + src + ' to ' + dst +
': ' + strerror)
sys.stderr.write('ERROR: Failed to copy directory from ' + src + ' to ' +
dst + ': ' + strerror)
raise
def remove_dir(name, quiet=True):
def remove_dir(path, quiet=True):
""" Remove the specified directory. """
try:
if path_exists(name):
shutil.rmtree(name)
if path_exists(path):
if not quiet:
sys.stdout.write('Removing ' + name + ' directory.\n')
print('Removing ' + path + ' directory.')
shutil.rmtree(path)
return True
except IOError as e:
(errno, strerror) = e.args
sys.stderr.write('Failed to remove directory ' + name + ': ' + strerror)
sys.stderr.write('ERROR: Failed to remove directory ' + path + ': ' +
strerror)
raise
return False
def make_dir(name, quiet=True):
def make_dir(path, quiet=True):
""" Create the specified directory. """
try:
if not path_exists(name):
if not path_exists(path):
if not quiet:
sys.stdout.write('Creating ' + name + ' directory.\n')
os.makedirs(name)
print('Creating ' + path + ' directory.')
os.makedirs(path)
except IOError as e:
(errno, strerror) = e.args
sys.stderr.write('Failed to create directory ' + name + ': ' + strerror)
sys.stderr.write('ERROR: Failed to create directory ' + path + ': ' +
strerror)
raise
@ -180,13 +206,17 @@ def get_files_recursive(directory, pattern):
yield filename
def read_version_file(file, args):
def read_version_file(path, args):
""" Read and parse a version file (key=value pairs, one per line). """
lines = read_file(file).split("\n")
contents = read_file(path)
if contents is None:
return False
lines = contents.split("\n")
for line in lines:
parts = line.split('=', 1)
if len(parts) == 2:
args[parts[0]] = parts[1]
return True
def eval_file(src):
@ -199,3 +229,48 @@ def normalize_path(path):
if sys.platform == 'win32':
return path.replace('\\', '/')
return path
def read_json_file(path):
""" Read and parse a JSON file. Returns a list/dictionary or None. """
if os.path.isfile(path):
try:
with codecs.open(path, 'r', encoding='utf-8') as fp:
return json.load(fp)
except IOError as e:
(errno, strerror) = e.args
sys.stderr.write('ERROR: Failed to read file ' + path + ': ' + strerror)
raise
return None
def _bytes_encoder(z):
if isinstance(z, bytes):
return str(z, 'utf-8')
else:
type_name = z.__class__.__name__
raise TypeError(f"Object of type {type_name} is not serializable")
def write_json_file(path, data, overwrite=True, quiet=True):
""" Serialize and write a JSON file. Returns True on success. """
if not overwrite and os.path.exists(path):
return False
if not quiet:
print('Writing file %s' % path)
try:
with open(path, 'w', encoding='utf-8') as fp:
json.dump(
data,
fp,
ensure_ascii=False,
indent=2,
sort_keys=True,
default=_bytes_encoder)
except IOError as e:
(errno, strerror) = e.args
sys.stderr.write('ERROR: Failed to write file ' + path + ': ' + strerror)
raise
return True