summaryrefslogtreecommitdiffstats
path: root/scripts/lib/devtool/ide_plugins/ide_code.py
diff options
context:
space:
mode:
authorAdrian Freihofer <adrian.freihofer@gmail.com>2024-01-22 14:58:21 +0100
committerRichard Purdie <richard.purdie@linuxfoundation.org>2024-02-18 07:34:42 +0000
commit3ccb4d8ab1d7f4103f245f754086ec19f0195cc1 (patch)
treefee7516c0628dd0cdb93c38a534507404cfd9620 /scripts/lib/devtool/ide_plugins/ide_code.py
parentf909d235c95d89bd44a7d3fc719adfc82cdc1d98 (diff)
downloadpoky-3ccb4d8ab1d7f4103f245f754086ec19f0195cc1.tar.gz
devtool: new ide-sdk plugin
The new devtool ide plugin provides the eSDK and configures an IDE to work with the eSDK. In doing so, bitbake should be used to generate the IDE configuration and update the SDK, but it should no longer play a role when working on the source code. The work on the source code should take place exclusively with the IDE, which, for example, calls cmake directly to compile the code and execute the unit tests from the IDE. The plugin works for recipes inheriting the cmake or the meson bbclass. Support for more programming languages and build tools may be added in the future. There are various IDEs that can be used for the development of embedded Linux applications. Therefore, devtool ide-sdk, like devtool itself, supports plugins to support IDEs. VSCode is the default IDE for this first implementation. Additionally, some generic helper scripts can be generated with --ide none instead of a specific IDE configuration. This can be used for any IDE that supports calling some scripts. There are two different modes supported: - devtool modify mode (default): devtool ide-sdk configures the IDE to manage the build-tool used by the recipe (e.g. cmake or meson). The workflow looks like: $ devtool modify a-recipe $ devtool ide-sdk a-recipe a-image $ code "$BUILDDIR/workspace/sources/a-recipe" Work in VSCode, after installing the proposed plugins Deploying the artifacts to the target device and running a remote debugging session is supported as well. This first implementation still calls bitbake and devtool to copy the binary artifacts to the target device. In contrast to compiling, installation and copying must be performed with the file rights of the target device. The pseudo tool must be used for this. Therefore bitbake -c install a-recipe && devtool deploy-target a-recipe are called by the IDE for the deployment. This might be improved later on. Executing the unit tests out of the IDE is supported via Qemu user if the build tool supports that. CMake (if cmake-qemu.bbclass is inherited) and Meson support Qemu usermode. - Shared sysroots mode: bootstraps the eSDK with shared sysroots for all the recipes passed to devtool ide-sdk. This is basically a wrapper for bitbake meta-ide-support && bitbake build-sysroots. The workflow looks like: $ devtool ide-sdk --share-sysroots a-recipe another-recipe vscode where/the/sources/are If the IDE and the build tool support it, the IDE gets configured to offer the cross tool-chain provided by the eSDK. In case of VSCode and cmake a cmake-kit is generated. This offers to use the cross tool-chain from the UI of the IDE. Many thanks to Enguerrand de Ribaucourt for testing and bug fixing. (From OE-Core rev: 3f8af7a36589cd05fd07d16cbdd03d6b3dff1f82) Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com> Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
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