Skip to content

Commit e88e98f

Browse files
committed
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
1 parent 8fa5244 commit e88e98f

7 files changed

+707
-6
lines changed

tools/exec_util.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import sys
88

99

10-
def exec_cmd(cmd, path, input_string=None):
10+
def exec_cmd(cmd, path, input_string=None, output_file=None):
1111
""" Execute the specified command and return the result. """
1212
out = ''
1313
err = ''
@@ -18,7 +18,7 @@ def exec_cmd(cmd, path, input_string=None):
1818
process = Popen(
1919
parts,
2020
cwd=path,
21-
stdout=PIPE,
21+
stdout=PIPE if output_file is None else output_file,
2222
stderr=PIPE,
2323
shell=(sys.platform == 'win32'))
2424
out, err = process.communicate()
@@ -28,10 +28,14 @@ def exec_cmd(cmd, path, input_string=None):
2828
parts,
2929
cwd=path,
3030
stdin=PIPE,
31-
stdout=PIPE,
31+
stdout=PIPE if output_file is None else output_file,
3232
stderr=PIPE,
3333
shell=(sys.platform == 'win32'))
3434
out, err = process.communicate(input=input_string)
3535
ret = process.returncode
3636

37-
return {'out': out.decode('utf-8'), 'err': err.decode('utf-8'), 'ret': ret}
37+
return {
38+
'out': out.decode('utf-8') if output_file is None else None,
39+
'err': err.decode('utf-8'),
40+
'ret': ret
41+
}

tools/file_util.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def write_file(path, data, overwrite=True, quiet=True):
3838
return False
3939

4040
if not quiet:
41-
print('Writing file %s' % path)
41+
print('Writing %s file.' % path)
4242

4343
try:
4444
with open(path, 'w', encoding='utf-8', newline='\n') as f:

tools/gclient_hook.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from gn_args import GetAllPlatformConfigs, GetConfigFileContents
1111
import issue_1999
1212
import os
13+
from setup_vscode import GetPreferredOutputDirectory, UpdateCompileCommandsJSON
1314
import sys
1415

1516
# The CEF directory is the parent directory of _this_ script.
@@ -26,7 +27,7 @@
2627
platform = 'linux'
2728
else:
2829
print('Unknown operating system platform')
29-
sys.exit()
30+
sys.exit(1)
3031

3132
print("\nGenerating CEF translated files...")
3233
cmd = [sys.executable, 'tools/version_manager.py', '-u', '--fast-check']
@@ -134,6 +135,10 @@
134135
gn_args['windows_sdk_version'] = os.environ['SDK_VERSION']
135136

136137
configs = GetAllPlatformConfigs(gn_args)
138+
139+
# Returns the preferred output directory for VSCode, or None.
140+
preferred_dir = GetPreferredOutputDirectory(configs.keys())
141+
137142
for dir, config in configs.items():
138143
# Create out directories and write the args.gn file.
139144
out_path = os.path.join(src_dir, 'out', dir)
@@ -149,3 +154,7 @@
149154
RunAction(src_dir, cmd)
150155
if platform == 'windows':
151156
issue_1999.apply(out_path)
157+
158+
if dir == preferred_dir and not UpdateCompileCommandsJSON(
159+
src_dir, out_path, create=False):
160+
sys.exit(1)

tools/setup_vscode.py

