summaryrefslogtreecommitdiffstats
path: root/scripts/lib/devtool/ide_plugins
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
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')
-rw-r--r--scripts/lib/devtool/ide_plugins/__init__.py267
-rw-r--r--scripts/lib/devtool/ide_plugins/ide_code.py438
-rw-r--r--scripts/lib/devtool/ide_plugins/ide_none.py53
3 files changed, 758 insertions, 0 deletions
diff --git a/scripts/lib/devtool/ide_plugins/__init__.py b/scripts/lib/devtool/ide_plugins/__init__.py
new file mode 100644
index 0000000000..3371b24264
--- /dev/null
+++ b/scripts/lib/devtool/ide_plugins/__init__.py
@@ -0,0 +1,267 @@
1#
2# Copyright (C) 2023-2024 Siemens AG
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6"""Devtool ide-sdk IDE plugin interface definition and helper functions"""
7
8import errno
9import json
10import logging
11import os
12import stat
13from enum import Enum, auto
14from devtool import DevtoolError
15from bb.utils import mkdirhier
16
17logger = logging.getLogger('devtool')
18
19
20class BuildTool(Enum):
21 UNDEFINED = auto()
22 CMAKE = auto()
23 MESON = auto()
24
25 @property
26 def is_c_ccp(self):
27 if self is BuildTool.CMAKE:
28 return True
29 if self is BuildTool.MESON:
30 return True
31 return False
32
33
34class GdbCrossConfig:
35 """Base class defining the GDB configuration generator interface
36
37 Generate a GDB configuration for a binary on the target device.
38 Only one instance per binary is allowed. This allows to assign unique port
39 numbers for all gdbserver instances.
40 """
41 _gdbserver_port_next = 1234
42 _binaries = []
43
44 def __init__(self, image_recipe, modified_recipe, binary, gdbserver_multi=True):
45 self.image_recipe = image_recipe
46 self.modified_recipe = modified_recipe
47 self.gdb_cross = modified_recipe.gdb_cross
48 self.binary = binary
49 if binary in GdbCrossConfig._binaries:
50 raise DevtoolError(
51 "gdbserver config for binary %s is already generated" % binary)
52 GdbCrossConfig._binaries.append(binary)
53 self.script_dir = modified_recipe.ide_sdk_scripts_dir
54 self.gdbinit_dir = os.path.join(self.script_dir, 'gdbinit')
55 self.gdbserver_multi = gdbserver_multi
56 self.binary_pretty = self.binary.replace(os.sep, '-').lstrip('-')
57 self.gdbserver_port = GdbCrossConfig._gdbserver_port_next
58 GdbCrossConfig._gdbserver_port_next += 1
59 self.id_pretty = "%d_%s" % (self.gdbserver_port, self.binary_pretty)
60 # gdbserver start script
61 gdbserver_script_file = 'gdbserver_' + self.id_pretty
62 if self.gdbserver_multi:
63 gdbserver_script_file += "_m"
64 self.gdbserver_script = os.path.join(
65 self.script_dir, gdbserver_script_file)
66 # gdbinit file
67 self.gdbinit = os.path.join(
68 self.gdbinit_dir, 'gdbinit_' + self.id_pretty)
69 # gdb start script
70 self.gdb_script = os.path.join(
71 self.script_dir, 'gdb_' + self.id_pretty)
72
73 def _gen_gdbserver_start_script(self):
74 """Generate a shell command starting the gdbserver on the remote device via ssh
75
76 GDB supports two modes:
77 multi: gdbserver remains running over several debug sessions
78 once: gdbserver terminates after the debugged process terminates
79 """
80 cmd_lines = ['#!/bin/sh']
81 if self.gdbserver_multi:
82 temp_dir = "TEMP_DIR=/tmp/gdbserver_%s; " % self.id_pretty
83 gdbserver_cmd_start = temp_dir
84 gdbserver_cmd_start += "test -f \$TEMP_DIR/pid && exit 0; "
85 gdbserver_cmd_start += "mkdir -p \$TEMP_DIR; "
86 gdbserver_cmd_start += "%s --multi :%s > \$TEMP_DIR/log 2>&1 & " % (
87 self.gdb_cross.gdbserver_path, self.gdbserver_port)
88 gdbserver_cmd_start += "echo \$! > \$TEMP_DIR/pid;"
89
90 gdbserver_cmd_stop = temp_dir
91 gdbserver_cmd_stop += "test -f \$TEMP_DIR/pid && kill \$(cat \$TEMP_DIR/pid); "
92 gdbserver_cmd_stop += "rm -rf \$TEMP_DIR; "
93
94 gdbserver_cmd_l = []
95 gdbserver_cmd_l.append('if [ "$1" = "stop" ]; then')
96 gdbserver_cmd_l.append(' shift')
97 gdbserver_cmd_l.append(" %s %s %s %s 'sh -c \"%s\"'" % (
98 self.gdb_cross.target_device.ssh_sshexec, self.gdb_cross.target_device.ssh_port, self.gdb_cross.target_device.extraoptions, self.gdb_cross.target_device.target, gdbserver_cmd_stop))
99 gdbserver_cmd_l.append('else')
100 gdbserver_cmd_l.append(" %s %s %s %s 'sh -c \"%s\"'" % (
101 self.gdb_cross.target_device.ssh_sshexec, self.gdb_cross.target_device.ssh_port, self.gdb_cross.target_device.extraoptions, self.gdb_cross.target_device.target, gdbserver_cmd_start))
102 gdbserver_cmd_l.append('fi')
103 gdbserver_cmd = os.linesep.join(gdbserver_cmd_l)
104 else:
105 gdbserver_cmd_start = "%s --once :%s %s" % (
106 self.gdb_cross.gdbserver_path, self.gdbserver_port, self.binary)
107 gdbserver_cmd = "%s %s %s %s 'sh -c \"%s\"'" % (
108 self.gdb_cross.target_device.ssh_sshexec, self.gdb_cross.target_device.ssh_port, self.gdb_cross.target_device.extraoptions, self.gdb_cross.target_device.target, gdbserver_cmd_start)
109 cmd_lines.append(gdbserver_cmd)
110 GdbCrossConfig.write_file(self.gdbserver_script, cmd_lines, True)
111
112 def _gen_gdbinit_config(self):
113 """Generate a gdbinit file for this binary and the corresponding gdbserver configuration"""
114 gdbinit_lines = ['# This file is generated by devtool ide-sdk']
115 if self.gdbserver_multi:
116 target_help = '# gdbserver --multi :%d' % self.gdbserver_port
117 remote_cmd = 'target extended-remote'
118 else:
119 target_help = '# gdbserver :%d %s' % (
120 self.gdbserver_port, self.binary)
121 remote_cmd = 'target remote'
122 gdbinit_lines.append('# On the remote target:')
123 gdbinit_lines.append(target_help)
124 gdbinit_lines.append('# On the build machine:')
125 gdbinit_lines.append('# cd ' + self.modified_recipe.real_srctree)
126 gdbinit_lines.append(
127 '# ' + self.gdb_cross.gdb + ' -ix ' + self.gdbinit)
128
129 gdbinit_lines.append('set sysroot ' + self.modified_recipe.d)
130 gdbinit_lines.append('set substitute-path "/usr/include" "' +
131 os.path.join(self.modified_recipe.recipe_sysroot, 'usr', 'include') + '"')
132 # Disable debuginfod for now, the IDE configuration uses rootfs-dbg from the image workdir.
133 gdbinit_lines.append('set debuginfod enabled off')
134 if self.image_recipe.rootfs_dbg:
135 gdbinit_lines.append(
136 'set solib-search-path "' + self.modified_recipe.solib_search_path_str(self.image_recipe) + '"')
137 gdbinit_lines.append('set substitute-path "/usr/src/debug" "' + os.path.join(
138 self.image_recipe.rootfs_dbg, 'usr', 'src', 'debug') + '"')
139 gdbinit_lines.append(
140 '%s %s:%d' % (remote_cmd, self.gdb_cross.host, self.gdbserver_port))
141 gdbinit_lines.append('set remote exec-file ' + self.binary)
142 gdbinit_lines.append(
143 'run ' + os.path.join(self.modified_recipe.d, self.binary))
144
145 GdbCrossConfig.write_file(self.gdbinit, gdbinit_lines)
146
147 def _gen_gdb_start_script(self):
148 """Generate a script starting GDB with the corresponding gdbinit configuration."""
149 cmd_lines = ['#!/bin/sh']
150 cmd_lines.append('cd ' + self.modified_recipe.real_srctree)
151 cmd_lines.append(self.gdb_cross.gdb + ' -ix ' +
152 self.gdbinit + ' "$@"')
153 GdbCrossConfig.write_file(self.gdb_script, cmd_lines, True)
154
155 def initialize(self):
156 self._gen_gdbserver_start_script()
157 self._gen_gdbinit_config()
158 self._gen_gdb_start_script()
159
160 @staticmethod
161 def write_file(script_file, cmd_lines, executable=False):
162 script_dir = os.path.dirname(script_file)
163 mkdirhier(script_dir)
164 with open(script_file, 'w') as script_f:
165 script_f.write(os.linesep.join(cmd_lines))
166 script_f.write(os.linesep)
167 if executable:
168 st = os.stat(script_file)
169 os.chmod(script_file, st.st_mode | stat.S_IEXEC)
170 logger.info("Created: %s" % script_file)
171
172
173class IdeBase:
174 """Base class defining the interface for IDE plugins"""
175
176 def __init__(self):
177 self.ide_name = 'undefined'
178 self.gdb_cross_configs = []
179
180 @classmethod
181 def ide_plugin_priority(cls):
182 """Used to find the default ide handler if --ide is not passed"""
183 return 10
184
185 def setup_shared_sysroots(self, shared_env):
186 logger.warn("Shared sysroot mode is not supported for IDE %s" %
187 self.ide_name)
188
189 def setup_modified_recipe(self, args, image_recipe, modified_recipe):
190 logger.warn("Modified recipe mode is not supported for IDE %s" %
191 self.ide_name)
192
193 def initialize_gdb_cross_configs(self, image_recipe, modified_recipe, gdb_cross_config_class=GdbCrossConfig):
194 binaries = modified_recipe.find_installed_binaries()
195 for binary in binaries:
196 gdb_cross_config = gdb_cross_config_class(
197 image_recipe, modified_recipe, binary)
198 gdb_cross_config.initialize()
199 self.gdb_cross_configs.append(gdb_cross_config)
200
201 @staticmethod
202 def gen_oe_scrtips_sym_link(modified_recipe):
203 # create a sym-link from sources to the scripts directory
204 if os.path.isdir(modified_recipe.ide_sdk_scripts_dir):
205 IdeBase.symlink_force(modified_recipe.ide_sdk_scripts_dir,
206 os.path.join(modified_recipe.real_srctree, 'oe-scripts'))
207
208 @staticmethod
209 def update_json_file(json_dir, json_file, update_dict):
210 """Update a json file
211
212 By default it uses the dict.update function. If this is not sutiable
213 the update function might be passed via update_func parameter.
214 """
215 json_path = os.path.join(json_dir, json_file)
216 logger.info("Updating IDE config file: %s (%s)" %
217 (json_file, json_path))
218 if not os.path.exists(json_dir):
219 os.makedirs(json_dir)
220 try:
221 with open(json_path) as f:
222 orig_dict = json.load(f)
223 except json.decoder.JSONDecodeError:
224 logger.info(
225 "Decoding %s failed. Probably because of comments in the json file" % json_path)
226 orig_dict = {}
227 except FileNotFoundError:
228 orig_dict = {}
229 orig_dict.update(update_dict)
230 with open(json_path, 'w') as f:
231 json.dump(orig_dict, f, indent=4)
232
233 @staticmethod
234 def symlink_force(tgt, dst):
235 try:
236 os.symlink(tgt, dst)
237 except OSError as err:
238 if err.errno == errno.EEXIST:
239 if os.readlink(dst) != tgt:
240 os.remove(dst)
241 os.symlink(tgt, dst)
242 else:
243 raise err
244
245
246def get_devtool_deploy_opts(args):
247 """Filter args for devtool deploy-target args"""
248 if not args.target:
249 return None
250 devtool_deploy_opts = [args.target]
251 if args.no_host_check:
252 devtool_deploy_opts += ["-c"]
253 if args.show_status:
254 devtool_deploy_opts += ["-s"]
255 if args.no_preserve:
256 devtool_deploy_opts += ["-p"]
257 if args.no_check_space:
258 devtool_deploy_opts += ["--no-check-space"]
259 if args.ssh_exec:
260 devtool_deploy_opts += ["-e", args.ssh.exec]
261 if args.port:
262 devtool_deploy_opts += ["-P", args.port]
263 if args.key:
264 devtool_deploy_opts += ["-I", args.key]
265 if args.strip is False:
266 devtool_deploy_opts += ["--no-strip"]
267 return devtool_deploy_opts
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
diff --git a/scripts/lib/devtool/ide_plugins/ide_none.py b/scripts/lib/devtool/ide_plugins/ide_none.py
new file mode 100644
index 0000000000..f106c5a026
--- /dev/null
+++ b/scripts/lib/devtool/ide_plugins/ide_none.py
@@ -0,0 +1,53 @@
1#
2# Copyright (C) 2023-2024 Siemens AG
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6"""Devtool ide-sdk generic IDE plugin"""
7
8import os
9import logging
10from devtool.ide_plugins import IdeBase, GdbCrossConfig
11
12logger = logging.getLogger('devtool')
13
14
15class IdeNone(IdeBase):
16 """Generate some generic helpers for other IDEs
17
18 Modified recipe mode:
19 Generate some helper scripts for remote debugging with GDB
20
21 Shared sysroot mode:
22 A wrapper for bitbake meta-ide-support and bitbake build-sysroots
23 """
24
25 def __init__(self):
26 super().__init__()
27
28 def setup_shared_sysroots(self, shared_env):
29 real_multimach_target_sys = shared_env.ide_support.real_multimach_target_sys
30 deploy_dir_image = shared_env.ide_support.deploy_dir_image
31 env_script = os.path.join(
32 deploy_dir_image, 'environment-setup-' + real_multimach_target_sys)
33 logger.info(
34 "To use this SDK please source this: %s" % env_script)
35
36 def setup_modified_recipe(self, args, image_recipe, modified_recipe):
37 """generate some helper scripts and config files
38
39 - Execute the do_install task
40 - Execute devtool deploy-target
41 - Generate a gdbinit file per executable
42 - Generate the oe-scripts sym-link
43 """
44 script_path = modified_recipe.gen_install_deploy_script(args)
45 logger.info("Created: %s" % script_path)
46
47 self.initialize_gdb_cross_configs(image_recipe, modified_recipe)
48
49 IdeBase.gen_oe_scrtips_sym_link(modified_recipe)
50
51
52def register_ide_plugin(ide_plugins):
53 ide_plugins['none'] = IdeNone