Files
cef/tools/setup_vscode.py
Marshall Greenblatt e88e98f061 tools: Add VSCode setup (fixes #3906)
Add tooling to set up a Visual Studio Code development environment
for CEF. See script output for usage.

Run: python3 tools/setup_vscode.py
2025-03-20 13:53:33 -04:00

293 lines
10 KiB
Python

# Copyright (c) 2025 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 __future__ import print_function
from exec_util import exec_cmd
from file_util import backup_file, copy_file, make_dir, read_file, write_file
import os
import platform as python_platform
import sys
COMPILE_COMMANDS_JSON = 'compile_commands.json'
LLDBINIT = '.lldbinit'
def UpdateCompileCommandsJSON(src_dir, out_dir, create):
out_path = os.path.join(src_dir, COMPILE_COMMANDS_JSON)
if not create and not os.path.isfile(out_path):
return True
print(f'Writing {COMPILE_COMMANDS_JSON} for clangd...')
with open(out_path, 'w') as f:
result = exec_cmd(
sys.executable +
f' tools/clang/scripts/generate_compdb.py -p {out_dir}',
src_dir,
output_file=f)
if result['err']:
print('ERROR: generate_compdb.py failed: %s' % result['err'])
return False
return True
def GetPreferredOutputDirectory(all_dirs):
if sys.platform == 'win32':
# Windows machines report 'ARM64' or 'AMD64'.
machine = 'arm64' if python_platform.machine() == 'ARM64' else 'x64'
elif sys.platform == 'darwin':
# Mac machines report 'arm64' or 'x86_64'.
machine = 'arm64' if python_platform.machine() == 'arm64' else 'x64'
elif sys.platform.startswith('linux'):
# Linux machines report 'aarch64', 'armv7l', 'x86_64', 'i386', etc.
machine = 'arm64' if python_platform.machine() == 'aarch64' else 'x64'
# Return the preferred directory that matches the host architecture.
for dir in (f'Debug_GN_{machine}', f'Release_GN_{machine}'):
if dir in all_dirs:
return dir
return None
if __name__ == "__main__":
from optparse import OptionParser
desc = """
This utility sets up Visual Studio Code (VSCode) integration for CEF.
"""
epilog = """
This utility sets up Visual Studio Code (VSCode) integration for an existing
CEF/Chromium development environment. See
https://bitbucket.org/chromiumembedded/cef/wiki/MasterBuildQuickStart.md for
prerequisite environment setup instructions.
The VSCode application and recommended extensions should be installed manually
upon completion of this setup. Instructions for this will be provided.
This setup is an automation and customization of the Chromium setup documented
at https://chromium.googlesource.com/chromium/src/+/main/docs/vscode.md. After
completion of this setup the VSCode configuration can be further customized by
modifying JSON files in the src/.vscode directory (either directly or via the
VSCode interface).
This setup includes configuration of clangd for improved C/C++ IntelliSense.
This functionality depends on a src/compile_commands.json file that will
be created by this utility and then regenerated each time cef_create_projects
is called in the future. Delete this json file manually if you do not wish to
utilize clangd with VSCode.
"""
class CustomParser(OptionParser):
def format_epilog(self, formatter):
return self.epilog
parser = CustomParser(description=desc, epilog=epilog + '\n')
parser.add_option(
'-v',
'--verbose',
action='store_true',
dest='verbose',
default=False,
help='output detailed status information')
parser.add_option(
'--headless',
action='store_true',
dest='headless',
default=False,
help='run without requiring user interaction')
parser.add_option(
'--force-update',
action='store_true',
dest='force_update',
default=False,
help='force update all JSON configuration files')
(options, args) = parser.parse_args()
print(epilog)
if not options.headless:
input("Press Enter to proceed with setup...")
quiet = not options.verbose
script_dir = os.path.dirname(__file__)
cef_dir = os.path.abspath(os.path.join(script_dir, os.pardir))
src_dir = os.path.abspath(os.path.join(cef_dir, os.pardir))
chromium_dir = os.path.abspath(os.path.join(src_dir, os.pardir))
in_dir = os.path.join(cef_dir, 'tools', 'vscode')
print('Running setup for VSCode environment...\n')
# Determine the platform and architecture.
# Chromium development only supports ARM64 and x64 host systems. All other
# configurations will be cross-compiled.
if sys.platform == 'win32':
platform = 'windows'
# Windows machines report 'ARM64' or 'AMD64'.
machine = 'arm64' if python_platform.machine() == 'ARM64' else 'x64'
sampleapp = 'cefclient.exe'
testsapp = 'ceftests.exe'
elif sys.platform == 'darwin':
platform = 'mac'
# Mac machines report 'arm64' or 'x86_64'.
machine = 'arm64' if python_platform.machine() == 'arm64' else 'x64'
sampleapp = 'cefclient.app/Contents/MacOS/cefclient'
testsapp = 'ceftests.app/Contents/MacOS/ceftests'
elif sys.platform.startswith('linux'):
platform = 'linux'
# Linux machines report 'aarch64', 'armv7l', 'x86_64', 'i386', etc.
machine = 'arm64' if python_platform.machine() == 'aarch64' else 'x64'
sampleapp = 'cefclient'
testsapp = 'ceftests'
else:
print('ERROR: Unknown operating system platform.')
sys.exit(1)
debug_out_dir = os.path.join(src_dir, 'out', 'Debug_GN_' + machine)
debug_out_dir_exists = os.path.isdir(debug_out_dir)
release_out_dir = os.path.join(src_dir, 'out', 'Release_GN_' + machine)
release_out_dir_exists = os.path.isdir(release_out_dir)
if not debug_out_dir_exists and not release_out_dir_exists:
print(
f'ERROR: Output directories matching your host architecture ({machine}) do not exist.\n'
'Check your GN_OUT_CONFIGS environment variable and re-run cef_create_projects before proceeding.'
)
sys.exit(1)
out_dir = debug_out_dir if debug_out_dir_exists else release_out_dir
print(
f'Configuring VSCode project files for your host architecture ({machine})...\n'
)
variables = {
'ARCH': machine,
'DEFAULT': 'Debug' if debug_out_dir_exists else 'Release',
'DEBUGGER': 'cppvsdbg' if platform == 'windows' else 'cppdbg',
'EXEEXT': '.exe' if platform == 'windows' else '',
'SAMPLEAPP': sampleapp,
'TESTSAPP': testsapp,
}
vscode_dir = os.path.join(src_dir, '.vscode')
if not os.path.isdir(vscode_dir):
make_dir(vscode_dir, quiet)
change_ct = 0
# Update JSON files if necessary.
for json_name in ('cpp.code-snippets', 'extensions.json', 'keybindings.json',
'settings.json', 'launch.json', 'tasks.json'):
out_path = os.path.join(vscode_dir, json_name)
if os.path.isfile(out_path):
if not options.force_update:
print(f'Skipping existing file {json_name}')
continue
else:
print(f'Backing up existing file {json_name}')
backup_file(out_path)
in_path = os.path.join(in_dir, json_name)
if os.path.isfile(in_path):
# Copying a CEF file as-is.
copy_file(in_path, out_path, quiet)
change_ct += 1
continue
in_path += '.in'
if os.path.isfile(in_path):
# Copying a CEF file with variable substitution.
content = read_file(in_path)
for name, val in variables.items():
content = content.replace('{{' + name + '}}', val)
write_file(out_path, content, quiet=quiet)
change_ct += 1
continue
in_path = os.path.join(src_dir, 'tools', 'vscode', json_name)
if os.path.isfile(in_path):
# Copying a Chromium file as-is.
copy_file(in_path, out_path, quiet)
change_ct += 1
continue
print(f'ERROR: Required input file {json_name} does not exist.')
sys.exit(1)
gclient_path = os.path.join(chromium_dir, '.gclient')
if not os.path.isfile(gclient_path):
print(f'ERROR: Required input file {gclient_path} does not exist.')
sys.exit(1)
# Setup for clangd.
# https://chromium.googlesource.com/chromium/src/+/main/docs/clangd.md
content = read_file(gclient_path)
if content.find('checkout_clangd') < 0:
insert = "'custom_vars': {"
content = content.replace(insert, insert + "'checkout_clangd': True, ")
write_file(gclient_path, content, quiet=quiet)
change_ct += 1
print('Downloading clangd...')
result = exec_cmd('gclient sync --with_branch_heads --nohooks', src_dir)
if len(result['err']) > 0:
print('ERROR: gclient sync failed: %s' % result['err'])
sys.exit(1)
if not os.path.isfile(os.path.join(src_dir, COMPILE_COMMANDS_JSON)):
if UpdateCompileCommandsJSON(src_dir, out_dir, create=True):
change_ct += 1
else:
sys.exit(1)
if platform == 'mac':
# Setup for lldb.
# https://chromium.googlesource.com/chromium/src/+/main/docs/lldbinit.md
lldbinit_path = os.path.join(src_dir, LLDBINIT)
if os.path.isfile(lldbinit_path):
if not options.force_update:
print(f'Skipping existing file {LLDBINIT}')
else:
print(f'Backing up existing file {LLDBINIT}')
backup_file(lldbinit_path)
if not os.path.isfile(lldbinit_path):
content = "# So that lldbinit.py takes precedence.\n" \
f"script sys.path[:0] = ['{src_dir}/tools/lldb']\n" \
"script import lldbinit"
write_file(lldbinit_path, content, quiet=quiet)
change_ct += 1
if change_ct == 0:
print('No work performed.')
else:
print(f'Updated {change_ct} files.')
missing_dirs = []
if not debug_out_dir_exists:
missing_dirs.append('Debug')
if not release_out_dir_exists:
missing_dirs.append('Release')
for dir in missing_dirs:
print(
f'\nWARNING: A {dir} output directory matching your host architecture ({machine}) does\n'
f'not exist. You will not be able to build or run {dir} builds with VSCode.'
)
print(
'\nFIRST TIME USAGE INSTRUCTIONS\n\n'
'1. Install VSCode (including command-line integration) by following the instructions at'
'\n https://code.visualstudio.com/docs/setup/setup-overview'
'\n\n2. Launch VSCode with the following console commands:\n\n'
f' $ cd {src_dir}\n'
' $ code .\n'
'\n3. Install recommended VSCode extensions when prompted or by following the instructions at'
'\n https://chromium.googlesource.com/chromium/src/+/main/docs/vscode.md#Install-Recommended-Extensions\n'
)