blob: f44665ed8729bda25e7c320506015c047d854664 [file] [log] [blame]
#
# Copyright (C) 2023-2024 Siemens AG
#
# SPDX-License-Identifier: GPL-2.0-only
#
"""Devtool ide-sdk IDE plugin for VSCode and VSCodium"""
import json
import logging
import os
import shutil
from devtool.ide_plugins import BuildTool, IdeBase, GdbCrossConfig, get_devtool_deploy_opts
logger = logging.getLogger('devtool')
class GdbCrossConfigVSCode(GdbCrossConfig):
def __init__(self, image_recipe, modified_recipe, binary):
super().__init__(image_recipe, modified_recipe, binary, False)
def initialize(self):
self._gen_gdbserver_start_script()
class IdeVSCode(IdeBase):
"""Manage IDE configurations for VSCode
Modified recipe mode:
- cmake: use the cmake-preset generated by devtool ide-sdk
- meson: meson is called via a wrapper script generated by devtool ide-sdk
Shared sysroot mode:
In shared sysroot mode, the cross tool-chain is exported to the user's global configuration.
A workspace cannot be created because there is no recipe that defines how a workspace could
be set up.
- cmake: adds a cmake-kit to .local/share/CMakeTools/cmake-tools-kits.json
The cmake-kit uses the environment script and the tool-chain file
generated by meta-ide-support.
- meson: Meson needs manual workspace configuration.
"""
@classmethod
def ide_plugin_priority(cls):
"""If --ide is not passed this is the default plugin"""
if shutil.which('code'):
return 100
return 0
def setup_shared_sysroots(self, shared_env):
"""Expose the toolchain of the shared sysroots SDK"""
datadir = shared_env.ide_support.datadir
deploy_dir_image = shared_env.ide_support.deploy_dir_image
real_multimach_target_sys = shared_env.ide_support.real_multimach_target_sys
standalone_sysroot_native = shared_env.build_sysroots.standalone_sysroot_native
vscode_ws_path = os.path.join(
os.environ['HOME'], '.local', 'share', 'CMakeTools')
cmake_kits_path = os.path.join(vscode_ws_path, 'cmake-tools-kits.json')
oecmake_generator = "Ninja"
env_script = os.path.join(
deploy_dir_image, 'environment-setup-' + real_multimach_target_sys)
if not os.path.isdir(vscode_ws_path):
os.makedirs(vscode_ws_path)
cmake_kits_old = []
if os.path.exists(cmake_kits_path):
with open(cmake_kits_path, 'r', encoding='utf-8') as cmake_kits_file:
cmake_kits_old = json.load(cmake_kits_file)
cmake_kits = cmake_kits_old.copy()
cmake_kit_new = {
"name": "OE " + real_multimach_target_sys,
"environmentSetupScript": env_script,
"toolchainFile": standalone_sysroot_native + datadir + "/cmake/OEToolchainConfig.cmake",
"preferredGenerator": {
"name": oecmake_generator
}
}
def merge_kit(cmake_kits, cmake_kit_new):
i = 0
while i < len(cmake_kits):
if 'environmentSetupScript' in cmake_kits[i] and \
cmake_kits[i]['environmentSetupScript'] == cmake_kit_new['environmentSetupScript']:
cmake_kits[i] = cmake_kit_new
return
i += 1
cmake_kits.append(cmake_kit_new)
merge_kit(cmake_kits, cmake_kit_new)
if cmake_kits != cmake_kits_old:
logger.info("Updating: %s" % cmake_kits_path)
with open(cmake_kits_path, 'w', encoding='utf-8') as cmake_kits_file:
json.dump(cmake_kits, cmake_kits_file, indent=4)
else:
logger.info("Already up to date: %s" % cmake_kits_path)
cmake_native = os.path.join(
shared_env.build_sysroots.standalone_sysroot_native, 'usr', 'bin', 'cmake')
if os.path.isfile(cmake_native):
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)
else:
logger.error("Cannot find cmake native at: %s" % cmake_native)
def dot_code_dir(self, modified_recipe):
return os.path.join(modified_recipe.srctree, '.vscode')
def __vscode_settings_meson(self, settings_dict, modified_recipe):
if modified_recipe.build_tool is not BuildTool.MESON:
return
settings_dict["mesonbuild.mesonPath"] = modified_recipe.meson_wrapper
confopts = modified_recipe.mesonopts.split()
confopts += modified_recipe.meson_cross_file.split()
confopts += modified_recipe.extra_oemeson.split()
settings_dict["mesonbuild.configureOptions"] = confopts
settings_dict["mesonbuild.buildFolder"] = modified_recipe.b
def __vscode_settings_cmake(self, settings_dict, modified_recipe):
"""Add cmake specific settings to settings.json.
Note: most settings are passed to the cmake preset.
"""
if modified_recipe.build_tool is not BuildTool.CMAKE:
return
settings_dict["cmake.configureOnOpen"] = True
settings_dict["cmake.sourceDirectory"] = modified_recipe.real_srctree
def vscode_settings(self, modified_recipe, image_recipe):
files_excludes = {
"**/.git/**": True,
"**/oe-logs/**": True,
"**/oe-workdir/**": True,
"**/source-date-epoch/**": True
}
python_exclude = [
"**/.git/**",
"**/oe-logs/**",
"**/oe-workdir/**",
"**/source-date-epoch/**"
]
files_readonly = {
modified_recipe.recipe_sysroot + '/**': True,
modified_recipe.recipe_sysroot_native + '/**': True,
}
if image_recipe.rootfs_dbg is not None:
files_readonly[image_recipe.rootfs_dbg + '/**'] = True
settings_dict = {
"files.watcherExclude": files_excludes,
"files.exclude": files_excludes,
"files.readonlyInclude": files_readonly,
"python.analysis.exclude": python_exclude
}
self.__vscode_settings_cmake(settings_dict, modified_recipe)
self.__vscode_settings_meson(settings_dict, modified_recipe)
settings_file = 'settings.json'
IdeBase.update_json_file(
self.dot_code_dir(modified_recipe), settings_file, settings_dict)
def __vscode_extensions_cmake(self, modified_recipe, recommendations):
if modified_recipe.build_tool is not BuildTool.CMAKE:
return
recommendations += [
"twxs.cmake",
"ms-vscode.cmake-tools",
"ms-vscode.cpptools",
"ms-vscode.cpptools-extension-pack",
"ms-vscode.cpptools-themes"
]
def __vscode_extensions_meson(self, modified_recipe, recommendations):
if modified_recipe.build_tool is not BuildTool.MESON:
return
recommendations += [
'mesonbuild.mesonbuild',
"ms-vscode.cpptools",
"ms-vscode.cpptools-extension-pack",
"ms-vscode.cpptools-themes"
]
def vscode_extensions(self, modified_recipe):
recommendations = []
self.__vscode_extensions_cmake(modified_recipe, recommendations)
self.__vscode_extensions_meson(modified_recipe, recommendations)
extensions_file = 'extensions.json'
IdeBase.update_json_file(
self.dot_code_dir(modified_recipe), extensions_file, {"recommendations": recommendations})
def vscode_c_cpp_properties(self, modified_recipe):
properties_dict = {
"name": modified_recipe.recipe_id_pretty,
}
if modified_recipe.build_tool is BuildTool.CMAKE:
properties_dict["configurationProvider"] = "ms-vscode.cmake-tools"
elif modified_recipe.build_tool is BuildTool.MESON:
properties_dict["configurationProvider"] = "mesonbuild.mesonbuild"
else: # no C/C++ build
return
properties_dicts = {
"configurations": [
properties_dict
],
"version": 4
}
prop_file = 'c_cpp_properties.json'
IdeBase.update_json_file(
self.dot_code_dir(modified_recipe), prop_file, properties_dicts)
def vscode_launch_bin_dbg(self, gdb_cross_config):
modified_recipe = gdb_cross_config.modified_recipe
launch_config = {
"name": gdb_cross_config.id_pretty,
"type": "cppdbg",
"request": "launch",
"program": os.path.join(modified_recipe.d, gdb_cross_config.binary.lstrip('/')),
"stopAtEntry": True,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": False,
"MIMode": "gdb",
"preLaunchTask": gdb_cross_config.id_pretty,
"miDebuggerPath": modified_recipe.gdb_cross.gdb,
"miDebuggerServerAddress": "%s:%d" % (modified_recipe.gdb_cross.host, gdb_cross_config.gdbserver_port)
}
# Search for header files in recipe-sysroot.
src_file_map = {
"/usr/include": os.path.join(modified_recipe.recipe_sysroot, "usr", "include")
}
# First of all search for not stripped binaries in the image folder.
# These binaries are copied (and optionally stripped) by deploy-target
setup_commands = [
{
"description": "sysroot",
"text": "set sysroot " + modified_recipe.d
}
]
if gdb_cross_config.image_recipe.rootfs_dbg:
launch_config['additionalSOLibSearchPath'] = modified_recipe.solib_search_path_str(
gdb_cross_config.image_recipe)
src_file_map["/usr/src/debug"] = os.path.join(
gdb_cross_config.image_recipe.rootfs_dbg, "usr", "src", "debug")
else:
logger.warning(
"Cannot setup debug symbols configuration for GDB. IMAGE_GEN_DEBUGFS is not enabled.")
launch_config['sourceFileMap'] = src_file_map
launch_config['setupCommands'] = setup_commands
return launch_config
def vscode_launch(self, modified_recipe):
"""GDB Launch configuration for binaries (elf files)"""
configurations = [self.vscode_launch_bin_dbg(
gdb_cross_config) for gdb_cross_config in self.gdb_cross_configs]
launch_dict = {
"version": "0.2.0",
"configurations": configurations
}
launch_file = 'launch.json'
IdeBase.update_json_file(
self.dot_code_dir(modified_recipe), launch_file, launch_dict)
def vscode_tasks_cpp(self, args, modified_recipe):
run_install_deploy = modified_recipe.gen_install_deploy_script(args)
install_task_name = "install && deploy-target %s" % modified_recipe.recipe_id_pretty
tasks_dict = {
"version": "2.0.0",
"tasks": [
{
"label": install_task_name,
"type": "shell",
"command": run_install_deploy,
"problemMatcher": []
}
]
}
for gdb_cross_config in self.gdb_cross_configs:
tasks_dict['tasks'].append(
{
"label": gdb_cross_config.id_pretty,
"type": "shell",
"isBackground": True,
"dependsOn": [
install_task_name
],
"command": gdb_cross_config.gdbserver_script,
"problemMatcher": [
{
"pattern": [
{
"regexp": ".",
"file": 1,
"location": 2,
"message": 3
}
],
"background": {
"activeOnStart": True,
"beginsPattern": ".",
"endsPattern": ".",
}
}
]
})
tasks_file = 'tasks.json'
IdeBase.update_json_file(
self.dot_code_dir(modified_recipe), tasks_file, tasks_dict)
def vscode_tasks_fallback(self, args, modified_recipe):
oe_init_dir = modified_recipe.oe_init_dir
oe_init = ". %s %s > /dev/null && " % (modified_recipe.oe_init_build_env, modified_recipe.topdir)
dt_build = "devtool build "
dt_build_label = dt_build + modified_recipe.recipe_id_pretty
dt_build_cmd = dt_build + modified_recipe.bpn
clean_opt = " --clean"
dt_build_clean_label = dt_build + modified_recipe.recipe_id_pretty + clean_opt
dt_build_clean_cmd = dt_build + modified_recipe.bpn + clean_opt
dt_deploy = "devtool deploy-target "
dt_deploy_label = dt_deploy + modified_recipe.recipe_id_pretty
dt_deploy_cmd = dt_deploy + modified_recipe.bpn
dt_build_deploy_label = "devtool build & deploy-target %s" % modified_recipe.recipe_id_pretty
deploy_opts = ' '.join(get_devtool_deploy_opts(args))
tasks_dict = {
"version": "2.0.0",
"tasks": [
{
"label": dt_build_label,
"type": "shell",
"command": "bash",
"linux": {
"options": {
"cwd": oe_init_dir
}
},
"args": [
"--login",
"-c",
"%s%s" % (oe_init, dt_build_cmd)
],
"problemMatcher": []
},
{
"label": dt_deploy_label,
"type": "shell",
"command": "bash",
"linux": {
"options": {
"cwd": oe_init_dir
}
},
"args": [
"--login",
"-c",
"%s%s %s" % (
oe_init, dt_deploy_cmd, deploy_opts)
],
"problemMatcher": []
},
{
"label": dt_build_deploy_label,
"dependsOrder": "sequence",
"dependsOn": [
dt_build_label,
dt_deploy_label
],
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": True
}
},
{
"label": dt_build_clean_label,
"type": "shell",
"command": "bash",
"linux": {
"options": {
"cwd": oe_init_dir
}
},
"args": [
"--login",
"-c",
"%s%s" % (oe_init, dt_build_clean_cmd)
],
"problemMatcher": []
}
]
}
if modified_recipe.gdb_cross:
for gdb_cross_config in self.gdb_cross_configs:
tasks_dict['tasks'].append(
{
"label": gdb_cross_config.id_pretty,
"type": "shell",
"isBackground": True,
"dependsOn": [
dt_build_deploy_label
],
"command": gdb_cross_config.gdbserver_script,
"problemMatcher": [
{
"pattern": [
{
"regexp": ".",
"file": 1,
"location": 2,
"message": 3
}
],
"background": {
"activeOnStart": True,
"beginsPattern": ".",
"endsPattern": ".",
}
}
]
})
tasks_file = 'tasks.json'
IdeBase.update_json_file(
self.dot_code_dir(modified_recipe), tasks_file, tasks_dict)
def vscode_tasks(self, args, modified_recipe):
if modified_recipe.build_tool.is_c_ccp:
self.vscode_tasks_cpp(args, modified_recipe)
else:
self.vscode_tasks_fallback(args, modified_recipe)
def setup_modified_recipe(self, args, image_recipe, modified_recipe):
self.vscode_settings(modified_recipe, image_recipe)
self.vscode_extensions(modified_recipe)
self.vscode_c_cpp_properties(modified_recipe)
if args.target:
self.initialize_gdb_cross_configs(
image_recipe, modified_recipe, gdb_cross_config_class=GdbCrossConfigVSCode)
self.vscode_launch(modified_recipe)
self.vscode_tasks(args, modified_recipe)
def register_ide_plugin(ide_plugins):
ide_plugins['code'] = IdeVSCode