Custom commands¶
It’s possible to create your own Conan commands to solve self-needs thanks to Python and Conan public API powers altogether.
Location and naming¶
All the custom commands must be located in [YOUR_CONAN_HOME]/extensions/commands/
folder. If you don’t know where
[YOUR_CONAN_HOME]
is located, you can run conan config home to check it.
If _commands_ sub-directory is not created yet, you will have to create it. Those custom commands files must be Python
files and start with the prefix cmd_[your_command_name].py
. The call to the custom commands is like any other
existing Conan one: conan your_command_name.
Scoping¶
It’s possible to have another folder layer to group some commands under the same topic.
For instance:
| - [YOUR_CONAN_HOME]/extensions/commands/greet/
| - cmd_hello.py
| - cmd_bye.py
The call to those commands change a little bit: conan [topic_name]:your_command_name. Following the previous example:
$ conan greet:hello
$ conan greet:bye
Note
It’s possible for only one folder layer, so it won’t work to have something like
[YOUR_CONAN_HOME]/extensions/commands/topic1/topic2/cmd_command.py
Decorators¶
conan_command(group=None, formatters=None)¶
Main decorator to declare a function as a new Conan command. Where the parameters are:
group
is the name of the group of commands declared under the same name. This grouping will appear executing the conan -h command.formatters
is a dict-like Python object where thekey
is the formatter name and the value is the function instance where will be processed the information returned by the command one.
import json
from conan.api.conan_api import ConanAPI
from conan.api.output import ConanOutput
from conan.cli.command import conan_command
def output_json(msg):
return json.dumps({"greet": msg})
@conan_command(group="Custom commands", formatters={"json": output_json})
def hello(conan_api: ConanAPI, parser, *args):
"""
Simple command to print "Hello World!" line
"""
msg = "Hello World!"
ConanOutput().info(msg)
return msg
Important
The function decorated by @conan_command(....)
must have the same name as the suffix used by the Python file.
For instance, the previous example, the file name is cmd_hello.py
, and the command function decorated is def hello(....)
.
conan_subcommand(formatters=None)¶
Similar to conan_command
, but this one is declaring a sub-command of an existing custom command. For instance:
from conan.api.conan_api import ConanAPI
from conan.api.output import ConanOutput
from conan.cli.command import conan_command, conan_subcommand
@conan_subcommand()
def hello_moon(conan_api, parser, subparser, *args):
"""
Sub-command of "hello" that prints "Hello Moon!" line
"""
ConanOutput().info("Hello Moon!")
@conan_command(group="Custom commands")
def hello(conan_api: ConanAPI, parser, *args):
"""
Simple command "hello"
"""
The command call looks like conan hello moon.
Note
Notice that to declare a sub-command is required an empty Python function acts as the main command.
Argument definition and parsing¶
Commands can define their own arguments with the argparse
Python library.
@conan_command(group='Creator')
def build(conan_api, parser, *args):
"""
Command help
"""
parser.add_argument("path", nargs="?", help='help for command')
...
args = parser.parse_args(*args)
# Use args.path
When there are sub-commands, the base command cannot define arguments, only the sub-commands can do it. If you have a set of common arguments to all sub-commands, you can define a function that adds them.
@conan_command(group="MyGroup")
def mycommand(conan_api, parser, *args):
"""
Command help
"""
# Do not define arguments in the base command
pass
@conan_subcommand()
def mycommand_mysubcommand(conan_api: ConanAPI, parser, subparser, *args):
"""
Subcommand help
"""
# Arguments are added to "subparser"
subparser.add_argument("reference", help="Recipe reference or Package reference")
# You can add common args with your helper
# add_my_common_args(subparser)
# But parsing all of them happens to "parser"
args = parser.parse_args(*args)
# use args.reference
Formatters¶
The return of the command will be passed as argument to the formatters. If there are different formatters that
require different arguments, the approach is to return a dictionary, and let the formatters chose the
arguments they need. For example, the graph info
command uses several formatters like:
def format_graph_html(result):
graph = result["graph"]
conan_api = result["conan_api"]
...
def format_graph_info(result):
graph = result["graph"]
field_filter = result["field_filter"]
package_filter = result["package_filter"]
...
@conan_subcommand(formatters={"text": format_graph_info,
"html": format_graph_html,
"json": format_graph_json,
"dot": format_graph_dot})
def graph_info(conan_api, parser, subparser, *args):
...
return {"graph": deps_graph,
"field_filter": args.filter,
"package_filter": args.package_filter,
"conan_api": conan_api}
Commands parameters¶
These are the passed arguments to any custom command and its sub-commands functions:
from conan.cli.command import conan_command, conan_subcommand
@conan_subcommand()
def command_subcommand(conan_api, parser, subparser, *args):
"""
subcommand information. This info will appear on ``conan command subcommand -h``.
:param conan_api: <object conan.api.conan_api.ConanAPI> instance
:param parser: root <object argparse.ArgumentParser> instance (coming from main command)
:param subparser: <object argparse.ArgumentParser> instance for sub-command
:param args: ``list`` of all the arguments passed after sub-command call
:return: (optional) whatever is returned will be passed to formatters functions (if declared)
"""
# ...
@conan_command(group="Custom commands")
def command(conan_api, parser, *args):
"""
command information. This info will appear on ``conan command -h``.
:param conan_api: <object conan.api.conan_api.ConanAPI> instance
:param parser: root <object argparse.ArgumentParser> instance
:param args: ``list`` of all the arguments passed after command call
:return: (optional) whatever is returned will be passed to formatters functions (if declared)
"""
# ...
conan_api
: instance ofConanAPI
class. See more about it in conan.api.conan_api.ConanAPI sectionparser
: root instance of Pythonargparse.ArgumentParser
class to be used by the main command function. See more information in argparse official website.subparser
(only for sub-commands): child instance of Pythonargparse.ArgumentParser
class for each sub-command function.*args
: list of all the arguments passed via command line to be parsed and used inside the command function. Normally, they’ll be parsed asargs = parser.parse_args(*args)
. For instance, running conan mycommand arg1 arg2 arg3, the command function will receive them as a Python list-like["arg1", "arg2", "arg3"]
.
See also
You can check more examples of Conan custom command in the conan-extensions repository https://github.com/conan-io/conan-extensions