diff --git a/doc/develop/west/zephyr-cmds.rst b/doc/develop/west/zephyr-cmds.rst index 0c665c7f64a0..b4ef9532cff6 100644 --- a/doc/develop/west/zephyr-cmds.rst +++ b/doc/develop/west/zephyr-cmds.rst @@ -219,3 +219,52 @@ You can list all known standard descriptor names using:: You can print the offset of the descriptors inside the image using:: west bindesc get_offset + +Indexing the sources with GNU Global: ``west gtags`` +**************************************************** + +.. important:: You must install the ``gtags`` and ``global`` programs provided + by `GNU Global`_ to use this command. + +The ``west gtags`` command lets you create a GNU Global tags file for the entire +west workspace:: + + west gtags + +.. _GNU Global: https://www.gnu.org/software/global/ + +This will create a tags file named ``GTAGS`` in the workspace :ref:`topdir +` (it will also create other Global-related metadata files +named ``GPATH`` and ``GRTAGS`` in the same place). + +You can then run the ``global`` command anywhere inside the +workspace to search for symbol locations using this tags file. + +For example, to search for definitions of the ``arch_system_halt()`` function, +starting from the ``zephyr/drivers`` directory:: + + $ cd zephyr/drivers + $ global -x arch_system_halt + arch_system_halt 65 ../arch/arc/core/fatal.c FUNC_NORETURN void arch_system_halt(unsigned int reason) + arch_system_halt 455 ../arch/arm64/core/fatal.c FUNC_NORETURN void arch_system_halt(unsigned int reason) + arch_system_halt 137 ../arch/nios2/core/fatal.c FUNC_NORETURN void arch_system_halt(unsigned int reason) + arch_system_halt 18 ../arch/posix/core/fatal.c FUNC_NORETURN void arch_system_halt(unsigned int reason) + arch_system_halt 17 ../arch/x86/core/fatal.c FUNC_NORETURN void arch_system_halt(unsigned int reason) + arch_system_halt 126 ../arch/xtensa/core/fatal.c FUNC_NORETURN void arch_system_halt(unsigned int reason) + arch_system_halt 21 ../kernel/fatal.c FUNC_NORETURN __weak void arch_system_halt(unsigned int reason) + +This prints the search symbol, the line it is defined on, a relative path to +the file it is defined in, and the line itself, for all places where the symbol +is defined. + +Additional tips: + +- This can also be useful to search for vendor HAL function definitions. + +- See the ``global`` command's manual page for more information on how to use + this tool. + +- You should run ``global``, **not** ``west global``. There is no need for a + separate ``west global`` command since ``global`` already searches for the + ``GTAGS`` file starting from your current working directory. This is why you + need to run ``global`` from inside the workspace. diff --git a/scripts/west-commands.yml b/scripts/west-commands.yml index 68a0951daf8f..3096a43881d0 100644 --- a/scripts/west-commands.yml +++ b/scripts/west-commands.yml @@ -94,3 +94,8 @@ west-commands: - name: patch class: Patch help: manage patches for Zephyr modules + - file: scripts/west_commands/gtags.py + commands: + - name: gtags + class: Gtags + help: create a GNU global tags file for the current workspace diff --git a/scripts/west_commands/gtags.py b/scripts/west_commands/gtags.py new file mode 100644 index 000000000000..ae8ca2327c63 --- /dev/null +++ b/scripts/west_commands/gtags.py @@ -0,0 +1,98 @@ +# Copyright (c) 2025 Qualcomm Innovation Center, Inc. +# SPDX-License-Identifier: Apache-2.0 + +"""gtags.py + +A west extension for creating tags files (GTAGS) for GNU Global. +For more information on Global, see: https://www.gnu.org/software/global +""" + +import argparse +import os.path +import subprocess +import tempfile + +from west.commands import WestCommand + + +class Gtags(WestCommand): + def __init__(self): + super().__init__( + "gtags", + "create a GNU Global tags file for the current workspace", + """\ +Indexes source code files in the west workspace using GNU Global's +"gtags" tool. For more information on Global and gtags, see: + + https://www.gnu.org/software/global/ + +The index can be useful to find definitions of functions, etc., +especially across repository boundaries. One example is +finding the definition of a vendor HAL function that is +provided by a Zephyr module for the HAL. + +By default, this west command only indexes files that are +tracked by git projects defined in the west. Inactive west +projects are ignored by default. For more information on +projects etc., see the west documentation.""", + ) + + def do_add_parser(self, parser_adder): + parser = parser_adder.add_parser( + self.name, + help=self.help, + description=self.description, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + + parser.add_argument( + "projects", + nargs="*", + metavar="PROJECT", + help="""Name of west project to index, or + its path. May be given more than once. Use + "manifest" to refer to the manifest + repository""", + ) + + return parser + + def do_run(self, args, unknown_args): + all_files = [] + for project in self.manifest.get_projects(args.projects): + all_files.extend(self.files_in_project(project)) + + with tempfile.TemporaryDirectory(suffix="gtags") as d: + gtags_files = os.path.join(d, "gtags.files") + with open(gtags_files, "w") as f: + # Due to what looks like a limitation in GNU Global, + # this won't work if there are newlines in file names. + # Unlike xargs and other commands, though, gtags + # doesn't seem to have a way to accept a NUL-delimited + # list of input file; its manpage says file names must + # be delimited by newlines. + f.write("\n".join(all_files)) + subprocess.run( + # Note that "gtags -f -" and passing files via stdin + # could run into issues on windows, and there seem to + # be win32 builds of global out there + ["gtags", "-f", gtags_files], + cwd=self.manifest.topdir, + check=True, + ) + + def files_in_project(self, project): + if not project.is_cloned() or not self.manifest.is_active(project): + return [] + ls_files = ( + project.git(["ls-files", "**"], capture_stdout=True).stdout.decode("utf-8").splitlines() + ) + ret = [] + for filename in ls_files: + absolute = os.path.join(project.abspath, filename) + # Filter out directories from the git ls-files output. + # There didn't seem to be a way to tell it to do that by + # itself. + if os.path.isfile(absolute): + ret.append(absolute) + return ret