* [RFC PATCH v1 0/1] Add Visual Studio Code configuration script @ 2024-07-26 12:42 Anatoly Burakov 2024-07-26 12:42 ` [RFC PATCH v1 1/1] devtools: add vscode configuration generator Anatoly Burakov ` (3 more replies) 0 siblings, 4 replies; 23+ messages in thread From: Anatoly Burakov @ 2024-07-26 12:42 UTC (permalink / raw) To: dev; +Cc: john.mcnamara Lots of developers (myself included) uses Visual Studio Code as their primary IDE for DPDK development. I have been successfully using various incarnations of this script internally to quickly set up my development trees whenever I need a new configuration, so this script is being shared in hopes that it will be useful both to new developers starting with DPDK, and to seasoned DPDK developers who are already using Visual Studio Code. It makes starting working on DPDK in Visual Studio Code so much easier! Philosophy behind this script is as follows: - The assumption is made that a developer will not be using wildly different configurations from build to build - usually, they build the same things, work with the same set of apps/drivers for a while, then switch to something else, at which point a new configuration is needed - Some configurations I consider to be "common" are included: debug build, debug optimized build, release build with docs, and ASan build (feel free to make suggestions here!) - By default, the script will suggest enabling test, testpmd, and helloworld example - No drivers are being enabled by default - use needs to explicitly enable them (another option could be to leave things as default and build everything, but I rather prefer minimalistic builds as they're faster to compile, and it would be semantically weird to not have any drivers selected yet all of them being built) - All parameters that can be adjusted by TUI are also available as command line arguments, so while user interaction is the default (using whiptail), it's actually not required and can be bypassed. - I usually work as a local user not as root, so by default the script will attempt to use "gdbsudo" (a "sudo gdb $@" script in /usr/local/bin) for launch tasks, and stop if it is not available. Currently, it is only possible to define custom per-build configurations, while any "global" meson settings would have to involve editing settings.json file. This can be changed easily if required, but I've never needed this functionality. Please feel free to make any suggestions! Anatoly Burakov (1): devtools: add vscode configuration generator devtools/gen-vscode-config.py | 640 ++++++++++++++++++++++++++++++++++ 1 file changed, 640 insertions(+) create mode 100755 devtools/gen-vscode-config.py -- 2.43.5 ^ permalink raw reply [flat|nested] 23+ messages in thread
* [RFC PATCH v1 1/1] devtools: add vscode configuration generator 2024-07-26 12:42 [RFC PATCH v1 0/1] Add Visual Studio Code configuration script Anatoly Burakov @ 2024-07-26 12:42 ` Anatoly Burakov 2024-07-26 15:36 ` Stephen Hemminger 2024-07-29 13:05 ` [RFC PATCH v2 0/1] Add Visual Studio Code configuration script Anatoly Burakov ` (2 subsequent siblings) 3 siblings, 1 reply; 23+ messages in thread From: Anatoly Burakov @ 2024-07-26 12:42 UTC (permalink / raw) To: dev; +Cc: john.mcnamara A lot of developers use Visual Studio Code as their primary IDE. This script generates a configuration file for VSCode that sets up basic build tasks, launch tasks, as well as C/C++ code analysis settings that will take into account compile_commands.json that is automatically generated by meson. Files generated by script: - .vscode/settings.json: stores variables needed by other files - .vscode/tasks.json: defines build tasks - .vscode/launch.json: defines launch tasks - .vscode/c_cpp_properties.json: defines code analysis settings The script uses a combination of globbing and meson file parsing to discover available apps, examples, and drivers, and generates a project-wide settings file, so that the user can later switch between debug/release/etc. configurations while keeping their desired apps, examples, and drivers, built by meson, and ensuring launch configurations still work correctly whatever the configuration selected. This script uses whiptail as TUI, which is expected to be universally available as it is shipped by default on most major distributions. However, the script is also designed to be scriptable and can be run without user interaction, and have its configuration supplied from command-line arguments. Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com> --- devtools/gen-vscode-config.py | 640 ++++++++++++++++++++++++++++++++++ 1 file changed, 640 insertions(+) create mode 100755 devtools/gen-vscode-config.py diff --git a/devtools/gen-vscode-config.py b/devtools/gen-vscode-config.py new file mode 100755 index 0000000000..0d291b6c17 --- /dev/null +++ b/devtools/gen-vscode-config.py @@ -0,0 +1,640 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2024 Intel Corporation +# + +"""Visual Studio Code configuration generator script.""" + +import os +import json +import argparse +import fnmatch +import shutil +from typing import List, Dict, Tuple, Any +from sys import exit as _exit, stderr +from subprocess import run, CalledProcessError, PIPE +from mesonbuild import mparser +from mesonbuild.mesonlib import MesonException + + +class DPDKBuildTask: + """A build task for DPDK""" + + def __init__(self, label: str, description: str, param: str): + # label as it appears in build configuration + self.label = label + # description to be given in menu + self.description = description + # task-specific configuration parameters + self.param = param + + def to_json_dict(self) -> Dict[str, Any]: + """Generate JSON dictionary for this task""" + return { + "label": f"Configure {self.label}", + "detail": self.description, + "type": "shell", + "dependsOn": "Remove builddir", + "command": f"meson setup ${{config:BUILDCONFIG}} {self.param} ${{config:BUILDDIR}}", + "problemMatcher": [], + "group": "build" + } + + +class CmdlineCtx: + """POD class to set up command line parameters""" + + def __init__(self): + self.use_ui = False + self.use_gdbsudo = False + self.build_dir: str = "" + self.dpdk_dir: str = "" + self.gdb_path: str = "" + + self.avail_configs: List[Tuple[str, str, str]] = [] + self.avail_apps: List[str] = [] + self.avail_examples: List[str] = [] + self.avail_drivers: List[str] = [] + + self.enabled_configs: List[Tuple[str, str, str]] = [] + self.enabled_apps: List[str] = [] + self.enabled_examples: List[str] = [] + self.enabled_drivers: List[str] = [] + + self.driver_dep_map: Dict[str, List[str]] = {} + + +class DPDKLaunchTask: + """A launch task for DPDK""" + + def __init__(self, label: str, exe: str, gdb_path: str): + # label as it appears in launch configuration + self.label = label + # path to executable + self.exe = exe + self.gdb_path = gdb_path + + def to_json_dict(self) -> Dict[str, Any]: + """Generate JSON dictionary for this task""" + return { + "name": f"Run {self.label}", + "type": "cppdbg", + "request": "launch", + "program": f"${{config:BUILDDIR}}/{self.exe}", + "args": [], + "stopAtEntry": False, + "cwd": "${workspaceFolder}", + "externalConsole": False, + "preLaunchTask": "Build", + "MIMode": "gdb", + "miDebuggerPath": self.gdb_path, + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-gdb-set print pretty on", + "ignoreFailures": True + } + ] + } + + +class VSCodeConfig: + """Configuration for VSCode""" + + def __init__(self, builddir: str, commoncfg: str): + # where will our build dir be located + self.builddir = builddir + # meson configuration common to all configs + self.commonconfig = commoncfg + # meson build configurations + self.build_tasks: List[DPDKBuildTask] = [] + # meson launch configurations + self.launch_tasks: List[DPDKLaunchTask] = [] + + def settings_to_json_dict(self) -> Dict[str, Any]: + """Generate settings.json""" + return { + "BUILDDIR": self.builddir, + "BUILDCONFIG": self.commonconfig, + } + + def tasks_to_json_dict(self) -> Dict[str, Any]: + """Generate tasks.json""" + # generate outer layer + build_tasks: Dict[str, Any] = { + "version": "2.0.0", + "tasks": [] + } + # generate inner layer + tasks = build_tasks["tasks"] + # add common tasks + tasks.append({ + "label": "Remove builddir", + "type": "shell", + "command": "rm -rf ${config:BUILDDIR}", + }) + tasks.append({ + "label": "Build", + "detail": "Run build command", + "type": "shell", + "command": "ninja", + "options": { + "cwd": "${config:BUILDDIR}" + }, + "problemMatcher": { + "base": "$gcc", + "fileLocation": ["relative", "${config:BUILDDIR}"] + }, + "group": "build" + }) + # now, add generated tasks + tasks.extend([task.to_json_dict() for task in self.build_tasks]) + + # we're done + return build_tasks + + def launch_to_json_dict(self) -> Dict[str, Any]: + """Generate launch.json""" + return { + "version": "0.2.0", + "configurations": [task.to_json_dict() for task in self.launch_tasks] + } + + def c_cpp_properties_to_json_dict(self) -> Dict[str, Any]: + """Generate c_cpp_properties.json""" + return { + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${config:BUILDDIR}/", + "${workspaceFolder}/lib/eal/x86", + "${workspaceFolder}/lib/eal/linux", + "${workspaceFolder}/**" + ], + "compilerPath": "/usr/bin/gcc", + "cStandard": "c99", + "cppStandard": "c++17", + "intelliSenseMode": "${default}", + "compileCommands": "${config:BUILDDIR}/compile_commands.json" + } + ], + "version": 4 + } + + +def _whiptail_checklist(prompt: str, labels: List[str], + descriptions: List[str], + checked: List[bool]) -> List[str]: + """Display a checklist and get user input.""" + # build whiptail checklist + checklist = [ + (label, desc, "on" if checked[i] else "off") + for i, (label, desc) in enumerate(zip(labels, descriptions)) + ] + # flatten the list + checklist = [item for sublist in checklist for item in sublist] + # build whiptail arguments + args = [ + "whiptail", "--separate-output", "--checklist", + prompt, "15", "80", "10" + ] + checklist + + try: + result = run(args, stderr=PIPE, check=True) + except CalledProcessError: + # user probably pressed cancel, so bail out + _exit(1) + # capture selected options + selected = result.stderr.decode().strip().split() + return selected + + +def _whiptail_inputbox(prompt: str, default: str = "") -> str: + """Display an input box and get user input.""" + args = [ + "whiptail", "--inputbox", + prompt, "10", "70", default + ] + result = run(args, stderr=PIPE, check=True) + return result.stderr.decode().strip() + + +def _get_enabled_configurations(configs: List[Tuple[str, str, str]], + enabled: List[Tuple[str, str, str]]) \ + -> List[Tuple[str, str, str]]: + """Ask user which build configurations they want.""" + stop = False + while not stop: + labels = [task[0] for task in configs] + descriptions = [task[1] for task in configs] + checked = [c in enabled for c in configs] + # when interacting using UI, allow user to specify one custom meson + # item + labels += ["add"] + descriptions += ["Add new option"] + checked += [False] + + # ask user to select options + selected = _whiptail_checklist("Select build configurations to enable:", + labels, descriptions, checked) + + # enable all previously existing selected configs + enabled.clear() + for task in configs: + if task[0] in selected: + # enable this task + enabled.append(task) + # if user selected "add", ask for custom meson configuration + if "add" in selected: + custom_label = _whiptail_inputbox( + "Enter custom meson configuration label:") + custom_description = _whiptail_inputbox( + "Enter custom meson configuration description:") + custom_mesonstr = _whiptail_inputbox( + "Enter custom meson configuration string:") + new_task = (custom_label, custom_description, custom_mesonstr) + configs += [new_task] + # enable the new configuration + enabled += [new_task] + else: + stop = True + # return our list of enabled configurations + return enabled + + +def _get_enabled_list(apps: List[str], enabled: List[str]) -> List[str]: + """Display a list of items, optionally some enabled by default.""" + checked = [app in enabled for app in apps] + + # ask user to select options + selected = _whiptail_checklist("Select apps to enable:", + apps, ["" for _ in apps], checked) + + return selected + + +def _extract_var(path: str, var: str) -> Any: + """Extract a variable from a meson.build file.""" + try: + # we don't want to deal with multiline variable assignments + # so just read entire file in one go + with open(path, 'r', encoding='utf-8') as file: + content = file.read() + parser = mparser.Parser(content, path) + ast = parser.parse() + + for node in ast.lines: + # we're only interested in variable assignments + if not isinstance(node, mparser.AssignmentNode): + continue + # we're only interested in the variable we're looking for + if node.var_name.value != var: + continue + # we're expecting string or array + if isinstance(node.value, mparser.StringNode): + return node.value.value + if isinstance(node.value, mparser.ArrayNode): + return [item.value for item in node.value.args.arguments] + except (MesonException, FileNotFoundError): + return [] + return None + + +def _update_ctx_from_ui(ctx: CmdlineCtx) -> int: + """Use whiptail dialogs to update context contents.""" + try: + # update build dir + ctx.build_dir = _whiptail_inputbox( + "Enter build directory:", ctx.build_dir) + + # update configs + ctx.enabled_configs = _get_enabled_configurations( + ctx.avail_configs, ctx.enabled_configs) + + # update enabled apps, examples, and drivers + ctx.enabled_apps = _get_enabled_list(ctx.avail_apps, ctx.enabled_apps) + ctx.enabled_examples = _get_enabled_list( + ctx.avail_examples, ctx.enabled_examples) + ctx.enabled_drivers = _get_enabled_list( + ctx.avail_drivers, ctx.enabled_drivers) + + return 0 + except CalledProcessError: + # use probably pressed cancel, so bail out + return 1 + + +def _build_configs(ctx: CmdlineCtx) -> None: + # if builddir is a relative path, make it absolute from DPDK root + if not os.path.isabs(ctx.build_dir): + ctx.build_dir = os.path.realpath( + os.path.join(ctx.dpdk_dir, ctx.build_dir)) + + # first, build our common meson param string + common_param = "" + # if no apps enabled, disable all apps, otherwise they get built by default + if not ctx.enabled_apps: + common_param += " -Ddisable_apps=*" + else: + common_param += f" -Denable_apps={','.join(ctx.enabled_apps)}" + # examples don't get build unless user asks + if ctx.enabled_examples: + common_param += f" -Dexamples={','.join(ctx.enabled_examples)}" + # if no drivers enabled, disable all drivers, otherwise they get built by + # default + if not ctx.enabled_drivers: + common_param += " -Ddisable_drivers=*/*" + else: + common_param += f" -Denable_drivers={','.join(ctx.enabled_drivers)}" + + # create build tasks + build_tasks = [DPDKBuildTask(l, d, m) for l, d, m in ctx.enabled_configs] + + # create launch tasks + launch_tasks: List[DPDKLaunchTask] = [] + for app in ctx.enabled_apps: + label = app + exe = os.path.join("app", f"dpdk-{app}") + launch_tasks.append(DPDKLaunchTask(label, exe, ctx.gdb_path)) + for app in ctx.enabled_examples: + # examples may have complex paths but they always flatten + label = os.path.basename(app) + exe = os.path.join("examples", f"dpdk-{label}") + launch_tasks.append(DPDKLaunchTask(label, exe, ctx.gdb_path)) + + # build our config + vscode_cfg = VSCodeConfig(ctx.build_dir, common_param) + vscode_cfg.build_tasks = build_tasks + vscode_cfg.launch_tasks = launch_tasks + + # we're done! now, create .vscode directory + os.makedirs(os.path.join(ctx.dpdk_dir, ".vscode"), exist_ok=True) + + # ...and create VSCode configuration + print("Creating VSCode configuration files...") + config_root = os.path.join(ctx.dpdk_dir, ".vscode") + func_map = { + "settings.json": vscode_cfg.settings_to_json_dict, + "tasks.json": vscode_cfg.tasks_to_json_dict, + "launch.json": vscode_cfg.launch_to_json_dict, + "c_cpp_properties.json": vscode_cfg.c_cpp_properties_to_json_dict + } + for filename, func in func_map.items(): + with open(os.path.join(config_root, filename), "w", encoding="utf-8") as f: + print(f"Writing {filename}...") + f.write(json.dumps(func(), indent=4)) + print("Done!") + + +def _process_ctx(ctx: CmdlineCtx) -> None: + """Map command-line enabled options to available options.""" + # for each enabled app, see if it's a wildcard and if so, do a wildcard + # match + for app in ctx.enabled_apps[:]: + if "*" in app: + ctx.enabled_apps.remove(app) + ctx.enabled_apps.extend(fnmatch.filter(ctx.avail_apps, app)) + # do the same with examples + for example in ctx.enabled_examples[:]: + if "*" in example: + ctx.enabled_examples.remove(example) + ctx.enabled_examples.extend( + fnmatch.filter(ctx.avail_examples, example)) + # do the same with drivers + for driver in ctx.enabled_drivers[:]: + if "*" in driver: + ctx.enabled_drivers.remove(driver) + ctx.enabled_drivers.extend( + fnmatch.filter(ctx.avail_drivers, driver)) + + # due to wildcard, there may be dupes, so sort(set()) everything + ctx.enabled_apps = sorted(set(ctx.enabled_apps)) + ctx.enabled_examples = sorted(set(ctx.enabled_examples)) + ctx.enabled_drivers = sorted(set(ctx.enabled_drivers)) + + +def _resolve_deps(ctx: CmdlineCtx) -> None: + """Resolve driver dependencies.""" + for driver in ctx.enabled_drivers[:]: + ctx.enabled_drivers.extend(ctx.driver_dep_map.get(driver, [])) + ctx.enabled_drivers = sorted(set(ctx.enabled_drivers)) + + +def _discover_ctx(ctx: CmdlineCtx) -> int: + """Discover available apps/drivers etc. from DPDK.""" + # find out where DPDK root is located + _self = os.path.realpath(__file__) + dpdk_root = os.path.realpath(os.path.join(os.path.dirname(_self), "..")) + ctx.dpdk_dir = dpdk_root + + # find gdb path + if ctx.use_gdbsudo: + gdb = "gdbsudo" + else: + gdb = "gdb" + ctx.gdb_path = shutil.which(gdb) + if not ctx.gdb_path: + print(f"Error: Cannot find {gdb} in PATH!", file=stderr) + return 1 + + # we want to extract information from DPDK build files, but we don't have a + # good way of doing it without already having a meson build directory. for + # some things we can use meson AST parsing to extract this information, but + # for drivers extracting this information is not straightforward because + # they have complex build-time logic to determine which drivers need to be + # built (e.g. qat). so, we'll use meson AST for apps and examples, but for + # drivers we'll do it the old-fashioned way: by globbing directories. + + apps: List[str] = [] + examples: List[str] = [] + drivers: List[str] = [] + + app_root = os.path.join(dpdk_root, "app") + examples_root = os.path.join(dpdk_root, "examples") + drivers_root = os.path.join(dpdk_root, "drivers") + + apps = _extract_var(os.path.join(app_root, "meson.build"), "apps") + # special case for apps: test isn't added by default + apps.append("test") + # some apps will have overridden names using 'name' variable, extract it + for i, app in enumerate(apps[:]): + new_name = _extract_var(os.path.join( + app_root, app, "meson.build"), "name") + if new_name: + apps[i] = new_name + + # examples don't have any special cases + examples = _extract_var(os.path.join( + examples_root, "meson.build"), "all_examples") + + for root, _, _ in os.walk(drivers_root): + # some directories are drivers, while some are there simply to + # organize source in a certain way (e.g. base drivers), so we're + # going to cheat a little and only consider directories that have + # exactly two levels (e.g. net/ixgbe) and no others. + if root == drivers_root: + continue + rel_root = os.path.relpath(root, drivers_root) + if len(rel_root.split(os.sep)) != 2: + continue + category = os.path.dirname(rel_root) + # see if there's a name override + name = os.path.basename(rel_root) + new_name = _extract_var(os.path.join(root, "meson.build"), "name") + if new_name: + name = new_name + driver_name = os.path.join(category, name) + drivers.append(driver_name) + + # some drivers depend on other drivers, so parse these dependencies + # using the "deps" variable + deps: List[str] = _extract_var( + os.path.join(root, "meson.build"), "deps") + if not deps: + continue + for dep in deps: + # by convention, drivers are named as <category>_<name>, so we can + # infer that dependency is a driver if it has an underscore + if not "_" in dep: + continue + dep_driver = dep.replace("_", "/") + ctx.driver_dep_map.setdefault(driver_name, []).append(dep_driver) + + # sort all lists alphabetically + apps.sort() + examples.sort() + drivers.sort() + + # save all of this information into our context + ctx.avail_apps = apps + ctx.avail_examples = examples + ctx.avail_drivers = drivers + + return 0 + + +def _main() -> int: + """Parse command line arguments and direct program flow.""" + # this is primarily a TUI script, but we also want to be able to automate + # everything, or set defaults to enhance user interaction and + # customization. + + # valid parameters: + # --no-ui: run without any user interaction + # --no-gdbsudo: set up launch targets to use gdb directly + # --no-defaults: do not add default build configurations + # --help: show help message + # -B/--build-dir: set build directory + # -b/--build-configs: set default build configurations + # format: <label> <description> <meson-param> + # can be specified multiple times + # -a/--apps: comma-separated list of enabled apps + # -e/--examples: comma-separated list of enabled examples + # -d/--drivers: comma-separated list of enabled drivers + ap = argparse.ArgumentParser( + description="Generate VSCode configuration for DPDK") + ap.add_argument("--no-ui", action="store_true", + help="Run without any user interaction") + ap.add_argument("--no-gdbsudo", action="store_true", + help="Set up launch targets to use gdb directly") + ap.add_argument("--no-defaults", action="store_true", + help="Do not enable built-in build configurations") + ap.add_argument("-B", "--build-dir", default="build", + help="Set build directory") + ap.add_argument("-b", "--build-config", action="append", default=[], + help="Comma-separated build task configuration of format [label,description,meson setup arguments]") + ap.add_argument("-a", "--apps", default="", + help="Comma-separated list of enabled apps (wildcards accepted)") + ap.add_argument("-e", "--examples", default="", + help="Comma-separated list of enabled examples (wildcards accepted)") + ap.add_argument("-d", "--drivers", default="", + help="Comma-separated list of enabled drivers (wildcards accepted)") + ap.epilog = """\ +When script is run in interactive mode, parameters will be used to set up dialog defaults. \ +Otherwise, they will be used to create configuration directly.""" + args = ap.parse_args() + + def_configs = [ + ("debug", "Debug build", "--buildtype=debug"), + ("debugopt", "Debug optimized build", "--buildtype=debugoptimized"), + ("release", "Release build", "--buildtype=release -Denable_docs=true"), + ("asan", "Address sanitizer build", + "--buildtype=debugoptimized -Db_sanitize=address -Db_lundef=false"), + ] + def_apps = [ + "test", "testpmd" + ] + def_examples = [ + "helloworld" + ] + # parse build configs + arg_configs = [] + for c in args.build_config: + parts = c.split(",") + if len(parts) != 3: + print( + f"Error: Invalid build configuration format: {c}", file=stderr) + return 1 + arg_configs.append(tuple(parts)) + + # set up command line context. all wildcards will be passed directly to _main, and will be + # resolved later, when we have a list of things to enable/disable. + ctx = CmdlineCtx() + ctx.use_ui = not args.no_ui + ctx.use_gdbsudo = not args.no_gdbsudo + ctx.build_dir = args.build_dir + ctx.enabled_apps = args.apps.split(",") if args.apps else [] + ctx.enabled_examples = args.examples.split(",") if args.examples else [] + ctx.enabled_drivers = args.drivers.split(",") if args.drivers else [] + ctx.enabled_configs = arg_configs + ctx.avail_configs = def_configs + ctx.enabled_configs + + if not args.no_defaults: + # enable default configs + ctx.enabled_configs.extend(def_configs) + + # for apps and examples, we only want to add defaults if + # user didn't directly specify anything + if not ctx.enabled_apps: + ctx.enabled_apps.extend(def_apps) + if not ctx.enabled_examples: + ctx.enabled_examples.extend(def_examples) + + # if UI interaction is requested, check if whiptail is installed + if ctx.use_ui and os.system("which whiptail &> /dev/null") != 0: + print( + "whiptail is not installed! Please install it and try again.", + file=stderr) + return 1 + + # check if gdbsudo is available + if ctx.use_gdbsudo and os.system("which gdbsudo &> /dev/null") != 0: + print( + "Generated configuration will use gdbsudo script to run applications.", + file=stderr) + print( + "If you want to use gdb directly, please run with --no-gdbsudo argument.", + file=stderr) + print( + "Otherwise, run the following snippet in your terminal and try again:", + file=stderr) + print("""sudo tee <<EOF /usr/local/bin/gdbsudo &> /dev/null + #!/usr/bin/bash + sudo gdb $@ + EOF + sudo chmod a+x /usr/local/bin/gdbsudo""", file=stderr) + return 1 + + _discover_ctx(ctx) + _process_ctx(ctx) + if ctx.use_ui and _update_ctx_from_ui(ctx): + return 1 + _resolve_deps(ctx) + _build_configs(ctx) + + return 0 + + +if __name__ == "__main__": + _exit(_main()) -- 2.43.5 ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC PATCH v1 1/1] devtools: add vscode configuration generator 2024-07-26 12:42 ` [RFC PATCH v1 1/1] devtools: add vscode configuration generator Anatoly Burakov @ 2024-07-26 15:36 ` Stephen Hemminger 2024-07-26 16:05 ` Burakov, Anatoly 0 siblings, 1 reply; 23+ messages in thread From: Stephen Hemminger @ 2024-07-26 15:36 UTC (permalink / raw) To: Anatoly Burakov; +Cc: dev, john.mcnamara On Fri, 26 Jul 2024 13:42:56 +0100 Anatoly Burakov <anatoly.burakov@intel.com> wrote: > A lot of developers use Visual Studio Code as their primary IDE. This > script generates a configuration file for VSCode that sets up basic build > tasks, launch tasks, as well as C/C++ code analysis settings that will > take into account compile_commands.json that is automatically generated > by meson. > > Files generated by script: > - .vscode/settings.json: stores variables needed by other files > - .vscode/tasks.json: defines build tasks > - .vscode/launch.json: defines launch tasks > - .vscode/c_cpp_properties.json: defines code analysis settings > > The script uses a combination of globbing and meson file parsing to > discover available apps, examples, and drivers, and generates a > project-wide settings file, so that the user can later switch between > debug/release/etc. configurations while keeping their desired apps, > examples, and drivers, built by meson, and ensuring launch configurations > still work correctly whatever the configuration selected. > > This script uses whiptail as TUI, which is expected to be universally > available as it is shipped by default on most major distributions. > However, the script is also designed to be scriptable and can be run > without user interaction, and have its configuration supplied from > command-line arguments. > > Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com> The TUI doesn't matter much since I would expect this gets run 100% on Windows. In general looks good, you might want to address $ flake8 ./devtools/gen-vscode-config.py --max-line 100 ./devtools/gen-vscode-config.py:352:47: E741 ambiguous variable name 'l' ./devtools/gen-vscode-config.py:499:16: E713 test for membership should be 'not in' ./devtools/gen-vscode-config.py:546:101: E501 line too long (120 > 100 characters) ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC PATCH v1 1/1] devtools: add vscode configuration generator 2024-07-26 15:36 ` Stephen Hemminger @ 2024-07-26 16:05 ` Burakov, Anatoly 0 siblings, 0 replies; 23+ messages in thread From: Burakov, Anatoly @ 2024-07-26 16:05 UTC (permalink / raw) To: Stephen Hemminger; +Cc: dev, john.mcnamara On 7/26/2024 5:36 PM, Stephen Hemminger wrote: > On Fri, 26 Jul 2024 13:42:56 +0100 > Anatoly Burakov <anatoly.burakov@intel.com> wrote: > >> A lot of developers use Visual Studio Code as their primary IDE. This >> script generates a configuration file for VSCode that sets up basic build >> tasks, launch tasks, as well as C/C++ code analysis settings that will >> take into account compile_commands.json that is automatically generated >> by meson. >> >> Files generated by script: >> - .vscode/settings.json: stores variables needed by other files >> - .vscode/tasks.json: defines build tasks >> - .vscode/launch.json: defines launch tasks >> - .vscode/c_cpp_properties.json: defines code analysis settings >> >> The script uses a combination of globbing and meson file parsing to >> discover available apps, examples, and drivers, and generates a >> project-wide settings file, so that the user can later switch between >> debug/release/etc. configurations while keeping their desired apps, >> examples, and drivers, built by meson, and ensuring launch configurations >> still work correctly whatever the configuration selected. >> >> This script uses whiptail as TUI, which is expected to be universally >> available as it is shipped by default on most major distributions. >> However, the script is also designed to be scriptable and can be run >> without user interaction, and have its configuration supplied from >> command-line arguments. >> >> Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com> > > The TUI doesn't matter much since I would expect this gets run > 100% on Windows. I run it on Linux using Remote SSH, and that's the primary target audience as far as I'm concerned (a lot of people do the same at our office). Just in case it wasn't clear, this is not for *Visual Studio* the Windows IDE, this is for *Visual Studio Code* the cross-platform code editor. I didn't actually think of testing this on Windows. I assume Windows doesn't have whiptail, so this will most likely refuse to run in TUI mode (unless run under WSL - I assume WSL ships whiptail). > > In general looks good, you might want to address > $ flake8 ./devtools/gen-vscode-config.py --max-line 100 > ./devtools/gen-vscode-config.py:352:47: E741 ambiguous variable name 'l' > ./devtools/gen-vscode-config.py:499:16: E713 test for membership should be 'not in' > ./devtools/gen-vscode-config.py:546:101: E501 line too long (120 > 100 characters) Thanks, I had Pylance linter but not flake8. -- Thanks, Anatoly ^ permalink raw reply [flat|nested] 23+ messages in thread
* [RFC PATCH v2 0/1] Add Visual Studio Code configuration script 2024-07-26 12:42 [RFC PATCH v1 0/1] Add Visual Studio Code configuration script Anatoly Burakov 2024-07-26 12:42 ` [RFC PATCH v1 1/1] devtools: add vscode configuration generator Anatoly Burakov @ 2024-07-29 13:05 ` Anatoly Burakov 2024-07-29 13:05 ` [RFC PATCH v2 1/1] devtools: add vscode configuration generator Anatoly Burakov 2024-07-30 15:01 ` [RFC PATCH v2 0/1] Add Visual Studio Code configuration script Bruce Richardson 2024-07-31 13:33 ` [RFC PATCH v3 " Anatoly Burakov 2024-09-02 12:17 ` [PATCH v1 0/1] Add Visual Studio Code configuration script Anatoly Burakov 3 siblings, 2 replies; 23+ messages in thread From: Anatoly Burakov @ 2024-07-29 13:05 UTC (permalink / raw) To: dev; +Cc: john.mcnamara Lots of developers (myself included) uses Visual Studio Code as their primary IDE for DPDK development. I have been successfully using various incarnations of this script internally to quickly set up my development trees whenever I need a new configuration, so this script is being shared in hopes that it will be useful both to new developers starting with DPDK, and to seasoned DPDK developers who are already using Visual Studio Code. It makes starting working on DPDK in Visual Studio Code so much easier! ** NOTE: Currently, only x86 configuration is generated as I have no way to test the code analysis configuration on any other platforms. ** NOTE 2: this is not for *Visual Studio* the Windows IDE, this is for *Visual Studio Code* the cross-platform code editor. Specifically, main target audience for this script is people who either run DPDK directly on their Linux machine, or who use Remote SSH functionality to connect to a remote Linux machine and set up VSCode build there. No other OS's are currently supported by the script. (if you're unaware of what is Remote SSH, I highly suggest checking it out [1]) Philosophy behind this script is as follows: - The assumption is made that a developer will not be using wildly different configurations from build to build - usually, they build the same things, work with the same set of apps/drivers for a while, then switch to something else, at which point a new configuration is needed - Some configurations I consider to be "common" are included: debug build, debug optimized build, release build with docs, and ASan build (feel free to make suggestions here!) - By default, the script will not add any meson flags unless user requested it, however it will create launch configurations for all apps because not specifying any flags leads to all apps being enabled - All parameters that can be adjusted by TUI are also available as command line arguments, so while user interaction is the default (using whiptail), it's actually not required and can be bypassed - I usually work as a local user not as root, so by default the script will attempt to use "gdbsudo" (a "sudo gdb $@" script in /usr/local/bin) for launch tasks, unless user specifically requests using gdb or is running as root With the above assumptions, the script is expected to work for basic DPDK configuration, as well as allow scripting any and all parts of it: - Any build configurations specified on the command-line will be added to the interactive configuration selector - Any apps/examples/drivers specified on the command-line will be selected by default in the interactive configuration selector - Any custom meson strings will also be filled in by default - When --no-ui is passed, all of the above parameters will be used to generate config as if the user has selected them There are a few usability issues with the script that are very difficult, if not impossible to solve for all cases, so a few other decisions have been made to hopefully address these issues for most cases. For starters, we can only know which apps/examples will get built after we create a build directory and process dependencies; we cannot infer this from our script. Therefore, by default all apps will get launch configurations created, even though these apps might not be built later. We could add another step after build to check config for missing apps and removing them, but it's difficult to do that without destroying any custom configuration user might have created. We remove generated configuration as it is! More importantly, our build system supports wildcards - for example, we can request "net/*" drivers to be built, and they will be built on a best-effort basis; that is, if the driver lacks a dependency, it will get ignored without any errors. For this script, a design decision has been made to handle internal driver dependencies (such as "net/ice" depending on "common/iavf") automatically to enhance user experience, until such time as the build system can do it better. However, the downside of this decision is that we need to handle dependencies between drivers for when user specifies wildcards on the command-line, which can be solved in three ways: * Not allow wildcards at all * Allow wildcards and always expand them * Allow wildcards and be smarter about handling them The first option is, IMO, inferior one: wildcards are convenient and quick to work with, so I would rather leave them as an option available to the user. The second options is nice, but it has a fundamental issue: when we expand very broad wildcards like "net/*", we will definitely cover a lot of drivers we do not have any dependencies for, but since we're now explicitly requesting them, the build will now error out if we requested something we didn't necessarily expect to be built. For this reason, I decided against this option. The third option is what I went with, with "smarter" being defined as follows: * User is allowed to use dialogs to edit configuration that is generated from parsing wildcard: if user changed something, we cannot keep wildcard any more and we assume user knows what they're doing and is OK with explicitly requesting compilation for drivers they selected. So, if user didn't change anything in the dialog, we keep the wildcard, otherwise we expand it. * If, by the time we get to resolving driver dependencies, we have wildcards in our driver param string, we see which drivers match this wildcard, and add wildcards for their dependencies. For example, if "net/ice" requires "common/iavf", and we have a "net/*" wildcard, one of the dependencies that we will add is "common/*". This behavior is, IMO, far better than the default one from our build system, where if a driver matches wildcard but cannot be built due to another internal dependency not being enabled (e.g. if "net/ice" is requested but "common/iavf" isn't requested), the build will fail to configure even though it would've been possible to build them otherwise So, explicitly enabled drivers get explicit dependencies, implicitly enabled drivers get implicit dependencies. The resulting build will be bigger than when using meson command line directly, but if the user is worried about build size, they can customize it via common meson parameters as well as being more granular about requested apps/examples/drivers. Thus, we address the "simple" usecase of "let's build everything by default", we handle some common use cases smarter than we otherwise would have, and we allow user to be as in-depth as they want by allowing to specify explicit meson command strings. I feel like this is a good compromise between usability and robustness. Please feel free to make any suggestions! [1] https://code.visualstudio.com/docs/remote/ssh Anatoly Burakov (1): devtools: add vscode configuration generator devtools/gen-vscode-config.py | 871 ++++++++++++++++++++++++++++++++++ 1 file changed, 871 insertions(+) create mode 100755 devtools/gen-vscode-config.py -- 2.43.5 ^ permalink raw reply [flat|nested] 23+ messages in thread
* [RFC PATCH v2 1/1] devtools: add vscode configuration generator 2024-07-29 13:05 ` [RFC PATCH v2 0/1] Add Visual Studio Code configuration script Anatoly Burakov @ 2024-07-29 13:05 ` Anatoly Burakov 2024-07-29 13:14 ` Bruce Richardson 2024-07-29 14:30 ` Bruce Richardson 2024-07-30 15:01 ` [RFC PATCH v2 0/1] Add Visual Studio Code configuration script Bruce Richardson 1 sibling, 2 replies; 23+ messages in thread From: Anatoly Burakov @ 2024-07-29 13:05 UTC (permalink / raw) To: dev; +Cc: john.mcnamara A lot of developers use Visual Studio Code as their primary IDE. This script generates a configuration file for VSCode that sets up basic build tasks, launch tasks, as well as C/C++ code analysis settings that will take into account compile_commands.json that is automatically generated by meson. Files generated by script: - .vscode/settings.json: stores variables needed by other files - .vscode/tasks.json: defines build tasks - .vscode/launch.json: defines launch tasks - .vscode/c_cpp_properties.json: defines code analysis settings The script uses a combination of globbing and meson file parsing to discover available apps, examples, and drivers, and generates a project-wide settings file, so that the user can later switch between debug/release/etc. configurations while keeping their desired apps, examples, and drivers, built by meson, and ensuring launch configurations still work correctly whatever the configuration selected. This script uses whiptail as TUI, which is expected to be universally available as it is shipped by default on most major distributions. However, the script is also designed to be scriptable and can be run without user interaction, and have its configuration supplied from command-line arguments. Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com> --- Notes: RFCv1 -> RFCv2: - No longer disable apps and drivers if nothing was specified via command line or TUI, and warn user about things being built by default - Generate app launch configuration by default for when no apps are selected - Added paramters: - --force to avoid overwriting existing config - --common-conf to specify global meson flags applicable to all configs - --gdbsudo/--no-gdbsudo to specify gdbsudo behavior - Autodetect gdbsudo/gdb from UID - Updated comments, error messages, fixed issues with user interaction - Improved handling of wildcards and driver dependencies - Fixed a few bugs in dependency detection due to incorrect parsing - [Stephen] flake8 is happy devtools/gen-vscode-config.py | 871 ++++++++++++++++++++++++++++++++++ 1 file changed, 871 insertions(+) create mode 100755 devtools/gen-vscode-config.py diff --git a/devtools/gen-vscode-config.py b/devtools/gen-vscode-config.py new file mode 100755 index 0000000000..f0d6044c1b --- /dev/null +++ b/devtools/gen-vscode-config.py @@ -0,0 +1,871 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2024 Intel Corporation +# + +"""Visual Studio Code configuration generator script.""" + +import os +import json +import argparse +import fnmatch +import shutil +from typing import List, Dict, Tuple, Any +from sys import exit as _exit, stderr +from subprocess import run, CalledProcessError, PIPE +from mesonbuild import mparser +from mesonbuild.mesonlib import MesonException + + +class DPDKBuildTask: + """A build task for DPDK""" + + def __init__(self, label: str, description: str, param: str): + # label as it appears in build configuration + self.label = label + # description to be given in menu + self.description = description + # task-specific configuration parameters + self.param = param + + def to_json_dict(self) -> Dict[str, Any]: + """Generate JSON dictionary for this task""" + return { + "label": f"Configure {self.label}", + "detail": self.description, + "type": "shell", + "dependsOn": "Remove builddir", + # take configuration from settings.json using config: namespace + "command": f"meson setup ${{config:BUILDCONFIG}} " \ + f"{self.param} ${{config:BUILDDIR}}", + "problemMatcher": [], + "group": "build" + } + + +class DPDKLaunchTask: + """A launch task for DPDK""" + + def __init__(self, label: str, exe: str, gdb_path: str): + # label as it appears in launch configuration + self.label = label + # path to executable + self.exe = exe + self.gdb_path = gdb_path + + def to_json_dict(self) -> Dict[str, Any]: + """Generate JSON dictionary for this task""" + return { + "name": f"Run {self.label}", + "type": "cppdbg", + "request": "launch", + # take configuration from settings.json using config: namespace + "program": f"${{config:BUILDDIR}}/{self.exe}", + "args": [], + "stopAtEntry": False, + "cwd": "${workspaceFolder}", + "externalConsole": False, + "preLaunchTask": "Build", + "MIMode": "gdb", + "miDebuggerPath": self.gdb_path, + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-gdb-set print pretty on", + "ignoreFailures": True + } + ] + } + + +class VSCodeConfig: + """Configuration for VSCode""" + + def __init__(self, builddir: str, commoncfg: str): + # where will our build dir be located + self.builddir = builddir + # meson configuration common to all configs + self.commonconfig = commoncfg + # meson build configurations + self.build_tasks: List[DPDKBuildTask] = [] + # meson launch configurations + self.launch_tasks: List[DPDKLaunchTask] = [] + + def settings_to_json_dict(self) -> Dict[str, Any]: + """Generate settings.json""" + return { + "BUILDDIR": self.builddir, + "BUILDCONFIG": self.commonconfig, + } + + def tasks_to_json_dict(self) -> Dict[str, Any]: + """Generate tasks.json""" + # generate outer layer + build_tasks: Dict[str, Any] = { + "version": "2.0.0", + "tasks": [] + } + # generate inner layer + tasks = build_tasks["tasks"] + # add common tasks + tasks.append({ + "label": "Remove builddir", + "type": "shell", + "command": "rm -rf ${config:BUILDDIR}", + }) + tasks.append({ + "label": "Build", + "detail": "Run build command", + "type": "shell", + "command": "ninja", + "options": { + "cwd": "${config:BUILDDIR}" + }, + "problemMatcher": { + "base": "$gcc", + "fileLocation": ["relative", "${config:BUILDDIR}"] + }, + "group": "build" + }) + # now, add generated tasks + tasks.extend([task.to_json_dict() for task in self.build_tasks]) + + # we're done + return build_tasks + + def launch_to_json_dict(self) -> Dict[str, Any]: + """Generate launch.json""" + return { + "version": "0.2.0", + "configurations": [task.to_json_dict() + for task in self.launch_tasks] + } + + def c_cpp_properties_to_json_dict(self) -> Dict[str, Any]: + """Generate c_cpp_properties.json""" + return { + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${config:BUILDDIR}/", + "${workspaceFolder}/lib/eal/x86", + "${workspaceFolder}/lib/eal/linux", + "${workspaceFolder}/**" + ], + "compilerPath": "/usr/bin/gcc", + "cStandard": "c99", + "cppStandard": "c++17", + "intelliSenseMode": "${default}", + "compileCommands": + "${config:BUILDDIR}/compile_commands.json" + } + ], + "version": 4 + } + + +class CmdlineCtx: + """POD class to set up command line parameters""" + + def __init__(self): + self.use_ui = False + self.use_gdbsudo = False + self.force_overwrite = False + self.build_dir = "" + self.dpdk_dir = "" + self.gdb_path = "" + + self.avail_configs: List[Tuple[str, str, str]] = [] + self.avail_apps: List[str] = [] + self.avail_examples: List[str] = [] + self.avail_drivers: List[str] = [] + + self.enabled_configs_str = "" + self.enabled_apps_str = "" + self.enabled_examples_str = "" + self.enabled_drivers_str = "" + self.enabled_configs: List[Tuple[str, str, str]] = [] + self.enabled_apps: List[str] = [] + self.enabled_examples: List[str] = [] + self.enabled_drivers: List[str] = [] + + self.driver_dep_map: Dict[str, List[str]] = {} + self.common_conf = "" + + # this is only used by TUI to decide which windows to show + self.show_apps = False + self.show_examples = False + self.show_drivers = False + self.show_configs = False + self.show_common_config = False + + +def _whiptail_msgbox(message: str) -> None: + """Display a message box.""" + args = ["whiptail", "--msgbox", message, "10", "70"] + run(args, check=True) + + +def _whiptail_checklist(title: str, prompt: str, + options: List[Tuple[str, str]], + checked: List[str]) -> List[str]: + """Display a checklist and get user input.""" + # build whiptail checklist + checklist = [ + (label, desc, "on" if label in checked else "off") + for label, desc in options + ] + # flatten the list + flat = [item for sublist in checklist for item in sublist] + # build whiptail arguments + args = [ + "whiptail", "--separate-output", "--checklist", + "--title", title, prompt, "15", "80", "8" + ] + flat + + result = run(args, stderr=PIPE, check=True) + # capture selected options + return result.stderr.decode().strip().split() + + +def _whiptail_inputbox(title: str, prompt: str, default: str = "") -> str: + """Display an input box and get user input.""" + args = [ + "whiptail", "--inputbox", + "--title", title, + prompt, "10", "70", default + ] + result = run(args, stderr=PIPE, check=True) + return result.stderr.decode().strip() + + +def _get_enabled_configurations(configs: List[Tuple[str, str, str]], + checked: List[str]) \ + -> List[Tuple[str, str, str]]: + """Ask user which build configurations they want.""" + stop = False + while not stop: + opts = [ + (c[0], c[1]) for c in configs + ] + # when interacting using UI, allow adding items + opts += [("add", "Add new option")] + + # ask user to select options + checked = _whiptail_checklist( + "Build configurations", "Select build configurations to enable:", + opts, checked) + + # if user selected "add", ask for custom meson configuration + if "add" in checked: + # remove "" from checked because it's a special option + checked.remove("add") + while True: + custom_label = _whiptail_inputbox( + "Configuration name", + "Enter custom meson configuration label:") + custom_description = _whiptail_inputbox( + "Configuration description", + "Enter custom meson configuration description:") + custom_mesonstr = _whiptail_inputbox( + "Configuration parameters", + "Enter custom meson configuration string:") + # do we have meaningful label? + if not custom_label: + _whiptail_msgbox("Configuration label cannot be empty!") + continue + # don't allow "add", don't allow duplicates + existing = [task[0] for task in configs] + ["add"] + if custom_label in existing: + _whiptail_msgbox( + f"Label '{custom_label}' is not allowed!") + continue + # we passed all checks, stop + break + new_task = (custom_label, custom_description, custom_mesonstr) + configs += [new_task] + # enable new configuration + checked += [custom_label] + else: + stop = True + # return our list of enabled configurations + return [ + c for c in configs if c[0] in checked + ] + + +def _select_from_list(title: str, prompt: str, items: List[str], + enabled: List[str]) -> List[str]: + """Display a list of items, optionally some enabled by default.""" + opts = [ + (item, "") for item in items + ] + # ask user to select options + return _whiptail_checklist(title, prompt, opts, enabled) + + +def _extract_var(path: str, var: str) -> Any: + """Extract a variable from a meson.build file.""" + try: + # we don't want to deal with multiline variable assignments + # so just read entire file in one go + with open(path, 'r', encoding='utf-8') as file: + content = file.read() + parser = mparser.Parser(content, path) + ast = parser.parse() + + for node in ast.lines: + # we're only interested in variable assignments + if not isinstance(node, mparser.AssignmentNode): + continue + # we're only interested in the variable we're looking for + if node.var_name.value != var: + continue + # we're expecting string or array + if isinstance(node.value, mparser.StringNode): + return node.value.value + if isinstance(node.value, mparser.ArrayNode): + return [item.value for item in node.value.args.arguments] + except (MesonException, FileNotFoundError): + return None + return None + + +def _pick_ui_options(ctx: CmdlineCtx) -> None: + """Use whiptail dialogs to decide which setup options to show.""" + opts = [ + ("config", "Select build configurations to enable"), + ("common", "Customize meson flags"), + ("apps", "Select apps to enable"), + ("examples", "Select examples to enable"), + ("drivers", "Select drivers to enable"), + ] + # whether any options are enabled depends on whether user has specified + # anything on the command-line, but also enable examples by default + checked_opts = ["examples"] + if ctx.enabled_configs_str: + checked_opts.append("config") + if ctx.enabled_apps_str: + checked_opts.append("apps") + if ctx.enabled_drivers_str: + checked_opts.append("drivers") + if ctx.common_conf: + checked_opts.append("common") + + enabled = _whiptail_checklist( + "Options", + "Select options to configure (deselecting will pick defaults):", + opts, checked_opts) + for opt in enabled: + if opt == "config": + ctx.show_configs = True + elif opt == "common": + ctx.show_common_config = True + elif opt == "apps": + ctx.show_apps = True + elif opt == "examples": + ctx.show_examples = True + elif opt == "drivers": + ctx.show_drivers = True + + +def _build_configs(ctx: CmdlineCtx) -> int: + """Build VSCode configuration files.""" + # if builddir is a relative path, make it absolute + if not os.path.isabs(ctx.build_dir): + ctx.build_dir = os.path.realpath(ctx.build_dir) + + # first, build our common meson param string + force_apps = False + force_drivers = False + common_param = ctx.common_conf + + # if no apps are specified, all apps are built, so enable all of them. this + # isn't ideal because some of them might not be able to run because in + # actuality they don't get built due to missing dependencies. however, the + # alternative is to not generate any apps in launch configuration at all, + # which is worse than having some apps defined in config but not available. + if ctx.enabled_apps_str: + common_param += f" -Denable_apps={ctx.enabled_apps_str}" + else: + # special case: user might have specified -Dtests or apps flags in + # common param, so if the user did that, assume user knows what they're + # doing and don't display any warnings about enabling apps, and don't + # enable them in launch config and leave it up to the user to handle. + avoid_opts = ['-Dtests=', '-Denable_apps=', '-Ddisable_apps='] + if not any(opt in common_param for opt in avoid_opts): + force_apps = True + ctx.enabled_apps = ctx.avail_apps + + # examples don't get build unless user asks + if ctx.enabled_examples_str: + common_param += f" -Dexamples={ctx.enabled_examples_str}" + + # if no drivers enabled, let user know they will be built anyway + if ctx.enabled_drivers_str: + common_param += f" -Denable_drivers={ctx.enabled_drivers_str}" + else: + avoid_opts = ['-Denable_drivers=', '-Ddisable_drivers='] + if not any(opt in common_param for opt in avoid_opts): + # special case: user might have specified driver flags in common + # param, so if the user did that, assume user knows what they're + # doing and don't display any warnings about enabling drivers. + force_drivers = True + + if force_drivers or force_apps: + ena: List[str] = [] + dis: List[str] = [] + if force_apps: + ena += ["apps"] + dis += ["-Ddisable_apps=*"] + if force_drivers: + ena += ["drivers"] + dis += ["-Ddisable_drivers=*/*"] + ena_str = " or ".join(ena) + dis_str = " or ".join(dis) + msg = f"""\ +No {ena_str} are specified in configuration, so all of them will be built. \ +To disable {ena_str}, add {dis_str} to common meson flags.""" + + _whiptail_msgbox(msg) + + # create build tasks + build_tasks = [DPDKBuildTask(n, d, p) for n, d, p in ctx.enabled_configs] + + # create launch tasks + launch_tasks: List[DPDKLaunchTask] = [] + for app in ctx.enabled_apps: + label = app + exe = os.path.join("app", f"dpdk-{app}") + launch_tasks.append(DPDKLaunchTask(label, exe, ctx.gdb_path)) + for app in ctx.enabled_examples: + # examples may have complex paths but they always flatten + label = os.path.basename(app) + exe = os.path.join("examples", f"dpdk-{label}") + launch_tasks.append(DPDKLaunchTask(label, exe, ctx.gdb_path)) + + # build our config + vscode_cfg = VSCodeConfig(ctx.build_dir, common_param) + vscode_cfg.build_tasks = build_tasks + vscode_cfg.launch_tasks = launch_tasks + + # we're done! now, create .vscode directory + config_root = os.path.join(ctx.dpdk_dir, ".vscode") + os.makedirs(config_root, exist_ok=True) + + # ...and create VSCode configuration + print("Creating VSCode configuration files...") + func_map = { + "settings.json": vscode_cfg.settings_to_json_dict, + "tasks.json": vscode_cfg.tasks_to_json_dict, + "launch.json": vscode_cfg.launch_to_json_dict, + "c_cpp_properties.json": vscode_cfg.c_cpp_properties_to_json_dict + } + # check if any of the files exist, and refuse to overwrite them unless + # --force was specified on the command line + for filename in func_map.keys(): + fpath = os.path.join(config_root, filename) + if os.path.exists(fpath) and not ctx.force_overwrite: + print(f"Error: {filename} already exists! \ + Use --force to overwrite.", file=stderr) + return 1 + for filename, func in func_map.items(): + with open(os.path.join(config_root, filename), + "w", encoding="utf-8") as f: + print(f"Writing {filename}...") + f.write(json.dumps(func(), indent=4)) + print("Done!") + return 0 + + +def _resolve_deps(ctx: CmdlineCtx) -> None: + """Resolve driver dependencies.""" + # resolving dependencies is not straightforward, because DPDK build system + # treats wildcards differently from explicitly requested drivers: namely, + # it will treat wildcard-matched drivers on a best-effort basis, and will + # skip them if driver's dependencies aren't met without error. contrary to + # that, when a driver is explicitly requested, it will cause an error if + # any of its dependencies are unmet. + # + # to resolve this, we need to be smarter about how we add dependencies. + # specifically, when we're dealing with wildcards, we will need to add + # wildcard dependencies, whereas when we're dealing with explicitly + # requested drivers, we will add explicit dependencies. for example, + # requesting net/ice will add common/iavf, but requesting net/*ce will + # add common/* as a dependency. We will build more that we would've + # otherwise, but that's an acceptable compromise to enable as many drivers + # as we can while avoiding build errors due to erroneous wildcard matches. + new_deps: List[str] = [] + for driver in ctx.enabled_drivers_str.split(","): + # is this a wildcard? + if "*" in driver: + # find all drivers matching this wildcard, figure out which + # category (bus, common, etc.) of driver they request as + # dependency, and add a wildcarded match on that category + wc_matches = fnmatch.filter(ctx.avail_drivers, driver) + # find all of their dependencies + deps = [d + for dl in wc_matches + for d in ctx.driver_dep_map.get(dl, [])] + categories: List[str] = [] + for d in deps: + category, _ = d.split("/") + categories += [category] + # find all categories we've added + categories = sorted(set(categories)) + # add them as dependencies + new_deps += [f"{cat}/*" for cat in categories] + continue + # this is a driver, so add its dependencies explicitly + new_deps += ctx.driver_dep_map.get(driver, []) + + # add them to enabled_drivers_str, this will be resolved later + if new_deps: + # this might add some dupes but we don't really care + ctx.enabled_drivers_str += f',{",".join(new_deps)}' + + +def _update_ctx_from_ui(ctx: CmdlineCtx) -> int: + """Use whiptail dialogs to update context contents.""" + try: + # update build dir + ctx.build_dir = _whiptail_inputbox( + "Build directory", "Enter build directory:", ctx.build_dir) + + # first, decide what we are going to set up + _pick_ui_options(ctx) + + # update configs + if ctx.show_configs: + ctx.enabled_configs = _get_enabled_configurations( + ctx.avail_configs, [c[0] for c in ctx.enabled_configs]) + + # update common config + if ctx.show_common_config: + ctx.common_conf = _whiptail_inputbox( + "Meson configuration", + "Enter common meson configuration flags (if any):", + ctx.common_conf) + + # when user interaction is requestted, we cannot really keep any values + # we got from arguments, because if user has changed something in those + # checklists, any wildcards will become invalid. however, we can do a + # heuristic: if user didn't *change* anything, we can infer that + # they're happy with the configuration they have picked, so we will + # only update meson param strings if the user has changed the + # configuration from TUI, or if we didn't have any to begin with + + old_enabled_apps = ctx.enabled_apps.copy() + old_enabled_examples = ctx.enabled_examples.copy() + old_enabled_drivers = ctx.enabled_drivers.copy() + if ctx.show_apps: + ctx.enabled_apps = _select_from_list( + "Apps", "Select apps to enable:", + ctx.avail_apps, ctx.enabled_apps) + if ctx.show_examples: + ctx.enabled_examples = _select_from_list( + "Examples", "Select examples to enable:", + ctx.avail_examples, ctx.enabled_examples) + if ctx.show_drivers: + ctx.enabled_drivers = _select_from_list( + "Drivers", "Select drivers to enable:", + ctx.avail_drivers, ctx.enabled_drivers) + + # did we change anything, assuming we even had anything at all? + if not ctx.enabled_apps_str or \ + set(old_enabled_apps) != set(ctx.enabled_apps): + ctx.enabled_apps_str = ",".join(ctx.enabled_apps) + if not ctx.enabled_examples_str or \ + set(old_enabled_examples) != set(ctx.enabled_examples): + ctx.enabled_examples_str = ",".join(ctx.enabled_examples) + if not ctx.enabled_drivers_str or \ + set(old_enabled_drivers) != set(ctx.enabled_drivers): + ctx.enabled_drivers_str = ",".join(ctx.enabled_drivers) + + return 0 + except CalledProcessError: + # use probably pressed cancel, so bail out + return 1 + + +def _resolve_ctx(ctx: CmdlineCtx) -> int: + """Map command-line enabled options to available options.""" + # for each enabled app, see if it's a wildcard and if so, do a wildcard + # match + for app in ctx.enabled_apps_str.split(","): + if "*" in app: + ctx.enabled_apps.extend(fnmatch.filter(ctx.avail_apps, app)) + elif app in ctx.avail_apps: + ctx.enabled_apps.append(app) + elif app: + print(f"Error: Unknown app: {app}", file=stderr) + return 1 + + # do the same with examples + for example in ctx.enabled_examples_str.split(","): + if "*" in example: + ctx.enabled_examples.extend( + fnmatch.filter(ctx.avail_examples, example)) + elif example in ctx.avail_examples: + ctx.enabled_examples.append(example) + elif example: + print(f"Error: Unknown example: {example}", file=stderr) + return 1 + + # do the same with drivers + for driver in ctx.enabled_drivers_str.split(","): + if "*" in driver: + ctx.enabled_drivers.extend( + fnmatch.filter(ctx.avail_drivers, driver)) + elif driver in ctx.avail_drivers: + ctx.enabled_drivers.append(driver) + elif driver: + print(f"Error: Unknown driver: {driver}", file=stderr) + return 1 + + # due to wildcard, there may be dupes, so sort(set()) everything + ctx.enabled_apps = sorted(set(ctx.enabled_apps)) + ctx.enabled_examples = sorted(set(ctx.enabled_examples)) + ctx.enabled_drivers = sorted(set(ctx.enabled_drivers)) + + return 0 + + +def _discover_ctx(ctx: CmdlineCtx) -> int: + """Discover available apps/drivers etc. from DPDK.""" + # find out where DPDK root is located + _self = os.path.realpath(__file__) + dpdk_root = os.path.realpath(os.path.join(os.path.dirname(_self), "..")) + ctx.dpdk_dir = dpdk_root + + # find gdb path + if ctx.use_gdbsudo: + gdb = "gdbsudo" + else: + gdb = "gdb" + ctx.gdb_path = shutil.which(gdb) + if not ctx.gdb_path: + print(f"Error: Cannot find {gdb} in PATH!", file=stderr) + return 1 + + # we want to extract information from DPDK build files, but we don't have a + # good way of doing it without already having a meson build directory. for + # some things we can use meson AST parsing to extract this information, but + # for drivers extracting this information is not straightforward because + # they have complex build-time logic to determine which drivers need to be + # built (e.g. qat). so, we'll use meson AST for apps and examples, but for + # drivers we'll do it the old-fashioned way: by globbing directories. + + apps: List[str] = [] + examples: List[str] = [] + drivers: List[str] = [] + + app_root = os.path.join(dpdk_root, "app") + examples_root = os.path.join(dpdk_root, "examples") + drivers_root = os.path.join(dpdk_root, "drivers") + + apps = _extract_var(os.path.join(app_root, "meson.build"), "apps") + # special case for apps: test isn't added by default + apps.append("test") + # some apps will have overridden names using 'name' variable, extract it + for i, app in enumerate(apps[:]): + new_name = _extract_var(os.path.join( + app_root, app, "meson.build"), "name") + if new_name: + apps[i] = new_name + + # examples don't have any special cases + examples = _extract_var(os.path.join( + examples_root, "meson.build"), "all_examples") + + for root, _, _ in os.walk(drivers_root): + # some directories are drivers, while some are there simply to + # organize source in a certain way (e.g. base drivers), so we're + # going to cheat a little and only consider directories that have + # exactly two levels (e.g. net/ixgbe) and no others. + if root == drivers_root: + continue + rel_root = os.path.relpath(root, drivers_root) + if len(rel_root.split(os.sep)) != 2: + continue + category = os.path.dirname(rel_root) + # see if there's a name override + name = os.path.basename(rel_root) + new_name = _extract_var(os.path.join(root, "meson.build"), "name") + if new_name: + name = new_name + driver_name = os.path.join(category, name) + drivers.append(driver_name) + + # some drivers depend on other drivers, so parse these dependencies + # using the "deps" variable + deps: Any = _extract_var( + os.path.join(root, "meson.build"), "deps") + if not deps: + continue + # occasionally, deps will be a string, so convert it to a list + if isinstance(deps, str): + deps = [deps] + for dep in deps: + # by convention, drivers are named as <category>_<name>, so we can + # infer that dependency is a driver if it has an underscore + if "_" not in dep: + continue + dep_driver = dep.replace("_", "/", 1) + ctx.driver_dep_map.setdefault(driver_name, []).append(dep_driver) + + # sort all lists alphabetically + apps.sort() + examples.sort() + drivers.sort() + + # save all of this information into our context + ctx.avail_apps = apps + ctx.avail_examples = examples + ctx.avail_drivers = drivers + + return 0 + + +def _main() -> int: + """Parse command line arguments and direct program flow.""" + # this is primarily a TUI script, but we also want to be able to automate + # everything, or set defaults to enhance user interaction and + # customization. + + # valid parameters: + # --no-ui: run without any user interaction + # --no-gdbsudo: set up launch targets to use gdb directly + # --gdbsudo: set up launch targets to use gdbsudo + # --no-defaults: do not enable built-in build configurations + # --help: show help message + # -B/--build-dir: set build directory + # -b/--build-config: set default build configurations + # format: <label>,<description>,<meson-param> + # can be specified multiple times + # -c/--common-conf: additional configuration common to all build tasks + # -a/--apps: comma-separated list of enabled apps + # -e/--examples: comma-separated list of enabled examples + # -d/--drivers: comma-separated list of enabled drivers + # -f/--force: overwrite existing configuration + ap = argparse.ArgumentParser( + description="Generate VSCode configuration for DPDK") + ap.add_argument("--no-ui", action="store_true", + help="Run without any user interaction") + gdbgrp = ap.add_mutually_exclusive_group() + gdbgrp.add_argument("--no-gdbsudo", action="store_true", + help="Set up launch targets to use gdb directly") + gdbgrp.add_argument("--gdbsudo", action="store_true", + help="Set up launch targets to use gdbsudo") + ap.add_argument("--no-defaults", action="store_true", + help="Do not enable built-in build configurations") + ap.add_argument("-B", "--build-dir", default="build", + help="Set build directory") + ap.add_argument("-b", "--build-config", action="append", default=[], + help="Comma-separated build task configuration of format \ + [label,description,meson setup arguments]") + ap.add_argument("-c", "--common-conf", + help="Additional configuration common to all build tasks", + default="") + ap.add_argument("-a", "--apps", default="", + help="Comma-separated list of enabled apps \ + (wildcards accepted)") + ap.add_argument("-e", "--examples", default="", + help="Comma-separated list of enabled examples \ + (wildcards accepted)") + ap.add_argument("-d", "--drivers", default="", + help="Comma-separated list of enabled drivers \ + (wildcards accepted)") + ap.add_argument("-f", "--force", action="store_true", + help="Overwrite existing configuration") + ap.epilog = """\ +When script is run in interactive mode, parameters will be \ +used to set up dialog defaults. Otherwise, they will be used \ +to create configuration directly.""" + args = ap.parse_args() + + def_configs = [ + ("debug", "Debug build", "--buildtype=debug"), + ("debugopt", "Debug build with optimizations", + "--buildtype=debugoptimized"), + ("release", "Release build with documentation", + "--buildtype=release -Denable_docs=true"), + ("asan", "Address Sanitizer build", + "--buildtype=debugoptimized -Db_sanitize=address -Db_lundef=false"), + ] + # parse build configs + arg_configs: List[Tuple[str, str, str]] = [] + for c in args.build_config: + parts: List[str] = c.split(",") + if len(parts) != 3: + print( + f"Error: Invalid build configuration format: {c}", file=stderr) + return 1 + arg_configs.append(tuple(parts)) + + # set up command line context. all wildcards will be passed directly to + # _main, and will be resolved later, when we have a list of things to + # enable/disable. + ctx = CmdlineCtx() + ctx.use_ui = not args.no_ui + ctx.force_overwrite = args.force + ctx.build_dir = args.build_dir + ctx.common_conf = args.common_conf + ctx.enabled_configs_str = args.build_config + ctx.enabled_apps_str = args.apps + ctx.enabled_examples_str = args.examples + ctx.enabled_drivers_str = args.drivers + ctx.enabled_configs = arg_configs + ctx.avail_configs = def_configs + ctx.enabled_configs + + # if user has specified gdbsudo argument, use that + if args.gdbsudo or args.no_gdbsudo: + ctx.use_gdbsudo = args.gdbsudo or not args.no_gdbsudo + else: + # use gdb if we're root + ctx.use_gdbsudo = os.geteuid() != 0 + print(f"Autodetected gdbsudo usage: {ctx.use_gdbsudo}") + + if not args.no_defaults: + # enable default configs + ctx.enabled_configs.extend(def_configs) + + # if UI interaction is requested, check if whiptail is installed + if ctx.use_ui and os.system("which whiptail &> /dev/null") != 0: + print("whiptail is not installed! Please install it and try again.", + file=stderr) + return 1 + + # check if gdbsudo is available + if ctx.use_gdbsudo and os.system("which gdbsudo &> /dev/null") != 0: + print("Generated configuration will use \ + gdbsudo script to run applications.", file=stderr) + print("If you want to use gdb directly, \ + please run with --no-gdbsudo argument.", file=stderr) + print("Otherwise, run the following snippet \ + in your terminal and try again:", file=stderr) + print("""\ +sudo tee <<EOF /usr/local/bin/gdbsudo &> /dev/null +#!/usr/bin/bash +sudo gdb $@ +EOF +sudo chmod a+x /usr/local/bin/gdbsudo +""", file=stderr) + return 1 + + if _discover_ctx(ctx): + return 1 + if _resolve_ctx(ctx): + return 1 + if ctx.use_ui and _update_ctx_from_ui(ctx): + return 1 + _resolve_deps(ctx) + # resolve again because we might have added some dependencies + if _resolve_ctx(ctx): + return 1 + return _build_configs(ctx) + + +if __name__ == "__main__": + _exit(_main()) -- 2.43.5 ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC PATCH v2 1/1] devtools: add vscode configuration generator 2024-07-29 13:05 ` [RFC PATCH v2 1/1] devtools: add vscode configuration generator Anatoly Burakov @ 2024-07-29 13:14 ` Bruce Richardson 2024-07-29 13:17 ` Burakov, Anatoly 2024-07-29 14:30 ` Bruce Richardson 1 sibling, 1 reply; 23+ messages in thread From: Bruce Richardson @ 2024-07-29 13:14 UTC (permalink / raw) To: Anatoly Burakov; +Cc: dev, john.mcnamara On Mon, Jul 29, 2024 at 02:05:52PM +0100, Anatoly Burakov wrote: > A lot of developers use Visual Studio Code as their primary IDE. This > script generates a configuration file for VSCode that sets up basic build > tasks, launch tasks, as well as C/C++ code analysis settings that will > take into account compile_commands.json that is automatically generated > by meson. > > Files generated by script: > - .vscode/settings.json: stores variables needed by other files > - .vscode/tasks.json: defines build tasks > - .vscode/launch.json: defines launch tasks > - .vscode/c_cpp_properties.json: defines code analysis settings > > The script uses a combination of globbing and meson file parsing to > discover available apps, examples, and drivers, and generates a > project-wide settings file, so that the user can later switch between > debug/release/etc. configurations while keeping their desired apps, > examples, and drivers, built by meson, and ensuring launch configurations > still work correctly whatever the configuration selected. > > This script uses whiptail as TUI, which is expected to be universally > available as it is shipped by default on most major distributions. > However, the script is also designed to be scriptable and can be run > without user interaction, and have its configuration supplied from > command-line arguments. > > Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com> > --- > Not sure where it would go - contributors guide probably - but I think this script could do with some docs, especially a quick-setup example on how to use. Having it in the docs will also make it more likely someone will use this. /Bruce ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC PATCH v2 1/1] devtools: add vscode configuration generator 2024-07-29 13:14 ` Bruce Richardson @ 2024-07-29 13:17 ` Burakov, Anatoly 0 siblings, 0 replies; 23+ messages in thread From: Burakov, Anatoly @ 2024-07-29 13:17 UTC (permalink / raw) To: Bruce Richardson; +Cc: dev, john.mcnamara On 7/29/2024 3:14 PM, Bruce Richardson wrote: > On Mon, Jul 29, 2024 at 02:05:52PM +0100, Anatoly Burakov wrote: >> A lot of developers use Visual Studio Code as their primary IDE. This >> script generates a configuration file for VSCode that sets up basic build >> tasks, launch tasks, as well as C/C++ code analysis settings that will >> take into account compile_commands.json that is automatically generated >> by meson. >> >> Files generated by script: >> - .vscode/settings.json: stores variables needed by other files >> - .vscode/tasks.json: defines build tasks >> - .vscode/launch.json: defines launch tasks >> - .vscode/c_cpp_properties.json: defines code analysis settings >> >> The script uses a combination of globbing and meson file parsing to >> discover available apps, examples, and drivers, and generates a >> project-wide settings file, so that the user can later switch between >> debug/release/etc. configurations while keeping their desired apps, >> examples, and drivers, built by meson, and ensuring launch configurations >> still work correctly whatever the configuration selected. >> >> This script uses whiptail as TUI, which is expected to be universally >> available as it is shipped by default on most major distributions. >> However, the script is also designed to be scriptable and can be run >> without user interaction, and have its configuration supplied from >> command-line arguments. >> >> Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com> >> --- >> > Not sure where it would go - contributors guide probably - but I think this > script could do with some docs, especially a quick-setup example on how to > use. Having it in the docs will also make it more likely someone will use > this. > > /Bruce Yep, this is the next step :) I've left it until v1 to add docs. -- Thanks, Anatoly ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC PATCH v2 1/1] devtools: add vscode configuration generator 2024-07-29 13:05 ` [RFC PATCH v2 1/1] devtools: add vscode configuration generator Anatoly Burakov 2024-07-29 13:14 ` Bruce Richardson @ 2024-07-29 14:30 ` Bruce Richardson 2024-07-29 16:16 ` Burakov, Anatoly 1 sibling, 1 reply; 23+ messages in thread From: Bruce Richardson @ 2024-07-29 14:30 UTC (permalink / raw) To: Anatoly Burakov; +Cc: dev, john.mcnamara On Mon, Jul 29, 2024 at 02:05:52PM +0100, Anatoly Burakov wrote: > A lot of developers use Visual Studio Code as their primary IDE. This > script generates a configuration file for VSCode that sets up basic build > tasks, launch tasks, as well as C/C++ code analysis settings that will > take into account compile_commands.json that is automatically generated > by meson. > > Files generated by script: > - .vscode/settings.json: stores variables needed by other files > - .vscode/tasks.json: defines build tasks > - .vscode/launch.json: defines launch tasks > - .vscode/c_cpp_properties.json: defines code analysis settings > > The script uses a combination of globbing and meson file parsing to > discover available apps, examples, and drivers, and generates a > project-wide settings file, so that the user can later switch between > debug/release/etc. configurations while keeping their desired apps, > examples, and drivers, built by meson, and ensuring launch configurations > still work correctly whatever the configuration selected. > > This script uses whiptail as TUI, which is expected to be universally > available as it is shipped by default on most major distributions. > However, the script is also designed to be scriptable and can be run > without user interaction, and have its configuration supplied from > command-line arguments. > > Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com> > --- > Just was trying this out, nice script, thanks. Initial thoughts concerning the build directory: - the script doesn't actually create the build directory, so there is no guarantee that the build directory created will have the same parameters as that specified in the script run. I'd suggest in the case where the user runs the script and specifies build settings, that the build directory is then configured using those settings. - On the other hand, when the build directory already exists - I think the script should pull all settings from there, rather than prompting the user. - I'm not sure I like the idea for reconfiguring of just removing the build directory and doing a whole meson setup command all over again. This seems excessive and also removes the possibility of the user having made changes in config to the build dir without re-running the whole config script. For example, having tweaked the LTO setting, or the instruction_set_isa_setting. Rather than deleting it and running meson setup, it would be better to use "meson configure" to adjust the one required setting and let ninja figure out how to propagate that change. That saves the script from having to track all meson parameters itself. - Finally, and semi-related, this script assumes that the user does everything in a single build directory. Just something to consider, but my own workflow till now has tended to keep multiple build directories around, generally a "build" directory, which is either release or debugoptimized type, and a separate "build-debug" directory + occasionally others for build testing. When doing incremental builds, the time taken to do two builds following a change is a lot less noticable than the time taken for periodic switches of a single build directory between debug and release mode. Final thoughts on usability: - Please don't write gdbsudo to /usr/local/bin without asking the user first. Instead I think it should default to $HOME/.local/bin, but with a prompt for the user to specify a path. - While I realise your primary concern here is an interactive script, I'd tend towards requiring a cmdline arg to run in interactive mode and instead printing the help usage when run without parameters. Just a personal preference on my part though. /Bruce ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC PATCH v2 1/1] devtools: add vscode configuration generator 2024-07-29 14:30 ` Bruce Richardson @ 2024-07-29 16:16 ` Burakov, Anatoly 2024-07-29 16:41 ` Bruce Richardson 0 siblings, 1 reply; 23+ messages in thread From: Burakov, Anatoly @ 2024-07-29 16:16 UTC (permalink / raw) To: Bruce Richardson; +Cc: dev, john.mcnamara On 7/29/2024 4:30 PM, Bruce Richardson wrote: > On Mon, Jul 29, 2024 at 02:05:52PM +0100, Anatoly Burakov wrote: >> A lot of developers use Visual Studio Code as their primary IDE. This >> script generates a configuration file for VSCode that sets up basic build >> tasks, launch tasks, as well as C/C++ code analysis settings that will >> take into account compile_commands.json that is automatically generated >> by meson. >> >> Files generated by script: >> - .vscode/settings.json: stores variables needed by other files >> - .vscode/tasks.json: defines build tasks >> - .vscode/launch.json: defines launch tasks >> - .vscode/c_cpp_properties.json: defines code analysis settings >> >> The script uses a combination of globbing and meson file parsing to >> discover available apps, examples, and drivers, and generates a >> project-wide settings file, so that the user can later switch between >> debug/release/etc. configurations while keeping their desired apps, >> examples, and drivers, built by meson, and ensuring launch configurations >> still work correctly whatever the configuration selected. >> >> This script uses whiptail as TUI, which is expected to be universally >> available as it is shipped by default on most major distributions. >> However, the script is also designed to be scriptable and can be run >> without user interaction, and have its configuration supplied from >> command-line arguments. >> >> Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com> >> --- >> > Just was trying this out, nice script, thanks. Thanks for the feedback! Comments below. > > Initial thoughts concerning the build directory: > - the script doesn't actually create the build directory, so there is no > guarantee that the build directory created will have the same parameters > as that specified in the script run. I'd suggest in the case where the > user runs the script and specifies build settings, that the build > directory is then configured using those settings. I'm not sure I follow. The script creates a command for VSCode to create a build directory using configuration the user has supplied at script's run time. The directory is not created by the script, that is the job of meson build system. This script is merely codifying commands for meson to do that, with the expectation that the user is familiar with VSCode workflow and will go straight to build commands anyway, and will pick one of them. Are you suggesting running `meson setup` right after? Assuming we do that, it would actually then be possible to adjust launch tasks to only include *actual* built apps/examples (as well as infer things like platform, compiler etc.), as that's one weakness of my current "flying blind" approach, so I wouldn't be opposed to adding an extra step here, just want to make sure I understand what you're saying correctly. > > - On the other hand, when the build directory already exists - I think the > script should pull all settings from there, rather than prompting the > user. > That can be done, however, my own workflow has been that I do not ever keep build directories inside my source directory, so it would not be possible to pick up directories anywhere but the source directory. I also think from the point of view of the script it would be easier to start from known quantities rather than guess what user was trying to do from current configuration, but I guess a few common-sense heuristics should suffice for most use cases, such as e.g. inferring debug builds. > - I'm not sure I like the idea for reconfiguring of just removing the build > directory and doing a whole meson setup command all over again. This > seems excessive and also removes the possibility of the user having made > changes in config to the build dir without re-running the whole config > script. For example, having tweaked the LTO setting, or the > instruction_set_isa_setting. Rather than deleting it and running meson > setup, it would be better to use "meson configure" to adjust the one > required setting and let ninja figure out how to propagate that change. > That saves the script from having to track all meson parameters itself. Last I checked, meson doesn't have a command that would "setup or configure existing" a directory, it's either "set up new one" or "configure existing one". I guess we could set up a fallback of "configure || setup". > > - Finally, and semi-related, this script assumes that the user does > everything in a single build directory. Just something to consider, but > my own workflow till now has tended to keep multiple build directories > around, generally a "build" directory, which is either release or > debugoptimized type, and a separate "build-debug" directory + occasionally > others for build testing. When doing incremental builds, the time taken to > do two builds following a change is a lot less noticable than the time taken > for periodic switches of a single build directory between debug and release > mode. The problem with that approach is the launch tasks, because a launch task can only ever point to one executable, so if you have multiple build directories, you'll have to have multiple launch tasks per app/example. I guess we can either tag them (e.g. "Launch dpdk-testpmd [debug]", "Launch dpdk-testpmd [asan]" etc.), or use some kind of indirection to "select active build configuration" (e.g. have one launch task but overwrite ${config:BUILDDIR} after request for configuration, so that launch tasks would pick up actual executable path at run time from settings). I would prefer the latter to be honest, as it's much easier to drop a script into ./vscode and run it together with "configure" command to switch between different build/launch configurations. What do you think? > > Final thoughts on usability: > > - Please don't write gdbsudo to /usr/local/bin without asking the user > first. Instead I think it should default to $HOME/.local/bin, but with a > prompt for the user to specify a path. It's not creating anything, it's just printing out a snippet, which, if run by user, would do that - the implication is obviously that the user may correct it if necessary. The script actually picks up path to `gdbsudo` from `which` command, so if the user puts their command to $HOME/.local/bin or something, it would get picked up if it's in PATH. I see your point about maybe suggesting using a home directory path instead of a system wide path, I can change that. > > - While I realise your primary concern here is an interactive script, I'd > tend towards requiring a cmdline arg to run in interactive mode and > instead printing the help usage when run without parameters. Just a > personal preference on my part though. I found it to be much faster to pick my targets, apps etc. using a couple of interactive windows than to type out parameters I probably don't even remember ahead of time (especially build configurations!), and I believe it's more newbie-friendly that way, as I imagine very few people will want to learn arguments for yet-another-script just to start using VSCode. It would be my personal preference to leave it as default-to-TUI, but maybe recognizing a widely used `-i` parameter would be a good compromise for instant familiarity. > > /Bruce -- Thanks, Anatoly ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC PATCH v2 1/1] devtools: add vscode configuration generator 2024-07-29 16:16 ` Burakov, Anatoly @ 2024-07-29 16:41 ` Bruce Richardson 2024-07-30 9:21 ` Burakov, Anatoly 0 siblings, 1 reply; 23+ messages in thread From: Bruce Richardson @ 2024-07-29 16:41 UTC (permalink / raw) To: Burakov, Anatoly; +Cc: dev, john.mcnamara On Mon, Jul 29, 2024 at 06:16:48PM +0200, Burakov, Anatoly wrote: > On 7/29/2024 4:30 PM, Bruce Richardson wrote: > > On Mon, Jul 29, 2024 at 02:05:52PM +0100, Anatoly Burakov wrote: > > > A lot of developers use Visual Studio Code as their primary IDE. This > > > script generates a configuration file for VSCode that sets up basic build > > > tasks, launch tasks, as well as C/C++ code analysis settings that will > > > take into account compile_commands.json that is automatically generated > > > by meson. > > > > > > Files generated by script: > > > - .vscode/settings.json: stores variables needed by other files > > > - .vscode/tasks.json: defines build tasks > > > - .vscode/launch.json: defines launch tasks > > > - .vscode/c_cpp_properties.json: defines code analysis settings > > > > > > The script uses a combination of globbing and meson file parsing to > > > discover available apps, examples, and drivers, and generates a > > > project-wide settings file, so that the user can later switch between > > > debug/release/etc. configurations while keeping their desired apps, > > > examples, and drivers, built by meson, and ensuring launch configurations > > > still work correctly whatever the configuration selected. > > > > > > This script uses whiptail as TUI, which is expected to be universally > > > available as it is shipped by default on most major distributions. > > > However, the script is also designed to be scriptable and can be run > > > without user interaction, and have its configuration supplied from > > > command-line arguments. > > > > > > Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com> > > > --- > > > > > Just was trying this out, nice script, thanks. > > Thanks for the feedback! Comments below. > > > > > Initial thoughts concerning the build directory: > > - the script doesn't actually create the build directory, so there is no > > guarantee that the build directory created will have the same parameters > > as that specified in the script run. I'd suggest in the case where the > > user runs the script and specifies build settings, that the build > > directory is then configured using those settings. > > I'm not sure I follow. > > The script creates a command for VSCode to create a build directory using > configuration the user has supplied at script's run time. The directory is > not created by the script, that is the job of meson build system. This > script is merely codifying commands for meson to do that, with the > expectation that the user is familiar with VSCode workflow and will go > straight to build commands anyway, and will pick one of them. Are you > suggesting running `meson setup` right after? > Yes, that is what I was thinking, based on the assumption that running "meson setup" should be a once-only task. I suppose overall it's a different workflow to what you have - where you run meson setup repeatedly each time you change a build type. My thinking for the approach to take here is that your script firstly asks for a build directory, and then: * if build-dir exists, pull settings you need from there, such as build type and the apps being built. * if not existing, ask for config settings as you do now, and then run meson setup to create the build dir. Thereafter, the source for all build settings is not in vscode, but in meson, and you just use "meson configure" from vscode to tweak whatever needs tweaking, without affecting any other settings. Since you don't affect any other settings there is no need to record what they should be. > Assuming we do that, it would actually then be possible to adjust launch > tasks to only include *actual* built apps/examples (as well as infer things > like platform, compiler etc.), as that's one weakness of my current "flying > blind" approach, so I wouldn't be opposed to adding an extra step here, just > want to make sure I understand what you're saying correctly. > > > > > - On the other hand, when the build directory already exists - I think the > > script should pull all settings from there, rather than prompting the > > user. > > > > That can be done, however, my own workflow has been that I do not ever keep > build directories inside my source directory, so it would not be possible to > pick up directories anywhere but the source directory. > Why is that, and how does it work now, for e.g. getting the compile_commands.json file from your build folder? > I also think from the point of view of the script it would be easier to > start from known quantities rather than guess what user was trying to do > from current configuration, but I guess a few common-sense heuristics should > suffice for most use cases, such as e.g. inferring debug builds. > What you need depends on whether you want to keep running "meson setup" - which means you need to track all settings - or want to use "meson configure" where you don't really need to track much at all. > > - I'm not sure I like the idea for reconfiguring of just removing the build > > directory and doing a whole meson setup command all over again. This > > seems excessive and also removes the possibility of the user having made > > changes in config to the build dir without re-running the whole config > > script. For example, having tweaked the LTO setting, or the > > instruction_set_isa_setting. Rather than deleting it and running meson > > setup, it would be better to use "meson configure" to adjust the one > > required setting and let ninja figure out how to propagate that change. > > That saves the script from having to track all meson parameters itself. > > Last I checked, meson doesn't have a command that would "setup or configure > existing" a directory, it's either "set up new one" or "configure existing > one". I guess we could set up a fallback of "configure || setup". > This goes back to the whole "create build directory after running the script" option. If the script creates the build dir, the vscode commands never need to use meson setup, only ever meson configure. > > > > - Finally, and semi-related, this script assumes that the user does > > everything in a single build directory. Just something to consider, but > > my own workflow till now has tended to keep multiple build directories > > around, generally a "build" directory, which is either release or > > debugoptimized type, and a separate "build-debug" directory + occasionally > > others for build testing. When doing incremental builds, the time taken to > > do two builds following a change is a lot less noticable than the time taken > > for periodic switches of a single build directory between debug and release > > mode. > > The problem with that approach is the launch tasks, because a launch task > can only ever point to one executable, so if you have multiple build > directories, you'll have to have multiple launch tasks per app/example. I > guess we can either tag them (e.g. "Launch dpdk-testpmd [debug]", "Launch > dpdk-testpmd [asan]" etc.), or use some kind of indirection to "select > active build configuration" (e.g. have one launch task but overwrite > ${config:BUILDDIR} after request for configuration, so that launch tasks > would pick up actual executable path at run time from settings). I would > prefer the latter to be honest, as it's much easier to drop a script into > ./vscode and run it together with "configure" command to switch between > different build/launch configurations. What do you think? > I think I'd prefer the former actually - to have explicit tasks always listed for debug and release builds. Not a big deal for me either way, I'll just hack in the extra tasks as I need them, so it's a low-priority support item for me. > > > > Final thoughts on usability: > > > > - Please don't write gdbsudo to /usr/local/bin without asking the user > > first. Instead I think it should default to $HOME/.local/bin, but with a > > prompt for the user to specify a path. > > It's not creating anything, it's just printing out a snippet, which, if run > by user, would do that - the implication is obviously that the user may > correct it if necessary. The script actually picks up path to `gdbsudo` from > `which` command, so if the user puts their command to $HOME/.local/bin or > something, it would get picked up if it's in PATH. I see your point about > maybe suggesting using a home directory path instead of a system wide path, > I can change that. Yep, thanks, and thanks for the explanation. BTW: even if the user is running as non-root user, they don't always need to use sudo (I set up my system to not need it for running DPDK). I see there is a cmdline option for "no-gdbsudo" but I think you should make that accessible via TUI also if non-root. And for the cmdline parameters, how about shortening them to "--sudo" and "--nosudo". For debug vs release builds you may want to have the latter run without gdb at all, just with or without sudo.] > > > > > - While I realise your primary concern here is an interactive script, I'd > > tend towards requiring a cmdline arg to run in interactive mode and > > instead printing the help usage when run without parameters. Just a > > personal preference on my part though. > > I found it to be much faster to pick my targets, apps etc. using a couple of > interactive windows than to type out parameters I probably don't even > remember ahead of time (especially build configurations!), and I believe > it's more newbie-friendly that way, as I imagine very few people will want > to learn arguments for yet-another-script just to start using VSCode. It > would be my personal preference to leave it as default-to-TUI, but maybe > recognizing a widely used `-i` parameter would be a good compromise for > instant familiarity. > Ok. There's always a -h option for me to get the cmdline parameters. I also think if the script is ok with working off an existing build directory (or directories!), and only prompting for that, it would remove for me the real necessity of asking for a more cmdline-fieldly version. ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC PATCH v2 1/1] devtools: add vscode configuration generator 2024-07-29 16:41 ` Bruce Richardson @ 2024-07-30 9:21 ` Burakov, Anatoly 2024-07-30 10:31 ` Bruce Richardson 0 siblings, 1 reply; 23+ messages in thread From: Burakov, Anatoly @ 2024-07-30 9:21 UTC (permalink / raw) To: Bruce Richardson; +Cc: dev, john.mcnamara On 7/29/2024 6:41 PM, Bruce Richardson wrote: > On Mon, Jul 29, 2024 at 06:16:48PM +0200, Burakov, Anatoly wrote: >> On 7/29/2024 4:30 PM, Bruce Richardson wrote: >>> On Mon, Jul 29, 2024 at 02:05:52PM +0100, Anatoly Burakov wrote: >>>> A lot of developers use Visual Studio Code as their primary IDE. This >>>> script generates a configuration file for VSCode that sets up basic build >>>> tasks, launch tasks, as well as C/C++ code analysis settings that will >>>> take into account compile_commands.json that is automatically generated >>>> by meson. >>>> >>>> Files generated by script: >>>> - .vscode/settings.json: stores variables needed by other files >>>> - .vscode/tasks.json: defines build tasks >>>> - .vscode/launch.json: defines launch tasks >>>> - .vscode/c_cpp_properties.json: defines code analysis settings >>>> >>>> The script uses a combination of globbing and meson file parsing to >>>> discover available apps, examples, and drivers, and generates a >>>> project-wide settings file, so that the user can later switch between >>>> debug/release/etc. configurations while keeping their desired apps, >>>> examples, and drivers, built by meson, and ensuring launch configurations >>>> still work correctly whatever the configuration selected. >>>> >>>> This script uses whiptail as TUI, which is expected to be universally >>>> available as it is shipped by default on most major distributions. >>>> However, the script is also designed to be scriptable and can be run >>>> without user interaction, and have its configuration supplied from >>>> command-line arguments. >>>> >>>> Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com> >>>> --- >>>> >>> Just was trying this out, nice script, thanks. >> >> Thanks for the feedback! Comments below. >> >>> >>> Initial thoughts concerning the build directory: >>> - the script doesn't actually create the build directory, so there is no >>> guarantee that the build directory created will have the same parameters >>> as that specified in the script run. I'd suggest in the case where the >>> user runs the script and specifies build settings, that the build >>> directory is then configured using those settings. >> >> I'm not sure I follow. >> >> The script creates a command for VSCode to create a build directory using >> configuration the user has supplied at script's run time. The directory is >> not created by the script, that is the job of meson build system. This >> script is merely codifying commands for meson to do that, with the >> expectation that the user is familiar with VSCode workflow and will go >> straight to build commands anyway, and will pick one of them. Are you >> suggesting running `meson setup` right after? >> > > Yes, that is what I was thinking, based on the assumption that running > "meson setup" should be a once-only task. I suppose overall it's a > different workflow to what you have - where you run meson setup repeatedly > each time you change a build type. My thinking for the approach to take > here is that your script firstly asks for a build directory, and then: > * if build-dir exists, pull settings you need from there, such as build > type and the apps being built. > * if not existing, ask for config settings as you do now, and then run > meson setup to create the build dir. I guess the disconnect comes from the fact that I treat meson build directories as transient and disposable and never hesitate to wipe them and start over, whereas you seem to favor persistence. Since 99% of the time I'm using heavily reduced builds anyway (e.g. one app, one driver), repeatedly doing meson setup doesn't really hinder me in any way, but I suppose it would if I had more meaty builds. I think we also have a slightly different view of what this script should be - I envisioned a "one-stop-shop" for "open freshly cloned DPDK directory, run one script and off you go" (which stems from the fact that I heavily rely on Git Worktrees [1] in my workflow, so having an untouched source directory without any configuration in it is something I am constantly faced with), while you seem to favor picking up existing meson build and building a VSCode configuration around it. I can see point in this, and I guess I actually unwittingly rolled two scripts into one - a TUI meson frontend, and a VSCode configuration generator. Perhaps it should very well be two scripts, not one? Because honestly, having something like what I built for TUI (the meson configuration frontend) is something I missed a lot and something I always wished our long-gone `setup.sh` script had, but didn't, and now with meson it's much simpler to do but we still don't have anything like that. Maybe this is our opportunity to provide a quicker "quick start" script, one that I could run and configure meson build without having to type anything. WDYT? > > Thereafter, the source for all build settings is not in vscode, but in > meson, and you just use "meson configure" from vscode to tweak whatever > needs tweaking, without affecting any other settings. Since you don't > affect any other settings there is no need to record what they should be. Why would I be using meson configure from vscode then? I mean, the whole notion of having tasks in VSCode is so that I define a few common configurations and never touch them until I'm working on something else. If I have to type in configure commands anyway (because I would have to, to adjust configuration in meson), I might as well do so using terminal, and avoid dealing with meson from VSCode entirely? (technically, there's a way to read arguments from a popup window - I suppose we could have this step recorded as a VSCode task) > > >> Assuming we do that, it would actually then be possible to adjust launch >> tasks to only include *actual* built apps/examples (as well as infer things >> like platform, compiler etc.), as that's one weakness of my current "flying >> blind" approach, so I wouldn't be opposed to adding an extra step here, just >> want to make sure I understand what you're saying correctly. >> >>> >>> - On the other hand, when the build directory already exists - I think the >>> script should pull all settings from there, rather than prompting the >>> user. >>> >> >> That can be done, however, my own workflow has been that I do not ever keep >> build directories inside my source directory, so it would not be possible to >> pick up directories anywhere but the source directory. >> > > Why is that, and how does it work now, for e.g. getting the > compile_commands.json file from your build folder? Right now, at configuration time I store build directory in settings.json, and all of the other tasks (build, launch, C++ analysis) pick it up via ${config:...} variable. This is why I suggested having a notion of "active configuration" - if we just rewrite that variable, both ninja and launch tasks can be switched to a different build directory without actually having to create new tasks. More on that below, as that raises a few questions. As for why, it's a personal preference - it's annoying to have the build/ directory in my file view (it's possible to hide it, I guess I could automatically add a setting to do that in settings.json), and it interferes with e.g. source-tree-wide searches and stuff like that. This isn't a hard requirement though, I can switch to in-tree builds and automatic directory hiding if it means more automation :) > >> I also think from the point of view of the script it would be easier to >> start from known quantities rather than guess what user was trying to do >> from current configuration, but I guess a few common-sense heuristics should >> suffice for most use cases, such as e.g. inferring debug builds. >> > > What you need depends on whether you want to keep running "meson setup" - > which means you need to track all settings - or want to use "meson > configure" where you don't really need to track much at all. I guess I was aiming for the former, but doing only the latter makes sense if we assume we want to separate setup from VSCode config generation (which it seems like we're heading that way). > >>> - I'm not sure I like the idea for reconfiguring of just removing the build >>> directory and doing a whole meson setup command all over again. This >>> seems excessive and also removes the possibility of the user having made >>> changes in config to the build dir without re-running the whole config >>> script. For example, having tweaked the LTO setting, or the >>> instruction_set_isa_setting. Rather than deleting it and running meson >>> setup, it would be better to use "meson configure" to adjust the one >>> required setting and let ninja figure out how to propagate that change. >>> That saves the script from having to track all meson parameters itself. >> >> Last I checked, meson doesn't have a command that would "setup or configure >> existing" a directory, it's either "set up new one" or "configure existing >> one". I guess we could set up a fallback of "configure || setup". >> > > This goes back to the whole "create build directory after running the > script" option. If the script creates the build dir, the vscode commands > never need to use meson setup, only ever meson configure. Sure, and like I suggested above, it doesn't even have to be *this* script, it can be another, a Python-based TUI reimplementation of setup.sh :) > >>> >>> - Finally, and semi-related, this script assumes that the user does >>> everything in a single build directory. Just something to consider, but >>> my own workflow till now has tended to keep multiple build directories >>> around, generally a "build" directory, which is either release or >>> debugoptimized type, and a separate "build-debug" directory + occasionally >>> others for build testing. When doing incremental builds, the time taken to >>> do two builds following a change is a lot less noticable than the time taken >>> for periodic switches of a single build directory between debug and release >>> mode. >> >> The problem with that approach is the launch tasks, because a launch task >> can only ever point to one executable, so if you have multiple build >> directories, you'll have to have multiple launch tasks per app/example. I >> guess we can either tag them (e.g. "Launch dpdk-testpmd [debug]", "Launch >> dpdk-testpmd [asan]" etc.), or use some kind of indirection to "select >> active build configuration" (e.g. have one launch task but overwrite >> ${config:BUILDDIR} after request for configuration, so that launch tasks >> would pick up actual executable path at run time from settings). I would >> prefer the latter to be honest, as it's much easier to drop a script into >> ./vscode and run it together with "configure" command to switch between >> different build/launch configurations. What do you think? >> > > I think I'd prefer the former actually - to have explicit tasks always > listed for debug and release builds. > Not a big deal for me either way, I'll just hack in the extra tasks as I > need them, so it's a low-priority support item for me. There's another issue though: code analysis. If you have multiple build directories, your C++ analysis settings (the .vscode/c_cpp_properties.json file) can only ever use one specific compile_commands.json from a specific build directory. I think if we were to support having multiple build dirs, we would *have to* implement something like "switch active configuration" thing anyway. Whether we add tagged launch tasks is honestly a problem to be solved, because on the one hand we want them to be generated automatically (ideally after configure step), and on the other we also want to not interfere with any custom configuration the user has already added (such as e.g. command line arguments to existing launch tasks). We'll probably have to do config file parsing and updating the configuration, rather than regenerating it each time. Python's JSON also doesn't support comments, so for any comments that were added to configurations, we'd either lose them, or find a way to reinsert them post-generation. > >>> >>> Final thoughts on usability: >>> >>> - Please don't write gdbsudo to /usr/local/bin without asking the user >>> first. Instead I think it should default to $HOME/.local/bin, but with a >>> prompt for the user to specify a path. >> >> It's not creating anything, it's just printing out a snippet, which, if run >> by user, would do that - the implication is obviously that the user may >> correct it if necessary. The script actually picks up path to `gdbsudo` from >> `which` command, so if the user puts their command to $HOME/.local/bin or >> something, it would get picked up if it's in PATH. I see your point about >> maybe suggesting using a home directory path instead of a system wide path, >> I can change that. > > Yep, thanks, and thanks for the explanation. > BTW: even if the user is running as non-root user, they don't always need > to use sudo (I set up my system to not need it for running DPDK). I see > there is a cmdline option for "no-gdbsudo" but I think you should make that > accessible via TUI also if non-root. Sure, all of that can be done. > > And for the cmdline parameters, how about shortening them to "--sudo" and > "--nosudo". For debug vs release builds you may want to have the latter run > without gdb at all, just with or without sudo.] I honestly never run anything outside gdb, but that's a consequence of me not really working on things that routinely require it (e.g. performace code). We can add that no problem though. > >> >>> >>> - While I realise your primary concern here is an interactive script, I'd >>> tend towards requiring a cmdline arg to run in interactive mode and >>> instead printing the help usage when run without parameters. Just a >>> personal preference on my part though. >> >> I found it to be much faster to pick my targets, apps etc. using a couple of >> interactive windows than to type out parameters I probably don't even >> remember ahead of time (especially build configurations!), and I believe >> it's more newbie-friendly that way, as I imagine very few people will want >> to learn arguments for yet-another-script just to start using VSCode. It >> would be my personal preference to leave it as default-to-TUI, but maybe >> recognizing a widely used `-i` parameter would be a good compromise for >> instant familiarity. >> > > Ok. There's always a -h option for me to get the cmdline parameters. > > I also think if the script is ok with working off an existing build > directory (or directories!), and only prompting for that, it would remove > for me the real necessity of asking for a more cmdline-fieldly version. It sounds like this would really be something that a setup script would do better than a VSCode config generator. So, assuming we want to move setup steps to another script and concentrate on VSCode configuration exclusively, my thinking of how it would work is as follows: 1) Assume we want multiple build directories, suggest automatically picking them up from source directory but support specifying one or more from command line arguments (or TUI, although I suspect if setup moves to a separate script, there's very little need for TUI in VSCode config generator - it should be a mostly mechanical process at that point) 2) Now that we track multiple build directories, we can store them in a YAML file or something (e.g. .vscode/.dpdk_builds.yaml), and create tasks to switch between them as "active" to support e.g. different code analysis settings and stuff like that 3) All build tasks can work off "active configuration" which means we don't need multiple compile tasks, but for launch tasks we may need different ones because different build dirs may have different launch tasks Let's assume user just ran `meson configure` and changed something about one of their configurations. What do you think should happen next? I mean, if they added/removed an app/example to the build, we can detect that and auto-generate (or remove) a launch task, for example? Or do you think it should be a manual step, e.g. user should explicitly request regenerating/updating launch tasks? Maybe there should be an --update flag, to indicate that we're not creating new configurations but merely refreshing existing ones? [1] https://git-scm.com/docs/git-worktree -- Thanks, Anatoly ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC PATCH v2 1/1] devtools: add vscode configuration generator 2024-07-30 9:21 ` Burakov, Anatoly @ 2024-07-30 10:31 ` Bruce Richardson 2024-07-30 10:50 ` Burakov, Anatoly 0 siblings, 1 reply; 23+ messages in thread From: Bruce Richardson @ 2024-07-30 10:31 UTC (permalink / raw) To: Burakov, Anatoly; +Cc: dev, john.mcnamara On Tue, Jul 30, 2024 at 11:21:25AM +0200, Burakov, Anatoly wrote: > On 7/29/2024 6:41 PM, Bruce Richardson wrote: > > On Mon, Jul 29, 2024 at 06:16:48PM +0200, Burakov, Anatoly wrote: > > > On 7/29/2024 4:30 PM, Bruce Richardson wrote: > > > > On Mon, Jul 29, 2024 at 02:05:52PM +0100, Anatoly Burakov wrote: > > > > > A lot of developers use Visual Studio Code as their primary IDE. This > > > > > script generates a configuration file for VSCode that sets up basic build > > > > > tasks, launch tasks, as well as C/C++ code analysis settings that will > > > > > take into account compile_commands.json that is automatically generated > > > > > by meson. > > > > > > > > > > Files generated by script: > > > > > - .vscode/settings.json: stores variables needed by other files > > > > > - .vscode/tasks.json: defines build tasks > > > > > - .vscode/launch.json: defines launch tasks > > > > > - .vscode/c_cpp_properties.json: defines code analysis settings > > > > > > > > > > The script uses a combination of globbing and meson file parsing to > > > > > discover available apps, examples, and drivers, and generates a > > > > > project-wide settings file, so that the user can later switch between > > > > > debug/release/etc. configurations while keeping their desired apps, > > > > > examples, and drivers, built by meson, and ensuring launch configurations > > > > > still work correctly whatever the configuration selected. > > > > > > > > > > This script uses whiptail as TUI, which is expected to be universally > > > > > available as it is shipped by default on most major distributions. > > > > > However, the script is also designed to be scriptable and can be run > > > > > without user interaction, and have its configuration supplied from > > > > > command-line arguments. > > > > > > > > > > Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com> > > > > > --- > > > > > > > > > Just was trying this out, nice script, thanks. > > > > > > Thanks for the feedback! Comments below. > > > More comments inline below, but summarising after the fact here. Still not entirely sure what way is best for all this so please take all current and previous suggestions with a pinch of salt. Based off what you suggest and the ongoing discuss my current thinking is: * +1 to split the vscode config generation from the TUI. Both are also targetting different end-users - the TUI is for everyone looking to build DPDK, both devs and users, while the vscode config is for developers only. * Let's ignore the multi-build-directory setup for now, if it makes it more complex for the simple cases of one build-dir. * I think we should investigate having the vscode config generated from meson rather than the other way around. See also below. /Bruce > > > > > > > > Initial thoughts concerning the build directory: > > > > - the script doesn't actually create the build directory, so there is no > > > > guarantee that the build directory created will have the same parameters > > > > as that specified in the script run. I'd suggest in the case where the > > > > user runs the script and specifies build settings, that the build > > > > directory is then configured using those settings. > > > > > > I'm not sure I follow. > > > > > > The script creates a command for VSCode to create a build directory using > > > configuration the user has supplied at script's run time. The directory is > > > not created by the script, that is the job of meson build system. This > > > script is merely codifying commands for meson to do that, with the > > > expectation that the user is familiar with VSCode workflow and will go > > > straight to build commands anyway, and will pick one of them. Are you > > > suggesting running `meson setup` right after? > > > > > > > Yes, that is what I was thinking, based on the assumption that running > > "meson setup" should be a once-only task. I suppose overall it's a > > different workflow to what you have - where you run meson setup repeatedly > > each time you change a build type. My thinking for the approach to take > > here is that your script firstly asks for a build directory, and then: > > * if build-dir exists, pull settings you need from there, such as build > > type and the apps being built. > > * if not existing, ask for config settings as you do now, and then run > > meson setup to create the build dir. > > I guess the disconnect comes from the fact that I treat meson build > directories as transient and disposable and never hesitate to wipe them and > start over, whereas you seem to favor persistence. Since 99% of the time I'm > using heavily reduced builds anyway (e.g. one app, one driver), repeatedly > doing meson setup doesn't really hinder me in any way, but I suppose it > would if I had more meaty builds. > It mainly just seems inefficient to me. Ninja does a lot of processing to minimise the work done whenever you make a configuration change, and you bypass all that by nuking the directory from orbit (only way to be sure!) and then creating a new one! The other main downside of it (to my mind), is that the tracking of settings for the build needs to be in vscode. I'd prefer meson itself to be the "one source of truth" and vscode to be tweaking that, rather than tracking everything itself. > I think we also have a slightly different view of what this script should be > - I envisioned a "one-stop-shop" for "open freshly cloned DPDK directory, > run one script and off you go" (which stems from the fact that I heavily > rely on Git Worktrees [1] in my workflow, so having an untouched source > directory without any configuration in it is something I am constantly faced > with), while you seem to favor picking up existing meson build and building > a VSCode configuration around it. > > I can see point in this, and I guess I actually unwittingly rolled two > scripts into one - a TUI meson frontend, and a VSCode configuration > generator. Perhaps it should very well be two scripts, not one? Because > honestly, having something like what I built for TUI (the meson > configuration frontend) is something I missed a lot and something I always > wished our long-gone `setup.sh` script had, but didn't, and now with meson > it's much simpler to do but we still don't have anything like that. Maybe > this is our opportunity to provide a quicker "quick start" script, one that > I could run and configure meson build without having to type anything. WDYT? > Splitting it into two makes sense to me, yes, since as you point out there are really two separate jobs involved here that one may want to roll with separately. > > > > Thereafter, the source for all build settings is not in vscode, but in > > meson, and you just use "meson configure" from vscode to tweak whatever > > needs tweaking, without affecting any other settings. Since you don't > > affect any other settings there is no need to record what they should be. > > Why would I be using meson configure from vscode then? I mean, the whole > notion of having tasks in VSCode is so that I define a few common > configurations and never touch them until I'm working on something else. If > I have to type in configure commands anyway (because I would have to, to > adjust configuration in meson), I might as well do so using terminal, and > avoid dealing with meson from VSCode entirely? > > (technically, there's a way to read arguments from a popup window - I > suppose we could have this step recorded as a VSCode task) > The reason I was thinking about this is that you don't want to expose dozens of tasks in vscode for tweaking every possible meson setting. Sure, have build and run tasks for the common options, but for "advanced use" where the user wants to tweak a build setting, let them just do it from commandline without having to re-run a config script to adjust the settings. <Snip> > > > > > > > > - Finally, and semi-related, this script assumes that the user does > > > > everything in a single build directory. Just something to consider, but > > > > my own workflow till now has tended to keep multiple build directories > > > > around, generally a "build" directory, which is either release or > > > > debugoptimized type, and a separate "build-debug" directory + occasionally > > > > others for build testing. When doing incremental builds, the time taken to > > > > do two builds following a change is a lot less noticable than the time taken > > > > for periodic switches of a single build directory between debug and release > > > > mode. > > > > > > The problem with that approach is the launch tasks, because a launch task > > > can only ever point to one executable, so if you have multiple build > > > directories, you'll have to have multiple launch tasks per app/example. I > > > guess we can either tag them (e.g. "Launch dpdk-testpmd [debug]", "Launch > > > dpdk-testpmd [asan]" etc.), or use some kind of indirection to "select > > > active build configuration" (e.g. have one launch task but overwrite > > > ${config:BUILDDIR} after request for configuration, so that launch tasks > > > would pick up actual executable path at run time from settings). I would > > > prefer the latter to be honest, as it's much easier to drop a script into > > > ./vscode and run it together with "configure" command to switch between > > > different build/launch configurations. What do you think? > > > > > > > I think I'd prefer the former actually - to have explicit tasks always > > listed for debug and release builds. > > Not a big deal for me either way, I'll just hack in the extra tasks as I > > need them, so it's a low-priority support item for me. > > There's another issue though: code analysis. If you have multiple build > directories, your C++ analysis settings (the .vscode/c_cpp_properties.json > file) can only ever use one specific compile_commands.json from a specific > build directory. I think if we were to support having multiple build dirs, > we would *have to* implement something like "switch active configuration" > thing anyway. > Strictly speaking, yes. However, in my experience using eclipse as an IDE in the past it doesn't matter that much which or how many build directories are analysed. However, vscode may well be different in this regard. Since I don't ever envisage myself doing everything always through vscode, I'm happy enough with vscode managing a single build directory, and I can manually worry about a second build directory myself. Maybe let's park the multi-build-dir stuff for now, unless others feel that it's something they need. > Whether we add tagged launch tasks is honestly a problem to be solved, > because on the one hand we want them to be generated automatically (ideally > after configure step), and on the other we also want to not interfere with > any custom configuration the user has already added (such as e.g. command > line arguments to existing launch tasks). We'll probably have to do config > file parsing and updating the configuration, rather than regenerating it > each time. Python's JSON also doesn't support comments, so for any comments > that were added to configurations, we'd either lose them, or find a way to > reinsert them post-generation. > Have you considered generating the launch tasks from a script launched from meson itself? Any time the configuration is changed, meson will re-run at the next build and that can trigger re-generation of whatever vscode config you need, including launch tasks for all the binaries. This would be another advantage of splitting the script into two - one should look to make the vscode-settings generation script usable from meson. <snip> > It sounds like this would really be something that a setup script would do > better than a VSCode config generator. > > So, assuming we want to move setup steps to another script and concentrate > on VSCode configuration exclusively, my thinking of how it would work is as > follows: > > 1) Assume we want multiple build directories, suggest automatically picking > them up from source directory but support specifying one or more from > command line arguments (or TUI, although I suspect if setup moves to a > separate script, there's very little need for TUI in VSCode config generator > - it should be a mostly mechanical process at that point) > I'm probably an outlier, so lets not over-design things for the multi-build-directory case. If we think about generating the vscode config from a meson run (via a "generate-vscode-config" setting or something), that may switch the way in which things are actually being done. In that case, a build directory would register itself with the vscode config - creating a new one if not already present. > 2) Now that we track multiple build directories, we can store them in a YAML > file or something (e.g. .vscode/.dpdk_builds.yaml), and create tasks to > switch between them as "active" to support e.g. different code analysis > settings and stuff like that > Initially, maybe don't add this. For first draft supporting multi-directories, I'd start by adding prefixed duplicate tasks for each directory registered. > 3) All build tasks can work off "active configuration" which means we don't > need multiple compile tasks, but for launch tasks we may need different ones > because different build dirs may have different launch tasks > Again, I'd just add tasks rather than bothering with active configs. > Let's assume user just ran `meson configure` and changed something about one > of their configurations. What do you think should happen next? I mean, if > they added/removed an app/example to the build, we can detect that and > auto-generate (or remove) a launch task, for example? Or do you think it > should be a manual step, e.g. user should explicitly request > regenerating/updating launch tasks? Maybe there should be an --update flag, > to indicate that we're not creating new configurations but merely refreshing > existing ones? See above, this is a case where having the vscode config script callable from meson would be perfect. > > [1] https://git-scm.com/docs/git-worktree > Thanks for the link - seems to be automating a setup which I've been approximately doing manually for some years now! :-) ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC PATCH v2 1/1] devtools: add vscode configuration generator 2024-07-30 10:31 ` Bruce Richardson @ 2024-07-30 10:50 ` Burakov, Anatoly 0 siblings, 0 replies; 23+ messages in thread From: Burakov, Anatoly @ 2024-07-30 10:50 UTC (permalink / raw) To: Bruce Richardson; +Cc: dev, john.mcnamara On 7/30/2024 12:31 PM, Bruce Richardson wrote: > On Tue, Jul 30, 2024 at 11:21:25AM +0200, Burakov, Anatoly wrote: >> On 7/29/2024 6:41 PM, Bruce Richardson wrote: >>> On Mon, Jul 29, 2024 at 06:16:48PM +0200, Burakov, Anatoly wrote: >>>> On 7/29/2024 4:30 PM, Bruce Richardson wrote: >>>>> On Mon, Jul 29, 2024 at 02:05:52PM +0100, Anatoly Burakov wrote: >>>>>> A lot of developers use Visual Studio Code as their primary IDE. This >>>>>> script generates a configuration file for VSCode that sets up basic build >>>>>> tasks, launch tasks, as well as C/C++ code analysis settings that will >>>>>> take into account compile_commands.json that is automatically generated >>>>>> by meson. >>>>>> >>>>>> Files generated by script: >>>>>> - .vscode/settings.json: stores variables needed by other files >>>>>> - .vscode/tasks.json: defines build tasks >>>>>> - .vscode/launch.json: defines launch tasks >>>>>> - .vscode/c_cpp_properties.json: defines code analysis settings >>>>>> >>>>>> The script uses a combination of globbing and meson file parsing to >>>>>> discover available apps, examples, and drivers, and generates a >>>>>> project-wide settings file, so that the user can later switch between >>>>>> debug/release/etc. configurations while keeping their desired apps, >>>>>> examples, and drivers, built by meson, and ensuring launch configurations >>>>>> still work correctly whatever the configuration selected. >>>>>> >>>>>> This script uses whiptail as TUI, which is expected to be universally >>>>>> available as it is shipped by default on most major distributions. >>>>>> However, the script is also designed to be scriptable and can be run >>>>>> without user interaction, and have its configuration supplied from >>>>>> command-line arguments. >>>>>> >>>>>> Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com> >>>>>> --- >>>>>> >>>>> Just was trying this out, nice script, thanks. >>>> >>>> Thanks for the feedback! Comments below. >>>> > > More comments inline below, but summarising after the fact here. > > Still not entirely sure what way is best for all this so please take all > current and previous suggestions with a pinch of salt. Based off what you > suggest and the ongoing discuss my current thinking is: > > * +1 to split the vscode config generation from the TUI. Both are also > targetting different end-users - the TUI is for everyone looking to build > DPDK, both devs and users, while the vscode config is for developers only. > * Let's ignore the multi-build-directory setup for now, if it makes it more > complex for the simple cases of one build-dir. Not really *that* much more complex, IMO. The only real issue is possible differences in code analysis behavior stemming from having "wrong" build directory set up as a source of compile_commands.json. If you're OK with adding multiple tasks per multiple build directories, then all of the rest of it becomes trivial because if launch tasks are per-build, they can reference per-build build commands and work seamlessly using "duplicate" commands. And even then, for first version we can probably drop the code analysis angle (just use the first detected config as the source), in which case we can pretty much support multiple build dirs for free as we'd have to build all the infrastructure (e.g. config updates etc.) anyway if we want this process to be seamless. > * I think we should investigate having the vscode config generated from > meson rather than the other way around. It didn't occur to me that it was possible, it sounds like that's really the way to go! <snip> > Strictly speaking, yes. However, in my experience using eclipse as an IDE > in the past it doesn't matter that much which or how many build directories > are analysed. However, vscode may well be different in this regard. Unless the user does *wildly* different things in their build directories (i.e. two dirs, one of which used for cross-build or something), I expect things to work without any additional effort, so you're right in that for most practical purposes, the result wouldn't really be different to having "proper" C++ analysis configurations. > Since I don't ever envisage myself doing everything always through vscode, > I'm happy enough with vscode managing a single build directory, and I can > manually worry about a second build directory myself. Maybe let's park the > multi-build-dir stuff for now, unless others feel that it's something they > need. Well, I do strive to do most things with VSCode (that's why I had multiple configurations to begin with!), so it would benefit *my* workflow to support that :) -- Thanks, Anatoly ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC PATCH v2 0/1] Add Visual Studio Code configuration script 2024-07-29 13:05 ` [RFC PATCH v2 0/1] Add Visual Studio Code configuration script Anatoly Burakov 2024-07-29 13:05 ` [RFC PATCH v2 1/1] devtools: add vscode configuration generator Anatoly Burakov @ 2024-07-30 15:01 ` Bruce Richardson 2024-07-30 15:14 ` Burakov, Anatoly 1 sibling, 1 reply; 23+ messages in thread From: Bruce Richardson @ 2024-07-30 15:01 UTC (permalink / raw) To: Anatoly Burakov; +Cc: dev, john.mcnamara On Mon, Jul 29, 2024 at 02:05:51PM +0100, Anatoly Burakov wrote: > Lots of developers (myself included) uses Visual Studio Code as their primary > IDE for DPDK development. I have been successfully using various incarnations of > this script internally to quickly set up my development trees whenever I need a > new configuration, so this script is being shared in hopes that it will be > useful both to new developers starting with DPDK, and to seasoned DPDK > developers who are already using Visual Studio Code. It makes starting working > on DPDK in Visual Studio Code so much easier! > > ** NOTE: Currently, only x86 configuration is generated as I have no way to test > the code analysis configuration on any other platforms. > > ** NOTE 2: this is not for *Visual Studio* the Windows IDE, this is for *Visual > Studio Code* the cross-platform code editor. Specifically, main target > audience for this script is people who either run DPDK directly on their > Linux machine, or who use Remote SSH functionality to connect to a remote > Linux machine and set up VSCode build there. No other OS's are currently > supported by the script. > > (if you're unaware of what is Remote SSH, I highly suggest checking it out [1]) > > Philosophy behind this script is as follows: > > - The assumption is made that a developer will not be using wildly different > configurations from build to build - usually, they build the same things, work > with the same set of apps/drivers for a while, then switch to something else, > at which point a new configuration is needed > > - Some configurations I consider to be "common" are included: debug build, debug > optimized build, release build with docs, and ASan build (feel free to make > suggestions here!) > > - By default, the script will not add any meson flags unless user requested it, > however it will create launch configurations for all apps because not > specifying any flags leads to all apps being enabled > > - All parameters that can be adjusted by TUI are also available as command line > arguments, so while user interaction is the default (using whiptail), it's > actually not required and can be bypassed > The management of dependencies of components to be built is obviously a tricky area here, when specifying e.g. enable_drivers flags. It may be possible to improve the situation in meson itself, but that probably requires massive rework of the lib/meson.build, drivers/meson.build and app/meson.build files to process the subdirs and save the results for later use (effectively process them twice within the restrictions of meson only allowing subdir once). In the meantime, as a better-than-nothing improvement, I've pushed a draft patch to have meson produce a dependencies file as part of its processing[1]. That may be of use to you in doing new versions of the TUI - i.e. in the background you could run a dummy meson config to /tmp and then process the resulting deps file from it, to allow you to recursively enable dependencies of the user-selected components.. Regards, /Bruce [1] https://patches.dpdk.org/project/dpdk/patch/20240730145508.551075-1-bruce.richardson@intel.com/ ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC PATCH v2 0/1] Add Visual Studio Code configuration script 2024-07-30 15:01 ` [RFC PATCH v2 0/1] Add Visual Studio Code configuration script Bruce Richardson @ 2024-07-30 15:14 ` Burakov, Anatoly 2024-07-30 15:19 ` Bruce Richardson 0 siblings, 1 reply; 23+ messages in thread From: Burakov, Anatoly @ 2024-07-30 15:14 UTC (permalink / raw) To: Bruce Richardson; +Cc: dev, john.mcnamara On 7/30/2024 5:01 PM, Bruce Richardson wrote: > On Mon, Jul 29, 2024 at 02:05:51PM +0100, Anatoly Burakov wrote: >> Lots of developers (myself included) uses Visual Studio Code as their primary >> IDE for DPDK development. I have been successfully using various incarnations of >> this script internally to quickly set up my development trees whenever I need a >> new configuration, so this script is being shared in hopes that it will be >> useful both to new developers starting with DPDK, and to seasoned DPDK >> developers who are already using Visual Studio Code. It makes starting working >> on DPDK in Visual Studio Code so much easier! >> >> ** NOTE: Currently, only x86 configuration is generated as I have no way to test >> the code analysis configuration on any other platforms. >> >> ** NOTE 2: this is not for *Visual Studio* the Windows IDE, this is for *Visual >> Studio Code* the cross-platform code editor. Specifically, main target >> audience for this script is people who either run DPDK directly on their >> Linux machine, or who use Remote SSH functionality to connect to a remote >> Linux machine and set up VSCode build there. No other OS's are currently >> supported by the script. >> >> (if you're unaware of what is Remote SSH, I highly suggest checking it out [1]) >> >> Philosophy behind this script is as follows: >> >> - The assumption is made that a developer will not be using wildly different >> configurations from build to build - usually, they build the same things, work >> with the same set of apps/drivers for a while, then switch to something else, >> at which point a new configuration is needed >> >> - Some configurations I consider to be "common" are included: debug build, debug >> optimized build, release build with docs, and ASan build (feel free to make >> suggestions here!) >> >> - By default, the script will not add any meson flags unless user requested it, >> however it will create launch configurations for all apps because not >> specifying any flags leads to all apps being enabled >> >> - All parameters that can be adjusted by TUI are also available as command line >> arguments, so while user interaction is the default (using whiptail), it's >> actually not required and can be bypassed >> > > The management of dependencies of components to be built is obviously a > tricky area here, when specifying e.g. enable_drivers flags. It may be > possible to improve the situation in meson itself, but that probably > requires massive rework of the lib/meson.build, drivers/meson.build and > app/meson.build files to process the subdirs and save the results for later > use (effectively process them twice within the restrictions of meson only > allowing subdir once). > > In the meantime, as a better-than-nothing improvement, I've pushed a draft > patch to have meson produce a dependencies file as part of its processing[1]. > That may be of use to you in doing new versions of the TUI - i.e. in the > background you could run a dummy meson config to /tmp and then process the > resulting deps file from it, to allow you to recursively enable > dependencies of the user-selected components.. Thanks, this looks very interesting! It's a shame it can't be done without creating a build directory at all (e.g. by using meson dummy runs or something), but like you said, better than nothing! > > Regards, > /Bruce > > [1] https://patches.dpdk.org/project/dpdk/patch/20240730145508.551075-1-bruce.richardson@intel.com/ -- Thanks, Anatoly ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [RFC PATCH v2 0/1] Add Visual Studio Code configuration script 2024-07-30 15:14 ` Burakov, Anatoly @ 2024-07-30 15:19 ` Bruce Richardson 0 siblings, 0 replies; 23+ messages in thread From: Bruce Richardson @ 2024-07-30 15:19 UTC (permalink / raw) To: Burakov, Anatoly; +Cc: dev, john.mcnamara On Tue, Jul 30, 2024 at 05:14:29PM +0200, Burakov, Anatoly wrote: > On 7/30/2024 5:01 PM, Bruce Richardson wrote: > > On Mon, Jul 29, 2024 at 02:05:51PM +0100, Anatoly Burakov wrote: > > > Lots of developers (myself included) uses Visual Studio Code as their primary > > > IDE for DPDK development. I have been successfully using various incarnations of > > > this script internally to quickly set up my development trees whenever I need a > > > new configuration, so this script is being shared in hopes that it will be > > > useful both to new developers starting with DPDK, and to seasoned DPDK > > > developers who are already using Visual Studio Code. It makes starting working > > > on DPDK in Visual Studio Code so much easier! > > > > > > ** NOTE: Currently, only x86 configuration is generated as I have no way to test > > > the code analysis configuration on any other platforms. > > > > > > ** NOTE 2: this is not for *Visual Studio* the Windows IDE, this is for *Visual > > > Studio Code* the cross-platform code editor. Specifically, main target > > > audience for this script is people who either run DPDK directly on their > > > Linux machine, or who use Remote SSH functionality to connect to a remote > > > Linux machine and set up VSCode build there. No other OS's are currently > > > supported by the script. > > > > > > (if you're unaware of what is Remote SSH, I highly suggest checking it out [1]) > > > > > > Philosophy behind this script is as follows: > > > > > > - The assumption is made that a developer will not be using wildly different > > > configurations from build to build - usually, they build the same things, work > > > with the same set of apps/drivers for a while, then switch to something else, > > > at which point a new configuration is needed > > > > > > - Some configurations I consider to be "common" are included: debug build, debug > > > optimized build, release build with docs, and ASan build (feel free to make > > > suggestions here!) > > > > > > - By default, the script will not add any meson flags unless user requested it, > > > however it will create launch configurations for all apps because not > > > specifying any flags leads to all apps being enabled > > > > > > - All parameters that can be adjusted by TUI are also available as command line > > > arguments, so while user interaction is the default (using whiptail), it's > > > actually not required and can be bypassed > > > > > > > The management of dependencies of components to be built is obviously a > > tricky area here, when specifying e.g. enable_drivers flags. It may be > > possible to improve the situation in meson itself, but that probably > > requires massive rework of the lib/meson.build, drivers/meson.build and > > app/meson.build files to process the subdirs and save the results for later > > use (effectively process them twice within the restrictions of meson only > > allowing subdir once). > > > > In the meantime, as a better-than-nothing improvement, I've pushed a draft > > patch to have meson produce a dependencies file as part of its processing[1]. > > That may be of use to you in doing new versions of the TUI - i.e. in the > > background you could run a dummy meson config to /tmp and then process the > > resulting deps file from it, to allow you to recursively enable > > dependencies of the user-selected components.. > > Thanks, this looks very interesting! It's a shame it can't be done without > creating a build directory at all (e.g. by using meson dummy runs or > something), but like you said, better than nothing! Yes. I was wracking my brains to find a better way to do this, but haven't come up with one yet. ^ permalink raw reply [flat|nested] 23+ messages in thread
* [RFC PATCH v3 0/1] Add Visual Studio Code configuration script 2024-07-26 12:42 [RFC PATCH v1 0/1] Add Visual Studio Code configuration script Anatoly Burakov 2024-07-26 12:42 ` [RFC PATCH v1 1/1] devtools: add vscode configuration generator Anatoly Burakov 2024-07-29 13:05 ` [RFC PATCH v2 0/1] Add Visual Studio Code configuration script Anatoly Burakov @ 2024-07-31 13:33 ` Anatoly Burakov 2024-07-31 13:33 ` [RFC PATCH v3 1/1] buildtools: add vscode configuration generator Anatoly Burakov 2024-09-02 12:17 ` [PATCH v1 0/1] Add Visual Studio Code configuration script Anatoly Burakov 3 siblings, 1 reply; 23+ messages in thread From: Anatoly Burakov @ 2024-07-31 13:33 UTC (permalink / raw) To: dev; +Cc: bruce.richardson, john.mcnamara Lots of developers (myself included) uses Visual Studio Code as their primary IDE for DPDK development. I have been successfully using various incarnations of this script internally to quickly set up my development trees whenever I need a new configuration, so this script is being shared in hopes that it will be useful both to new developers starting with DPDK, and to seasoned DPDK developers who are already using Visual Studio Code. It makes starting working on DPDK in Visual Studio Code so much easier! ** NOTE: Currently, only x86 configuration is generated as I have no way to test the code analysis configuration on any other platforms. ** NOTE 2: this is not for *Visual Studio* the Windows IDE, this is for *Visual Studio Code* the cross-platform code editor. Specifically, main target audience for this script is people who either run DPDK directly on their Linux machine, or who use Remote SSH functionality to connect to a remote Linux machine and set up VSCode build there. No other OS's are currently supported by the script. (if you're unaware of what is Remote SSH, I highly suggest checking it out [1]) Philosophy behind this script is as follows: - Any build directory created will automatically add itself to VSCode configuration (ignore mechanism for e.g. test-meson-build.sh is WIP) - Launch configuration is created using `which gdb`, so by default non-root users will have to do additional system configuration for things to work - All of the interactive stuff has now been taken out and is planned to be included in a separate set of scripts, so this script now concerns itself only with adding build/launch targets to user's configuration and not much else Please feel free to make any suggestions! [1] https://code.visualstudio.com/docs/remote/ssh Anatoly Burakov (1): buildtools: add vscode configuration generator app/meson.build | 12 +- buildtools/gen-vscode-conf.py | 442 ++++++++++++++++++++++++++++++++++ buildtools/meson.build | 5 + examples/meson.build | 13 +- meson.build | 11 + 5 files changed, 481 insertions(+), 2 deletions(-) create mode 100755 buildtools/gen-vscode-conf.py -- 2.43.5 ^ permalink raw reply [flat|nested] 23+ messages in thread
* [RFC PATCH v3 1/1] buildtools: add vscode configuration generator 2024-07-31 13:33 ` [RFC PATCH v3 " Anatoly Burakov @ 2024-07-31 13:33 ` Anatoly Burakov 0 siblings, 0 replies; 23+ messages in thread From: Anatoly Burakov @ 2024-07-31 13:33 UTC (permalink / raw) To: dev, Bruce Richardson; +Cc: john.mcnamara A lot of developers use Visual Studio Code as their primary IDE. This script will be called from within meson build process, and will generate a configuration file for VSCode that sets up basic build tasks, launch tasks, as well as C/C++ code analysis settings that will take into account compile_commands.json that is automatically generated by meson. Files generated by script: - .vscode/settings.json: stores variables needed by other files - .vscode/tasks.json: defines build tasks - .vscode/launch.json: defines launch tasks - .vscode/c_cpp_properties.json: defines code analysis settings Multiple, as well as out-of-source-tree, build directories are supported, and the script will generate separate configuration items for each build directory created by user, tagging them for convenience. Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com> --- Notes: RFCv3 -> RFCv2: - Following feedback from Bruce, reworked to be minimal script run from meson - Moved to buildtools - Support for multiple build directories is now the default - All targets are automatically added to all configuration files RFCv1 -> RFCv2: - No longer disable apps and drivers if nothing was specified via command line or TUI, and warn user about things being built by default - Generate app launch configuration by default for when no apps are selected - Added paramters: - --force to avoid overwriting existing config - --common-conf to specify global meson flags applicable to all configs - --gdbsudo/--no-gdbsudo to specify gdbsudo behavior - Autodetect gdbsudo/gdb from UID - Updated comments, error messages, fixed issues with user interaction - Improved handling of wildcards and driver dependencies - Fixed a few bugs in dependency detection due to incorrect parsing - [Stephen] flake8 is happy app/meson.build | 12 +- buildtools/gen-vscode-conf.py | 442 ++++++++++++++++++++++++++++++++++ buildtools/meson.build | 5 + examples/meson.build | 13 +- meson.build | 11 + 5 files changed, 481 insertions(+), 2 deletions(-) create mode 100755 buildtools/gen-vscode-conf.py diff --git a/app/meson.build b/app/meson.build index 5b2c80c7a1..cf0eda3d5f 100644 --- a/app/meson.build +++ b/app/meson.build @@ -114,7 +114,17 @@ foreach app:apps link_libs = dpdk_static_libraries + dpdk_drivers endif - exec = executable('dpdk-' + name, + # add to Visual Studio Code launch configuration + exe_name = 'dpdk-' + name + launch_path = join_paths(meson.current_build_dir(), exe_name) + # we don't want to block the build if this command fails + result = run_command(vscode_conf_gen_cmd + ['--launch', launch_path], check: false) + if result.returncode() != 0 + warning('Failed to generate Visual Studio Code launch configuration for "' + name + '"') + message(result.stderr()) + endif + + exec = executable(exe_name, sources, c_args: cflags, link_args: ldflags, diff --git a/buildtools/gen-vscode-conf.py b/buildtools/gen-vscode-conf.py new file mode 100755 index 0000000000..fcc6469065 --- /dev/null +++ b/buildtools/gen-vscode-conf.py @@ -0,0 +1,442 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2024 Intel Corporation +# + +"""Visual Studio Code configuration generator script.""" + +# This script is meant to be run by meson build system to generate build and +# launch commands for a specific build directory for Visual Studio Code IDE. +# +# Even though this script will generate settings/tasks/launch/code analysis +# configuration for VSCode, we can't actually just regenerate the files, +# because we want to support multiple build directories, as well as not +# destroy any configuration user has created between runs of this script. +# Therefore, we need some config file handling infrastructure. Luckily, VSCode +# configs are all JSON, so we can just use json module to handle them. Of +# course, we will lose any user comments in the files, but that's a small price +# to pay for this sort of automation. +# +# Since this script will be run by meson, we can forego any parsing or anything +# to do with the build system, and just rely on the fact that we get all of our +# configuration from command-line. + +import argparse +import ast +import json +import os +import shutil +from collections import OrderedDict +from sys import stderr, exit as _exit +from typing import List, Dict, Any + + +class ConfigCtx: + """POD class to keep data associated with config.""" + def __init__(self, build_dir: str, source_dir: str, launch: List[str]): + self.build_dir = build_dir + self.source_dir = source_dir + self.config_dir = os.path.join(source_dir, '.vscode') + # we don't have any mechanism to label things, so we're just going to + # use build dir basename as the label, and hope user doesn't create + # different build directories with the same name + self.label = os.path.basename(build_dir) + self.builddir_var = f'{self.label}.builddir' + self.launch = launch + + settings_fname = 'settings.json' + tasks_fname = 'tasks.json' + launch_fname = 'launch.json' + analysis_fname = 'c_cpp_properties.json' + settings_tmp_fname = f'.{settings_fname}.{self.label}.tmp' + tasks_tmp_fname = f'.{tasks_fname}.{self.label}.tmp' + launch_tmp_fname = f'.{launch_fname}.{self.label}.tmp' + analysis_tmp_fname = f'.{analysis_fname}.{self.label}.tmp' + + self.settings_path = os.path.join(self.config_dir, settings_fname) + self.tasks_path = os.path.join(self.config_dir, tasks_fname) + self.launch_path = os.path.join(self.config_dir, launch_fname) + self.analysis_path = os.path.join(self.config_dir, analysis_fname) + + # we want to write into temporary files at first + self.settings_tmp = os.path.join(self.config_dir, settings_tmp_fname) + self.tasks_tmp = os.path.join(self.config_dir, tasks_tmp_fname) + self.launch_tmp = os.path.join(self.config_dir, launch_tmp_fname) + self.analysis_tmp = os.path.join(self.config_dir, analysis_tmp_fname) + + # we don't want to mess with files if we didn't change anything + self.settings_changed = False + self.tasks_changed = False + self.launch_changed = False + self.analysis_changed = False + + +class Boolifier(ast.NodeTransformer): + """Replace JSON "true" with Python "True".""" + def visit_Name(self, node: ast.Name) -> ast.Constant: + """Visitor for Name nodes.""" + if node.id == 'true': + return ast.Constant(value=True) + elif node.id == 'false': + return ast.Constant(value=False) + return node + + +def _parse_eval(data: str) -> Dict[str, Any]: + """Use AST and literal_eval to parse JSON.""" + # JSON syntax is, for the most part, valid Python dictionary literal, aside + # from a small issue of capitalized booleans. so, we will try to parse + # JSON into an AST, replace "true"/"false" with "True"/"False", and then + # reparse the AST into a Python object + parsed = ast.parse(data) + unparsed = ast.unparse(Boolifier().visit(parsed)) + # we parsed AST, now walk it and replace ast.Name nodes with booleans for + # actual AST boolean literals of type ast.Boolean + ast_data = ast.literal_eval(unparsed) + return ast_data + + +def _load_json(file: str) -> Dict[str, Any]: + """Load JSON file.""" + with open(file, 'r', encoding='utf-8') as f: + data = f.read() + try: + return json.loads(data) + except json.JSONDecodeError: + # Python's JSON parser doesn't like trailing commas but VSCode's + # JSON parser does not consider them to be syntax errors, so they + # may be present in user's configuration files. we can try to parse + # JSON as Python dictionary literal, and see if it works. if it + # doesn't, there's probably a syntax error anyway, so re-raise. + try: + return _parse_eval(data) + except (ValueError, TypeError, SyntaxError, + MemoryError, RecursionError): + pass + raise + + +def _dump_json(file: str, obj: Dict[str, Any]) -> None: + """Write JSON file.""" + with open(file, 'w') as f: + json.dump(obj, f, indent=4) + + +def _overwrite(src: str, dst: str) -> None: + """Overwrite dst file with src file.""" + shutil.copyfile(src, dst) + # unlink src + os.unlink(src) + + +def _gen_sorter(order: List[str]) -> Any: + """Sort dictionary by order.""" + + # JSON doesn't have sort order, but we want to be user friendly and display + # certain properties above others as they're more important. This function + # will return a closure that can be used to re-sort a specific object using + # OrderedDict and an ordered list of properties. + def _sorter(obj: Dict[str, Any]) -> OrderedDict[str, Any]: + d = OrderedDict() + # step 1: go through all properties in order and re-add them + for prop in order: + if prop in obj: + d[prop] = obj[prop] + # step 2: get all properties of the object, remove those that we have + # already added, and sort them alphabetically + for prop in sorted(set(obj.keys()) - set(order)): + d[prop] = obj[prop] + # we're done: now all objects will have vaguely constant sort order + return d + return _sorter + + +def _add_to_obj_list(obj_list: List[Dict[str, Any]], + key: str, obj: Dict[str, Any]) -> bool: + """Add object to list if it doesn't already exist.""" + for o in obj_list: + if o[key] == obj[key]: + return False + obj_list.append(obj) + return True + + +def _process_settings(ctx: ConfigCtx) -> Dict[str, Any]: + """Update settings.json.""" + try: + settings_obj = _load_json(ctx.settings_path) + except FileNotFoundError: + settings_obj = {} + + # add build to settings if it doesn't exist + if ctx.builddir_var not in settings_obj: + ctx.settings_changed = True + settings_obj.setdefault(ctx.builddir_var, ctx.build_dir) + + # add path ignore setting if it's inside the source dir + cpath = os.path.commonpath([ctx.source_dir, ctx.build_dir]) + if cpath == ctx.source_dir: + # find path within source tree + relpath = os.path.relpath(ctx.build_dir, ctx.source_dir) + os.sep + + # note if we need to change anything + if 'files.exclude' not in settings_obj: + ctx.settings_changed = True + elif relpath not in settings_obj['files.exclude']: + ctx.settings_changed = True + + exclude = settings_obj.setdefault('files.exclude', {}) + exclude.setdefault(relpath, True) + settings_obj['files.exclude'] = exclude + + return settings_obj + + +def _process_tasks(ctx: ConfigCtx) -> Dict[str, Any]: + """Update tasks.json.""" + try: + outer_tasks_obj = _load_json(ctx.tasks_path) + except FileNotFoundError: + outer_tasks_obj = { + "version": "2.0.0", + "tasks": [], + "inputs": [] + } + inner_tasks_obj = outer_tasks_obj.setdefault('tasks', []) + inputs_obj = outer_tasks_obj.setdefault('inputs', []) + + # generate task object sorter + _sort_task = _gen_sorter(['label', 'detail', 'type', 'command', 'args', + 'options', 'problemMatcher', 'group']) + + # generate our would-be configuration + + # first, we need a build task + build_task = { + "label": f"[{ctx.label}] Compile", + "detail": f"Run `ninja` command for {ctx.label}", + "type": "shell", + "command": "meson compile", + "options": { + "cwd": f'${{config:{ctx.builddir_var}}}' + }, + "problemMatcher": { + "base": "$gcc", + "fileLocation": ["relative", f"${{config:{ctx.builddir_var}}}"] + }, + "group": "build" + } + # we also need a meson configure task with input + configure_task = { + "label": f"[{ctx.label}] Configure", + "detail": f"Run `meson configure` command for {ctx.label}", + "type": "shell", + "command": "meson configure ${input:mesonConfigureArg}", + "options": { + "cwd": f'${{config:{ctx.builddir_var}}}' + }, + "problemMatcher": [], + "group": "build" + } + # finally, add input object + input_arg = { + "id": "mesonConfigureArg", + "type": "promptString", + "description": "Enter meson configure arguments", + "default": "" + } + + # sort our tasks + build_task = _sort_task(build_task) + configure_task = _sort_task(configure_task) + + # add only if task doesn't already exist + ctx.tasks_changed |= _add_to_obj_list(inner_tasks_obj, 'label', + build_task) + ctx.tasks_changed |= _add_to_obj_list(inner_tasks_obj, 'label', + configure_task) + ctx.tasks_changed |= _add_to_obj_list(inputs_obj, 'id', input_arg) + + # replace nodes + outer_tasks_obj['tasks'] = inner_tasks_obj + outer_tasks_obj['inputs'] = inputs_obj + + # we're ready + return outer_tasks_obj + + +def _process_launch(ctx: ConfigCtx) -> Dict[str, Any]: + """Update launch.json.""" + try: + launch_obj = _load_json(ctx.launch_path) + except FileNotFoundError: + launch_obj = { + "version": "0.2.0", + "configurations": [] + } + configurations_obj = launch_obj.setdefault('configurations', []) + + # generate launch task sorter + _sort_launch = _gen_sorter(['name', 'type', 'request', 'program', 'cwd', + 'preLaunchTask', 'environment', 'args', + 'MIMode', 'miDebuggerPath', 'setupCommands']) + + gdb_path = shutil.which('gdb') + for target in ctx.launch: + # target will be a full path, we need to get relative to build path + exe_path = os.path.relpath(target, ctx.build_dir) + name = f"[{ctx.label}] Launch {exe_path}" + # generate config from template + launch_config = { + "name": name, + "type": "cppdbg", + "request": "launch", + "program": f"${{config:{ctx.builddir_var}}}/{exe_path}", + "args": [], + "cwd": "${workspaceFolder}", + "environment": [], + "MIMode": "gdb", + "miDebuggerPath": gdb_path, + "preLaunchTask": f"[{ctx.label}] Compile", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-gdb-set print pretty on", + "ignoreFailures": True + } + ], + } + # sort keys + launch_config = _sort_launch(launch_config) + # add to configurations + ctx.launch_changed |= _add_to_obj_list(configurations_obj, 'name', + launch_config) + + # replace the configuration object + launch_obj['configurations'] = configurations_obj + + # we're ready + return launch_obj + + +def _process_analysis(ctx: ConfigCtx) -> Dict[str, Any]: + """Update c_cpp_properties.json.""" + try: + analysis_obj = _load_json(ctx.analysis_path) + except FileNotFoundError: + analysis_obj = { + "version": 4, + "configurations": [] + } + configurations_obj = analysis_obj.setdefault('configurations', []) + + # generate analysis config sorter + _sort_analysis = _gen_sorter(['name', 'includePath', 'compilerPath', + 'cStandard', 'cppStandard', + 'intelliSenseMode', 'compileCommands']) + + # TODO: pick up more configuration from meson (e.g. OS, platform, compiler) + + config_obj = { + "name": "Linux", + "includePath": [ + f"${{config:{ctx.builddir_var}}}/", + # hardcode everything to x86/Linux for now + "${workspaceFolder}/lib/eal/x86", + "${workspaceFolder}/lib/eal/linux", + "${workspaceFolder}/**" + ], + "compilerPath": "/usr/bin/gcc", + "cStandard": "c99", + "cppStandard": "c++17", + "intelliSenseMode": "${default}", + "compileCommands": + f"${{config:{ctx.builddir_var}}}/compile_commands.json" + } + # sort configuration + config_obj = _sort_analysis(config_obj) + + # add it to config obj + ctx.analysis_changed |= _add_to_obj_list(configurations_obj, 'name', + config_obj) + + # we're done + analysis_obj['configurations'] = configurations_obj + + return analysis_obj + + +def _gen_config(ctx: ConfigCtx) -> None: + """Generate all config files.""" + # ensure config dir exists + os.makedirs(ctx.config_dir, exist_ok=True) + + # generate all JSON objects and write them to temp files + settings_obj = _process_settings(ctx) + _dump_json(ctx.settings_tmp, settings_obj) + + tasks_obj = _process_tasks(ctx) + _dump_json(ctx.tasks_tmp, tasks_obj) + + launch_obj = _process_launch(ctx) + _dump_json(ctx.launch_tmp, launch_obj) + + analysis_obj = _process_analysis(ctx) + _dump_json(ctx.analysis_tmp, analysis_obj) + + +def _main() -> int: + parser = argparse.ArgumentParser( + description='Generate VSCode configuration') + # where we are being called from + parser.add_argument('--build-dir', required=True, help='Build directory') + # where the sources are + parser.add_argument('--source-dir', required=True, help='Source directory') + # launch configuration item, can be multiple + parser.add_argument('--launch', action='append', + help='Launch path for executable') + parser.epilog = "This script is not meant to be run manually." + # parse arguments + args = parser.parse_args() + + # canonicalize all paths + build_dir = os.path.realpath(args.build_dir) + source_dir = os.path.realpath(args.source_dir) + if args.launch: + launch = [os.path.realpath(lp) for lp in args.launch] + else: + launch = [] + + ctx = ConfigCtx(build_dir, source_dir, launch) + + try: + _gen_config(ctx) + # we finished configuration successfully, update if needed + update_dict = { + ctx.settings_path: (ctx.settings_tmp, ctx.settings_changed), + ctx.tasks_path: (ctx.tasks_tmp, ctx.tasks_changed), + ctx.launch_path: (ctx.launch_tmp, ctx.launch_changed), + ctx.analysis_path: (ctx.analysis_tmp, ctx.analysis_changed) + } + for path, t in update_dict.items(): + tmp_path, changed = t + if changed: + _overwrite(tmp_path, path) + else: + os.unlink(tmp_path) + + return 0 + except json.JSONDecodeError as e: + # remove all temporary files we may have created + for tmp in [ctx.settings_tmp, ctx.tasks_tmp, ctx.launch_tmp, + ctx.analysis_tmp]: + if os.path.exists(tmp): + os.unlink(tmp) + # if we fail to load JSON, output error + print(f"Error: {e}", file=stderr) + + return 1 + + +if __name__ == '__main__': + _exit(_main()) diff --git a/buildtools/meson.build b/buildtools/meson.build index 3adf34e1a8..7d2dc501d6 100644 --- a/buildtools/meson.build +++ b/buildtools/meson.build @@ -24,6 +24,11 @@ get_numa_count_cmd = py3 + files('get-numa-count.py') get_test_suites_cmd = py3 + files('get-test-suites.py') has_hugepages_cmd = py3 + files('has-hugepages.py') cmdline_gen_cmd = py3 + files('dpdk-cmdline-gen.py') +# Visual Studio Code conf generator always requires build and source root +vscode_conf_gen_cmd = py3 + files('gen-vscode-conf.py') + [ + '--build-dir', dpdk_build_root, + '--source-dir', dpdk_source_root + ] # install any build tools that end-users might want also install_data([ diff --git a/examples/meson.build b/examples/meson.build index 8e8968a1fa..9e59223d3f 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -124,7 +124,18 @@ foreach example: examples if allow_experimental_apis cflags += '-DALLOW_EXPERIMENTAL_API' endif - executable('dpdk-' + name, sources, + + # add to Visual Studio Code launch configuration + exe_name = 'dpdk-' + name + launch_path = join_paths(meson.current_build_dir(), exe_name) + # we don't want to block the build if this command fails + result = run_command(vscode_conf_gen_cmd + ['--launch', launch_path], check: false) + if result.returncode() != 0 + warning('Failed to generate Visual Studio Code launch configuration for "' + name + '"') + message(result.stderr()) + endif + + executable(exe_name, sources, include_directories: includes, link_whole: link_whole_libs, link_args: ldflags, diff --git a/meson.build b/meson.build index 8b248d4505..df6115d098 100644 --- a/meson.build +++ b/meson.build @@ -117,6 +117,17 @@ if meson.is_subproject() subdir('buildtools/subproject') endif +# if no apps or examples were enabled, no Visual Studio Code config was +# generated, but we still need build, code analysis etc. configuration to be +# present, so generate it just in case (it will have no effect if the +# configuration was already generated by apps/examples). also, when running +# this command, we don't want to block the build if it fails. +result = run_command(vscode_conf_gen_cmd, check: false) +if result.returncode() != 0 + warning('Failed to generate Visual Studio Code configuration') + message(result.stderr()) +endif + # Final output, list all the parts to be built. # This does not affect any part of the build, for information only. output_message = '\n=================\nApplications Enabled\n=================\n' -- 2.43.5 ^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v1 0/1] Add Visual Studio Code configuration script 2024-07-26 12:42 [RFC PATCH v1 0/1] Add Visual Studio Code configuration script Anatoly Burakov ` (2 preceding siblings ...) 2024-07-31 13:33 ` [RFC PATCH v3 " Anatoly Burakov @ 2024-09-02 12:17 ` Anatoly Burakov 2024-09-02 12:17 ` [PATCH v1 1/1] buildtools: add VSCode configuration generator Anatoly Burakov 2024-11-26 13:30 ` [PATCH v2 0/1] Add Visual Studio Code configuration script Anatoly Burakov 3 siblings, 2 replies; 23+ messages in thread From: Anatoly Burakov @ 2024-09-02 12:17 UTC (permalink / raw) To: dev; +Cc: john.mcnamara, bruce.richardson Lots of developers (myself included) uses Visual Studio Code as their primary IDE for DPDK development. I have been successfully using various incarnations of this script internally to quickly set up my development trees whenever I need a new configuration, so this script is being shared in hopes that it will be useful both to new developers starting with DPDK, and to seasoned DPDK developers who are already or may want to start using Visual Studio Code. It makes starting working on DPDK in Visual Studio Code so much easier! ** NOTE: While code analysis configuration is now populated from Meson and should pick up platform- or OS-specifc configuration, I have no way to test the code analysis configuration on anything but Linux/x86. ** NOTE 2: this is not for *Visual Studio* the Windows IDE, this is for *Visual Studio Code* the cross-platform code editor. Specifically, main target audience for this script is people who either run DPDK directly on their Linux machine, or who use Remote SSH functionality to connect to a remote Linux machine and set up VSCode build there. While the script should in theory work with any OS/platform supported by DPDK, it was not tested under anything but Linux/x86. (if you're unaware of what is Remote SSH, I highly suggest checking it out [1]) Philosophy behind this script is as follows: - Any build directory created will automatically add itself to VSCode configuration - Launch configuration is created using `which gdb`, so by default non-root users will have to do additional system configuration for things to work. This is now documented in a new section called "Integration with IDE's". - All of the interactive stuff has now been taken out and is planned to be included in a separate set of scripts, so this script now concerns itself only with adding build/launch targets to user's configuration and not much else Please feel free to make any suggestions! [1] https://code.visualstudio.com/docs/remote/ssh Anatoly Burakov (1): buildtools: add VSCode configuration generator app/meson.build | 14 +- buildtools/gen-vscode-conf.py | 570 ++++++++++++++++++++ buildtools/meson.build | 5 + devtools/test-meson-builds.sh | 3 + doc/guides/contributing/ide_integration.rst | 85 +++ doc/guides/contributing/index.rst | 1 + doc/guides/rel_notes/release_24_11.rst | 5 + examples/meson.build | 15 +- meson.build | 14 + 9 files changed, 710 insertions(+), 2 deletions(-) create mode 100755 buildtools/gen-vscode-conf.py create mode 100644 doc/guides/contributing/ide_integration.rst -- 2.43.5 ^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v1 1/1] buildtools: add VSCode configuration generator 2024-09-02 12:17 ` [PATCH v1 0/1] Add Visual Studio Code configuration script Anatoly Burakov @ 2024-09-02 12:17 ` Anatoly Burakov 2024-11-26 13:30 ` [PATCH v2 0/1] Add Visual Studio Code configuration script Anatoly Burakov 1 sibling, 0 replies; 23+ messages in thread From: Anatoly Burakov @ 2024-09-02 12:17 UTC (permalink / raw) To: dev, Bruce Richardson; +Cc: john.mcnamara A lot of developers use Visual Studio Code as their primary IDE. This script will be called from within meson build process, and will generate a configuration file for VSCode that sets up basic build tasks, launch tasks, as well as C/C++ code analysis settings that will take into account compile_commands.json that is automatically generated by meson. Files generated by script: - .vscode/settings.json: stores variables needed by other files - .vscode/tasks.json: defines configure/build tasks - .vscode/launch.json: defines launch tasks - .vscode/c_cpp_properties.json: defines code analysis settings Multiple, as well as out-of-source-tree, build directories are supported, and the script will generate separate configuration items for each build directory created by user, tagging them for convenience. In addition to all of the above, some default quality-of-life improvements are also included, such as build-on-save (needs an extension), as well as disablement of some default VSCode settings that are not needed for DPDK. Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com> --- Notes: RFCv3 -> v1: - Format code with Ruff - Use T. as typing alias - Fixed wrong comments in places - Added more typing information to keep PyLance happy - Refactor for resilience to various error conditions and proper cleanup - Added preprocessor to workaround issues with Python's JSON parser - Added a way to disable config generation from shell variable - Default to build-on-save if user has appropriate extension installed - Disable some common Javascript things that slow down task list - Configure code analysis from exec-env and arch-subdir variables - Added documentation RFCv2 -> RFCv3: - Following feedback from Bruce, reworked to be minimal script run from meson - Moved to buildtools - Support for multiple build directories is now the default - All targets are automatically added to all configuration files RFCv1 -> RFCv2: - No longer disable apps and drivers if nothing was specified via command line or TUI, and warn user about things being built by default - Generate app launch configuration by default for when no apps are selected - Added paramters: - --force to avoid overwriting existing config - --common-conf to specify global meson flags applicable to all configs - --gdbsudo/--no-gdbsudo to specify gdbsudo behavior - Autodetect gdbsudo/gdb from UID - Updated comments, error messages, fixed issues with user interaction - Improved handling of wildcards and driver dependencies - Fixed a few bugs in dependency detection due to incorrect parsing - [Stephen] flake8 is happy app/meson.build | 14 +- buildtools/gen-vscode-conf.py | 570 ++++++++++++++++++++ buildtools/meson.build | 5 + devtools/test-meson-builds.sh | 3 + doc/guides/contributing/ide_integration.rst | 85 +++ doc/guides/contributing/index.rst | 1 + doc/guides/rel_notes/release_24_11.rst | 5 + examples/meson.build | 15 +- meson.build | 14 + 9 files changed, 710 insertions(+), 2 deletions(-) create mode 100755 buildtools/gen-vscode-conf.py create mode 100644 doc/guides/contributing/ide_integration.rst diff --git a/app/meson.build b/app/meson.build index 5b2c80c7a1..6a3fbea65b 100644 --- a/app/meson.build +++ b/app/meson.build @@ -114,7 +114,19 @@ foreach app:apps link_libs = dpdk_static_libraries + dpdk_drivers endif - exec = executable('dpdk-' + name, + # add to Visual Studio Code launch configuration + exe_name = 'dpdk-' + name + launch_path = join_paths(meson.current_build_dir(), exe_name) + # we also need exec env/arch, but they were not available at the time buildtools command was generated + cfg_args = ['--launch', launch_path, '--exec-env', exec_env, '--arch', arch_subdir] + # we don't want to block the build if this command fails + result = run_command(vscode_conf_gen_cmd + cfg_args, check: false) + if result.returncode() != 0 + warning('Failed to generate Visual Studio Code launch configuration for "' + name + '"') + message(result.stderr()) + endif + + exec = executable(exe_name, sources, c_args: cflags, link_args: ldflags, diff --git a/buildtools/gen-vscode-conf.py b/buildtools/gen-vscode-conf.py new file mode 100755 index 0000000000..3e88c9e4ff --- /dev/null +++ b/buildtools/gen-vscode-conf.py @@ -0,0 +1,570 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2024 Intel Corporation +# + +"""Visual Studio Code configuration generator script.""" + +# This script is meant to be run by meson build system to generate build and launch commands for a +# specific build directory for Visual Studio Code IDE. +# +# Even though this script will generate settings/tasks/launch/code analysis configuration for +# VSCode, we can't actually just regenerate the files, because we want to support multiple build +# directories, as well as not destroy any configuration user has created between runs of this +# script. Therefore, we need some config file handling infrastructure. Luckily, VSCode configs are +# all JSON, so we can just use json module to handle them. Of course, we will lose any user +# comments in the files, but that's a small price to pay for this sort of automation. +# +# Since this script will be run by meson, we can forego any parsing or anything to do with the +# build system, and just rely on the fact that we get all of our configuration from command-line. + +import argparse +import json +import os +import shutil +from collections import OrderedDict +from sys import stderr, exit as _exit +import typing as T + +# if this variable is defined, we will not generate any configuration files +ENV_DISABLE = "DPDK_DISABLE_VSCODE_CONFIG" + + +def _preprocess_json(data: str) -> str: + """Preprocess JSON to remove trailing commas, whitespace, and comments.""" + preprocessed_data: T.List[str] = [] + # simple state machine + in_comment = False + in_string = False + escape = False + comma = False + maybe_comment = False + for c in data: + _fwdslash = c == "/" + _newline = c == "\n" + _comma = c == "," + _obj_end = c in ["}", "]"] + _space = c.isspace() + _backslash = c == "\\" + _quote = c == '"' + + # are we looking to start a comment? + if maybe_comment: + maybe_comment = False + if _fwdslash: + in_comment = True + continue + # slash is illegal JSON but this is not our job + preprocessed_data.append("/") + # c will receive further processing + # are we inside a comment? + if in_comment: + if _newline: + in_comment = False + # eat everything + continue + # do we have a trailing comma? + if comma: + # there may be whitespace after the comma + if _space: + continue + comma = False + if _obj_end: + # throw away trailing comma + preprocessed_data.append(c) + continue + # comma was needed + preprocessed_data.append(",") + # c needs further processing + # are we inside a string? + if in_string: + # are we in an escape? + if escape: + escape = False + # are we trying to escape? + elif _backslash: + escape = True + # are we ending the string? + elif _quote: + in_string = False + # we're inside a string + preprocessed_data.append(c) + continue + # are we looking to start a string? + if _quote: + in_string = True + preprocessed_data.append(c) + continue + # are we looking to start a comment? + elif _fwdslash: + maybe_comment = True + continue + # are we looking at a comma? + elif _comma: + comma = True + continue + # are we looking at whitespace? + elif _space: + continue + # this is a regular character, just add it + preprocessed_data.append(c) + + return "".join(preprocessed_data) + + +def _load_json(file: str) -> T.Dict[str, T.Any]: + """Load JSON file.""" + with open(file, "r", encoding="utf-8") as f: + # Python's JSON parser doesn't like trailing commas, but VSCode's JSON parser does not + # consider them to be syntax errors, so they may be present in user's configuration files. + # remove them from the file before processing. + data = _preprocess_json(f.read()) + try: + return json.loads(data) + except json.JSONDecodeError as e: + print(f"Error parsing {os.path.basename(file)}: {e}", file=stderr) + raise + + +def _save_json(file: str, obj: T.Dict[str, T.Any]) -> None: + """Write JSON file.""" + with open(file, "w", encoding="utf-8") as f: + json.dump(obj, f, indent="\t") + + +class ConfigCtx: + """Data associated with config processing.""" + + def __init__( + self, + build_dir: str, + source_dir: str, + launch: T.List[str], + exec_env: str, + arch: str, + ): + self.build_dir = build_dir + self.source_dir = source_dir + self.config_dir = os.path.join(source_dir, ".vscode") + self.exec_env = exec_env + self.arch = arch + # we don't have any mechanism to label things, so we're just going to + # use build dir basename as the label, and hope user doesn't create + # different build directories with the same name + self.label = os.path.basename(build_dir) + self.builddir_var = f"{self.label}-builddir" + # default to gdb + self.dbg_path_var = f"{self.label}-dbg-path" + self.launch_dbg_path = shutil.which("gdb") + self.dbg_mode_var = f"{self.label}-dbg-mode" + self.dbg_mode = "gdb" + self.launch = launch + self.compile_task = f"[{self.label}] Compile" + + # filenames for configs + self.settings_fname = "settings.json" + self.tasks_fname = "tasks.json" + self.launch_fname = "launch.json" + self.analysis_fname = "c_cpp_properties.json" + + # temporary filenames to avoid touching user's configuration until last moment + self._tmp_fnames = { + self.settings_fname: f".{self.settings_fname}.{self.label}.tmp", + self.tasks_fname: f".{self.tasks_fname}.{self.label}.tmp", + self.launch_fname: f".{self.launch_fname}.{self.label}.tmp", + self.analysis_fname: f".{self.analysis_fname}.{self.label}.tmp", + } + # when there is no user configuration, use these templates + self._templates: T.Dict[str, T.Dict[str, T.Any]] = { + self.settings_fname: {}, + self.tasks_fname: {"version": "2.0.0", "tasks": [], "inputs": []}, + self.launch_fname: {"version": "0.2.0", "configurations": []}, + self.analysis_fname: {"version": 4, "configurations": []}, + } + + def _get_fname(self, fname: str) -> str: + """Get filename for configuration.""" + if fname not in self._tmp_fnames: + raise ValueError(f"Unknown configuration file {fname}") + return os.path.join(self.config_dir, self._tmp_fnames[fname]) + + def load(self, fname: str) -> T.Dict[str, T.Any]: + """Load or generate JSON data from template.""" + path = self._get_fname(fname) + try: + return _load_json(path) + except FileNotFoundError: + return self._templates[fname] + + def save(self, fname: str, obj: T.Dict[str, T.Any]) -> None: + """Save JSON data to temporary file.""" + path = self._get_fname(fname) + _save_json(path, obj) + + def commit(self): + """Commit previously saved settings to configuration.""" + for dst, tmp in self._tmp_fnames.items(): + fp_tmp = os.path.join(self.config_dir, tmp) + fp_dst = os.path.join(self.config_dir, dst) + if os.path.exists(fp_tmp): + shutil.copyfile(fp_tmp, fp_dst) + + def cleanup(self): + """Cleanup any temporary files.""" + for tmp in self._tmp_fnames.values(): + fp_tmp = os.path.join(self.config_dir, tmp) + if os.path.exists(fp_tmp): + os.unlink(fp_tmp) + + +def _gen_sorter(order: T.List[str]) -> T.Any: + """Sort dictionary by order.""" + + # JSON doesn't have sort order, but we want to be user friendly and display certain properties + # above others as they're more important. This function will return a closure that can be used + # to re-sort a specific object using OrderedDict and an ordered list of properties. + def _sorter(obj: T.Dict[str, T.Any]) -> OrderedDict[str, T.Any]: + d: OrderedDict[str, T.Any] = OrderedDict() + # step 1: go through all properties in order and re-add them + for prop in order: + if prop in obj: + d[prop] = obj[prop] + # step 2: get all properties of the object, remove those that we have already added, and + # sort them alphabetically + for prop in sorted(set(obj.keys()) - set(order)): + d[prop] = obj[prop] + # we're done: now all objects will have vaguely constant sort order + return d + + return _sorter + + +def _add_obj_to_list( + obj_list: T.List[T.Dict[str, T.Any]], key: str, obj: T.Dict[str, T.Any] +) -> bool: + """Add object to list if it doesn't already exist.""" + for o in obj_list: + if o[key] == obj[key]: + return False + obj_list.append(obj) + return True + + +def _add_var_to_obj(obj: T.Dict[str, T.Any], var: str, value: T.Any) -> bool: + """Add variable to object if it doesn't exist.""" + if var in obj: + return False + obj[var] = value + return True + + +def _update_settings(ctx: ConfigCtx) -> T.Optional[T.Dict[str, T.Any]]: + """Update settings.json.""" + settings_obj = ctx.load(ctx.settings_fname) + dirty = False + + ttos_tasks = "triggerTaskOnSave.tasks" + ttos_on = "triggerTaskOnSave.on" + default_vars: T.Dict[str, T.Any] = { + # store build dir + ctx.builddir_var: ctx.build_dir, + # store debug configuration + ctx.dbg_path_var: ctx.launch_dbg_path, + ctx.dbg_mode_var: ctx.dbg_mode, + # store dbg mode and path + # trigger build on save + ttos_tasks: {}, + ttos_on: True, + # improve responsiveness by disabling auto-detection of tasks + "npm.autoDetect": "off", + "gulp.autoDetect": "off", + "jake.autoDetect": "off", + "grunt.autoDetect": "off", + "typescript.tsc.autoDetect": "off", + "task.autoDetect": "off", + } + + for var, value in default_vars.items(): + dirty |= _add_var_to_obj(settings_obj, var, value) + + # add path ignore setting if it's inside the source dir + cpath = os.path.commonpath([ctx.source_dir, ctx.build_dir]) + if cpath == ctx.source_dir: + # find path within source tree + relpath = os.path.relpath(ctx.build_dir, ctx.source_dir) + os.sep + + # note if we need to change anything + if "files.exclude" not in settings_obj: + dirty = True + elif relpath not in settings_obj["files.exclude"]: + dirty = True + + exclude = settings_obj.setdefault("files.exclude", {}) + exclude.setdefault(relpath, True) + settings_obj["files.exclude"] = exclude + + # if user has installed "Trigger Task On Save" extension (extension id: + # Gruntfuggly.triggertaskonsave), this will enable build-on-save by default + if ctx.compile_task not in settings_obj[ttos_tasks]: + dirty = True + # trigger build on save for all files + settings_obj[ttos_tasks][ctx.compile_task] = ["**/*"] + + return settings_obj if dirty else None + + +def _update_tasks(ctx: ConfigCtx) -> T.Optional[T.Dict[str, T.Any]]: + """Update tasks.json.""" + outer_tasks_obj = ctx.load(ctx.tasks_fname) + inner_tasks_obj = outer_tasks_obj.setdefault("tasks", []) + inputs_obj = outer_tasks_obj.setdefault("inputs", []) + dirty = False + + # generate task object sorter + _sort_task = _gen_sorter( + [ + "label", + "detail", + "type", + "command", + "args", + "options", + "problemMatcher", + "group", + ] + ) + + # generate our would-be configuration + + # first, we need a build task + build_task: T.Dict[str, T.Any] = { + "label": ctx.compile_task, + "detail": f"Run `meson compile` command for {ctx.label}", + "type": "shell", + "command": "meson compile", + "options": {"cwd": f"${{config:{ctx.builddir_var}}}"}, + "problemMatcher": { + "base": "$gcc", + "fileLocation": ["relative", f"${{config:{ctx.builddir_var}}}"], + }, + "group": "build", + } + # we also need a meson configure task with input + configure_task: T.Dict[str, T.Any] = { + "label": f"[{ctx.label}] Configure", + "detail": f"Run `meson configure` command for {ctx.label}", + "type": "shell", + "command": "meson configure ${input:mesonConfigureArg}", + "options": {"cwd": f"${{config:{ctx.builddir_var}}}"}, + "problemMatcher": [], + "group": "build", + } + # finally, add input object + input_arg: T.Dict[str, T.Any] = { + "id": "mesonConfigureArg", + "type": "promptString", + "description": "Enter meson configure arguments", + "default": "", + } + + # sort our tasks + build_task = _sort_task(build_task) + configure_task = _sort_task(configure_task) + + # add only if task doesn't already exist + dirty |= _add_obj_to_list(inner_tasks_obj, "label", build_task) + dirty |= _add_obj_to_list(inner_tasks_obj, "label", configure_task) + dirty |= _add_obj_to_list(inputs_obj, "id", input_arg) + + # replace nodes + outer_tasks_obj["tasks"] = inner_tasks_obj + outer_tasks_obj["inputs"] = inputs_obj + + # we're ready + return outer_tasks_obj if dirty else None + + +def _update_launch(ctx: ConfigCtx) -> T.Optional[T.Dict[str, T.Any]]: + """Update launch.json.""" + launch_obj = ctx.load(ctx.launch_fname) + configurations_obj = launch_obj.setdefault("configurations", []) + dirty = False + + # generate launch task sorter + _sort_launch = _gen_sorter( + [ + "name", + "type", + "request", + "program", + "cwd", + "preLaunchTask", + "environment", + "args", + "MIMode", + "miDebuggerPath", + "setupCommands", + ] + ) + + for target in ctx.launch: + # target will be a full path, we need to get relative to build path + exe_path = os.path.relpath(target, ctx.build_dir) + name = f"[{ctx.label}] Launch {exe_path}" + # generate config from template + launch_config: T.Dict[str, T.Any] = { + "name": name, + "type": "cppdbg", + "request": "launch", + "program": f"${{config:{ctx.builddir_var}}}/{exe_path}", + "args": [], + "cwd": "${workspaceFolder}", + "environment": [], + "MIMode": f"${{config:{ctx.dbg_mode_var}", + "miDebuggerPath": f"${{config:{ctx.dbg_path_var}", + "preLaunchTask": ctx.compile_task, + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-gdb-set print pretty on", + "ignoreFailures": True, + } + ], + } + # sort keys + launch_config = _sort_launch(launch_config) + # add to configurations + dirty |= _add_obj_to_list(configurations_obj, "name", launch_config) + + # replace the configuration object + launch_obj["configurations"] = configurations_obj + + # we're ready + return launch_obj if dirty else None + + +def _update_analysis(ctx: ConfigCtx) -> T.Optional[T.Dict[str, T.Any]]: + """Update c_cpp_properties.json.""" + analysis_obj = ctx.load(ctx.analysis_fname) + configurations_obj = analysis_obj.setdefault("configurations", []) + dirty = False + + # generate analysis config sorter + _sort_analysis = _gen_sorter( + [ + "name", + "includePath", + "compilerPath", + "cStandard", + "cppStandard", + "intelliSenseMode", + "compileCommands", + ] + ) + + config_obj: T.Dict[str, T.Any] = { + "name": ctx.exec_env.capitalize(), + "includePath": [ + f"${{config:{ctx.builddir_var}}}/", + # hardcode everything to x86/Linux for now + f"${{workspaceFolder}}/lib/eal/{ctx.arch}/include", + f"${{workspaceFolder}}/lib/eal/{ctx.exec_env}/include", + "${workspaceFolder}/**", + ], + "compilerPath": "/usr/bin/gcc", + "cStandard": "c99", + "cppStandard": "c++17", + "intelliSenseMode": "${default}", + "compileCommands": f"${{config:{ctx.builddir_var}}}/compile_commands.json", + } + # sort configuration + config_obj = _sort_analysis(config_obj) + + # add it to config obj + dirty |= _add_obj_to_list(configurations_obj, "name", config_obj) + + # we're done + analysis_obj["configurations"] = configurations_obj + + return analysis_obj if dirty else None + + +def _gen_config(ctx: ConfigCtx) -> None: + """Generate all config files.""" + + # generate all JSON objects and save them if we changed anything about them + settings_obj = _update_settings(ctx) + tasks_obj = _update_tasks(ctx) + launch_obj = _update_launch(ctx) + analysis_obj = _update_analysis(ctx) + + if settings_obj is not None: + ctx.save(ctx.settings_fname, settings_obj) + if tasks_obj is not None: + ctx.save(ctx.tasks_fname, tasks_obj) + if launch_obj is not None: + ctx.save(ctx.launch_fname, launch_obj) + if analysis_obj is not None: + ctx.save(ctx.analysis_fname, analysis_obj) + + # the above saves only saved to temporary files, now overwrite real files + ctx.commit() + + +def _main() -> int: + if os.environ.get(ENV_DISABLE, "") == "1": + print( + "Visual Studio Code configuration generation " + f"disabled by environment variable {ENV_DISABLE}=1" + ) + return 0 + parser = argparse.ArgumentParser(description="Generate VSCode configuration") + # where we are being called from + parser.add_argument("--build-dir", required=True, help="Build directory") + # where the sources are + parser.add_argument("--source-dir", required=True, help="Source directory") + # exec-env - Windows, Linux etc. + parser.add_argument("--exec-env", required=True, help="Execution environment") + # arch - x86, arm etc. + parser.add_argument("--arch", required=True, help="Architecture") + # launch configuration item, can be multiple + parser.add_argument("--launch", action="append", help="Launch path for executable") + parser.epilog = "This script is not meant to be run manually." + # parse arguments + args = parser.parse_args() + + # canonicalize all paths + build_dir = os.path.realpath(args.build_dir) + source_dir = os.path.realpath(args.source_dir) + if args.launch: + launch = [os.path.realpath(lp) for lp in args.launch] + else: + launch = [] + exec_env = args.exec_env + arch = args.arch + + ctx = ConfigCtx(build_dir, source_dir, launch, exec_env, arch) + + try: + # ensure config dir exists + os.makedirs(ctx.config_dir, exist_ok=True) + + _gen_config(ctx) + + ret = 0 + except json.JSONDecodeError as e: + # if we fail to load JSON, output error + print(f"Error: {e}", file=stderr) + ret = 1 + except OSError as e: + # if we fail to write to disk, output error + print(f"Error: {e}", file=stderr) + ret = 1 + + # remove any temporary files + ctx.cleanup() + return ret + + +if __name__ == "__main__": + _exit(_main()) diff --git a/buildtools/meson.build b/buildtools/meson.build index 3adf34e1a8..f529189dbc 100644 --- a/buildtools/meson.build +++ b/buildtools/meson.build @@ -24,6 +24,11 @@ get_numa_count_cmd = py3 + files('get-numa-count.py') get_test_suites_cmd = py3 + files('get-test-suites.py') has_hugepages_cmd = py3 + files('has-hugepages.py') cmdline_gen_cmd = py3 + files('dpdk-cmdline-gen.py') +# Visual Studio Code conf generator always requires build/source roots +vscode_conf_gen_cmd = py3 + files('gen-vscode-conf.py') + [ + '--build-dir', dpdk_build_root, + '--source-dir', dpdk_source_root + ] # install any build tools that end-users might want also install_data([ diff --git a/devtools/test-meson-builds.sh b/devtools/test-meson-builds.sh index d71bb1ded0..4b80d4dea4 100755 --- a/devtools/test-meson-builds.sh +++ b/devtools/test-meson-builds.sh @@ -53,6 +53,8 @@ default_cppflags=$CPPFLAGS default_cflags=$CFLAGS default_ldflags=$LDFLAGS default_meson_options=$DPDK_MESON_OPTIONS +# disable VSCode config generation +export DPDK_DISABLE_VSCODE_CONFIG=1 opt_verbose= opt_vverbose= @@ -88,6 +90,7 @@ load_env () # <target compiler> export CFLAGS=$default_cflags export LDFLAGS=$default_ldflags export DPDK_MESON_OPTIONS=$default_meson_options + # set target hint for use in the loaded config file if [ -n "$target_override" ] ; then DPDK_TARGET=$target_override diff --git a/doc/guides/contributing/ide_integration.rst b/doc/guides/contributing/ide_integration.rst new file mode 100644 index 0000000000..9ad0c78004 --- /dev/null +++ b/doc/guides/contributing/ide_integration.rst @@ -0,0 +1,85 @@ +.. SPDX-License-Identifier: BSD-3-Clause + Copyright 2024 The DPDK contributors + +Integrating DPDK with IDEs +========================== + +DPDK does not mandate nor recommend a specific IDE for development. However, +some developers may prefer to use an IDE for their development work. This guide +provides information on how to integrate DPDK with some popular IDEs. + +Visual Studio Code +------------------ + +`Visual Studio Code <https://code.visualstudio.com/>` is a popular open-source +code editor with IDE features such as code completion, debugging, Git +integration, and more. It is available on most platforms. + +Configuration +~~~~~~~~~~~~~ + +When configuring a new Meson build directory for DPDK, configuration for Visual +Studio Code will be generated automatically. It will include both a compilation +task, as well as debugging targets for any applications or examples enabled in +meson at configuration step. Generated configuration will be available under +`.vscode` directory in DPDK source tree. The configuration will be updated each +time the build directory is reconfigured with Meson. + +Further information on configuring, building and installing DPDK is described in +:doc:`Linux Getting Started Guide <../linux_gsg/build_dpdk>`. + +.. note:: + + The configuration is generated based on the enabled applications and + examples at the time of configuration. When new applications or examples are + added to the configuration using the `meson configure` command (or through + running `Configure` task), new configuration will be added, but existing + configuration will never be amended or deleted, even if the application was + removed from build. + +Each generated file will refer to a few common variables defined under +`settings.json`. This is to allow easy reconfiguration of all generated launch +targets while also still allowing user to customize the configuration. Variables +contained within `settings.json` are as follows: + +- `<build-dir-name>-builddir`: Path to the build directory (can be in-tree or + out-of-tree) +- `<build-dir-name>-dbg-path`: Variable for `miDebuggerPath` in launch tasks +- `<build-dir-name>-dbg-mode`: Variable for `MIMode` in launch tasks + +It is not recommended to change these variables unless there is a specific need. + +.. note:: + + Due to the way the configuration generation is implemented, each time the + configuration is updated, any user comments will be lost. + +Running as unprivileged user +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If not running as privileged user, then by default the generated configuration +will not be able to run DPDK applications that require `root` privileges. To +address this, either the system will have to be configured to allow running DPDK +as non-privileged user, or the launch configuration has to be amended to run the +debugger (usually `GDB`) as root. + +Further information on configuring the system to allow running DPDK as +non-privileged user can be found in the :ref:`common Linux guide +<Running_Without_Root_Privileges>`. + +If the user prefers to run applications as `root` while still working as regular +user instead, the following steps must be taken: + +- Allow running GDB with password-less `sudo` (please consult relevant system + documentation on how to achieve this) +- Set up a local alias for running GDB with `sudo` (e.g. `sudo gdb $@`) +- Amend the `settings.json` file to set `<build-dir>-dbg-path` variable to this + new alias + +Once this is done, any existing or new launch targets will use the new debugger +alias to run DPDK applications. + +.. note:: + + The above steps are not recommended for production systems, as they may + introduce security vulnerabilities. diff --git a/doc/guides/contributing/index.rst b/doc/guides/contributing/index.rst index dcb9b1fbf0..a1f27d7f64 100644 --- a/doc/guides/contributing/index.rst +++ b/doc/guides/contributing/index.rst @@ -19,3 +19,4 @@ Contributor's Guidelines vulnerability stable cheatsheet + ide_integration diff --git a/doc/guides/rel_notes/release_24_11.rst b/doc/guides/rel_notes/release_24_11.rst index 0ff70d9057..bd21e600d0 100644 --- a/doc/guides/rel_notes/release_24_11.rst +++ b/doc/guides/rel_notes/release_24_11.rst @@ -55,6 +55,11 @@ New Features Also, make sure to start the actual text at the margin. ======================================================= +* **Generate Visual Studio Code configuration on build** + + The Meson build system now generates Visual Studio Code configuration + files for configuration, compilation, and debugging tasks. + Removed Items ------------- diff --git a/examples/meson.build b/examples/meson.build index 8e8968a1fa..45327b6f6b 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -124,7 +124,20 @@ foreach example: examples if allow_experimental_apis cflags += '-DALLOW_EXPERIMENTAL_API' endif - executable('dpdk-' + name, sources, + + # add to Visual Studio Code launch configuration + exe_name = 'dpdk-' + name + launch_path = join_paths(meson.current_build_dir(), exe_name) + # we also need exec env/arch, but they were not available at the time buildtools command was generated + cfg_args = ['--launch', launch_path, '--exec-env', exec_env, '--arch', arch_subdir] + # we don't want to block the build if this command fails + result = run_command(vscode_conf_gen_cmd + ['--launch', launch_path], check: false) + if result.returncode() != 0 + warning('Failed to generate Visual Studio Code launch configuration for "' + name + '"') + message(result.stderr()) + endif + + executable(exe_name, sources, include_directories: includes, link_whole: link_whole_libs, link_args: ldflags, diff --git a/meson.build b/meson.build index 8b248d4505..17ba1192c2 100644 --- a/meson.build +++ b/meson.build @@ -117,6 +117,20 @@ if meson.is_subproject() subdir('buildtools/subproject') endif +# if no apps or examples were enabled, no Visual Studio Code config was +# generated, but we still need build, code analysis etc. configuration to be +# present, so generate it just in case (it will have no effect if the +# configuration was already generated by apps/examples). also, when running +# this command, we don't want to block the build if it fails. + +# we need exec env/arch, but they were not available at the time buildtools command was generated +cfg_args = ['--exec-env', exec_env, '--arch', arch_subdir] +result = run_command(vscode_conf_gen_cmd + cfg_args, check: false) +if result.returncode() != 0 + warning('Failed to generate Visual Studio Code configuration') + message(result.stderr()) +endif + # Final output, list all the parts to be built. # This does not affect any part of the build, for information only. output_message = '\n=================\nApplications Enabled\n=================\n' -- 2.43.5 ^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v2 0/1] Add Visual Studio Code configuration script 2024-09-02 12:17 ` [PATCH v1 0/1] Add Visual Studio Code configuration script Anatoly Burakov 2024-09-02 12:17 ` [PATCH v1 1/1] buildtools: add VSCode configuration generator Anatoly Burakov @ 2024-11-26 13:30 ` Anatoly Burakov 2024-11-26 13:30 ` [PATCH v2 1/1] buildtools: add VSCode configuration generator Anatoly Burakov 1 sibling, 1 reply; 23+ messages in thread From: Anatoly Burakov @ 2024-11-26 13:30 UTC (permalink / raw) To: dev; +Cc: john.mcnamara, bruce.richardson Lots of developers (myself included) uses Visual Studio Code as their primary IDE for DPDK development. I have been successfully using various incarnations of this script internally to quickly set up my development trees whenever I need a new configuration, so this script is being shared in hopes that it will be useful both to new developers starting with DPDK, and to seasoned DPDK developers who are already or may want to start using Visual Studio Code. ** NOTE: While code analysis configuration is now populated from Meson and should pick up platform- or OS-specifc configuration, I have no way to test the code analysis configuration on anything but Linux/x86. ** NOTE 2: this is not for *Visual Studio* the Windows IDE, this is for *Visual Studio Code* the cross-platform code editor. Specifically, main target audience for this script is people who either run DPDK directly on their Linux machine, or who use Remote SSH functionality to connect to a remote Linux machine and set up VSCode build there. While the script should in theory work with any OS/platform supported by DPDK, it was not tested under anything but Linux/x86. (if you're unaware of what is Remote SSH, I highly suggest checking it out [1]) At a glance, this script will do the following: - At meson setup time, configuration entries will be created under <source dir>/.vscode - Any new compiled executable will be added to configuration - Nothing will ever be deleted or modified, only added if it doesn't exist - Launch configuration is created using `which gdb`, so by default non-root users will have to do additional system configuration for things to work. This is now documented in a new section called "Integration with IDE's". - For those concerned about "bloat", the configuration is just a few text files - .vscode directory starts with a dot, so it'll be excluded from any Git updates [1] https://code.visualstudio.com/docs/remote/ssh Anatoly Burakov (1): buildtools: add VSCode configuration generator app/meson.build | 14 +- buildtools/gen-vscode-conf.py | 570 ++++++++++++++++++++ buildtools/meson.build | 5 + devtools/test-meson-builds.sh | 3 + doc/guides/contributing/ide_integration.rst | 89 +++ doc/guides/contributing/index.rst | 1 + doc/guides/rel_notes/release_24_11.rst | 5 + examples/meson.build | 15 +- meson.build | 14 + 9 files changed, 714 insertions(+), 2 deletions(-) create mode 100755 buildtools/gen-vscode-conf.py create mode 100644 doc/guides/contributing/ide_integration.rst -- 2.43.5 ^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v2 1/1] buildtools: add VSCode configuration generator 2024-11-26 13:30 ` [PATCH v2 0/1] Add Visual Studio Code configuration script Anatoly Burakov @ 2024-11-26 13:30 ` Anatoly Burakov 0 siblings, 0 replies; 23+ messages in thread From: Anatoly Burakov @ 2024-11-26 13:30 UTC (permalink / raw) To: dev, Bruce Richardson; +Cc: john.mcnamara A lot of developers use Visual Studio Code as their primary IDE. This script will be called from within meson build process, and will generate a configuration file for VSCode that sets up basic build tasks, launch tasks, as well as C/C++ code analysis settings that will take into account compile_commands.json that is automatically generated by meson. Files generated by script: - .vscode/settings.json: stores variables needed by other files - .vscode/tasks.json: defines configure/build tasks - .vscode/launch.json: defines launch tasks - .vscode/c_cpp_properties.json: defines code analysis settings Multiple, as well as out-of-source-tree, build directories are supported, and the script will generate separate configuration items for each build directory created by user, tagging them for convenience. In addition to all of the above, some default quality-of-life improvements are also included, such as build-on-save (needs an extension), as well as disablement of some default VSCode settings that are not needed for DPDK. Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com> --- Notes: v1 -> v2: - Rebase on top of latest main RFCv3 -> v1: - Format code with Ruff - Use T. as typing alias - Fixed wrong comments in places - Added more typing information to keep PyLance happy - Refactor for resilience to various error conditions and proper cleanup - Added preprocessor to workaround issues with Python's JSON parser - Added a way to disable config generation from shell variable - Default to build-on-save if user has appropriate extension installed - Disable some common Javascript things that slow down task list - Configure code analysis from exec-env and arch-subdir variables - Added documentation RFCv2 -> RFCv3: - Following feedback from Bruce, reworked to be minimal script run from meson - Moved to buildtools - Support for multiple build directories is now the default - All targets are automatically added to all configuration files RFCv1 -> RFCv2: - No longer disable apps and drivers if nothing was specified via command line or TUI, and warn user about things being built by default - Generate app launch configuration by default for when no apps are selected - Added paramters: - --force to avoid overwriting existing config - --common-conf to specify global meson flags applicable to all configs - --gdbsudo/--no-gdbsudo to specify gdbsudo behavior - Autodetect gdbsudo/gdb from UID - Updated comments, error messages, fixed issues with user interaction - Improved handling of wildcards and driver dependencies - Fixed a few bugs in dependency detection due to incorrect parsing - [Stephen] flake8 is happy app/meson.build | 14 +- buildtools/gen-vscode-conf.py | 570 ++++++++++++++++++++ buildtools/meson.build | 5 + devtools/test-meson-builds.sh | 3 + doc/guides/contributing/ide_integration.rst | 89 +++ doc/guides/contributing/index.rst | 1 + doc/guides/rel_notes/release_24_11.rst | 5 + examples/meson.build | 15 +- meson.build | 14 + 9 files changed, 714 insertions(+), 2 deletions(-) create mode 100755 buildtools/gen-vscode-conf.py create mode 100644 doc/guides/contributing/ide_integration.rst diff --git a/app/meson.build b/app/meson.build index e2db888ae1..fe317f0d53 100644 --- a/app/meson.build +++ b/app/meson.build @@ -115,7 +115,19 @@ foreach app:apps link_libs = dpdk_static_libraries + dpdk_drivers endif - exec = executable('dpdk-' + name, + # add to Visual Studio Code launch configuration + exe_name = 'dpdk-' + name + launch_path = join_paths(meson.current_build_dir(), exe_name) + # we also need exec env/arch, but they were not available at the time buildtools command was generated + cfg_args = ['--launch', launch_path, '--exec-env', exec_env, '--arch', arch_subdir] + # we don't want to block the build if this command fails + result = run_command(vscode_conf_gen_cmd + cfg_args, check: false) + if result.returncode() != 0 + warning('Failed to generate Visual Studio Code launch configuration for "' + name + '"') + message(result.stderr()) + endif + + exec = executable(exe_name, [ sources, resources ], c_args: cflags, link_args: ldflags, diff --git a/buildtools/gen-vscode-conf.py b/buildtools/gen-vscode-conf.py new file mode 100755 index 0000000000..3e88c9e4ff --- /dev/null +++ b/buildtools/gen-vscode-conf.py @@ -0,0 +1,570 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2024 Intel Corporation +# + +"""Visual Studio Code configuration generator script.""" + +# This script is meant to be run by meson build system to generate build and launch commands for a +# specific build directory for Visual Studio Code IDE. +# +# Even though this script will generate settings/tasks/launch/code analysis configuration for +# VSCode, we can't actually just regenerate the files, because we want to support multiple build +# directories, as well as not destroy any configuration user has created between runs of this +# script. Therefore, we need some config file handling infrastructure. Luckily, VSCode configs are +# all JSON, so we can just use json module to handle them. Of course, we will lose any user +# comments in the files, but that's a small price to pay for this sort of automation. +# +# Since this script will be run by meson, we can forego any parsing or anything to do with the +# build system, and just rely on the fact that we get all of our configuration from command-line. + +import argparse +import json +import os +import shutil +from collections import OrderedDict +from sys import stderr, exit as _exit +import typing as T + +# if this variable is defined, we will not generate any configuration files +ENV_DISABLE = "DPDK_DISABLE_VSCODE_CONFIG" + + +def _preprocess_json(data: str) -> str: + """Preprocess JSON to remove trailing commas, whitespace, and comments.""" + preprocessed_data: T.List[str] = [] + # simple state machine + in_comment = False + in_string = False + escape = False + comma = False + maybe_comment = False + for c in data: + _fwdslash = c == "/" + _newline = c == "\n" + _comma = c == "," + _obj_end = c in ["}", "]"] + _space = c.isspace() + _backslash = c == "\\" + _quote = c == '"' + + # are we looking to start a comment? + if maybe_comment: + maybe_comment = False + if _fwdslash: + in_comment = True + continue + # slash is illegal JSON but this is not our job + preprocessed_data.append("/") + # c will receive further processing + # are we inside a comment? + if in_comment: + if _newline: + in_comment = False + # eat everything + continue + # do we have a trailing comma? + if comma: + # there may be whitespace after the comma + if _space: + continue + comma = False + if _obj_end: + # throw away trailing comma + preprocessed_data.append(c) + continue + # comma was needed + preprocessed_data.append(",") + # c needs further processing + # are we inside a string? + if in_string: + # are we in an escape? + if escape: + escape = False + # are we trying to escape? + elif _backslash: + escape = True + # are we ending the string? + elif _quote: + in_string = False + # we're inside a string + preprocessed_data.append(c) + continue + # are we looking to start a string? + if _quote: + in_string = True + preprocessed_data.append(c) + continue + # are we looking to start a comment? + elif _fwdslash: + maybe_comment = True + continue + # are we looking at a comma? + elif _comma: + comma = True + continue + # are we looking at whitespace? + elif _space: + continue + # this is a regular character, just add it + preprocessed_data.append(c) + + return "".join(preprocessed_data) + + +def _load_json(file: str) -> T.Dict[str, T.Any]: + """Load JSON file.""" + with open(file, "r", encoding="utf-8") as f: + # Python's JSON parser doesn't like trailing commas, but VSCode's JSON parser does not + # consider them to be syntax errors, so they may be present in user's configuration files. + # remove them from the file before processing. + data = _preprocess_json(f.read()) + try: + return json.loads(data) + except json.JSONDecodeError as e: + print(f"Error parsing {os.path.basename(file)}: {e}", file=stderr) + raise + + +def _save_json(file: str, obj: T.Dict[str, T.Any]) -> None: + """Write JSON file.""" + with open(file, "w", encoding="utf-8") as f: + json.dump(obj, f, indent="\t") + + +class ConfigCtx: + """Data associated with config processing.""" + + def __init__( + self, + build_dir: str, + source_dir: str, + launch: T.List[str], + exec_env: str, + arch: str, + ): + self.build_dir = build_dir + self.source_dir = source_dir + self.config_dir = os.path.join(source_dir, ".vscode") + self.exec_env = exec_env + self.arch = arch + # we don't have any mechanism to label things, so we're just going to + # use build dir basename as the label, and hope user doesn't create + # different build directories with the same name + self.label = os.path.basename(build_dir) + self.builddir_var = f"{self.label}-builddir" + # default to gdb + self.dbg_path_var = f"{self.label}-dbg-path" + self.launch_dbg_path = shutil.which("gdb") + self.dbg_mode_var = f"{self.label}-dbg-mode" + self.dbg_mode = "gdb" + self.launch = launch + self.compile_task = f"[{self.label}] Compile" + + # filenames for configs + self.settings_fname = "settings.json" + self.tasks_fname = "tasks.json" + self.launch_fname = "launch.json" + self.analysis_fname = "c_cpp_properties.json" + + # temporary filenames to avoid touching user's configuration until last moment + self._tmp_fnames = { + self.settings_fname: f".{self.settings_fname}.{self.label}.tmp", + self.tasks_fname: f".{self.tasks_fname}.{self.label}.tmp", + self.launch_fname: f".{self.launch_fname}.{self.label}.tmp", + self.analysis_fname: f".{self.analysis_fname}.{self.label}.tmp", + } + # when there is no user configuration, use these templates + self._templates: T.Dict[str, T.Dict[str, T.Any]] = { + self.settings_fname: {}, + self.tasks_fname: {"version": "2.0.0", "tasks": [], "inputs": []}, + self.launch_fname: {"version": "0.2.0", "configurations": []}, + self.analysis_fname: {"version": 4, "configurations": []}, + } + + def _get_fname(self, fname: str) -> str: + """Get filename for configuration.""" + if fname not in self._tmp_fnames: + raise ValueError(f"Unknown configuration file {fname}") + return os.path.join(self.config_dir, self._tmp_fnames[fname]) + + def load(self, fname: str) -> T.Dict[str, T.Any]: + """Load or generate JSON data from template.""" + path = self._get_fname(fname) + try: + return _load_json(path) + except FileNotFoundError: + return self._templates[fname] + + def save(self, fname: str, obj: T.Dict[str, T.Any]) -> None: + """Save JSON data to temporary file.""" + path = self._get_fname(fname) + _save_json(path, obj) + + def commit(self): + """Commit previously saved settings to configuration.""" + for dst, tmp in self._tmp_fnames.items(): + fp_tmp = os.path.join(self.config_dir, tmp) + fp_dst = os.path.join(self.config_dir, dst) + if os.path.exists(fp_tmp): + shutil.copyfile(fp_tmp, fp_dst) + + def cleanup(self): + """Cleanup any temporary files.""" + for tmp in self._tmp_fnames.values(): + fp_tmp = os.path.join(self.config_dir, tmp) + if os.path.exists(fp_tmp): + os.unlink(fp_tmp) + + +def _gen_sorter(order: T.List[str]) -> T.Any: + """Sort dictionary by order.""" + + # JSON doesn't have sort order, but we want to be user friendly and display certain properties + # above others as they're more important. This function will return a closure that can be used + # to re-sort a specific object using OrderedDict and an ordered list of properties. + def _sorter(obj: T.Dict[str, T.Any]) -> OrderedDict[str, T.Any]: + d: OrderedDict[str, T.Any] = OrderedDict() + # step 1: go through all properties in order and re-add them + for prop in order: + if prop in obj: + d[prop] = obj[prop] + # step 2: get all properties of the object, remove those that we have already added, and + # sort them alphabetically + for prop in sorted(set(obj.keys()) - set(order)): + d[prop] = obj[prop] + # we're done: now all objects will have vaguely constant sort order + return d + + return _sorter + + +def _add_obj_to_list( + obj_list: T.List[T.Dict[str, T.Any]], key: str, obj: T.Dict[str, T.Any] +) -> bool: + """Add object to list if it doesn't already exist.""" + for o in obj_list: + if o[key] == obj[key]: + return False + obj_list.append(obj) + return True + + +def _add_var_to_obj(obj: T.Dict[str, T.Any], var: str, value: T.Any) -> bool: + """Add variable to object if it doesn't exist.""" + if var in obj: + return False + obj[var] = value + return True + + +def _update_settings(ctx: ConfigCtx) -> T.Optional[T.Dict[str, T.Any]]: + """Update settings.json.""" + settings_obj = ctx.load(ctx.settings_fname) + dirty = False + + ttos_tasks = "triggerTaskOnSave.tasks" + ttos_on = "triggerTaskOnSave.on" + default_vars: T.Dict[str, T.Any] = { + # store build dir + ctx.builddir_var: ctx.build_dir, + # store debug configuration + ctx.dbg_path_var: ctx.launch_dbg_path, + ctx.dbg_mode_var: ctx.dbg_mode, + # store dbg mode and path + # trigger build on save + ttos_tasks: {}, + ttos_on: True, + # improve responsiveness by disabling auto-detection of tasks + "npm.autoDetect": "off", + "gulp.autoDetect": "off", + "jake.autoDetect": "off", + "grunt.autoDetect": "off", + "typescript.tsc.autoDetect": "off", + "task.autoDetect": "off", + } + + for var, value in default_vars.items(): + dirty |= _add_var_to_obj(settings_obj, var, value) + + # add path ignore setting if it's inside the source dir + cpath = os.path.commonpath([ctx.source_dir, ctx.build_dir]) + if cpath == ctx.source_dir: + # find path within source tree + relpath = os.path.relpath(ctx.build_dir, ctx.source_dir) + os.sep + + # note if we need to change anything + if "files.exclude" not in settings_obj: + dirty = True + elif relpath not in settings_obj["files.exclude"]: + dirty = True + + exclude = settings_obj.setdefault("files.exclude", {}) + exclude.setdefault(relpath, True) + settings_obj["files.exclude"] = exclude + + # if user has installed "Trigger Task On Save" extension (extension id: + # Gruntfuggly.triggertaskonsave), this will enable build-on-save by default + if ctx.compile_task not in settings_obj[ttos_tasks]: + dirty = True + # trigger build on save for all files + settings_obj[ttos_tasks][ctx.compile_task] = ["**/*"] + + return settings_obj if dirty else None + + +def _update_tasks(ctx: ConfigCtx) -> T.Optional[T.Dict[str, T.Any]]: + """Update tasks.json.""" + outer_tasks_obj = ctx.load(ctx.tasks_fname) + inner_tasks_obj = outer_tasks_obj.setdefault("tasks", []) + inputs_obj = outer_tasks_obj.setdefault("inputs", []) + dirty = False + + # generate task object sorter + _sort_task = _gen_sorter( + [ + "label", + "detail", + "type", + "command", + "args", + "options", + "problemMatcher", + "group", + ] + ) + + # generate our would-be configuration + + # first, we need a build task + build_task: T.Dict[str, T.Any] = { + "label": ctx.compile_task, + "detail": f"Run `meson compile` command for {ctx.label}", + "type": "shell", + "command": "meson compile", + "options": {"cwd": f"${{config:{ctx.builddir_var}}}"}, + "problemMatcher": { + "base": "$gcc", + "fileLocation": ["relative", f"${{config:{ctx.builddir_var}}}"], + }, + "group": "build", + } + # we also need a meson configure task with input + configure_task: T.Dict[str, T.Any] = { + "label": f"[{ctx.label}] Configure", + "detail": f"Run `meson configure` command for {ctx.label}", + "type": "shell", + "command": "meson configure ${input:mesonConfigureArg}", + "options": {"cwd": f"${{config:{ctx.builddir_var}}}"}, + "problemMatcher": [], + "group": "build", + } + # finally, add input object + input_arg: T.Dict[str, T.Any] = { + "id": "mesonConfigureArg", + "type": "promptString", + "description": "Enter meson configure arguments", + "default": "", + } + + # sort our tasks + build_task = _sort_task(build_task) + configure_task = _sort_task(configure_task) + + # add only if task doesn't already exist + dirty |= _add_obj_to_list(inner_tasks_obj, "label", build_task) + dirty |= _add_obj_to_list(inner_tasks_obj, "label", configure_task) + dirty |= _add_obj_to_list(inputs_obj, "id", input_arg) + + # replace nodes + outer_tasks_obj["tasks"] = inner_tasks_obj + outer_tasks_obj["inputs"] = inputs_obj + + # we're ready + return outer_tasks_obj if dirty else None + + +def _update_launch(ctx: ConfigCtx) -> T.Optional[T.Dict[str, T.Any]]: + """Update launch.json.""" + launch_obj = ctx.load(ctx.launch_fname) + configurations_obj = launch_obj.setdefault("configurations", []) + dirty = False + + # generate launch task sorter + _sort_launch = _gen_sorter( + [ + "name", + "type", + "request", + "program", + "cwd", + "preLaunchTask", + "environment", + "args", + "MIMode", + "miDebuggerPath", + "setupCommands", + ] + ) + + for target in ctx.launch: + # target will be a full path, we need to get relative to build path + exe_path = os.path.relpath(target, ctx.build_dir) + name = f"[{ctx.label}] Launch {exe_path}" + # generate config from template + launch_config: T.Dict[str, T.Any] = { + "name": name, + "type": "cppdbg", + "request": "launch", + "program": f"${{config:{ctx.builddir_var}}}/{exe_path}", + "args": [], + "cwd": "${workspaceFolder}", + "environment": [], + "MIMode": f"${{config:{ctx.dbg_mode_var}", + "miDebuggerPath": f"${{config:{ctx.dbg_path_var}", + "preLaunchTask": ctx.compile_task, + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-gdb-set print pretty on", + "ignoreFailures": True, + } + ], + } + # sort keys + launch_config = _sort_launch(launch_config) + # add to configurations + dirty |= _add_obj_to_list(configurations_obj, "name", launch_config) + + # replace the configuration object + launch_obj["configurations"] = configurations_obj + + # we're ready + return launch_obj if dirty else None + + +def _update_analysis(ctx: ConfigCtx) -> T.Optional[T.Dict[str, T.Any]]: + """Update c_cpp_properties.json.""" + analysis_obj = ctx.load(ctx.analysis_fname) + configurations_obj = analysis_obj.setdefault("configurations", []) + dirty = False + + # generate analysis config sorter + _sort_analysis = _gen_sorter( + [ + "name", + "includePath", + "compilerPath", + "cStandard", + "cppStandard", + "intelliSenseMode", + "compileCommands", + ] + ) + + config_obj: T.Dict[str, T.Any] = { + "name": ctx.exec_env.capitalize(), + "includePath": [ + f"${{config:{ctx.builddir_var}}}/", + # hardcode everything to x86/Linux for now + f"${{workspaceFolder}}/lib/eal/{ctx.arch}/include", + f"${{workspaceFolder}}/lib/eal/{ctx.exec_env}/include", + "${workspaceFolder}/**", + ], + "compilerPath": "/usr/bin/gcc", + "cStandard": "c99", + "cppStandard": "c++17", + "intelliSenseMode": "${default}", + "compileCommands": f"${{config:{ctx.builddir_var}}}/compile_commands.json", + } + # sort configuration + config_obj = _sort_analysis(config_obj) + + # add it to config obj + dirty |= _add_obj_to_list(configurations_obj, "name", config_obj) + + # we're done + analysis_obj["configurations"] = configurations_obj + + return analysis_obj if dirty else None + + +def _gen_config(ctx: ConfigCtx) -> None: + """Generate all config files.""" + + # generate all JSON objects and save them if we changed anything about them + settings_obj = _update_settings(ctx) + tasks_obj = _update_tasks(ctx) + launch_obj = _update_launch(ctx) + analysis_obj = _update_analysis(ctx) + + if settings_obj is not None: + ctx.save(ctx.settings_fname, settings_obj) + if tasks_obj is not None: + ctx.save(ctx.tasks_fname, tasks_obj) + if launch_obj is not None: + ctx.save(ctx.launch_fname, launch_obj) + if analysis_obj is not None: + ctx.save(ctx.analysis_fname, analysis_obj) + + # the above saves only saved to temporary files, now overwrite real files + ctx.commit() + + +def _main() -> int: + if os.environ.get(ENV_DISABLE, "") == "1": + print( + "Visual Studio Code configuration generation " + f"disabled by environment variable {ENV_DISABLE}=1" + ) + return 0 + parser = argparse.ArgumentParser(description="Generate VSCode configuration") + # where we are being called from + parser.add_argument("--build-dir", required=True, help="Build directory") + # where the sources are + parser.add_argument("--source-dir", required=True, help="Source directory") + # exec-env - Windows, Linux etc. + parser.add_argument("--exec-env", required=True, help="Execution environment") + # arch - x86, arm etc. + parser.add_argument("--arch", required=True, help="Architecture") + # launch configuration item, can be multiple + parser.add_argument("--launch", action="append", help="Launch path for executable") + parser.epilog = "This script is not meant to be run manually." + # parse arguments + args = parser.parse_args() + + # canonicalize all paths + build_dir = os.path.realpath(args.build_dir) + source_dir = os.path.realpath(args.source_dir) + if args.launch: + launch = [os.path.realpath(lp) for lp in args.launch] + else: + launch = [] + exec_env = args.exec_env + arch = args.arch + + ctx = ConfigCtx(build_dir, source_dir, launch, exec_env, arch) + + try: + # ensure config dir exists + os.makedirs(ctx.config_dir, exist_ok=True) + + _gen_config(ctx) + + ret = 0 + except json.JSONDecodeError as e: + # if we fail to load JSON, output error + print(f"Error: {e}", file=stderr) + ret = 1 + except OSError as e: + # if we fail to write to disk, output error + print(f"Error: {e}", file=stderr) + ret = 1 + + # remove any temporary files + ctx.cleanup() + return ret + + +if __name__ == "__main__": + _exit(_main()) diff --git a/buildtools/meson.build b/buildtools/meson.build index 4e2c1217a2..e2cc9635b2 100644 --- a/buildtools/meson.build +++ b/buildtools/meson.build @@ -26,6 +26,11 @@ header_gen_cmd = py3 + files('gen-header.py') has_hugepages_cmd = py3 + files('has-hugepages.py') cmdline_gen_cmd = py3 + files('dpdk-cmdline-gen.py') check_dts_requirements = py3 + files('check-dts-requirements.py') +# Visual Studio Code conf generator always requires build/source roots +vscode_conf_gen_cmd = py3 + files('gen-vscode-conf.py') + [ + '--build-dir', dpdk_build_root, + '--source-dir', dpdk_source_root + ] # install any build tools that end-users might want also install_data([ diff --git a/devtools/test-meson-builds.sh b/devtools/test-meson-builds.sh index 4fff1f7177..217d8dc773 100755 --- a/devtools/test-meson-builds.sh +++ b/devtools/test-meson-builds.sh @@ -53,6 +53,8 @@ default_cppflags=$CPPFLAGS default_cflags=$CFLAGS default_ldflags=$LDFLAGS default_meson_options=$DPDK_MESON_OPTIONS +# disable VSCode config generation +export DPDK_DISABLE_VSCODE_CONFIG=1 opt_verbose= opt_vverbose= @@ -88,6 +90,7 @@ load_env () # <target compiler> export CFLAGS=$default_cflags export LDFLAGS=$default_ldflags export DPDK_MESON_OPTIONS=$default_meson_options + # set target hint for use in the loaded config file if [ -n "$target_override" ] ; then DPDK_TARGET=$target_override diff --git a/doc/guides/contributing/ide_integration.rst b/doc/guides/contributing/ide_integration.rst new file mode 100644 index 0000000000..86345767ee --- /dev/null +++ b/doc/guides/contributing/ide_integration.rst @@ -0,0 +1,89 @@ +.. SPDX-License-Identifier: BSD-3-Clause + Copyright 2024 The DPDK contributors + +Integrating DPDK with IDEs +========================== + +DPDK does not mandate nor recommend a specific IDE for development. However, +some developers may prefer to use an IDE for their development work. This guide +provides information on how to integrate DPDK with some popular IDEs. + + +Visual Studio Code +------------------ + +`Visual Studio Code <https://code.visualstudio.com/>` is a popular open-source +code editor with IDE features such as code completion, debugging, Git +integration, and more. It is available on most platforms. + + +Configuration +~~~~~~~~~~~~~ + +When configuring a new Meson build directory for DPDK, configuration for Visual +Studio Code will be generated automatically. It will include both a compilation +task, as well as debugging targets for any applications or examples enabled in +meson at configuration step. Generated configuration will be available under +`.vscode` directory in DPDK source tree. The configuration will be updated each +time the build directory is reconfigured with Meson. + +Further information on configuring, building and installing DPDK is described in +:doc:`Linux Getting Started Guide <../linux_gsg/build_dpdk>`. + +.. note:: + + The configuration is generated based on the enabled applications and + examples at the time of configuration. When new applications or examples are + added to the configuration using the `meson configure` command (or through + running `Configure` task), new configuration will be added, but existing + configuration will never be amended or deleted, even if the application was + removed from build. + + +Each generated file will refer to a few common variables defined under +`settings.json`. This is to allow easy reconfiguration of all generated launch +targets while also still allowing user to customize the configuration. Variables +contained within `settings.json` are as follows: + +- `<build-dir-name>-builddir`: Path to the build directory (can be in-tree or + out-of-tree) +- `<build-dir-name>-dbg-path`: Variable for `miDebuggerPath` in launch tasks +- `<build-dir-name>-dbg-mode`: Variable for `MIMode` in launch tasks + +It is not recommended to change these variables unless there is a specific need. + +.. note:: + + Due to the way the configuration generation is implemented, each time the + configuration is updated, any user comments will be lost. + + +Running as unprivileged user +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If not running as privileged user, then by default the generated configuration +will not be able to run DPDK applications that require `root` privileges. To +address this, either the system will have to be configured to allow running DPDK +as non-privileged user, or the launch configuration has to be amended to run the +debugger (usually `GDB`) as root. + +Further information on configuring the system to allow running DPDK as +non-privileged user can be found in the :ref:`common Linux guide +<Running_Without_Root_Privileges>`. + +If the user prefers to run applications as `root` while still working as regular +user instead, the following steps must be taken: + +- Allow running GDB with password-less `sudo` (please consult relevant system + documentation on how to achieve this) +- Set up a local alias for running GDB with `sudo` (e.g. `sudo gdb $@`) +- Amend the `settings.json` file to set `<build-dir>-dbg-path` variable to this + new alias + +Once this is done, any existing or new launch targets will use the new debugger +alias to run DPDK applications. + +.. note:: + + The above steps are not recommended for production systems, as they may + introduce security vulnerabilities. diff --git a/doc/guides/contributing/index.rst b/doc/guides/contributing/index.rst index 7dc89b9afc..ba23216aed 100644 --- a/doc/guides/contributing/index.rst +++ b/doc/guides/contributing/index.rst @@ -21,3 +21,4 @@ Contributor's Guidelines vulnerability stable cheatsheet + ide_integration diff --git a/doc/guides/rel_notes/release_24_11.rst b/doc/guides/rel_notes/release_24_11.rst index 48b399cda7..c9b0791ac2 100644 --- a/doc/guides/rel_notes/release_24_11.rst +++ b/doc/guides/rel_notes/release_24_11.rst @@ -329,6 +329,11 @@ New Features Added ability for node to advertise and update multiple xstat counters, that can be retrieved using ``rte_graph_cluster_stats_get``. +* **Generate Visual Studio Code configuration on build** + + The Meson build system now generates Visual Studio Code configuration + files for configuration, compilation, and debugging tasks. + Removed Items ------------- diff --git a/examples/meson.build b/examples/meson.build index 8e8968a1fa..45327b6f6b 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -124,7 +124,20 @@ foreach example: examples if allow_experimental_apis cflags += '-DALLOW_EXPERIMENTAL_API' endif - executable('dpdk-' + name, sources, + + # add to Visual Studio Code launch configuration + exe_name = 'dpdk-' + name + launch_path = join_paths(meson.current_build_dir(), exe_name) + # we also need exec env/arch, but they were not available at the time buildtools command was generated + cfg_args = ['--launch', launch_path, '--exec-env', exec_env, '--arch', arch_subdir] + # we don't want to block the build if this command fails + result = run_command(vscode_conf_gen_cmd + ['--launch', launch_path], check: false) + if result.returncode() != 0 + warning('Failed to generate Visual Studio Code launch configuration for "' + name + '"') + message(result.stderr()) + endif + + executable(exe_name, sources, include_directories: includes, link_whole: link_whole_libs, link_args: ldflags, diff --git a/meson.build b/meson.build index 8436d1dff8..302d732e1d 100644 --- a/meson.build +++ b/meson.build @@ -118,6 +118,20 @@ if meson.is_subproject() subdir('buildtools/subproject') endif +# if no apps or examples were enabled, no Visual Studio Code config was +# generated, but we still need build, code analysis etc. configuration to be +# present, so generate it just in case (it will have no effect if the +# configuration was already generated by apps/examples). also, when running +# this command, we don't want to block the build if it fails. + +# we need exec env/arch, but they were not available at the time buildtools command was generated +cfg_args = ['--exec-env', exec_env, '--arch', arch_subdir] +result = run_command(vscode_conf_gen_cmd + cfg_args, check: false) +if result.returncode() != 0 + warning('Failed to generate Visual Studio Code configuration') + message(result.stderr()) +endif + # Final output, list all the parts to be built. # This does not affect any part of the build, for information only. output_message = '\n=================\nApplications Enabled\n=================\n' -- 2.43.5 ^ permalink raw reply [flat|nested] 23+ messages in thread
end of thread, other threads:[~2024-11-26 13:30 UTC | newest] Thread overview: 23+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2024-07-26 12:42 [RFC PATCH v1 0/1] Add Visual Studio Code configuration script Anatoly Burakov 2024-07-26 12:42 ` [RFC PATCH v1 1/1] devtools: add vscode configuration generator Anatoly Burakov 2024-07-26 15:36 ` Stephen Hemminger 2024-07-26 16:05 ` Burakov, Anatoly 2024-07-29 13:05 ` [RFC PATCH v2 0/1] Add Visual Studio Code configuration script Anatoly Burakov 2024-07-29 13:05 ` [RFC PATCH v2 1/1] devtools: add vscode configuration generator Anatoly Burakov 2024-07-29 13:14 ` Bruce Richardson 2024-07-29 13:17 ` Burakov, Anatoly 2024-07-29 14:30 ` Bruce Richardson 2024-07-29 16:16 ` Burakov, Anatoly 2024-07-29 16:41 ` Bruce Richardson 2024-07-30 9:21 ` Burakov, Anatoly 2024-07-30 10:31 ` Bruce Richardson 2024-07-30 10:50 ` Burakov, Anatoly 2024-07-30 15:01 ` [RFC PATCH v2 0/1] Add Visual Studio Code configuration script Bruce Richardson 2024-07-30 15:14 ` Burakov, Anatoly 2024-07-30 15:19 ` Bruce Richardson 2024-07-31 13:33 ` [RFC PATCH v3 " Anatoly Burakov 2024-07-31 13:33 ` [RFC PATCH v3 1/1] buildtools: add vscode configuration generator Anatoly Burakov 2024-09-02 12:17 ` [PATCH v1 0/1] Add Visual Studio Code configuration script Anatoly Burakov 2024-09-02 12:17 ` [PATCH v1 1/1] buildtools: add VSCode configuration generator Anatoly Burakov 2024-11-26 13:30 ` [PATCH v2 0/1] Add Visual Studio Code configuration script Anatoly Burakov 2024-11-26 13:30 ` [PATCH v2 1/1] buildtools: add VSCode configuration generator Anatoly Burakov
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).