summaryrefslogtreecommitdiffstats
path: root/scripts/lib/devtool/ide_plugins/ide_code.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/lib/devtool/ide_plugins/ide_code.py')
-rw-r--r--scripts/lib/devtool/ide_plugins/ide_code.py438
1 files changed, 438 insertions, 0 deletions
diff --git a/scripts/lib/devtool/ide_plugins/ide_code.py b/scripts/lib/devtool/ide_plugins/ide_code.py
new file mode 100644
index 0000000000..b2193130d2
--- /dev/null
+++ b/scripts/lib/devtool/ide_plugins/ide_code.py
@@ -0,0 +1,438 @@
1#
2# Copyright (C) 2023-2024 Siemens AG
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6"""Devtool ide-sdk IDE plugin for VSCode and VSCodium"""
7
8import json
9import logging
10import os
11import shutil
12from devtool.ide_plugins import BuildTool, IdeBase, GdbCrossConfig, get_devtool_deploy_opts
13
14logger = logging.getLogger('devtool')
15
16
17class GdbCrossConfigVSCode(GdbCrossConfig):
18 def __init__(self, image_recipe, modified_recipe, binary):
19 super().__init__(image_recipe, modified_recipe, binary, False)
20
21 def initialize(self):
22 self._gen_gdbserver_start_script()
23
24
25class IdeVSCode(IdeBase):
26 """Manage IDE configurations for VSCode
27
28 Modified recipe mode:
29 - cmake: use the cmake-preset generated by devtool ide-sdk
30 - meson: meson is called via a wrapper script generated by devtool ide-sdk
31
32 Shared sysroot mode:
33 In shared sysroot mode, the cross tool-chain is exported to the user's global configuration.
34 A workspace cannot be created because there is no recipe that defines how a workspace could
35 be set up.
36 - cmake: adds a cmake-kit to .local/share/CMakeTools/cmake-tools-kits.json
37 The cmake-kit uses the environment script and the tool-chain file
38 generated by meta-ide-support.
39 - meson: Meson needs manual workspace configuration.
40 """
41
42 @classmethod
43 def ide_plugin_priority(cls):
44 """If --ide is not passed this is the default plugin"""
45 if shutil.which('code'):
46 return 100
47 return 0
48
49 def setup_shared_sysroots(self, shared_env):
50 """Expose the toolchain of the shared sysroots SDK"""
51 datadir = shared_env.ide_support.datadir
52 deploy_dir_image = shared_env.ide_support.deploy_dir_image
53 real_multimach_target_sys = shared_env.ide_support.real_multimach_target_sys
54 standalone_sysroot_native = shared_env.build_sysroots.standalone_sysroot_native
55 vscode_ws_path = os.path.join(
56 os.environ['HOME'], '.local', 'share', 'CMakeTools')
57 cmake_kits_path = os.path.join(vscode_ws_path, 'cmake-tools-kits.json')
58 oecmake_generator = "Ninja"
59 env_script = os.path.join(
60 deploy_dir_image, 'environment-setup-' + real_multimach_target_sys)
61
62 if not os.path.isdir(vscode_ws_path):
63 os.makedirs(vscode_ws_path)
64 cmake_kits_old = []
65 if os.path.exists(cmake_kits_path):
66 with open(cmake_kits_path, 'r', encoding='utf-8') as cmake_kits_file:
67 cmake_kits_old = json.load(cmake_kits_file)
68 cmake_kits = cmake_kits_old.copy()
69
70 cmake_kit_new = {
71 "name": "OE " + real_multimach_target_sys,
72 "environmentSetupScript": env_script,
73 "toolchainFile": standalone_sysroot_native + datadir + "/cmake/OEToolchainConfig.cmake",
74 "preferredGenerator": {
75 "name": oecmake_generator
76 }
77 }
78
79 def merge_kit(cmake_kits, cmake_kit_new):
80 i = 0
81 while i < len(cmake_kits):
82 if 'environmentSetupScript' in cmake_kits[i] and \
83 cmake_kits[i]['environmentSetupScript'] == cmake_kit_new['environmentSetupScript']:
84 cmake_kits[i] = cmake_kit_new
85 return
86 i += 1
87 cmake_kits.append(cmake_kit_new)
88 merge_kit(cmake_kits, cmake_kit_new)
89
90 if cmake_kits != cmake_kits_old:
91 logger.info("Updating: %s" % cmake_kits_path)
92 with open(cmake_kits_path, 'w', encoding='utf-8') as cmake_kits_file:
93 json.dump(cmake_kits, cmake_kits_file, indent=4)
94 else:
95 logger.info("Already up to date: %s" % cmake_kits_path)
96
97 cmake_native = os.path.join(
98 shared_env.build_sysroots.standalone_sysroot_native, 'usr', 'bin', 'cmake')
99 if os.path.isfile(cmake_native):
100 logger.info('cmake-kits call cmake by default. If the cmake provided by this SDK should be used, please add the following line to ".vscode/settings.json" file: "cmake.cmakePath": "%s"' % cmake_native)
101 else:
102 logger.error("Cannot find cmake native at: %s" % cmake_native)
103
104 def dot_code_dir(self, modified_recipe):
105 return os.path.join(modified_recipe.srctree, '.vscode')
106
107 def __vscode_settings_meson(self, settings_dict, modified_recipe):
108 if modified_recipe.build_tool is not BuildTool.MESON:
109 return
110 settings_dict["mesonbuild.mesonPath"] = modified_recipe.meson_wrapper
111
112 confopts = modified_recipe.mesonopts.split()
113 confopts += modified_recipe.meson_cross_file.split()
114 confopts += modified_recipe.extra_oemeson.split()
115 settings_dict["mesonbuild.configureOptions"] = confopts
116 settings_dict["mesonbuild.buildFolder"] = modified_recipe.b
117
118 def __vscode_settings_cmake(self, settings_dict, modified_recipe):
119 """Add cmake specific settings to settings.json.
120
121 Note: most settings are passed to the cmake preset.
122 """
123 if modified_recipe.build_tool is not BuildTool.CMAKE:
124 return
125 settings_dict["cmake.configureOnOpen"] = True
126 settings_dict["cmake.sourceDirectory"] = modified_recipe.real_srctree
127
128 def vscode_settings(self, modified_recipe):
129 files_excludes = {
130 "**/.git/**": True,
131 "**/oe-logs/**": True,
132 "**/oe-workdir/**": True,
133 "**/source-date-epoch/**": True
134 }
135 python_exclude = [
136 "**/.git/**",
137 "**/oe-logs/**",
138 "**/oe-workdir/**",
139 "**/source-date-epoch/**"
140 ]
141 settings_dict = {
142 "files.watcherExclude": files_excludes,
143 "files.exclude": files_excludes,
144 "python.analysis.exclude": python_exclude
145 }
146 self.__vscode_settings_cmake(settings_dict, modified_recipe)
147 self.__vscode_settings_meson(settings_dict, modified_recipe)
148
149 settings_file = 'settings.json'
150 IdeBase.update_json_file(
151 self.dot_code_dir(modified_recipe), settings_file, settings_dict)
152
153 def __vscode_extensions_cmake(self, modified_recipe, recommendations):
154 if modified_recipe.build_tool is not BuildTool.CMAKE:
155 return
156 recommendations += [
157 "twxs.cmake",
158 "ms-vscode.cmake-tools",
159 "ms-vscode.cpptools",
160 "ms-vscode.cpptools-extension-pack",
161 "ms-vscode.cpptools-themes"
162 ]
163
164 def __vscode_extensions_meson(self, modified_recipe, recommendations):
165 if modified_recipe.build_tool is not BuildTool.MESON:
166 return
167 recommendations += [
168 'mesonbuild.mesonbuild',
169 "ms-vscode.cpptools",
170 "ms-vscode.cpptools-extension-pack",
171 "ms-vscode.cpptools-themes"
172 ]
173
174 def vscode_extensions(self, modified_recipe):
175 recommendations = []
176 self.__vscode_extensions_cmake(modified_recipe, recommendations)
177 self.__vscode_extensions_meson(modified_recipe, recommendations)
178 extensions_file = 'extensions.json'
179 IdeBase.update_json_file(
180 self.dot_code_dir(modified_recipe), extensions_file, {"recommendations": recommendations})
181
182 def vscode_c_cpp_properties(self, modified_recipe):
183 properties_dict = {
184 "name": modified_recipe.recipe_id_pretty,
185 }
186 if modified_recipe.build_tool is BuildTool.CMAKE:
187 properties_dict["configurationProvider"] = "ms-vscode.cmake-tools"
188 elif modified_recipe.build_tool is BuildTool.MESON:
189 properties_dict["configurationProvider"] = "mesonbuild.mesonbuild"
190 else: # no C/C++ build
191 return
192
193 properties_dicts = {
194 "configurations": [
195 properties_dict
196 ],
197 "version": 4
198 }
199 prop_file = 'c_cpp_properties.json'
200 IdeBase.update_json_file(
201 self.dot_code_dir(modified_recipe), prop_file, properties_dicts)
202
203 def vscode_launch_bin_dbg(self, gdb_cross_config):
204 modified_recipe = gdb_cross_config.modified_recipe
205
206 launch_config = {
207 "name": gdb_cross_config.id_pretty,
208 "type": "cppdbg",
209 "request": "launch",
210 "program": os.path.join(modified_recipe.d, gdb_cross_config.binary.lstrip('/')),
211 "stopAtEntry": True,
212 "cwd": "${workspaceFolder}",
213 "environment": [],
214 "externalConsole": False,
215 "MIMode": "gdb",
216 "preLaunchTask": gdb_cross_config.id_pretty,
217 "miDebuggerPath": modified_recipe.gdb_cross.gdb,
218 "miDebuggerServerAddress": "%s:%d" % (modified_recipe.gdb_cross.host, gdb_cross_config.gdbserver_port)
219 }
220
221 # Search for header files in recipe-sysroot.
222 src_file_map = {
223 "/usr/include": os.path.join(modified_recipe.recipe_sysroot, "usr", "include")
224 }
225 # First of all search for not stripped binaries in the image folder.
226 # These binaries are copied (and optionally stripped) by deploy-target
227 setup_commands = [
228 {
229 "description": "sysroot",
230 "text": "set sysroot " + modified_recipe.d
231 }
232 ]
233
234 if gdb_cross_config.image_recipe.rootfs_dbg:
235 launch_config['additionalSOLibSearchPath'] = modified_recipe.solib_search_path_str(
236 gdb_cross_config.image_recipe)
237 src_file_map["/usr/src/debug"] = os.path.join(
238 gdb_cross_config.image_recipe.rootfs_dbg, "usr", "src", "debug")
239 else:
240 logger.warning(
241 "Cannot setup debug symbols configuration for GDB. IMAGE_GEN_DEBUGFS is not enabled.")
242
243 launch_config['sourceFileMap'] = src_file_map
244 launch_config['setupCommands'] = setup_commands
245 return launch_config
246
247 def vscode_launch(self, modified_recipe):
248 """GDB Launch configuration for binaries (elf files)"""
249
250 configurations = [self.vscode_launch_bin_dbg(
251 gdb_cross_config) for gdb_cross_config in self.gdb_cross_configs]
252 launch_dict = {
253 "version": "0.2.0",
254 "configurations": configurations
255 }
256 launch_file = 'launch.json'
257 IdeBase.update_json_file(
258 self.dot_code_dir(modified_recipe), launch_file, launch_dict)
259
260 def vscode_tasks_cpp(self, args, modified_recipe):
261 run_install_deploy = modified_recipe.gen_install_deploy_script(args)
262 install_task_name = "install && deploy-target %s" % modified_recipe.recipe_id_pretty
263 tasks_dict = {
264 "version": "2.0.0",
265 "tasks": [
266 {
267 "label": install_task_name,
268 "type": "shell",
269 "command": run_install_deploy,
270 "problemMatcher": []
271 }
272 ]
273 }
274 for gdb_cross_config in self.gdb_cross_configs:
275 tasks_dict['tasks'].append(
276 {
277 "label": gdb_cross_config.id_pretty,
278 "type": "shell",
279 "isBackground": True,
280 "dependsOn": [
281 install_task_name
282 ],
283 "command": gdb_cross_config.gdbserver_script,
284 "problemMatcher": [
285 {
286 "pattern": [
287 {
288 "regexp": ".",
289 "file": 1,
290 "location": 2,
291 "message": 3
292 }
293 ],
294 "background": {
295 "activeOnStart": True,
296 "beginsPattern": ".",
297 "endsPattern": ".",
298 }
299 }
300 ]
301 })
302 tasks_file = 'tasks.json'
303 IdeBase.update_json_file(
304 self.dot_code_dir(modified_recipe), tasks_file, tasks_dict)
305
306 def vscode_tasks_fallback(self, args, modified_recipe):
307 oe_init_dir = modified_recipe.oe_init_dir
308 oe_init = ". %s %s > /dev/null && " % (modified_recipe.oe_init_build_env, modified_recipe.topdir)
309 dt_build = "devtool build "
310 dt_build_label = dt_build + modified_recipe.recipe_id_pretty
311 dt_build_cmd = dt_build + modified_recipe.bpn
312 clean_opt = " --clean"
313 dt_build_clean_label = dt_build + modified_recipe.recipe_id_pretty + clean_opt
314 dt_build_clean_cmd = dt_build + modified_recipe.bpn + clean_opt
315 dt_deploy = "devtool deploy-target "
316 dt_deploy_label = dt_deploy + modified_recipe.recipe_id_pretty
317 dt_deploy_cmd = dt_deploy + modified_recipe.bpn
318 dt_build_deploy_label = "devtool build & deploy-target %s" % modified_recipe.recipe_id_pretty
319 deploy_opts = ' '.join(get_devtool_deploy_opts(args))
320 tasks_dict = {
321 "version": "2.0.0",
322 "tasks": [
323 {
324 "label": dt_build_label,
325 "type": "shell",
326 "command": "bash",
327 "linux": {
328 "options": {
329 "cwd": oe_init_dir
330 }
331 },
332 "args": [
333 "--login",
334 "-c",
335 "%s%s" % (oe_init, dt_build_cmd)
336 ],
337 "problemMatcher": []
338 },
339 {
340 "label": dt_deploy_label,
341 "type": "shell",
342 "command": "bash",
343 "linux": {
344 "options": {
345 "cwd": oe_init_dir
346 }
347 },
348 "args": [
349 "--login",
350 "-c",
351 "%s%s %s" % (
352 oe_init, dt_deploy_cmd, deploy_opts)
353 ],
354 "problemMatcher": []
355 },
356 {
357 "label": dt_build_deploy_label,
358 "dependsOrder": "sequence",
359 "dependsOn": [
360 dt_build_label,
361 dt_deploy_label
362 ],
363 "problemMatcher": [],
364 "group": {
365 "kind": "build",
366 "isDefault": True
367 }
368 },
369 {
370 "label": dt_build_clean_label,
371 "type": "shell",
372 "command": "bash",
373 "linux": {
374 "options": {
375 "cwd": oe_init_dir
376 }
377 },
378 "args": [
379 "--login",
380 "-c",
381 "%s%s" % (oe_init, dt_build_clean_cmd)
382 ],
383 "problemMatcher": []
384 }
385 ]
386 }
387 if modified_recipe.gdb_cross:
388 for gdb_cross_config in self.gdb_cross_configs:
389 tasks_dict['tasks'].append(
390 {
391 "label": gdb_cross_config.id_pretty,
392 "type": "shell",
393 "isBackground": True,
394 "dependsOn": [
395 dt_build_deploy_label
396 ],
397 "command": gdb_cross_config.gdbserver_script,
398 "problemMatcher": [
399 {
400 "pattern": [
401 {
402 "regexp": ".",
403 "file": 1,
404 "location": 2,
405 "message": 3
406 }
407 ],
408 "background": {
409 "activeOnStart": True,
410 "beginsPattern": ".",
411 "endsPattern": ".",
412 }
413 }
414 ]
415 })
416 tasks_file = 'tasks.json'
417 IdeBase.update_json_file(
418 self.dot_code_dir(modified_recipe), tasks_file, tasks_dict)
419
420 def vscode_tasks(self, args, modified_recipe):
421 if modified_recipe.build_tool.is_c_ccp:
422 self.vscode_tasks_cpp(args, modified_recipe)
423 else:
424 self.vscode_tasks_fallback(args, modified_recipe)
425
426 def setup_modified_recipe(self, args, image_recipe, modified_recipe):
427 self.vscode_settings(modified_recipe)
428 self.vscode_extensions(modified_recipe)
429 self.vscode_c_cpp_properties(modified_recipe)
430 if args.target:
431 self.initialize_gdb_cross_configs(
432 image_recipe, modified_recipe, gdb_cross_config_class=GdbCrossConfigVSCode)
433 self.vscode_launch(modified_recipe)
434 self.vscode_tasks(args, modified_recipe)
435
436
437def register_ide_plugin(ide_plugins):
438 ide_plugins['code'] = IdeVSCode