Files
cef/tools/translator.py
Marshall Greenblatt dd81904a2f 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).
2025-01-08 17:19:43 -05:00

356 lines
12 KiB
Python

# Copyright (c) 2009 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.
from __future__ import absolute_import
from cef_parser import obj_header
from cef_version import VersionFormatter
from clang_util import clang_format
from file_util import *
import hashlib
from make_capi_header import write_capi_header
from make_capi_versions_header import write_capi_versions_header
from make_cpptoc_header import write_cpptoc_header
from make_cpptoc_impl import write_cpptoc_impl
from make_ctocpp_header import write_ctocpp_header
from make_ctocpp_impl import write_ctocpp_impl
from make_gypi_file import write_gypi_file
from make_libcef_dll_dylib_impl import write_libcef_dll_dylib_impl
from make_wrapper_types_header import write_wrapper_types_header
from optparse import OptionParser
import sys
FILE_HEADER = """#
# This file was generated by the CEF translator tool and should not edited
# by hand.
#
# $hash=$$HASH$$$
#
"""
def _write_version():
return FILE_HEADER + VersionFormatter().get_version_string()
def _write_gitignore(gitignore, gitignore_file, root_dir):
contents = FILE_HEADER
in_file = gitignore_file + '.in'
if os.path.isfile(in_file):
contents += read_file(in_file)
# Include ourselves in generated files.
gitignore.append(gitignore_file)
root_dir_len = len(root_dir)
contents += '\n'.join(
[p[root_dir_len:].replace('\\', '/') for p in sorted(gitignore)])
return contents
def _update_file(file, newcontents, customized, force, clean, backup,
gitignore):
""" Replaces the contents of |file| with |newcontents| if necessary. """
if clean:
if not customized:
return 1 if remove_file(file, quiet=False) else 0
print('File %s has customizations and will not be removed' % file)
return 0
if not customized and not gitignore is None:
gitignore.append(file)
oldcontents = ''
oldhash = ''
if newcontents[-1:] != "\n":
# Add newline at end of file.
newcontents += "\n"
# clang-format is slow so we don't want to apply it if the pre-formatted
# content hasn't changed. To check for changes we embed a hash of the pre-
# formatted content in the resulting file.
hash_start = "$hash="
hash_end = "$"
hash_token = "$$HASH$$"
if not force and path_exists(file):
oldcontents = read_file(file)
# Extract the existing hash.
start = oldcontents.find(hash_start)
if start > 0:
end = oldcontents.find(hash_end, start + len(hash_start))
if end > 0:
oldhash = oldcontents[start + len(hash_start):end]
# Compute the new hash.
newhash = hashlib.sha1(newcontents.encode('utf-8')).hexdigest()
if oldhash == newhash:
# Pre-formatted contents have not changed.
return 0
newcontents = newcontents.replace(hash_token, newhash, 1)
# Apply clang-format for C/C++ files. This is slow, so we only do it for
# customized files.
if customized and os.path.splitext(file)[1][1:] in ('c', 'cc', 'cpp', 'h'):
result = clang_format(file, newcontents)
if result != None:
newcontents = result
else:
raise Exception("Call to clang-format failed for %s" % file)
if backup and oldcontents != '':
backup_file(file)
filedir = os.path.split(file)[0]
if not os.path.isdir(filedir):
make_dir(filedir)
print('Writing file %s' % file)
write_file(file, newcontents)
return 1
def translate(cef_dir,
force=False,
clean=False,
backup=False,
verbose=False,
selected_classes=None):
# determine the paths
root_dir = os.path.abspath(cef_dir)
cpp_header_dir = os.path.join(root_dir, 'include')
cpp_header_test_dir = os.path.join(cpp_header_dir, 'test')
cpp_header_views_dir = os.path.join(cpp_header_dir, 'views')
capi_header_dir = os.path.join(cpp_header_dir, 'capi')
libcef_dll_dir = os.path.join(root_dir, 'libcef_dll')
cpptoc_global_impl = os.path.join(libcef_dll_dir, 'libcef_dll.cc')
ctocpp_global_impl = os.path.join(libcef_dll_dir, 'wrapper',
'libcef_dll_wrapper.cc')
wrapper_types_header = os.path.join(libcef_dll_dir, 'wrapper_types.h')
cpptoc_dir = os.path.join(libcef_dll_dir, 'cpptoc')
ctocpp_dir = os.path.join(libcef_dll_dir, 'ctocpp')
gypi_file = os.path.join(root_dir, 'cef_paths.gypi')
libcef_dll_dylib_impl = os.path.join(libcef_dll_dir, 'wrapper',
'libcef_dll_dylib.cc')
version_file = os.path.join(root_dir, 'VERSION.stamp')
gitignore_file = os.path.join(root_dir, '.gitignore')
# make sure the header directory exists
if not path_exists(cpp_header_dir):
sys.stderr.write('ERROR: Directory ' + cpp_header_dir +
' does not exist.\n')
sys.exit(1)
# create the header object
if verbose:
print('Parsing C++ headers from ' + cpp_header_dir + '...')
header = obj_header()
# add include files to be processed
header.set_root_directory(cpp_header_dir)
excluded_files = [
'cef_api_hash.h', 'cef_application_mac.h', 'cef_version_info.h'
]
header.add_directory(cpp_header_dir, excluded_files)
header.add_directory(cpp_header_test_dir)
header.add_directory(cpp_header_views_dir)
# Track the number of files that were written.
writect = 0
# Track files that are not customized.
gitignore = []
debug_string = ''
try:
# output the C API header
if verbose:
print('In C API header directory ' + capi_header_dir + '...')
filenames = sorted(header.get_file_names())
for filename in filenames:
if verbose:
print('Generating ' + filename + ' C API headers...')
debug_string = 'CAPI header for ' + filename
writect += _update_file(*write_capi_header(header, capi_header_dir,
filename), False, force, clean,
backup, gitignore)
debug_string = 'CAPI versions header for ' + filename
writect += _update_file(*write_capi_versions_header(
header, capi_header_dir, filename), False, force, clean, backup,
gitignore)
# output the wrapper types header
if verbose:
print('Generating wrapper types header...')
debug_string = 'wrapper types header'
writect += _update_file(*write_wrapper_types_header(
header, wrapper_types_header), False, force, clean, backup, gitignore)
# build the list of classes to parse
allclasses = header.get_class_names()
if not selected_classes is None:
for cls in selected_classes:
if not cls in allclasses:
sys.stderr.write('ERROR: Unknown class: %s\n' % cls)
sys.exit(1)
classes = selected_classes
else:
classes = allclasses
classes = sorted(classes)
# output CppToC global file
if verbose:
print('Generating CppToC global implementation...')
debug_string = 'CppToC global implementation'
writect += _update_file(*write_cpptoc_impl(
header, None, cpptoc_global_impl), force, clean, backup, gitignore)
# output CToCpp global file
if verbose:
print('Generating CToCpp global implementation...')
debug_string = 'CToCpp global implementation'
writect += _update_file(*write_ctocpp_impl(
header, None, ctocpp_global_impl), force, clean, backup, gitignore)
# output CppToC class files
if verbose:
print('In CppToC directory ' + cpptoc_dir + '...')
for cls in classes:
if verbose:
print('Generating ' + cls + 'CppToC class header...')
debug_string = 'CppToC class header for ' + cls
writect += _update_file(*write_cpptoc_header(header, cls, cpptoc_dir),
False, force, clean, backup, gitignore)
if verbose:
print('Generating ' + cls + 'CppToC class implementation...')
debug_string = 'CppToC class implementation for ' + cls
writect += _update_file(*write_cpptoc_impl(header, cls, cpptoc_dir),
force, clean, backup, gitignore)
# output CppToC class files
if verbose:
print('In CToCpp directory ' + ctocpp_dir + '...')
for cls in classes:
if verbose:
print('Generating ' + cls + 'CToCpp class header...')
debug_string = 'CToCpp class header for ' + cls
writect += _update_file(*write_ctocpp_header(header, cls, ctocpp_dir),
False, force, clean, backup, gitignore)
if verbose:
print('Generating ' + cls + 'CToCpp class implementation...')
debug_string = 'CToCpp class implementation for ' + cls
writect += _update_file(*write_ctocpp_impl(header, cls, ctocpp_dir),
force, clean, backup, gitignore)
# output the gypi file
if verbose:
print('Generating ' + gypi_file + ' file...')
debug_string = gypi_file
writect += _update_file(*write_gypi_file(header, gypi_file), False, force,
clean, backup, gitignore)
# output the libcef dll dylib file
if verbose:
print('Generating ' + libcef_dll_dylib_impl + ' file...')
debug_string = libcef_dll_dylib_impl
writect += _update_file(*write_libcef_dll_dylib_impl(
header, libcef_dll_dylib_impl), False, force, clean, backup, gitignore)
# output the VERSION.stamp file that triggers cef_version.h regen at build time
if verbose:
print('Generating ' + version_file + ' file...')
debug_string = version_file
writect += _update_file(version_file,
_write_version(), False, force, clean, backup,
gitignore)
# output the top-level .gitignore file that lists uncustomized files
if verbose:
print('Generating ' + gitignore_file + ' file...')
debug_string = gitignore_file
writect += _update_file(gitignore_file,
_write_gitignore(gitignore, gitignore_file,
root_dir), False, force, clean,
backup, None)
except (AssertionError, Exception) as e:
sys.stderr.write('ERROR: while processing %s\n' % debug_string)
raise
if verbose or writect > 0:
print('Done translating - %s %d files.' % ('Removed'
if clean else 'Wrote', writect))
return writect
if __name__ == "__main__":
from optparse import OptionParser
# parse command-line options
disc = """
This utility generates files for the CEF C++ to C API translation layer.
"""
parser = OptionParser(description=disc)
parser.add_option(
'--root-dir',
dest='rootdir',
metavar='DIR',
help='CEF root directory [required]')
parser.add_option(
'--backup',
action='store_true',
dest='backup',
default=False,
help='create a backup of modified files')
parser.add_option(
'--force',
action='store_true',
dest='force',
default=False,
help='force rewrite of the file')
parser.add_option(
'--clean',
action='store_true',
dest='clean',
default=False,
help='clean all files without custom modifications')
parser.add_option(
'-c',
'--classes',
dest='classes',
action='append',
help='only translate the specified classes')
parser.add_option(
'-v',
'--verbose',
action='store_true',
dest='verbose',
default=False,
help='output detailed status information')
(options, args) = parser.parse_args()
# the rootdir option is required
if options.rootdir is None:
parser.print_help(sys.stdout)
sys.exit()
if translate(options.rootdir, options.force, options.clean, options.backup,
options.verbose, options.classes) == 0:
if not options.verbose:
print('Nothing to do.')
elif not options.clean:
print('WARNING: You must run version_manager.py to update API hashes.')