Skip to content

Instantly share code, notes, and snippets.

@valgur
Created April 24, 2026 20:52
Show Gist options
  • Select an option

  • Save valgur/bbc47efa1b64fbf86c63f4f161f8b1c7 to your computer and use it in GitHub Desktop.

Select an option

Save valgur/bbc47efa1b64fbf86c63f4f161f8b1c7 to your computer and use it in GitHub Desktop.
Bash completions for Conan
# Bash completion for Conan 2.x CLI
# Save under ~/.local/share/bash-completion/completions/conan
#
# Features:
# - All subcommands and their options
# - --version: versions from $PWD/conandata.yml
# - -o/-o:a/-o:b/-o:h: options from $PWD/conanfile.py (binary choices
# auto-filled with non-default value); -o:a/-o:b/-o:h also offer *:shared=True
# - -pr/-pr:a/-pr:b/-pr:h: profiles from ~/.conan2/profiles/
# - -c/-c:a/-c:b/-c:h: config keys from `conan config list`
# - -s/-s:a/-s:b/-s:h: common setting keys (os, arch, compiler.*, cuda.*)
# - -r/--remote: remote names from `conan remote list`
# - conan remove: package refs from local cache
#
# SPDX-License-Identifier: CC0-1.0
_conan_versions_from_conandata() {
local f="$PWD/conandata.yml"
[[ -f "$f" ]] || return
sed -n '/^sources:/,/^[^ ]/{s/^ "\(.*\)":/\1/p}' "$f" 2>/dev/null
}
_conan_options_from_conanfile() {
local f="$PWD/conanfile.py"
[[ -f "$f" ]] || return
python3 -c "
import re, ast, sys
with open('$f') as f:
c = f.read()
mo = re.search(r'options\s*=\s*(\{[^}]+\})', c)
md = re.search(r'default_options\s*=\s*(\{[^}]+\})', c)
if not mo: sys.exit()
opts = ast.literal_eval(mo.group(1))
defs = ast.literal_eval(md.group(1)) if md else {}
for name, vals in opts.items():
d = defs.get(name)
if len(vals) == 2 and d is not None:
other = vals[1] if vals[0] == d else vals[0]
print(f'{name}={other}')
else:
print(f'{name}=')
" 2>/dev/null
}
_conan_profiles() {
local dir="${CONAN_HOME:-$HOME/.conan2}/profiles"
[[ -d "$dir" ]] && command ls "$dir" 2>/dev/null
}
_conan_conf_keys() {
conan config list 2>/dev/null | sed -n 's/^\([a-z][a-z.:_]*\):.*/\1/p'
}
_conan_remotes() {
conan remote list 2>/dev/null | sed -n 's/:.*//p'
}
_conan_cached_refs() {
conan list --format=compact 2>/dev/null | sed -n 's/^ //p'
}
_conan_complete_option() {
local cur="$1" include_star="$2"
local opts completions=""
if [[ "$include_star" == "1" ]]; then
completions="*:shared=True"
fi
opts=$(_conan_options_from_conanfile)
if [[ -n "$opts" ]]; then
[[ -n "$completions" ]] && completions+=$'\n'
completions+="$opts"
fi
[[ -z "$completions" ]] && return
if [[ "$cur" == *=* ]]; then
return
fi
COMPREPLY=($(compgen -W "$completions" -- "$cur"))
compopt -o nospace
}
_conan() {
local cur prev words cword
_init_completion || return
local commands="cache config graph inspect install list lock pkglist profile remote remove require run search version workspace build create download editable export export-pkg new source test upload audit report"
local common_build_opts="-h --help -f --format -v -b --build -r --remote -nr --no-remote -u --update -pr -pr:b -pr:h -pr:a -o -o:b -o:h -o:a -s -s:b -s:h -s:a -c -c:b -c:h -c:a --name --version --user --channel -l --lockfile"
# Find the subcommand
local subcmd=""
local i
for ((i = 1; i < cword; i++)); do
case "${words[i]}" in
-*) continue ;;
*)
if [[ " $commands " == *" ${words[i]} "* ]]; then
subcmd="${words[i]}"
break
fi
;;
esac
done
# Handle option arguments
case "$prev" in
--version)
if [[ -n "$subcmd" ]]; then
local versions
versions=$(_conan_versions_from_conandata)
if [[ -n "$versions" ]]; then
COMPREPLY=($(compgen -W "$versions" -- "$cur"))
return
fi
fi
;;
-o)
_conan_complete_option "$cur" 0
return
;;
-o:b|-o:h|-o:a)
_conan_complete_option "$cur" 1
return
;;
-pr|-pr:b|-pr:h|-pr:a|--profile)
local profiles
profiles=$(_conan_profiles)
if [[ -n "$profiles" ]]; then
COMPREPLY=($(compgen -W "$profiles" -- "$cur"))
fi
return
;;
-c|-c:b|-c:h|-c:a|--conf)
local keys
keys=$(_conan_conf_keys)
if [[ -n "$keys" ]]; then
COMPREPLY=($(compgen -W "$keys" -- "$cur"))
compopt -o nospace
fi
return
;;
-b|--build)
COMPREPLY=($(compgen -W "missing" -- "$cur"))
return
;;
-r|--remote)
local remotes
remotes=$(_conan_remotes)
[[ -n "$remotes" ]] && COMPREPLY=($(compgen -W "$remotes" -- "$cur"))
return
;;
-s|-s:b|-s:h|-s:a)
COMPREPLY=($(compgen -W "os= arch= build_type= compiler= compiler.version= compiler.cppstd= compiler.cstd= compiler.libcxx= compiler.runtime= cuda= cuda.version= cuda.architectures=" -- "$cur"))
compopt -o nospace
return
;;
-d|--deployer)
COMPREPLY=($(compgen -W "full_deploy direct_deploy runtime_deploy" -- "$cur"))
return
;;
-g|--generator)
COMPREPLY=($(compgen -W "CMakeDeps CMakeToolchain MSBuildDeps MSBuildToolchain MesonToolchain PkgConfigDeps" -- "$cur"))
return
;;
-v)
COMPREPLY=($(compgen -W "quiet error warning notice status verbose debug trace" -- "$cur"))
return
;;
esac
# Handle --build=, -b= style (equals-attached)
if [[ "$cur" == --build=* ]]; then
COMPREPLY=($(compgen -W "--build=missing" -- "$cur"))
return
fi
if [[ -z "$subcmd" ]]; then
if [[ "$cur" == -* ]]; then
COMPREPLY=($(compgen -W "-h --help -v --version" -- "$cur"))
else
COMPREPLY=($(compgen -W "$commands" -- "$cur"))
fi
return
fi
# Per-subcommand completions
case "$subcmd" in
install)
COMPREPLY=($(compgen -W "$common_build_opts -g --generator -of --output-folder -d --deployer --requires --tool-requires" -- "$cur"))
;;
create)
COMPREPLY=($(compgen -W "$common_build_opts -tf --test-folder -tm --test-missing -bt --build-test --build-require" -- "$cur"))
;;
build)
COMPREPLY=($(compgen -W "$common_build_opts -g --generator -of --output-folder -d --deployer" -- "$cur"))
;;
export)
COMPREPLY=($(compgen -W "-h --help -f --format -v --name --version --user --channel -r --remote -nr --no-remote -l --lockfile" -- "$cur"))
;;
test)
COMPREPLY=($(compgen -W "$common_build_opts" -- "$cur"))
;;
source)
COMPREPLY=($(compgen -W "-h --help -v --name --version --user --channel" -- "$cur"))
;;
inspect)
COMPREPLY=($(compgen -W "-h --help -f --format -v" -- "$cur"))
;;
search)
COMPREPLY=($(compgen -W "-h --help -f --format -v -r --remote" -- "$cur"))
;;
list)
if [[ "$cur" == -* ]]; then
COMPREPLY=($(compgen -W "-h --help -f --format -v -r --remote -p --package-query" -- "$cur"))
fi
;;
upload)
if [[ "$cur" == -* ]]; then
COMPREPLY=($(compgen -W "-h --help -f --format -v -r --remote --check --confirm -p --package-query -l --lockfile --only-recipe" -- "$cur"))
fi
;;
download)
if [[ "$cur" == -* ]]; then
COMPREPLY=($(compgen -W "-h --help -f --format -v -r --remote -p --package-query -l --lockfile" -- "$cur"))
fi
;;
remove)
if [[ "$cur" == -* ]]; then
COMPREPLY=($(compgen -W "-h --help -f --format -v -r --remote --confirm -p --package-query -l --lockfile" -- "$cur"))
else
local refs
refs=$(_conan_cached_refs)
[[ -n "$refs" ]] && COMPREPLY=($(compgen -W "$refs" -- "$cur"))
fi
;;
graph)
# Find graph subcommand
local graphsub=""
for ((i = 2; i < cword; i++)); do
case "${words[i]}" in
info|build-order|build-order-merge|explain|outdated)
graphsub="${words[i]}"
break ;;
esac
done
if [[ -z "$graphsub" ]]; then
COMPREPLY=($(compgen -W "info build-order build-order-merge explain outdated" -- "$cur"))
elif [[ "$cur" == -* ]]; then
COMPREPLY=($(compgen -W "$common_build_opts -f --format" -- "$cur"))
fi
;;
profile)
local profsub=""
for ((i = 2; i < cword; i++)); do
case "${words[i]}" in
detect|list|path|show) profsub="${words[i]}"; break ;;
esac
done
if [[ -z "$profsub" ]]; then
COMPREPLY=($(compgen -W "detect list path show" -- "$cur"))
elif [[ "$profsub" == "show" || "$profsub" == "path" ]]; then
local profiles
profiles=$(_conan_profiles)
[[ -n "$profiles" ]] && COMPREPLY=($(compgen -W "$profiles -h --help" -- "$cur"))
else
COMPREPLY=($(compgen -W "-h --help" -- "$cur"))
fi
;;
remote)
local remsub=""
for ((i = 2; i < cword; i++)); do
case "${words[i]}" in
add|disable|enable|list|login|logout|remove|rename|update) remsub="${words[i]}"; break ;;
esac
done
if [[ -z "$remsub" ]]; then
COMPREPLY=($(compgen -W "add disable enable list login logout remove rename update" -- "$cur"))
else
COMPREPLY=($(compgen -W "-h --help" -- "$cur"))
fi
;;
config)
local confsub=""
for ((i = 2; i < cword; i++)); do
case "${words[i]}" in
home|install|install-pkg|list|show) confsub="${words[i]}"; break ;;
esac
done
if [[ -z "$confsub" ]]; then
COMPREPLY=($(compgen -W "home install install-pkg list show" -- "$cur"))
else
COMPREPLY=($(compgen -W "-h --help" -- "$cur"))
fi
;;
cache)
local cachesub=""
for ((i = 2; i < cword; i++)); do
case "${words[i]}" in
check-integrity|clean|path|save|restore) cachesub="${words[i]}"; break ;;
esac
done
if [[ -z "$cachesub" ]]; then
COMPREPLY=($(compgen -W "check-integrity clean path save restore" -- "$cur"))
else
COMPREPLY=($(compgen -W "-h --help" -- "$cur"))
fi
;;
new)
if [[ "$cur" == -* ]]; then
COMPREPLY=($(compgen -W "-h --help -v -d --define -f --force" -- "$cur"))
fi
;;
editable)
local edsub=""
for ((i = 2; i < cword; i++)); do
case "${words[i]}" in
add|remove|list) edsub="${words[i]}"; break ;;
esac
done
if [[ -z "$edsub" ]]; then
COMPREPLY=($(compgen -W "add remove list" -- "$cur"))
else
COMPREPLY=($(compgen -W "-h --help" -- "$cur"))
fi
;;
lock)
local locksub=""
for ((i = 2; i < cword; i++)); do
case "${words[i]}" in
add|create|merge|remove|update) locksub="${words[i]}"; break ;;
esac
done
if [[ -z "$locksub" ]]; then
COMPREPLY=($(compgen -W "add create merge remove update" -- "$cur"))
else
COMPREPLY=($(compgen -W "-h --help" -- "$cur"))
fi
;;
*)
COMPREPLY=($(compgen -W "-h --help" -- "$cur"))
;;
esac
}
complete -F _conan conan
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment