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:

  1. | - [YOUR_CONAN_HOME]/extensions/commands/greet/
  2. | - cmd_hello.py
  3. | - cmd_bye.py

The call to those commands change a little bit: conan [topic_name]:your_command_name. Following the previous example:

  1. $ conan greet:hello
  2. $ 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 the key is the formatter name and the value is the function instance where will be processed the information returned by the command one.

cmd_hello.py

  1. import json
  2. from conan.api.conan_api import ConanAPI
  3. from conan.api.output import ConanOutput
  4. from conan.cli.command import conan_command
  5. def output_json(msg):
  6. return json.dumps({"greet": msg})
  7. @conan_command(group="Custom commands", formatters={"json": output_json})
  8. def hello(conan_api: ConanAPI, parser, *args):
  9. """
  10. Simple command to print "Hello World!" line
  11. """
  12. msg = "Hello World!"
  13. ConanOutput().info(msg)
  14. 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:

cmd_hello.py

  1. from conan.api.conan_api import ConanAPI
  2. from conan.api.output import ConanOutput
  3. from conan.cli.command import conan_command, conan_subcommand
  4. @conan_subcommand()
  5. def hello_moon(conan_api, parser, subparser, *args):
  6. """
  7. Sub-command of "hello" that prints "Hello Moon!" line
  8. """
  9. ConanOutput().info("Hello Moon!")
  10. @conan_command(group="Custom commands")
  11. def hello(conan_api: ConanAPI, parser, *args):
  12. """
  13. Simple command "hello"
  14. """

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.

  1. @conan_command(group='Creator')
  2. def build(conan_api, parser, *args):
  3. """
  4. Command help
  5. """
  6. parser.add_argument("path", nargs="?", help='help for command')
  7. ...
  8. args = parser.parse_args(*args)
  9. # 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.

  1. @conan_command(group="MyGroup")
  2. def mycommand(conan_api, parser, *args):
  3. """
  4. Command help
  5. """
  6. # Do not define arguments in the base command
  7. pass
  8. @conan_subcommand()
  9. def mycommand_mysubcommand(conan_api: ConanAPI, parser, subparser, *args):
  10. """
  11. Subcommand help
  12. """
  13. # Arguments are added to "subparser"
  14. subparser.add_argument("reference", help="Recipe reference or Package reference")
  15. # You can add common args with your helper
  16. # add_my_common_args(subparser)
  17. # But parsing all of them happens to "parser"
  18. args = parser.parse_args(*args)
  19. # 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:

  1. def format_graph_html(result):
  2. graph = result["graph"]
  3. conan_api = result["conan_api"]
  4. ...
  5. def format_graph_info(result):
  6. graph = result["graph"]
  7. field_filter = result["field_filter"]
  8. package_filter = result["package_filter"]
  9. ...
  10. @conan_subcommand(formatters={"text": format_graph_info,
  11. "html": format_graph_html,
  12. "json": format_graph_json,
  13. "dot": format_graph_dot})
  14. def graph_info(conan_api, parser, subparser, *args):
  15. ...
  16. return {"graph": deps_graph,
  17. "field_filter": args.filter,
  18. "package_filter": args.package_filter,
  19. "conan_api": conan_api}

Commands parameters

These are the passed arguments to any custom command and its sub-commands functions:

cmd_command.py

  1. from conan.cli.command import conan_command, conan_subcommand
  2. @conan_subcommand()
  3. def command_subcommand(conan_api, parser, subparser, *args):
  4. """
  5. subcommand information. This info will appear on ``conan command subcommand -h``.
  6. :param conan_api: <object conan.api.conan_api.ConanAPI> instance
  7. :param parser: root <object argparse.ArgumentParser> instance (coming from main command)
  8. :param subparser: <object argparse.ArgumentParser> instance for sub-command
  9. :param args: ``list`` of all the arguments passed after sub-command call
  10. :return: (optional) whatever is returned will be passed to formatters functions (if declared)
  11. """
  12. # ...
  13. @conan_command(group="Custom commands")
  14. def command(conan_api, parser, *args):
  15. """
  16. command information. This info will appear on ``conan command -h``.
  17. :param conan_api: <object conan.api.conan_api.ConanAPI> instance
  18. :param parser: root <object argparse.ArgumentParser> instance
  19. :param args: ``list`` of all the arguments passed after command call
  20. :return: (optional) whatever is returned will be passed to formatters functions (if declared)
  21. """
  22. # ...
  • conan_api: instance of ConanAPI class. See more about it in conan.api.conan_api.ConanAPI section

  • parser: root instance of Python argparse.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 Python argparse.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 as args = 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