+292
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
# Copyright (c) 2025 The Chromium Embedded Framework Authors. All rights
2+
# reserved. Use of this source code is governed by a BSD-style license that
3+
# can be found in the LICENSE file.
4+
5+
from __future__ import absolute_import
6+
from __future__ import print_function
7+
from exec_util import exec_cmd
8+
from file_util import backup_file, copy_file, make_dir, read_file, write_file
9+
import os
10+
import platform as python_platform
11+
import sys
12+
13+
COMPILE_COMMANDS_JSON = 'compile_commands.json'
14+
LLDBINIT = '.lldbinit'
15+
16+
17+
def UpdateCompileCommandsJSON(src_dir, out_dir, create):
18+
out_path = os.path.join(src_dir, COMPILE_COMMANDS_JSON)
19+
if not create and not os.path.isfile(out_path):
20+
return True
21+
22+
print(f'Writing {COMPILE_COMMANDS_JSON} for clangd...')
23+
with open(out_path, 'w') as f:
24+
result = exec_cmd(
25+
sys.executable +
26+
f' tools/clang/scripts/generate_compdb.py -p {out_dir}',
27+
src_dir,
28+
output_file=f)
29+
if result['err']:
30+
print('ERROR: generate_compdb.py failed: %s' % result['err'])
31+
return False
32+
return True
33+
34+
35+
def GetPreferredOutputDirectory(all_dirs):
36+
if sys.platform == 'win32':
37+
# Windows machines report 'ARM64' or 'AMD64'.
38+
machine = 'arm64' if python_platform.machine() == 'ARM64' else 'x64'
39+
elif sys.platform == 'darwin':
40+
# Mac machines report 'arm64' or 'x86_64'.
41+
machine = 'arm64' if python_platform.machine() == 'arm64' else 'x64'
42+
elif sys.platform.startswith('linux'):
43+
# Linux machines report 'aarch64', 'armv7l', 'x86_64', 'i386', etc.
44+
machine = 'arm64' if python_platform.machine() == 'aarch64' else 'x64'
45+
46+
# Return the preferred directory that matches the host architecture.
47+
for dir in (f'Debug_GN_{machine}', f'Release_GN_{machine}'):
48+
if dir in all_dirs:
49+
return dir
50+
51+
return None
52+
53+
54+
if __name__ == "__main__":
55+
from optparse import OptionParser
56+
57+
desc = """
58+
This utility sets up Visual Studio Code (VSCode) integration for CEF.
59+
"""
60+
61+
epilog = """
62+
This utility sets up Visual Studio Code (VSCode) integration for an existing
63+
CEF/Chromium development environment. See
64+
https://bitbucket.org/chromiumembedded/cef/wiki/MasterBuildQuickStart.md for
65+
prerequisite environment setup instructions.
66+
67+
The VSCode application and recommended extensions should be installed manually
68+
upon completion of this setup. Instructions for this will be provided.
69+
70+
This setup is an automation and customization of the Chromium setup documented
71+
at https://chromium.googlesource.com/chromium/src/+/main/docs/vscode.md. After
72+
completion of this setup the VSCode configuration can be further customized by
73+
modifying JSON files in the src/.vscode directory (either directly or via the
74+
VSCode interface).
75+
76+
This setup includes configuration of clangd for improved C/C++ IntelliSense.
77+
This functionality depends on a src/compile_commands.json file that will
78+
be created by this utility and then regenerated each time cef_create_projects
79+
is called in the future. Delete this json file manually if you do not wish to
80+
utilize clangd with VSCode.
81+
"""
82+
83+
class CustomParser(OptionParser):
84+
85+
def format_epilog(self, formatter):
86+
return self.epilog
87+
88+
parser = CustomParser(description=desc, epilog=epilog + '\n')
89+
parser.add_option(
90+
'-v',
91+
'--verbose',
92+
action='store_true',
93+
dest='verbose',
94+
default=False,
95+
help='output detailed status information')
96+
parser.add_option(
97+
'--headless',
98+
action='store_true',
99+
dest='headless',
100+
default=False,
101+
help='run without requiring user interaction')
102+
parser.add_option(
103+
'--force-update',
104+
action='store_true',
105+
dest='force_update',
106+
default=False,
107+
help='force update all JSON configuration files')
108+
(options, args) = parser.parse_args()
109+
110+
print(epilog)
111+
112+
if not options.headless:
113+
input("Press Enter to proceed with setup...")
114+
115+
quiet = not options.verbose
116+
117+
script_dir = os.path.dirname(__file__)
118+
cef_dir = os.path.abspath(os.path.join(script_dir, os.pardir))
119+
src_dir = os.path.abspath(os.path.join(cef_dir, os.pardir))
120+
chromium_dir = os.path.abspath(os.path.join(src_dir, os.pardir))
121+
in_dir = os.path.join(cef_dir, 'tools', 'vscode')
122+
123+
print('Running setup for VSCode environment...\n')
124+
125+
# Determine the platform and architecture.
126+
# Chromium development only supports ARM64 and x64 host systems. All other
127+
# configurations will be cross-compiled.
128+
if sys.platform == 'win32':
129+
platform = 'windows'
130+
# Windows machines report 'ARM64' or 'AMD64'.
131+
machine = 'arm64' if python_platform.machine() == 'ARM64' else 'x64'
132+
sampleapp = 'cefclient.exe'
133+
testsapp = 'ceftests.exe'
134+
elif sys.platform == 'darwin':
135+
platform = 'mac'
136+
# Mac machines report 'arm64' or 'x86_64'.
137+
machine = 'arm64' if python_platform.machine() == 'arm64' else 'x64'
138+
sampleapp = 'cefclient.app/Contents/MacOS/cefclient'
139+
testsapp = 'ceftests.app/Contents/MacOS/ceftests'
140+
elif sys.platform.startswith('linux'):
141+
platform = 'linux'
142+
# Linux machines report 'aarch64', 'armv7l', 'x86_64', 'i386', etc.
143+
machine = 'arm64' if python_platform.machine() == 'aarch64' else 'x64'
144+
sampleapp = 'cefclient'
145+
testsapp = 'ceftests'
146+
else:
147+
print('ERROR: Unknown operating system platform.')
148+
sys.exit(1)
149+
150+
debug_out_dir = os.path.join(src_dir, 'out', 'Debug_GN_' + machine)
151+
debug_out_dir_exists = os.path.isdir(debug_out_dir)
152+
release_out_dir = os.path.join(src_dir, 'out', 'Release_GN_' + machine)
153+
release_out_dir_exists = os.path.isdir(release_out_dir)
154+
155+
if not debug_out_dir_exists and not release_out_dir_exists:
156+
print(
157+
f'ERROR: Output directories matching your host architecture ({machine}) do not exist.\n'
158+
'Check your GN_OUT_CONFIGS environment variable and re-run cef_create_projects before proceeding.'
159+
)
160+
sys.exit(1)
161+
162+
out_dir = debug_out_dir if debug_out_dir_exists else release_out_dir
163+
164+
print(
165+
f'Configuring VSCode project files for your host architecture ({machine})...\n'
166+
)
167+
168+
variables = {
169+
'ARCH': machine,
170+
'DEFAULT': 'Debug' if debug_out_dir_exists else 'Release',
171+
'DEBUGGER': 'cppvsdbg' if platform == 'windows' else 'cppdbg',
172+
'EXEEXT': '.exe' if platform == 'windows' else '',
173+
'SAMPLEAPP': sampleapp,
174+
'TESTSAPP': testsapp,
175+
}
176+
177+
vscode_dir = os.path.join(src_dir, '.vscode')
178+
if not os.path.isdir(vscode_dir):
179+
make_dir(vscode_dir, quiet)
180+
181+
change_ct = 0
182+
183+
# Update JSON files if necessary.
184+
for json_name in ('cpp.code-snippets', 'extensions.json', 'keybindings.json',
185+
'settings.json', 'launch.json', 'tasks.json'):
186+
out_path = os.path.join(vscode_dir, json_name)
187+
if os.path.isfile(out_path):
188+
if not options.force_update:
189+
print(f'Skipping existing file {json_name}')
190+
continue
191+
else:
192+
print(f'Backing up existing file {json_name}')
193+
backup_file(out_path)
194+
195+
in_path = os.path.join(in_dir, json_name)
196+
if os.path.isfile(in_path):
197+
# Copying a CEF file as-is.
198+
copy_file(in_path, out_path, quiet)
199+
change_ct += 1
200+
continue
201+
202+
in_path += '.in'
203+
if os.path.isfile(in_path):
204+
# Copying a CEF file with variable substitution.
205+
content = read_file(in_path)
206+
for name, val in variables.items():
207+
content = content.replace('{{' + name + '}}', val)
208+
write_file(out_path, content, quiet=quiet)
209+
change_ct += 1
210+
continue
211+
212+
in_path = os.path.join(src_dir, 'tools', 'vscode', json_name)
213+
if os.path.isfile(in_path):
214+
# Copying a Chromium file as-is.
215+
copy_file(in_path, out_path, quiet)
216+
change_ct += 1
217+
continue
218+
219+
print(f'ERROR: Required input file {json_name} does not exist.')
220+
sys.exit(1)
221+
222+
gclient_path = os.path.join(chromium_dir, '.gclient')
223+
if not os.path.isfile(gclient_path):
224+
print(f'ERROR: Required input file {gclient_path} does not exist.')
225+
sys.exit(1)
226+
227+
# Setup for clangd.
228+
# https://chromium.googlesource.com/chromium/src/+/main/docs/clangd.md
229+
content = read_file(gclient_path)
230+
if content.find('checkout_clangd') < 0:
231+
insert = "'custom_vars': {"
232+
content = content.replace(insert, insert + "'checkout_clangd': True, ")
233+
write_file(gclient_path, content, quiet=quiet)
234+
change_ct += 1
235+
236+
print('Downloading clangd...')
237+
result = exec_cmd('gclient sync --with_branch_heads --nohooks', src_dir)
238+
if len(result['err']) > 0:
239+
print('ERROR: gclient sync failed: %s' % result['err'])
240+
sys.exit(1)
241+
242+
if not os.path.isfile(os.path.join(src_dir, COMPILE_COMMANDS_JSON)):
243+
if UpdateCompileCommandsJSON(src_dir, out_dir, create=True):
244+
change_ct += 1
245+
else:
246+
sys.exit(1)
247+
248+
if platform == 'mac':
249+
# Setup for lldb.
250+
# https://chromium.googlesource.com/chromium/src/+/main/docs/lldbinit.md
251+
lldbinit_path = os.path.join(src_dir, LLDBINIT)
252+
if os.path.isfile(lldbinit_path):
253+
if not options.force_update:
254+
print(f'Skipping existing file {LLDBINIT}')
255+
else:
256+
print(f'Backing up existing file {LLDBINIT}')
257+
backup_file(lldbinit_path)
258+
259+
if not os.path.isfile(lldbinit_path):
260+
content = "# So that lldbinit.py takes precedence.\n" \
261+
f"script sys.path[:0] = ['{src_dir}/tools/lldb']\n" \
262+
"script import lldbinit"
263+
write_file(lldbinit_path, content, quiet=quiet)
264+
change_ct += 1
265+
266+
if change_ct == 0:
267+
print('No work performed.')
268+
else:
269+
print(f'Updated {change_ct} files.')
270+
271+
missing_dirs = []
272+
if not debug_out_dir_exists:
273+
missing_dirs.append('Debug')
274+
if not release_out_dir_exists:
275+
missing_dirs.append('Release')
276+
277+
for dir in missing_dirs:
278+
print(
279+
f'\nWARNING: A {dir} output directory matching your host architecture ({machine}) does\n'
280+
f'not exist. You will not be able to build or run {dir} builds with VSCode.'
281+
)
282+
283+
print(
284+
'\nFIRST TIME USAGE INSTRUCTIONS\n\n'
285+
'1. Install VSCode (including command-line integration) by following the instructions at'
286+
'\n https://code.visualstudio.com/docs/setup/setup-overview'
287+
'\n\n2. Launch VSCode with the following console commands:\n\n'
288+
f' $ cd {src_dir}\n'
289+
' $ code .\n'
290+
'\n3. Install recommended VSCode extensions when prompted or by following the instructions at'
291+
'\n https://chromium.googlesource.com/chromium/src/+/main/docs/vscode.md#Install-Recommended-Extensions\n'
292+
)

0 commit comments

Comments
 (0)