mirror of
git://git.yoctoproject.org/poky.git
synced 2025-07-17 19:09:04 +02:00

Do no longer recommend the twxs.cmake VSCode plugin. There is now a language server built into the ms-vscode.cmake-tools plugin as well. >From Release notes 1.20.53 https://marketplace.visualstudio.com/items/ms-vscode.cmake-tools/changelog Add notification suggesting users to uninstall twxs.cmake now that we have built-in Language Services. Follow this advice. (From OE-Core rev: 83d3465a0536a2ffa9c2b6f042051881ec055f5a) Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
463 lines
18 KiB
Python
463 lines
18 KiB
Python
#
|
|
# 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 += [
|
|
"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"
|
|
properties_dict["compilerPath"] = os.path.join(modified_recipe.staging_bindir_toolchain, modified_recipe.cxx.split()[0])
|
|
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)
|
|
# First: Search for sources of this recipe in the workspace folder
|
|
if modified_recipe.pn in modified_recipe.target_dbgsrc_dir:
|
|
src_file_map[modified_recipe.target_dbgsrc_dir] = "${workspaceFolder}"
|
|
else:
|
|
logger.error(
|
|
"TARGET_DBGSRC_DIR must contain the recipe name PN.")
|
|
# Second: Search for sources of other recipes in the rootfs-dbg
|
|
if modified_recipe.target_dbgsrc_dir.startswith("/usr/src/debug"):
|
|
src_file_map["/usr/src/debug"] = os.path.join(
|
|
gdb_cross_config.image_recipe.rootfs_dbg, "usr", "src", "debug")
|
|
else:
|
|
logger.error(
|
|
"TARGET_DBGSRC_DIR must start with /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 = []
|
|
for gdb_cross_config in self.gdb_cross_configs:
|
|
if gdb_cross_config.modified_recipe is modified_recipe:
|
|
configurations.append(self.vscode_launch_bin_dbg(gdb_cross_config))
|
|
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:
|
|
if gdb_cross_config.modified_recipe is not modified_recipe:
|
|
continue
|
|
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:
|
|
if gdb_cross_config.modified_recipe is not modified_recipe:
|
|
continue
|
|
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
|