summaryrefslogtreecommitdiffstats
path: root/scripts/lib/devtool/ide_plugins/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/lib/devtool/ide_plugins/__init__.py')
-rw-r--r--scripts/lib/devtool/ide_plugins/__init__.py267
1 files changed, 267 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