mirror of
https://bitbucket.org/chromiumembedded/cef
synced 2025-06-05 21:39:12 +02:00
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:
@ -3,111 +3,66 @@
|
||||
# can be found in the LICENSE file.
|
||||
|
||||
from __future__ import absolute_import
|
||||
import sys
|
||||
from cef_parser 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_api_hash_header import *
|
||||
from make_capi_header import *
|
||||
from make_cpptoc_header import *
|
||||
from make_cpptoc_impl import *
|
||||
from make_ctocpp_header import *
|
||||
from make_ctocpp_impl import *
|
||||
from make_gypi_file import *
|
||||
from make_libcef_dll_dylib_impl import *
|
||||
from make_wrapper_types_header import *
|
||||
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
|
||||
|
||||
# cannot be loaded as a module
|
||||
if __name__ != "__main__":
|
||||
sys.stderr.write('This file cannot be loaded as a module!')
|
||||
sys.exit()
|
||||
FILE_HEADER = """#
|
||||
# This file was generated by the CEF translator tool and should not edited
|
||||
# by hand.
|
||||
#
|
||||
# $hash=$$HASH$$$
|
||||
#
|
||||
|
||||
# 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(
|
||||
'-c',
|
||||
'--classes',
|
||||
dest='classes',
|
||||
action='append',
|
||||
help='only translate the specified classes')
|
||||
parser.add_option(
|
||||
'-q',
|
||||
'--quiet',
|
||||
action='store_true',
|
||||
dest='quiet',
|
||||
default=False,
|
||||
help='do not 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()
|
||||
|
||||
# determine the paths
|
||||
root_dir = os.path.abspath(options.rootdir)
|
||||
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')
|
||||
api_hash_header = os.path.join(cpp_header_dir, 'cef_api_hash.h')
|
||||
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')
|
||||
|
||||
# make sure the header directory exists
|
||||
if not path_exists(cpp_header_dir):
|
||||
sys.stderr.write('Directory ' + cpp_header_dir + ' does not exist.')
|
||||
sys.exit()
|
||||
|
||||
# create the header object
|
||||
if not options.quiet:
|
||||
sys.stdout.write('Parsing C++ headers from ' + cpp_header_dir + '...\n')
|
||||
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.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
|
||||
def _write_version():
|
||||
return FILE_HEADER + VersionFormatter().get_version_string()
|
||||
|
||||
|
||||
def update_file(file, newcontents):
|
||||
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 = ''
|
||||
|
||||
@ -122,7 +77,7 @@ def update_file(file, newcontents):
|
||||
hash_end = "$"
|
||||
hash_token = "$$HASH$$"
|
||||
|
||||
if not options.force and path_exists(file):
|
||||
if not force and path_exists(file):
|
||||
oldcontents = read_file(file)
|
||||
|
||||
# Extract the existing hash.
|
||||
@ -137,106 +92,264 @@ def update_file(file, newcontents):
|
||||
|
||||
if oldhash == newhash:
|
||||
# Pre-formatted contents have not changed.
|
||||
return
|
||||
return 0
|
||||
|
||||
newcontents = newcontents.replace(hash_token, newhash, 1)
|
||||
|
||||
# Apply clang-format for C/C++ files.
|
||||
if os.path.splitext(file)[1][1:] in ('c', 'cc', 'cpp', 'h'):
|
||||
# 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")
|
||||
raise Exception("Call to clang-format failed for %s" % file)
|
||||
|
||||
if options.backup and oldcontents != '':
|
||||
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)
|
||||
|
||||
global writect
|
||||
writect += 1
|
||||
return 1
|
||||
|
||||
|
||||
# output the C API header
|
||||
if not options.quiet:
|
||||
sys.stdout.write('In C API header directory ' + capi_header_dir + '...\n')
|
||||
filenames = sorted(header.get_file_names())
|
||||
for filename in filenames:
|
||||
if not options.quiet:
|
||||
sys.stdout.write('Generating ' + filename + ' C API header...\n')
|
||||
update_file(*write_capi_header(header, capi_header_dir, filename))
|
||||
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')
|
||||
|
||||
# output the wrapper types header
|
||||
if not options.quiet:
|
||||
sys.stdout.write('Generating wrapper types header...\n')
|
||||
update_file(*write_wrapper_types_header(header, wrapper_types_header))
|
||||
# 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)
|
||||
|
||||
# build the list of classes to parse
|
||||
allclasses = header.get_class_names()
|
||||
if not options.classes is None:
|
||||
for cls in options.classes:
|
||||
if not cls in allclasses:
|
||||
sys.stderr.write('ERROR: Unknown class: ' + cls)
|
||||
sys.exit()
|
||||
classes = options.classes
|
||||
else:
|
||||
classes = allclasses
|
||||
# create the header object
|
||||
if verbose:
|
||||
print('Parsing C++ headers from ' + cpp_header_dir + '...')
|
||||
header = obj_header()
|
||||
|
||||
classes = sorted(classes)
|
||||
# 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)
|
||||
|
||||
# output CppToC global file
|
||||
if not options.quiet:
|
||||
sys.stdout.write('Generating CppToC global implementation...\n')
|
||||
update_file(*write_cpptoc_impl(header, None, cpptoc_global_impl))
|
||||
# Track the number of files that were written.
|
||||
writect = 0
|
||||
|
||||
# output CToCpp global file
|
||||
if not options.quiet:
|
||||
sys.stdout.write('Generating CToCpp global implementation...\n')
|
||||
update_file(*write_ctocpp_impl(header, None, ctocpp_global_impl))
|
||||
# Track files that are not customized.
|
||||
gitignore = []
|
||||
|
||||
# output CppToC class files
|
||||
if not options.quiet:
|
||||
sys.stdout.write('In CppToC directory ' + cpptoc_dir + '...\n')
|
||||
for cls in classes:
|
||||
if not options.quiet:
|
||||
sys.stdout.write('Generating ' + cls + 'CppToC class header...\n')
|
||||
update_file(*write_cpptoc_header(header, cls, cpptoc_dir))
|
||||
if not options.quiet:
|
||||
sys.stdout.write('Generating ' + cls + 'CppToC class implementation...\n')
|
||||
update_file(*write_cpptoc_impl(header, cls, cpptoc_dir))
|
||||
debug_string = ''
|
||||
|
||||
# output CppToC class files
|
||||
if not options.quiet:
|
||||
sys.stdout.write('In CToCpp directory ' + ctocpp_dir + '...\n')
|
||||
for cls in classes:
|
||||
if not options.quiet:
|
||||
sys.stdout.write('Generating ' + cls + 'CToCpp class header...\n')
|
||||
update_file(*write_ctocpp_header(header, cls, ctocpp_dir))
|
||||
if not options.quiet:
|
||||
sys.stdout.write('Generating ' + cls + 'CToCpp class implementation...\n')
|
||||
update_file(*write_ctocpp_impl(header, cls, ctocpp_dir))
|
||||
try:
|
||||
|
||||
# output the gypi file
|
||||
if not options.quiet:
|
||||
sys.stdout.write('Generating ' + gypi_file + ' file...\n')
|
||||
update_file(*write_gypi_file(header, gypi_file))
|
||||
# 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 libcef dll dylib file
|
||||
if not options.quiet:
|
||||
sys.stdout.write('Generating ' + libcef_dll_dylib_impl + ' file...\n')
|
||||
update_file(*write_libcef_dll_dylib_impl(header, libcef_dll_dylib_impl))
|
||||
# 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)
|
||||
|
||||
# Update the API hash header file if necessary. This must be done last because
|
||||
# it reads files that were potentially written by proceeding operations.
|
||||
if not options.quiet:
|
||||
sys.stdout.write('Generating API hash header...\n')
|
||||
if write_api_hash_header(api_hash_header, cpp_header_dir):
|
||||
writect += 1
|
||||
# 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
|
||||
|
||||
if not options.quiet:
|
||||
sys.stdout.write('Done - Wrote ' + str(writect) + ' files.\n')
|
||||
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.')
|
||||
|
Reference in New Issue
Block a user