
A library and a GHC compiler
plugin
which generates tags for each compiled module or component.
● Requirements
The plugin requires at least: ghc >= 9.6.
● Plugin options
Usage: <program> [-e|--etags] [--stream] [--debug] [--qualified] [-F|--no-filter] [file_path]
write tags from ghc abstract syntax tree
Available options:
-e,--etags produce emacs etags file
--stream stream existing tags (ctags only)
file_path tags file relative to cabal.project or stack.yaml:
default tags or TAGS (with --etags)
--debug tag counts, file sizes, and update timings
-q,--qualified also generate names qualified by the defining module
-F,--no-filter retain both signatures and definitions
The tags path can be absolute or relative to the nearest directory containing
cabal.project, cabal.project.local, or stack.yaml, searching upward from
the compiled package. Packages using the same project root can share one tags
file:
-fplugin-opt=Plugin.GhcTags:tags
A cabal.project.local file is a project-root marker even without a
cabal.project beside it. A nearer marker takes precedence over configurations
in parent directories. For example, with /repo/cabal.project and
/repo/package/cabal.project.local, compiling that package writes to
/repo/package/tags.
If STACK_YAML points to an existing configuration file, its directory is used
instead. If no project configuration is found, the plugin prints a warning and
resolves the path relative to the nearest *.cabal package file. When no package
file is found either, it uses the compiler's working directory. Absolute output
paths are used unchanged.
● Qualified names
To generate module-qualified names alongside ordinary names, enable:
-fplugin-opt=Plugin.GhcTags:--qualified
For example, a declaration lookup in Data.Map produces both lookup and
Data.Map.lookup, pointing to the same declaration. This works with ctags,
streaming ctags, etags, and declarations generated by Template Haskell. Module
tags themselves are kept unchanged. Modules without a module header use Main.
The prefix identifies the module containing the declaration. It does not resolve
import aliases or re-exports: import qualified Data.Map as M does not add
M.lookup. Local bindings also receive module-prefixed names; those names
identify their containing module, rather than a valid qualified import.
Only recompiled modules are updated. Rebuild all modules after enabling or
disabling this option to update existing tags consistently.
● Signature and definition filtering
By default, a type signature replaces the corresponding function or pattern
synonym definition in the tags file. A data constructor also takes precedence
over a type constructor with the same name. To retain both tags, use:
-fplugin-opt=Plugin.GhcTags:--no-filter
The short form is -fplugin-opt=Plugin.GhcTags:-F. This applies to ctags,
streaming ctags, etags, and Template Haskell signature/definition pairs. Tags
remain sorted. Recompile all modules after changing this option to update
existing tags consistently.
● Update timings
Enable timing diagnostics with:
-fplugin-opt=Plugin.GhcTags:--debug
Each completed update reports monotonic wall-clock durations in milliseconds:
lock-wait: opening and acquiring the tags-file lock.
read: reading the existing tags file.
parse: parsing its records.
generate: extracting and preparing tags for the current module or splice.
merge: combining old and new tags, including sorting where required.
write: formatting, writing, and flushing the replacement's contents.
total: elapsed time from before lock acquisition until after release,
including temporary-file close/rename, bookkeeping, and other debug output.
Reports include the module, tags path, output mode, and whether the update came
from parsing or a Template Haskell declaration splice. Streaming ctags measures
each stage as records flow through it, excluding upstream work from downstream
phase times. It does not load the existing file into memory for timing.
Debug mode forces lazy results within their measured phase and adds measurement
overhead, especially when streaming. The phase times need not sum to total.
With debug disabled, timing does not read the clock or force additional results.
● Emacs support
To produce etags file you will need to pass the following option
-fplugin-opt=Plugin.GhcTags:--etags
● Editor configuration
By default the generated tags file is put in the nearest directory containing
cabal.project, cabal.project.local, or stack.yaml. To let Vim or Neovim find
it while editing files in package subdirectories, enable searching parent
directories:
:set tags+=./tags;
You can also pass an option to change where tags are written, see below.
● Configuration: Ghc / Cabal / Stack
● Cabal: quick start with GHCi
To generate tags in a REPL session without installing the plugin separately or
editing any .cabal files, run:
cabal repl TARGET \
--build-depends=ghc-tags-plugin \
--repl-options=-fplugin=Plugin.GhcTags \
--repl-options=-fplugin-opt=Plugin.GhcTags:tags
Replace TARGET with a component such as lib:my-package or exe:my-program.
Cabal builds the plugin and makes it available to that REPL session. This
command was tested with Cabal 3.16.1.0 and GHC 9.10.3 using a local plugin
checkout and this repository's project configuration. See Cabal's
--build-depends documentation.
The plugin's dependencies are solved together with the target project's
dependencies, using that project's index-state, constraints, and
allow-newer settings. You might need to modify allow-newer via
--allow-newer documentation.
This configures the REPL session only. For normal builds and HLS, use the
persistent configuration below.
● Cabal: persistent configuration
This setup enables the plugin through cabal.project.local, without changing
any .cabal files.
First, install the plugin into Cabal's store:
cabal install --lib ghc-tags-plugin --package-env=ghc-tags
If installation encounters the GHC 9.14 dependency bounds described above,
add --allow-newer=pipes-safe:base,ghc-tags-pipes:base to this command too.
The named ghc-tags environment keeps this installation separate from the
default GHC package environment. The configuration below loads the plugin
through its package database, so it does not require selecting that environment.
See Cabal's package environment documentation.
Use the same GHC to install the plugin and build the target project. If the
project selects a particular compiler, pass -w /absolute/path/to/ghc to the
installation command as well. Reinstall the plugin when changing compilers.
With Cabal 3.16.1.0 or newer, run this in the target project to find its compiler
and the corresponding store directory:
cabal path --compiler-info
Append /package.db to the reported compiler-store-path to obtain
PACKAGE_DB. For example, the resulting path might be:
/home/alice/.cabal/store/ghc-9.10.3-1be3/package.db
Use the reported path rather than constructing it from the GHC version:
compiler ABI suffixes and custom store locations can change the directory name.
With older Cabal versions, locate the compiler's package.db in the Cabal store
(the store location can be queried with cabal path --store-dir where available).
Add the following program-options stanza to cabal.project.local:
program-options
ghc-options: -package-db=PACKAGE_DB
-plugin-package=ghc-tags-plugin
-fplugin=Plugin.GhcTags
-fplugin-opt=Plugin.GhcTags:tags
Replace PACKAGE_DB with the absolute path found above. Environment variables
such as $HOME are not expanded in this file. If the path contains spaces,
quote the entire argument, for example "-package-db=C:/Users/My Name/cabal/store/ghc-VERSION/package.db".
The stanza applies to all local packages. You can now run cabal build or
cabal repl normally. When HLS uses a Cabal cradle, restart it after changing
the configuration so it reloads the compiler options.
The default relative path shares a tags file across packages that resolve to the
same project root. To choose an absolute destination, use:
-fplugin-opt=Plugin.GhcTags:/absolute/path/to/tags
Relative paths in the examples above are resolved from the project configuration
directory. For example, ../tags writes one directory above the selected
cabal.project, cabal.project.local, or stack.yaml.
To apply the plugin to dependencies as well, use a package * stanza instead
of program-options. This can rebuild dependencies and applies the options to
the plugin's own dependencies too; install the plugin before enabling it.
● GHC directly
After installing the plugin, pass its package database and enable it:
ghc -package-db=PACKAGE_DB \
-plugin-package=ghc-tags-plugin \
-fplugin=Plugin.GhcTags Main.hs
Replace PACKAGE_DB as described above. If multiple plugin versions are
installed, select one with -plugin-package=ghc-tags-plugin-VERSION, or use
-plugin-package-id=UNIT_ID to select a particular installed instance. Inspect
the database with:
ghc-pkg --package-db=PACKAGE_DB list ghc-tags-plugin
See GHC's package documentation
and compiler plugin documentation.
● Stack
Add ghc-tags-plugin to build-depends in the relevant components of your
.cabal files, optionally behind a development flag. If the plugin is not in
your snapshot, add a release compatible with the snapshot's GHC to extra-deps
in stack.yaml.
Configure the target package in stack.yaml under ghc-options:
ghc-options:
my-package: -plugin-package=ghc-tags-plugin
-fplugin=Plugin.GhcTags
-fplugin-opt=Plugin.GhcTags:tags
Replace my-package with your package name. This Stack setup uses a declared
build dependency; the Cabal setups above avoid changing .cabal files.
● Ghcid
For automatic tag updates while editing, use the REPL quick start with ghcid:
ghcid --command="cabal repl TARGET --build-depends=ghc-tags-plugin --repl-options=-fplugin=Plugin.GhcTags --repl-options=-fplugin-opt=Plugin.GhcTags:tags"
If you have already enabled the plugin in cabal.project.local, use:
ghcid --command="cabal repl TARGET"
Replace TARGET with the component you want ghcid to load.
● Makefile
The Makefile
contains some useful commands, e.g. install, uninstall or reinstall the
package in a package.db (by default into cabal store). This is mostly for
development, but it could be useful in other scenarios as well.
The optional gtp-check development tool reports tag counts, ctags metadata,
parser diagnostics, sorting problems, repeated declaration locations, missing
or unreadable source files, and line numbers outside their source files:
cabal run -fgtp-check gtp-check -- tags
cabal run -fgtp-check gtp-check -- --etags TAGS
Source paths are resolved relative to the directory containing each tags file,
so the command can be run from another directory. Duplicate locations mean
entries with the same source file, tag name and line number; kinds and columns
are not used to distinguish them.
For a tags file copied elsewhere, pass --source-root /path/to/original/root
to resolve source paths from its original directory. This override applies to
both files when comparing snapshots.
To compare the declarations in two files:
cabal run -fgtp-check gtp-check -- tags --compare TAGS
cabal run -fgtp-check gtp-check -- --etags TAGS --compare tags
The format of the comparison file is detected automatically. Both files can
also use the same format. Comparison uses canonical source paths, names and
line numbers, and preserves duplicate counts. Differences are reported with
concrete declaration locations. Tags without numeric line addresses are
reported and excluded from line checks and comparison; a ctags line: field
can supply the line number for an entry addressed by a search pattern.
Each diagnostic category shows up to ten examples by default. Use
--max-examples N to change the limit, or --max-examples 0 to show all.
Tags-file read or parse errors and unequal comparisons return a nonzero exit
status. Other findings are warnings.
● Exceptions
If a GHC plugin throws an exception, GHC stops. This plugin wraps
IOExceptions, to make it obvious that it filed rather than GHC. This
might mean you misconfigured the plugin (by passing wrong options). The
result might look like this:
ghc: panic! (the 'impossible' happened)
(GHC version 8.6.5 for x86_64-unknown-linux):
GhcTagsPluginIOException ../: openFile: inappropriate type (Is a directory)
● Tips
-
If GHC cannot find the installed plugin, check that installation and
compilation use the same compiler and package database:
ghc-pkg --package-db=PACKAGE_DB list ghc-tags-plugin
After changing GHC versions, reinstall the plugin with the new compiler and
update PACKAGE_DB in cabal.project.local.
-
The plugin is safe for concurrent compilation, i.e. setting jobs: $ncpus is
safe. The plugin holds an exclusive (advisory) lock on a lock file. This
will create synchronisation between threads / process which are using
the same tags file.
-
Tag updates are written to a temporary file and renamed over the destination
only after a successful write. Interrupted compilation (including editor
cancellation) leaves the previous tags file intact. If tags were lost with an
older plugin version, rebuild the affected modules with -fforce-recomp to
regenerate them; an incremental build may skip those modules.
-
If you are working on a larger project, it might be better to not collect all
tags in a single tags file, since at every compilation step one will need
to parse a large tags file. Working with tag files of size 10000 tags (or
~1.5MB) is ok - though this will depend on the hardware.
-
If you're working on a project that is using safe-haskell, you will likely
need to pass
-fplugin-trustworthy
ghc flag.
● Security implications of compiler plugins
Such plugins can:
- run arbitrary
IO;
- modify abstract syntax tree in some way; a malicious plugin could change
some security parameter in your code exposing a security hole.
This plugin only reads & writes to tags file (and updates a shared mutable
state) as of IO, and does not
modify/
the syntax tree.
ghc-tags-fork is a fork of ghc-tags-hackage. Both provide a standalone
ghc-tags command. Unlike the hackage version [ghc-tags-fork] is using the
latest ghc-tags-core version.