# Copyright (c) 2017 CEF Python, see the Authors file.
# All rights reserved. Licensed under BSD 3-clause license.
# Project website: https://github.com/cztomczak/cefpython
"""
Build the cefpython module, install package and run example.
Before running you must first put cefpython ready CEF binaries and
libraries in the cefpython/build/ directory (create if doesn't exist).
You have two options for obtaining these binaries and libraries.
Option 1: Download upstream CEF binaries and libraries from cefpython
GitHub Releases page. These binaries are tagged eg. "v55-upstream".
Extract the archive so that for example you have such a directory:
cefpython/build/cef55_3.2883.1553.g80bd606_win32/ .
Option 2: Use the automate.py tool. With this tool you can build CEF
from sources or use ready binaries from Spotify Automated Builds.
Usage:
build.py VERSION [--rebuild-cpp] [--fast] [--clean] [--kivy]
[--hello-world]
Options:
VERSION Version number eg. 50.0
--no-run-examples Do not run examples after build, only unit tests
--fast Fast mode
--clean Clean C++ projects build files on Linux/Mac
--kivy Run only Kivy example
--hello-world Run only Hello World example
"""
# --rebuild-cpp Force rebuild of .vcproj C++ projects (DISABLED)
# NOTE: When passing string command to subprocess functions you must
# always use shell=True, otherwise on Linux error is thrown:
# "No such file or directory". Always pass string commands to
# subprocess functions with shell=True. If you pass a list of
# arguments instead, then on Linux a "Segmentation fault" error
# message is not shown. When passing a list of args to subprocess
# function then you can't pass shell=True on Linux. If you pass
# then it will execute args[0] and ignore others args.
# NOTE 2: When calling os.system() returned value may be 256 when eg.
# when running unit tests fail. If you pass 256 to sys.exit
# an undefined result will occur. In my case on Linux it caused
# that other script that called it sys.exit(256) was interpreted
# for the script execute successfully. So never pass to sys.exit
# a value returned from os.system. Check the value and call
# sys.exit(1).
# How to debug on Linux (OLD unsupported).
# 1. Install "python-dbg" package
# 2. Install "python-wxgtk2.8-dbg" package
# 3. Run "python compile.py debug"
# 4. In cygdb type "cy run"
# 5. To display debug backtrace type "cy bt"
# 6. More commands: http://docs.cython.org/src/userguide/debugging.html
from common import *
import sys
import os
import glob
import shutil
import subprocess
import re
# raw_input() was renamed to input() in Python 3
try:
# noinspection PyUnresolvedReferences
# noinspection PyShadowingBuiltins
input = raw_input
except NameError:
pass
# Command line args variables
VERSION = ""
NO_RUN_EXAMPLES = False
DEBUG_FLAG = False
FAST_FLAG = False
CLEAN_FLAG = False
KIVY_FLAG = False
HELLO_WORLD_FLAG = False
REBUILD_CPP = False
# First run
FIRST_RUN = False
def main():
command_line_args()
print("[build.py] Python version: {ver} {arch}"
.format(ver=platform.python_version(), arch=ARCH_STR))
print("[build.py] Python executable: %s" % sys.executable)
print("[build.py] PYVERSION = %s" % PYVERSION)
print("[build.py] OS_POSTFIX2 = %s" % OS_POSTFIX2)
check_cython_version()
check_directories()
setup_environ()
if os.path.exists(CEFPYTHON_API_HFILE):
fix_cefpython_api_header_file()
if WINDOWS:
compile_cpp_projects_with_setuptools()
elif MAC or LINUX:
compile_cpp_projects_unix()
else:
print("[build.py] INFO: Looks like first run, as"
" cefpython_py{pyver}.h is missing. Skip building"
" C++ projects."
.format(pyver=PYVERSION))
global FIRST_RUN
FIRST_RUN = True
clear_cache()
copy_and_fix_pyx_files()
build_cefpython_module()
fix_cefpython_api_header_file()
install_and_run()
def command_line_args():
global DEBUG_FLAG, FAST_FLAG, CLEAN_FLAG, KIVY_FLAG, HELLO_WORLD_FLAG, \
REBUILD_CPP, VERSION, NO_RUN_EXAMPLES
VERSION = get_version_from_command_line_args(__file__)
if not VERSION:
print(__doc__)
sys.exit(1)
print("[build.py] Parse command line arguments")
# --no-run-examples
if "--no-run-examples" in sys.argv:
NO_RUN_EXAMPLES = True
print("[build.py] Running examples disabled (--no-run-examples)")
# -- debug
if "--debug" in sys.argv:
DEBUG_FLAG = True
print("[build.py] DEBUG mode On")
# --fast
if "--fast" in sys.argv:
# Fast mode doesn't delete C++ .o .a files.
# Fast mode also disables optimization flags in setup/setup.py .
FAST_FLAG = True
print("[build.py] FAST mode On")
# --clean
if "--clean" in sys.argv:
CLEAN_FLAG = True
# --kivy
if "--kivy" in sys.argv:
KIVY_FLAG = True
print("[build.py] KIVY example")
# --kivy
if "--hello-world" in sys.argv:
HELLO_WORLD_FLAG = True
print("[build.py] HELLO WORLD example")
# --rebuild-cpp
# Rebuild c++ projects
if "--rebuild-cpp" in sys.argv:
REBUILD_CPP = True
print("[build.py] REBUILD_CPP mode enabled")
print("[build.py] VERSION=%s" % VERSION)
def check_cython_version():
print("[build.py] Check Cython version")
with open(os.path.join(TOOLS_DIR, "requirements.txt"), "rb") as fileobj:
contents = fileobj.read().decode("utf-8")
match = re.search(r"cython\s*==\s*([\d.]+)", contents,
flags=re.IGNORECASE)
assert match, "cython package not found in requirements.txt"
require_version = match.group(1)
try:
import Cython
version = Cython.__version__
except ImportError:
# noinspection PyUnusedLocal
Cython = None
print("[build.py] ERROR: Cython is not installed ({0} required)"
.format(require_version))
sys.exit(1)
if version != require_version:
print("[build.py] ERROR: Wrong Cython version: {0}. Required: {1}"
.format(version, require_version))
sys.exit(1)
print("[build.py] Cython version: {0}".format(version))
def check_directories():
print("[build.py] Check directories")
# Create directories if necessary
if not os.path.exists(CEFPYTHON_BINARY):
os.makedirs(CEFPYTHON_BINARY)
if not os.path.exists(BUILD_CEFPYTHON):
os.makedirs(BUILD_CEFPYTHON)
# Info if directory missing
if not os.path.exists(CEF_BINARIES_LIBRARIES):
prebuilt_name = get_cef_binaries_libraries_basename(OS_POSTFIX2)
print("[build.py] ERROR: Couldn't find CEF prebuilt binaries and"
" libraries: 'build/{prebuilt_dir}/'. Download it"
" from GitHub released tagged eg. 'v50-upstream` or download"
" CEF binaries from Spotify Automated Builds and then run"
"`automate.py --prebuilt-cef`."
.format(prebuilt_dir=prebuilt_name))
sys.exit(1)
# Check directories exist
assert os.path.exists(BUILD_DIR)
assert os.path.exists(BUILD_CEFPYTHON)
assert os.path.exists(CEF_BINARIES_LIBRARIES)
assert os.path.exists(CEFPYTHON_BINARY)
def setup_environ():
"""Set environment variables. Set PATH so that it contains only
minimum set of directories,to avoid any possible issues. Set Python
include path. Set Mac compiler options. Etc."""
print("[build.py] Setup environment variables")
# PATH
if WINDOWS:
path = [
"C:\\Windows\\system32",
"C:\\Windows",
"C:\\Windows\\System32\\Wbem",
get_python_path(),
]
os.environ["PATH"] = os.pathsep.join(path)
print("[build.py] environ PATH: {path}"
.format(path=os.environ["PATH"]))
# INCLUDE env for vcproj build
if WINDOWS:
if "INCLUDE" not in os.environ:
os.environ["INCLUDE"] = ""
os.environ["INCLUDE"] += os.pathsep + get_python_include_path()
print("[build.py] environ INCLUDE: {include}"
.format(include=os.environ["INCLUDE"]))
# LIB env for vcproj build
if WINDOWS:
os.environ["AdditionalLibraryDirectories"] = os.path.join(
CEF_BINARIES_LIBRARIES, "lib")
print("[build.py] environ AdditionalLibraryDirectories: {lib}"
.format(lib=os.environ["AdditionalLibraryDirectories"]))
if LINUX or MAC:
# Env variables for makefiles
os.environ["PYTHON_INCLUDE"] = get_python_include_path()
print("[build.py] PYTHON_INCLUDE: {python_include}"
.format(python_include=os.environ["PYTHON_INCLUDE"]))
os.environ["CEF_CCFLAGS"] = "-std=gnu++11 -DNDEBUG -Wall -Werror"
if FAST_FLAG:
os.environ["CEF_CCFLAGS"] += " -O0"
else:
os.environ["CEF_CCFLAGS"] += " -O3"
os.environ["CEF_LINK_FLAGS"] = ""
os.environ["CEF_BIN"] = os.path.join(CEF_BINARIES_LIBRARIES, "bin")
os.environ["CEF_LIB"] = os.path.join(CEF_BINARIES_LIBRARIES, "lib")
if LINUX:
# TODO: Set CEF_CCFLAGS and CEF_LINK_FLAGS according to what is
# in upstream cefclient, see cef/cmake/cef_variables.cmake.
pass
# Mac compiler options
if MAC:
os.environ["PATH"] = "/usr/local/bin:"+os.environ["PATH"]
os.environ["CC"] = "c++"
os.environ["CXX"] = "c++"
if ARCH32:
raise Exception("Python 32-bit is not supported on Mac")
os.environ["ARCHFLAGS"] = "-arch x86_64"
os.environ["CEF_CCFLAGS"] += " -arch x86_64"
os.environ["CEF_LINK_FLAGS"] += " -mmacosx-version-min=10.9"
# -Wno-return-type-c-linkage to ignore:
# > warning: 'somefunc' has C-linkage specified, but returns
# > user-defined type 'sometype' which is incompatible with C
os.environ["CEF_CCFLAGS"] += " -Wno-return-type-c-linkage"
# Compile against libc++ otherwise error "symbol not found"
# with cef::logging::LogMessage symbol. Also include -lc++
# and -lc++abi libraries.
os.environ["CEF_CCFLAGS"] += " -stdlib=libc++"
# See compile/link flags in upstream cefclient
os.environ["CEF_CCFLAGS"] += (
" -fno-strict-aliasing"
" -fno-rtti"
" -fno-threadsafe-statics"
" -fobjc-call-cxx-cdtors"
" -fvisibility=hidden"
" -fvisibility-inlines-hidden"
)
os.environ["CEF_LINK_FLAGS"] += (
" -lc++"
" -lc++abi"
" -Wl,-search_paths_first"
" -Wl,-ObjC"
" -Wl,-pie"
" -Wl,-dead_strip"
)
def fix_cefpython_api_header_file():
"""This function does two things: 1) Disable warnings in cefpython
API header file and 2) Make a copy named cefpython_pyXX_fixed.h,
this copy will be used by C++ projects and its modification time
won't change every time you run build.py script, thus C++ won't
rebuild each time."""
# Fix cefpython_pyXX.h to disable this warning:
# > warning: 'somefunc' has C-linkage specified, but returns
# > user-defined type 'sometype' which is incompatible with C
# On Mac this warning must be disabled using -Wno-return-type-c-linkage
# flag in makefiles.
print("[build.py] Fix cefpython API header file in the build_cefpython/"
" directory")
if not os.path.exists(CEFPYTHON_API_HFILE):
assert not os.path.exists(CEFPYTHON_API_HFILE_FIXED)
print("[build.py] cefpython API header file was not yet generated")
return
# Original contents
with open(CEFPYTHON_API_HFILE, "rb") as fo:
contents = fo.read().decode("utf-8")
# Pragma fix on Windows
if WINDOWS:
pragma = "#pragma warning(disable:4190)"
if pragma in contents:
print("[build.py] cefpython API header file is already fixed")
else:
contents = ("%s\n\n" % pragma) + contents
with open(CEFPYTHON_API_HFILE, "wb") as fo:
fo.write(contents.encode("utf-8"))
print("[build.py] Save {filename}"
.format(filename=CEFPYTHON_API_HFILE))
# Make a copy with a "_fixed" postfix
if os.path.exists(CEFPYTHON_API_HFILE_FIXED):
with open(CEFPYTHON_API_HFILE_FIXED, "rb") as fo:
contents_fixed = fo.read().decode("utf-8")
else:
contents_fixed = ""
# Resave fixed copy only if contents changed. Other scripts
# depend on "modified time" of the "_fixed" file.
if contents != contents_fixed:
print("[build.py] Save cefpython_fixed.h")
with open(CEFPYTHON_API_HFILE_FIXED, "wb") as fo:
fo.write(contents.encode("utf-8"))
def compile_cpp_projects_with_setuptools():
"""Use setuptools to build static libraries / executable."""
compile_cpp_projects = os.path.join(TOOLS_DIR, "build_cpp_projects.py")
retcode = subprocess.call([sys.executable, compile_cpp_projects])
if retcode != 0:
print("[build.py] ERROR: Failed to compile C++ projects")
sys.exit(1)
# Copy subprocess executable
print("[build.py] Copy subprocess executable")
shutil.copy(SUBPROCESS_EXE, CEFPYTHON_BINARY)
def compile_cpp_projects_windows_DEPRECATED():
"""DEPRECATED. Not used currently.
Build C++ projects using .vcproj files."""
# TODO: Remove code after setuptools compilation was tested for some time
print("[build.py] Compile C++ projects")
print("[build.py] ~~ Build CLIENT_HANDLER vcproj")
vcproj = ("client_handler_py{pyver}_{os}.vcproj"
.format(pyver=PYVERSION, os=OS_POSTFIX2))
vcproj = os.path.join(SRC_DIR, "client_handler", vcproj)
build_vcproj_DEPRECATED(vcproj)
print("[build.py] ~~ Build LIBCEFPYTHONAPP vcproj")
vcproj = ("libcefpythonapp_py{pyver}_{os}.vcproj"
.format(pyver=PYVERSION, os=OS_POSTFIX2))
vcproj = os.path.join(SRC_DIR, "subprocess", vcproj)
build_vcproj_DEPRECATED(vcproj)
print("[build.py] ~~ Build SUBPROCESS vcproj")
vcproj = ("subprocess_{os}.vcproj"
.format(os=OS_POSTFIX2))
vcproj = os.path.join(SRC_DIR, "subprocess", vcproj)
ret = build_vcproj_DEPRECATED(vcproj)
# Copy subprocess executable
subprocess_from = os.path.join(
SUBPROCESS_DIR,
"Release_{os}".format(os=OS_POSTFIX2),
"subprocess_{os}.exe".format(os=OS_POSTFIX2))
subprocess_to = os.path.join(CEFPYTHON_BINARY, "subprocess.exe")
if os.path.exists(subprocess_to):
os.remove(subprocess_to)
if ret == 0:
print("[build.py] Copy subprocess executable")
# shutil.copy() will also copy Permission bits
shutil.copy(subprocess_from, subprocess_to)
print("[build.py] ~~ Build CPP_UTILS vcproj")
vcproj = ("cpp_utils_{os}.vcproj"
.format(os=OS_POSTFIX2))
vcproj = os.path.join(SRC_DIR, "cpp_utils", vcproj)
build_vcproj_DEPRECATED(vcproj)
def build_vcproj_DEPRECATED(vcproj):
"""DEPRECATED. Not used currently."""
# TODO: Remove code after setuptools compilation was tested for some time
# In VS2010 vcbuild.exe was replaced by msbuild.exe.
# Ufortunately WinSDK 7.1 does not come with msbuild.exe,
# so it would be required to install Visual Studio 2010,
# and to support both 32-bit ad 64-bit compilations it
# a non-express version would have to be installed, which
# is not free. So to make it free open-source it was
# required migrate to a new compilation system that uses
# distutils/setuptools packages.
# msbuild.exe flags:
# /clp:disableconsolecolor
# msbuild /p:BuildProjectReferences=false project.proj
# MSBuild.exe MyProject.proj /t:build
if PYVERSION == "27":
args = list()
args.append(VS2008_VCVARS)
args.append(VS_PLATFORM_ARG)
args.append("&&")
args.append(VS2008_BUILD)
args.append("/nocolor")
args.append("/nologo")
args.append("/nohtmllog")
if REBUILD_CPP:
args.append("/rebuild")
args.append(vcproj)
ret = subprocess.call(args, shell=True)
if ret != 0:
compile_ask_to_continue()
return ret
else:
raise Exception("Only Python 2.7 32-bit is currently supported")
def compile_ask_to_continue():
# noinspection PyUnboundLocalVariable
what = input("[build.py] make failed, 'y' to continue, Enter to stop: ")
if what != "y":
sys.exit(1)
def clean_cpp_projects_unix():
delete_files_by_pattern("{0}/*.o".format(CLIENT_HANDLER_DIR))
delete_files_by_pattern("{0}/*.a".format(CLIENT_HANDLER_DIR))
delete_files_by_pattern("{0}/*.o".format(SUBPROCESS_DIR))
delete_files_by_pattern("{0}/*.a".format(SUBPROCESS_DIR))
delete_files_by_pattern("{0}/subprocess".format(SUBPROCESS_DIR))
delete_files_by_pattern("{0}/*.o".format(CPP_UTILS_DIR))
delete_files_by_pattern("{0}/*.a".format(CPP_UTILS_DIR))
def compile_cpp_projects_unix():
print("[build.py] Compile C++ projects")
if CLEAN_FLAG:
print("[build.py] Clean C++ projects (--clean flag passed)")
clean_cpp_projects_unix()
# Need to allow continuing even when make fails, as it may
# fail because the "public" function declaration is not yet
# in cefpython API header file, but for it to be generated we need
# to run cython compiling, so in this case you continue even when
# make fails and then run the compile.py script again and this time
# make should succeed.
# -- CLIENT_HANDLER
print("[build.py] ~~ Build CLIENT_HANDLER project")
os.chdir(CLIENT_HANDLER_DIR)
if not FAST_FLAG:
subprocess.call("rm -f *.o *.a", shell=True)
ret = subprocess.call("make -f Makefile", shell=True)
if ret != 0:
compile_ask_to_continue()
# -- LIBCEFPYTHONAPP
print("[build.py] ~~ Build LIBCEFPYTHONAPP project")
os.chdir(SUBPROCESS_DIR)
if not FAST_FLAG:
subprocess.call("rm -f *.o *.a", shell=True)
subprocess.call("rm -f subprocess", shell=True)
ret = subprocess.call("make -f Makefile-libcefpythonapp", shell=True)
if ret != 0:
compile_ask_to_continue()
# -- SUBPROCESS
print("[build.py] ~~ Build SUBPROCESS project")
ret = subprocess.call("make -f Makefile", shell=True)
if ret != 0:
compile_ask_to_continue()
# Copy subprocess executable
subprocess_from = os.path.join(SUBPROCESS_DIR, "subprocess")
subprocess_to = os.path.join(CEFPYTHON_BINARY, "subprocess")
if os.path.exists(subprocess_from):
# shutil.copy() will also copy Permission bits
shutil.copy(subprocess_from, subprocess_to)
# -- CPP_UTILS
print("[build.py] ~~ Build CPP_UTILS project")
os.chdir(CPP_UTILS_DIR)
if not FAST_FLAG:
subprocess.call("rm -f *.o *.a", shell=True)
ret = subprocess.call("make -f Makefile", shell=True)
if ret != 0:
compile_ask_to_continue()
def clear_cache():
print("[build.py] Clean build cache")
# Cache in CEFPYTHON_BINARY directory (eg. cefpython_linux64/)
os.chdir(CEFPYTHON_BINARY)
delete_files_by_pattern("./"+MODULE_NAME_TEMPLATE
.format(pyversion=PYVERSION, ext=MODULE_EXT))
# Cache in build_cefpython/ directory
os.chdir(BUILD_CEFPYTHON)
delete_files_by_pattern("./"+MODULE_NAME_TEMPLATE
.format(pyversion=PYVERSION, ext=MODULE_EXT))
delete_files_by_pattern("./*.pyx")
try:
if not FAST_FLAG:
# Cython's internal build/ directory
shutil.rmtree(os.path.join(BUILD_CEFPYTHON, "build"))
except OSError:
pass
def copy_and_fix_pyx_files():
print("[build.py] Copy and fix pyx files")
# First, it copies all .pyx files from upper directory to setup/.
# Then, fixes repeating of "include" statements in pyx files.
# Only the mainfile needs to have "include" statements,
# but we're using PyCharm and to get rid of "unresolved references"
# and other errors displayed in pycharm we are adding "include"
# statements in all of the pyx files.
# I'm not 100% sure how includes work in Cython, but I suspect that
# a few includes of the same file will include the same content more
# than once, it should work, but function and variable definitions are
# duplicated, it is some kind of overhead and it could lead to some
# problems in the future, better to fix it now.
# It also checks cdef & cpdef functions whether they are not missing
# "except *", it is required to add it when returning non-python type.
os.chdir(BUILD_CEFPYTHON)
print("\n")
mainfile_original = "cefpython.pyx"
mainfile_newname = "cefpython_py{pyver}.pyx".format(pyver=PYVERSION)
pyxfiles = glob.glob("../../src/*.pyx")
if not len(pyxfiles):
print("[build.py] ERROR: no .pyx files found in root")
sys.exit(1)
pyxfiles = [f for f in pyxfiles if f.find(mainfile_original) == -1]
# Now, pyxfiles contains all pyx files except mainfile_original
# (cefpython.pyx), we do not fix includes in mainfile.
pyxfiles2 = glob.glob("../../src/handlers/*.pyx")
if not len(pyxfiles2):
print("[build.py] ERROR: no .pyx files found in handlers/")
sys.exit(1)
pyxfiles = pyxfiles + pyxfiles2
# Remove old pyx files
oldpyxfiles = glob.glob("./*.pyx")
print("[build.py] Clean pyx files in build_cefpython/")
for pyxfile in oldpyxfiles:
if os.path.exists(pyxfile):
os.remove(pyxfile)
# Copying pyxfiles and reading its contents.
print("[build.py] Copy pyx files to build_cefpython/")
# Copy cefpython.pyx and fix includes in cefpython.pyx, eg.:
# include "handlers/focus_handler.pyx" becomes include "focus_handler.pyx"
shutil.copy("../../src/%s" % mainfile_original, "./%s" % mainfile_newname)
with open("./%s" % mainfile_newname, "rb") as fo:
content = fo.read().decode("utf-8")
(content, subs) = re.subn(r"^include \"handlers/",
"include \"",
content,
flags=re.MULTILINE)
# Add __version__ variable in cefpython.pyx
print("[build.py] Add __version__ variable to %s" % mainfile_newname)
content = generate_cefpython_module_variables() + content
with open("./%s" % mainfile_newname, "wb") as fo:
fo.write(content.encode("utf-8"))
print("[build.py] Fix %s includes in %s" % (subs, mainfile_newname))
# Copy the rest of the files
print("[build.py] Fix includes in other .pyx files")
for pyxfile in pyxfiles:
newfile = "./%s" % os.path.basename(pyxfile)
shutil.copy(pyxfile, newfile)
pyxfile = newfile
with open(pyxfile, "rb") as pyxfileopened:
content = pyxfileopened.read().decode("utf-8")
lineNumber = except_all_missing(content)
if lineNumber:
print("[build.py] WARNING: 'except *' missing"
" in a cdef/cpdef function,"
" in file %s on line %d"
% (os.path.basename(pyxfile), lineNumber))
sys.exit(1)
# Do not remove the newline - so that line numbers
# are exact with originals.
(content, subs) = re.subn(
r"^include[\t ]+[\"'][^\"'\n\r]+[\"'][\t ]*",
"",
content,
flags=re.MULTILINE)
if subs:
# print("[build.py] %s includes removed in: %s"
# % (subs, os.path.basename(pyxfile)))
pass
with open(pyxfile, "wb") as pyxfileopened:
pyxfileopened.write(content.encode("utf-8"))
print("\n")
def generate_cefpython_module_variables():
"""Global variables that will be appended to cefpython.pyx sources."""
ret = ('__version__ = "{0}"\n'.format(VERSION))
version = get_cefpython_version()
chrome_version = "{0}.{1}.{2}.{3}".format(
version["CHROME_VERSION_MAJOR"], version["CHROME_VERSION_MINOR"],
version["CHROME_VERSION_BUILD"], version["CHROME_VERSION_PATCH"])
ret += ('__chrome_version__ = "{0}"\n'.format(chrome_version))
ret += ('__cef_version__ = "{0}"\n'.format(version["CEF_VERSION"]))
ret += ('__cef_api_hash_platform__ = "{0}"\n'
.format(version["CEF_API_HASH_PLATFORM"]))
ret += ('__cef_api_hash_universal__ = "{0}"\n'
.format(version["CEF_API_HASH_UNIVERSAL"]))
ret += ('__cef_commit_hash__ = "{0}"\n'
.format(version["CEF_COMMIT_HASH"]))
ret += ('__cef_commit_number__ = "{0}"\n'
.format(version["CEF_COMMIT_NUMBER"]))
return ret
def except_all_missing(content):
# This is not perfect, won't detect C++ custom types, but will find
# the built-in types, templates and pointers.
patterns = list()
patterns.append(
r"\bcp?def\s+"
"((int|short|long|double|char|unsigned|float|double|cpp_bool"
"|cpp_string|cpp_wstring|uintptr_t|void"
"|int32|uint32|int64|uint64"
"|int32_t|uint32_t|int64_t|uint64_t"
"|CefString)\s+)+"
"\w+\([^)]*\)\s*(with\s+(gil|nogil))?\s*:")
patterns.append(
r"\bcp?def\s+"
# A template ends with bracket: CefRefPtr[CefBrowser]
# or a pointer ends with asterisk: CefBrowser*
"[^\s]+[\]*]\s+"
"\w+\([^)]*\)\s*(with\s+(gil|nogil))?\s*:")
patterns.append(
r"\bcp?def\s+"
# A reference, eg. CefString&
"[^\s]+&\s+"
"\w+\([^)]*\)\s*(with\s+(gil|nogil))?\s*:")
match = None
for pattern in patterns:
match = re.search(pattern, content)
if match:
break
if match:
lineNumber = (content.count("\n", 0, match.start()) + 1)
return lineNumber
def build_cefpython_module():
# if DEBUG_FLAG:
# ret = subprocess.call("python-dbg setup.py build_ext --inplace"
# " --cython-gdb", shell=True)
print("[build.py] Execute cython_setup.py script")
print("")
os.chdir(BUILD_CEFPYTHON)
command = ("\"{python}\" {tools_dir}/cython_setup.py build_ext"
.format(python=sys.executable, tools_dir=TOOLS_DIR))
if FAST_FLAG:
command += " --fast"
ret = subprocess.call(command, shell=True)
# if DEBUG_FLAG:
# shutil.rmtree("./../binaries_%s/cython_debug/" % BITS,
# ignore_errors=True)
# shutil.copytree("./cython_debug/",
# "./../binaries_%s/cython_debug/" % BITS)
# Remove .pyx files
oldpyxfiles = glob.glob("./*.pyx")
print("")
print("[build.py] Cleanup: remove pyx files in build_cefpython/")
for pyxfile in oldpyxfiles:
if os.path.exists(pyxfile):
os.remove(pyxfile)
# Check if built succeeded after pyx files were removed
if ret != 0:
if FIRST_RUN and os.path.exists(CEFPYTHON_API_HFILE):
print("[build.py] INFO: looks like this was first run and"
" building the cefpython module is expected to fail"
" in such case due to cefpython API header file not"
" being generated yet. Will re-run the build.py script"
" programmatically now.")
args = list()
args.append("\"{python}\"".format(python=sys.executable))
args.append(os.path.join(TOOLS_DIR, os.path.basename(__file__)))
assert __file__ in sys.argv[0]
args.extend(sys.argv[1:])
command = " ".join(args)
ret = subprocess.call(command, shell=True)
# Always pass fixed value to sys.exit, read note at
# the top of the script about os.system and sys.exit
# issue.
sys.exit(0 if ret == 0 else 1)
else:
print("[build.py] ERROR: failed to build the cefpython module")
sys.exit(1)
# Move the cefpython module
module_pattern = MODULE_NAME_TEMPLATE.format(pyversion=PYVERSION+"*",
ext=MODULE_EXT)
module_pattern = "./build/lib*/" + module_pattern
move_file_by_pattern(module_pattern, os.path.join(CEFPYTHON_BINARY,
MODULE_NAME))
print("[build.py] DONE building the cefpython module")
def move_file_by_pattern(pattern, move_to):
assert len(pattern) > 2
print("[build.py] Move file: {pattern} to {move_to}"
.format(pattern=pattern, move_to=move_to))
files = glob.glob(pattern)
assert(len(files) == 1)
os.rename(files[0], move_to)
def delete_files_by_pattern(pattern):
assert len(pattern) > 2
print("[build.py] Delete files by pattern: {pattern}"
.format(pattern=pattern))
files = glob.glob(pattern)
for f in files:
os.remove(f)
print("[build.py] Removed {0} files".format(len(files)))
def delete_directories_by_pattern(pattern):
assert len(pattern) > 2
print("[build.py] Delete directories by pattern: {pattern}"
.format(pattern=pattern))
paths = glob.glob(pattern)
for path in paths:
if os.path.isdir(path):
shutil.rmtree(path)
def install_and_run():
# if DEBUG_FLAG:
# os.chdir("./binaries_%s" % BITS)
# subprocess.call("cygdb . --args python-dbg wxpython.py", shell=True)
print("[build.py] Install and run...")
os.chdir(BUILD_DIR)
# Setup installer directory
setup_basename = get_setup_installer_basename(VERSION, OS_POSTFIX2)
setup_installer_dir = os.path.join(BUILD_DIR, setup_basename)
# Delete setup installer directory if exists
if os.path.exists(setup_installer_dir):
delete_directory_reliably(setup_installer_dir)
# Make setup installer
print("[build.py] Make setup installer")
make_tool = os.path.join(TOOLS_DIR, "make_installer.py")
command = ("\"{python}\" {make_tool} --version {version}"
.format(python=sys.executable,
make_tool=make_tool,
version=VERSION))
ret = os.system(command)
if ret != 0:
print("[build.py] ERROR while making installer package")
sys.exit(1)
# Install
print("[build.py] Install the cefpython package")
os.chdir(setup_installer_dir)
command = ("\"{python}\" setup.py install"
.format(python=sys.executable))
command = sudo_command(command, python=sys.executable)
ret = os.system(command)
if ret != 0:
print("[build.py] ERROR while installing package")
sys.exit(1)
os.chdir(BUILD_DIR)
# Delete setup installer directory after the package was installed
delete_directory_reliably(setup_installer_dir)
# Run unittests
print("[build.py] Run unittests")
test_runner = os.path.join(UNITTESTS_DIR, "_test_runner.py")
command = ("\"{python}\" {test_runner}"
.format(python=sys.executable,
test_runner=test_runner))
ret = os.system(command)
if ret != 0:
print("[build.py] ERROR while running unit tests")
sys.exit(1)
# Run examples
if not NO_RUN_EXAMPLES:
print("[build.py] Run examples")
os.chdir(EXAMPLES_DIR)
flags = ""
if KIVY_FLAG:
flags += " --kivy"
if HELLO_WORLD_FLAG:
flags += " --hello-world"
run_examples = os.path.join(TOOLS_DIR, "run_examples.py")
command = ("\"{python}\" {run_examples} {flags}"
.format(python=sys.executable,
run_examples=run_examples,
flags=flags))
ret = os.system(command)
if ret != 0:
print("[build.py] ERROR while running examples")
sys.exit(1)
print("[build.py] Everything OK")
def delete_directory_reliably(adir):
assert len(adir) > 2
assert os.path.isdir(adir)
print("[build.py] Delete directory: {dir}"
.format(dir=adir.replace(ROOT_DIR, "")))
if WINDOWS:
# rmtree is vulnerable to race conditions. Sometimes
# deleting directory fails with error:
# >> OSError: [WinError 145] The directory is not empty:
# >> 'C:\\github\\cefpython\\build\\cefpython3_56.2_win64\\build\\
# >> lib\\cefpython3'
shutil.rmtree(adir, ignore_errors=True)
else:
# On Linux sudo might be required to delete directory, as this
# might be a setup installer directory with package installed
# using sudo and in such case files were created with sudo.
command = "rm -rf {dir}".format(dir=adir)
command = sudo_command(command, python=sys.executable)
os.system(command)
if __name__ == "__main__":
main()