# 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
# Common stuff for tools such as automate.py, build.py, etc.
import glob
import os
import platform
import re
import shutil
import struct
import sys
# These sample apps will be deleted when creating setup/wheel packages
CEF_SAMPLE_APPS = ["cefclient", "cefsimple", "ceftests", "chrome-sandbox"]
# Architecture and OS postfixes
ARCH32 = (8 * struct.calcsize('P') == 32)
ARCH64 = (8 * struct.calcsize('P') == 64)
# Make sure platform.architecture()[0] shows correctly 32bit when
# running Python 32bit on Windows 64bit.
if ARCH32:
assert platform.architecture()[0] == "32bit"
if ARCH64:
assert platform.architecture()[0] == "64bit"
ARCH_STR = platform.architecture()[0]
# OS_POSTFIX is for directories/files names in cefpython sources
# and doesn't include architecture type, just OS name.
# OS_POSTFIX2 is for platform name in cefpython binaries
# and includes architecture type (32bit/64bit).
# CEF_POSTFIX2 is for platform name in upstream CEF binaries
# and includes architecture type (32bit/64bit).
OS_POSTFIX = ("win" if platform.system() == "Windows" else
"linux" if platform.system() == "Linux" else
"mac" if platform.system() == "Darwin" else "unknown")
OS_POSTFIX2 = "unknown"
CEF_POSTFIX2 = "unknown" # Upstream CEF binaries postfix
if OS_POSTFIX == "win":
OS_POSTFIX2 = "win32" if ARCH32 else "win64"
CEF_POSTFIX2 = "windows32" if ARCH32 else "windows64"
elif OS_POSTFIX == "mac":
OS_POSTFIX2 = "mac32" if ARCH32 else "mac64"
CEF_POSTFIX2 = "macosx32" if ARCH32 else "macosx64"
elif OS_POSTFIX == "linux":
OS_POSTFIX2 = "linux32" if ARCH32 else "linux64"
CEF_POSTFIX2 = "linux32" if ARCH32 else "linux64"
# Platforms
SYSTEM = platform.system().upper()
if SYSTEM == "DARWIN":
SYSTEM = "MAC"
WINDOWS = SYSTEM if SYSTEM == "WINDOWS" else False
LINUX = SYSTEM if SYSTEM == "LINUX" else False
MAC = SYSTEM if SYSTEM == "MAC" else False
OS_POSTFIX2_ARCH = dict(
WINDOWS={"32bit": "win32", "64bit": "win64"},
LINUX={"32bit": "linux32", "64bit": "linux64"},
MAC={"32bit": "mac32", "64bit": "mac64"},
)
CEF_POSTFIX2_ARCH = dict(
WINDOWS={"32bit": "windows32", "64bit": "windows64"},
LINUX={"32bit": "linux32", "64bit": "linux64"},
MAC={"64bit": "macosx64"},
)
PYPI_POSTFIX2_ARCH = dict(
WINDOWS={"32bit": "win32", "64bit": "win_amd64"},
LINUX={"32bit": "manylinux1_i686", "64bit": "manylinux1_x86_64"},
MAC={"64bit": "x86_64"},
)
# Python version eg. 27
PYVERSION = str(sys.version_info[0])+str(sys.version_info[1])
# Module extension
if WINDOWS:
MODULE_EXT = "pyd"
else:
MODULE_EXT = "so"
# CEF Python module name
MODULE_NAME_TEMPLATE = "cefpython_py{pyversion}.{ext}"
MODULE_NAME_TEMPLATE_NOEXT = "cefpython_py{pyversion}"
MODULE_NAME = MODULE_NAME_TEMPLATE.format(pyversion=PYVERSION, ext=MODULE_EXT)
MODULE_NAME_NOEXT = MODULE_NAME_TEMPLATE_NOEXT.format(pyversion=PYVERSION)
# App and Executable extensions
if WINDOWS:
APP_EXT = ".exe"
EXECUTABLE_EXT = ".exe"
elif MAC:
APP_EXT = ".app" # cefclient, cefsimple, ceftests
EXECUTABLE_EXT = "" # subprocess
else:
APP_EXT = ""
EXECUTABLE_EXT = ""
# Library extension
if WINDOWS:
LIB_EXT = ".lib"
else:
LIB_EXT = ".a"
# Compiled object extension
if WINDOWS:
OBJ_EXT = ".obj"
else:
OBJ_EXT = ".o"
# ----------------------------------------------------------------------------
# Directories
# ----------------------------------------------------------------------------
assert __file__
ROOT_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
# API reference
API_DIR = os.path.join(ROOT_DIR, "api")
# Build directories
BUILD_DIR = os.path.join(ROOT_DIR, "build")
BUILD_CEFPYTHON = os.path.join(BUILD_DIR, "build_cefpython")
# May be auto-overwritten through detect_cef_binaries_libraries_dir()
CEF_BINARIES_LIBRARIES = os.path.join(BUILD_DIR, "cef_"+OS_POSTFIX2)
# Will be overwritten through detect_cefpython_binary_dir()
CEFPYTHON_BINARY = "CEFPYTHON_BINARY_NOTSET"
# Distrib directory
DISTRIB_DIR = os.path.join(BUILD_DIR, "DISTRIB_NOTSET")
# Build C++ projects directories
BUILD_CEFPYTHON_APP = os.path.join(BUILD_CEFPYTHON,
"cefpython_app_py{pyver}_{os}"
.format(pyver=PYVERSION, os=OS_POSTFIX2))
BUILD_CLIENT_HANDLER = os.path.join(BUILD_CEFPYTHON,
"client_handler_py{pyver}_{os}"
.format(pyver=PYVERSION, os=OS_POSTFIX2))
BUILD_CPP_UTILS = os.path.join(BUILD_CEFPYTHON,
"cpp_utils_py{pyver}_{os}"
.format(pyver=PYVERSION, os=OS_POSTFIX2))
BUILD_SUBPROCESS = os.path.join(BUILD_CEFPYTHON,
"subprocess_py{pyver}_{os}"
.format(pyver=PYVERSION, os=OS_POSTFIX2))
# -- end build directories
EXAMPLES_DIR = os.path.join(ROOT_DIR, "examples")
SRC_DIR = os.path.join(ROOT_DIR, "src")
# Subdirectories in src/
CLIENT_HANDLER_DIR = os.path.join(SRC_DIR, "client_handler")
CPP_UTILS_DIR = os.path.join(SRC_DIR, "cpp_utils")
LINUX_DIR = os.path.join(SRC_DIR, "linux")
MAC_DIR = os.path.join(SRC_DIR, "mac")
SUBPROCESS_DIR = os.path.join(SRC_DIR, "subprocess")
WINDOWS_DIR = os.path.abspath(os.path.join(SRC_DIR, "windows"))
# -- end subdirectories in src/
TOOLS_DIR = os.path.join(ROOT_DIR, "tools")
INSTALLER_DIR = os.path.join(TOOLS_DIR, "installer")
UNITTESTS_DIR = os.path.abspath(os.path.join(ROOT_DIR, "unittests"))
# ----------------------------------------------------------------------------
# cefpython API header file and a fixed copy of it
CEFPYTHON_API_HFILE = os.path.join(BUILD_CEFPYTHON,
"cefpython_py{pyver}.h"
.format(pyver=PYVERSION))
CEFPYTHON_API_HFILE_FIXED = os.path.join(BUILD_CEFPYTHON,
"cefpython_py{pyver}_fixed.h"
.format(pyver=PYVERSION))
# Result libraries paths
CEFPYTHON_APP_LIB = os.path.join(BUILD_CEFPYTHON_APP,
"cefpython_app" + LIB_EXT)
CLIENT_HANDLER_LIB = os.path.join(BUILD_CLIENT_HANDLER,
"client_handler" + LIB_EXT)
CPP_UTILS_LIB = os.path.join(BUILD_CPP_UTILS,
"cpp_utils" + LIB_EXT)
SUBPROCESS_EXE = os.path.join(BUILD_SUBPROCESS,
"subprocess" + EXECUTABLE_EXT)
# Visual Studio constants
VS_PLATFORM_ARG = "x86" if ARCH32 else "amd64"
VS2015_VCVARS = ("C:\\Program Files (x86)\\Microsoft Visual Studio 14.0"
"\\VC\\vcvarsall.bat")
# For CEF build
VS2013_VCVARS = ("C:\\Program Files (x86)\\Microsoft Visual Studio 12.0"
"\\VC\\vcvarsall.bat")
# VS2010 vcvarsall not used, using detection with setuptools instead
VS2010_VCVARS = ("C:\\Program Files (x86)\\Microsoft Visual Studio 10.0"
"\\VC\\vcvarsall.bat")
VS2008_VCVARS = ("%LocalAppData%\\Programs\\Common\\Microsoft"
"\\Visual C++ for Python\\9.0\\vcvarsall.bat")
VS2008_BUILD = ("%LocalAppData%\\Programs\\Common\\"
"Microsoft\\Visual C++ for Python\\9.0\\"
"VC\\bin\\amd64\\vcbuild.exe")
if "LOCALAPPDATA" in os.environ:
VS2008_VCVARS = VS2008_VCVARS.replace("%LocalAppData%",
os.environ["LOCALAPPDATA"])
VS2008_BUILD = VS2008_BUILD.replace("%LocalAppData%",
os.environ["LOCALAPPDATA"])
# -----------------------------------------------------------------------------
def get_os_postfix2_for_arch(arch):
return OS_POSTFIX2_ARCH[SYSTEM][arch]
def get_cef_postfix2_for_arch(arch):
return CEF_POSTFIX2_ARCH[SYSTEM][arch]
def get_pypi_postfix2_for_arch(arch):
return PYPI_POSTFIX2_ARCH[SYSTEM][arch]
def sudo_command(command, python):
"""Prepends command with sudo when installing python packages
requires sudo."""
if python.startswith("/usr/"):
command = "sudo " + command
return command
def get_python_path():
"""Get Python path."""
return os.path.dirname(sys.executable)
def get_python_include_path():
# 1) C:\Python27\include
# 2) ~/.pyenv/versions/2.7.13/bin/python
# ~/.pyenv/versions/2.7.13/include/python2.7
# 3) ~/.pyenv/versions/3.4.6/include/python2.7m
# 4) /usr/include/python2.7
base_dir = os.path.dirname(sys.executable)
try_dirs = ["{base_dir}/include",
"{base_dir}/../include/python{ver}",
"{base_dir}/../include/python{ver}*",
("{base_dir}/../Frameworks/Python.framework/Versions/{ver}"
"/include/python{ver}*"),
"/usr/include/python{ver}"]
ver_tuple = sys.version_info[:2]
ver = "{major}.{minor}".format(major=ver_tuple[0], minor=ver_tuple[1])
for pattern in try_dirs:
pattern = pattern.format(base_dir=base_dir, ver=ver)
if WINDOWS:
pattern = pattern.replace("/", "\\")
results = glob.glob(pattern)
if len(results) == 1:
python_h = os.path.join(results[0], "Python.h")
if os.path.isfile(python_h):
return results[0]
return ".\\" if WINDOWS else "./"
def delete_cef_sample_apps(caller_script, bin_dir):
"""Delete CEF sample apps to reduce package size."""
for sample_app_name in CEF_SAMPLE_APPS:
sample_app = os.path.join(bin_dir, sample_app_name + APP_EXT)
# Not on all platforms sample apps may be available
if os.path.exists(sample_app):
print("[{script}] Delete {sample_app}"
.format(script=os.path.basename(caller_script),
sample_app=os.path.basename(sample_app)))
if os.path.isdir(sample_app):
shutil.rmtree(sample_app)
else:
os.remove(sample_app)
# Also delete subdirs eg. cefclient_files/, ceftests_files/
files_subdir = os.path.join(bin_dir, sample_app_name + "_files")
if os.path.isdir(files_subdir):
print("[build_distrib.py] Delete directory: {dir}/"
.format(dir=os.path.basename(files_subdir)))
shutil.rmtree(files_subdir)
def _detect_cef_binaries_libraries_dir():
"""Detect cef binary directory created by automate.py
eg. build/cef55_3.2883.1553.g80bd606_win32/
and set CEF_BINARIES_LIBRARIES to it, otherwise it will
point to eg. build/cef_win32/ ."""
global CEF_BINARIES_LIBRARIES
if not os.path.exists(CEF_BINARIES_LIBRARIES):
dirs = glob.glob(os.path.join(
BUILD_DIR,
get_cef_binaries_libraries_basename(OS_POSTFIX2)))
if len(dirs) == 1:
CEF_BINARIES_LIBRARIES = os.path.normpath(dirs[0])
def get_cef_binaries_libraries_basename(postfix2):
version = get_cefpython_version()
return ("cef{major}_{cef_version}_{os}"
.format(major=version["CHROME_VERSION_MAJOR"],
cef_version=version["CEF_VERSION"],
os=postfix2))
def get_cefpython_binary_basename(postfix2, ignore_error=False):
cef_version = get_cefpython_version()
cmdline_version = get_version_from_command_line_args(
__file__, ignore_error=ignore_error)
if not cmdline_version:
if not ignore_error:
raise Exception("Version arg not found in command line args")
return
# If cef_version is 56 then expect version from command line to
# start with "56.".
cef_major = cef_version["CHROME_VERSION_MAJOR"]
if not cmdline_version.startswith("{major}.".format(major=cef_major)):
if not ignore_error:
raise Exception("cmd line arg major version != Chrome version")
return
dirname = "cefpython_binary_{version}_{os}".format(
version=cmdline_version,
os=postfix2)
return dirname
def get_setup_installer_basename(version, postfix2):
setup_basename = ("cefpython3_{version}_{os}"
.format(version=version, os=postfix2))
return setup_basename
def _detect_cefpython_binary_dir():
"""Detect cefpython binary directory where cefpython modules
will be put. Eg. build/cefpython_56.0_win32/."""
# Check cef version from header file and check cefpython version
# that was passed as command line argument to either build.py
# or make-installer.py. The CEFPYTHON_BINARY constant should
# only be used in those two scripts, so version number in sys.argv
# is expected. If not found then keep the default
# "CEFPYTHON_BINARY_NOTSET" value intact.
dirname = get_cefpython_binary_basename(OS_POSTFIX2, ignore_error=True)
if not dirname:
return
binary_dir = os.path.join(BUILD_DIR, dirname)
global CEFPYTHON_BINARY
CEFPYTHON_BINARY = binary_dir
def _detect_distrib_dir():
global DISTRIB_DIR
version = get_version_from_command_line_args(__file__, ignore_error=True)
if version:
# Will only be set when called from scripts that had version
# number arg passed on command line: build.py, build_distrib.py,
# make_installer.py, etc.
if LINUX or MAC:
# - On Linux buildig 32bit and 64bit separately, so don't
# delete eg. 64bit distrib when building 32bit distrib.
# Keep them in different directories.
# - On Mac only 64bit is supported.
dirname = ("distrib_{version}_{postfix2}"
.format(version=version, postfix2=OS_POSTFIX2))
elif WINDOWS:
# On Windows both 32bit and 64bit distribs are built at
# the same time.
dirname = ("distrib_{version}_{win32}_{win64}"
.format(version=version,
win32=OS_POSTFIX2_ARCH[WINDOWS]["32bit"],
win64=OS_POSTFIX2_ARCH[WINDOWS]["64bit"]))
else:
dirname = ("distrib_{version}_{postfix}"
.format(version=version, postfix=OS_POSTFIX))
DISTRIB_DIR = os.path.join(BUILD_DIR, dirname)
def get_version_from_command_line_args(caller_script, ignore_error=False):
args = " ".join(sys.argv)
match = re.search(r"\b(\d+)\.\d+\b", args)
if match:
version = match.group(0)
major = match.group(1)
cef_version = get_cefpython_version()
if major != cef_version["CHROME_VERSION_MAJOR"]:
if ignore_error:
return ""
print("[{script}] ERROR: cmd arg major version != Chrome version"
.format(script=os.path.basename(caller_script)))
sys.exit(1)
return version
return
def get_cefpython_version():
"""Get CEF version from the 'src/version/' directory."""
header_file = os.path.join(SRC_DIR, "version",
"cef_version_"+OS_POSTFIX+".h")
return get_version_from_file(header_file)
def get_version_from_file(header_file):
with open(header_file, "rU") as fp:
contents = fp.read() # no need to decode() as "rU" specified
ret = dict()
matches = re.findall(r'^#define (\w+) "?([^\s"]+)"?', contents,
re.MULTILINE)
for match in matches:
ret[match[0]] = match[1]
return ret
def get_msvs_for_python(vs_prefix=False):
"""Get MSVS version (eg 2008) for current python running."""
if sys.version_info[:2] == (2, 7):
return "VS2008" if vs_prefix else "2008"
elif sys.version_info[:2] == (3, 4):
return "VS2010" if vs_prefix else "2010"
elif sys.version_info[:2] == (3, 5):
return "VS2015" if vs_prefix else "2015"
elif sys.version_info[:2] == (3, 6):
return "VS2015" if vs_prefix else "2015"
else:
print("ERROR: This version of Python is not supported")
sys.exit(1)
_detect_cef_binaries_libraries_dir()
_detect_cefpython_binary_dir()
_detect_distrib_dir()