Skip to content

Commit

Permalink
Add shorthand syntax in cli to specify host and build in 1 argument (#…
Browse files Browse the repository at this point in the history
…14727)

* Add shorthand for passing install args to both host & build context

* Add shorthand test

* Fix tests

* Try workaround for global.conf all profile

* Only modify settings, no need to compose new profiles

* Improve profiles args help messages

* Nicer python-fu

* Compromise between every possible option

* Remove sketch test for unrelated bug

* Better shorthand handling

* Tests!

* Remove unrelated test

* Simplify code

* Fix tests

* Show that mixing and matching works as expected

---------

Co-authored-by: James <james@conan.io>
  • Loading branch information
AbrilRBS and memsharded authored Oct 18, 2023
1 parent 48656d8 commit 91c5860
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 44 deletions.
9 changes: 4 additions & 5 deletions conan/api/subapi/profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,20 @@ def get_default_build(self):
return default_profile

def get_profiles_from_args(self, args):
build = [self.get_default_build()] if not args.profile_build else args.profile_build
host = [self.get_default_host()] if not args.profile_host else args.profile_host
build_profiles = args.profile_build or [self.get_default_build()]
host_profiles = args.profile_host or [self.get_default_host()]

cache = ClientCache(self._conan_api.cache_folder)
global_conf = cache.new_config
global_conf.validate() # TODO: Remove this from here
cache_settings = self._settings()
profile_plugin = self._load_profile_plugin()
cwd = os.getcwd()
profile_build = self._get_profile(build, args.settings_build, args.options_build,
profile_build = self._get_profile(build_profiles, args.settings_build, args.options_build,
args.conf_build, cwd, cache_settings,
profile_plugin, global_conf)
profile_host = self._get_profile(host, args.settings_host, args.options_host, args.conf_host,
profile_host = self._get_profile(host_profiles, args.settings_host, args.options_host, args.conf_host,
cwd, cache_settings, profile_plugin, global_conf)

return profile_host, profile_build

def get_profile(self, profiles, settings=None, options=None, conf=None, cwd=None):
Expand Down
78 changes: 41 additions & 37 deletions conan/cli/args.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import argparse

from conan.cli.command import OnceArgument
from conan.errors import ConanException

Expand Down Expand Up @@ -56,45 +58,47 @@ def add_common_install_arguments(parser):


def add_profiles_args(parser):
def profile_args(machine, short_suffix="", long_suffix=""):
parser.add_argument("-pr{}".format(short_suffix),
"--profile{}".format(long_suffix),
default=None, action="append",
dest='profile_{}'.format(machine),
help='Apply the specified profile to the {} machine'.format(machine))

def settings_args(machine, short_suffix="", long_suffix=""):
parser.add_argument("-s{}".format(short_suffix),
"--settings{}".format(long_suffix),
action="append",
dest='settings_{}'.format(machine),
help='Settings to build the package, overwriting the defaults'
' ({} machine). e.g.: -s{} compiler=gcc'.format(machine,
short_suffix))

def options_args(machine, short_suffix="", long_suffix=""):
parser.add_argument("-o{}".format(short_suffix),
"--options{}".format(long_suffix),
action="append",
dest="options_{}".format(machine),
help='Define options values ({} machine), e.g.:'
' -o{} Pkg:with_qt=true'.format(machine, short_suffix))
contexts = ["build", "host"]

# This comes from the _AppendAction code but modified to add to the contexts
class ContextAllAction(argparse.Action):

def __call__(self, action_parser, namespace, values, option_string=None):
for context in contexts:
items = getattr(namespace, self.dest + "_" + context, None)
items = items[:] if items else []
items.append(values)
setattr(namespace, self.dest + "_" + context, items)

def conf_args(machine, short_suffix="", long_suffix=""):
parser.add_argument("-c{}".format(short_suffix),
"--conf{}".format(long_suffix),
def create_config(short, long, example=None):
parser.add_argument(f"-{short}", f"--{long}",
default=None,
action="append",
dest='conf_{}'.format(machine),
help='Configuration to build the package, overwriting the defaults'
' ({} machine). e.g.: -c{} '
'tools.cmake.cmaketoolchain:generator=Xcode'.format(machine,
short_suffix))

for item_fn in [options_args, profile_args, settings_args, conf_args]:
item_fn("host", "",
"") # By default it is the HOST, the one we are building binaries for
item_fn("build", ":b", ":build")
item_fn("host", ":h", ":host")
dest=f"{long}_host",
metavar=long.upper(),
help='Apply the specified profile. '
f'By default, or if specifying -{short}:h (--{long}:host), it applies to the host context. '
f'Use -{short}:b (--{long}:build) to specify the build context, '
f'or -{short}:a (--{long}:all) to specify both contexts at once'
+ ('' if not example else f". Example: {example}"))
for context in contexts:
parser.add_argument(f"-{short}:{context[0]}", f"--{long}:{context}",
default=None,
action="append",
dest=f"{long}_{context}",
help="")

parser.add_argument(f"-{short}:a", f"--{long}:all",
default=None,
action=ContextAllAction,
dest=long,
metavar=f"{long.upper()}_ALL",
help="")

create_config("pr", "profile")
create_config("o", "options", "-o pkg:with_qt=true")
create_config("s", "settings", "-s compiler=gcc")
create_config("c", "conf", "-c tools.cmake.cmaketoolchain:generator=Xcode")


def add_reference_args(parser):
Expand Down
102 changes: 101 additions & 1 deletion conans/test/integration/command/test_profile.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import os
import textwrap

from conans.test.utils.tools import TestClient

Expand Down Expand Up @@ -31,11 +32,110 @@ def test_ignore_paths_when_listing_profiles():
assert ignore_path not in c.out


def test_shorthand_syntax():
tc = TestClient()
tc.save({"profile": "[conf]\nuser.profile=True"})
tc.run("profile show -h")
assert "[-pr:b" in tc.out
assert "[-pr:h" in tc.out
assert "[-pr:a" in tc.out

tc.run(
"profile show -o:a=both_options=True -pr:a=profile -s:a=os=WindowsCE -s:a=os.platform=conan -c:a=user.conf.cli=True -f=json")

out = json.loads(tc.out)
assert out == {'build': {'build_env': '',
'conf': {'user.conf.cli': True, 'user.profile': True},
'options': {'both_options': 'True'},
'package_settings': {},
'settings': {'os': 'WindowsCE', 'os.platform': 'conan'},
'tool_requires': {}},
'host': {'build_env': '',
'conf': {'user.conf.cli': True, 'user.profile': True},
'options': {'both_options': 'True'},
'package_settings': {},
'settings': {'os': 'WindowsCE', 'os.platform': 'conan'},
'tool_requires': {}}}

tc.save({"pre": textwrap.dedent("""
[settings]
os=Linux
compiler=gcc
compiler.version=11
"""),
"mid": textwrap.dedent("""
[settings]
compiler=clang
compiler.version=14
"""),
"post": textwrap.dedent("""
[settings]
compiler.version=13
""")})

tc.run("profile show -pr:a=pre -pr:a=mid -pr:a=post -f=json")
out = json.loads(tc.out)
assert out == {'build': {'build_env': '',
'conf': {},
'options': {},
'package_settings': {},
'settings': {'compiler': 'clang',
'compiler.version': '13',
'os': 'Linux'},
'tool_requires': {}},
'host': {'build_env': '',
'conf': {},
'options': {},
'package_settings': {},
'settings': {'compiler': 'clang',
'compiler.version': '13',
'os': 'Linux'},
'tool_requires': {}}}

tc.run("profile show -pr:a=pre -pr:h=post -f=json")
out = json.loads(tc.out)
assert out == {'build': {'build_env': '',
'conf': {},
'options': {},
'package_settings': {},
'settings': {'compiler': 'gcc',
'compiler.version': '11',
'os': 'Linux'},
'tool_requires': {}},
'host': {'build_env': '',
'conf': {},
'options': {},
'package_settings': {},
'settings': {'compiler': 'gcc',
'compiler.version': '13',
'os': 'Linux'},
'tool_requires': {}}}

tc.run("profile show -pr:a=pre -o:b foo=False -o:a foo=True -o:h foo=False -f=json")
out = json.loads(tc.out)
assert out == {'build': {'build_env': '',
'conf': {},
'options': {'foo': 'True'},
'package_settings': {},
'settings': {'compiler': 'gcc',
'compiler.version': '11',
'os': 'Linux'},
'tool_requires': {}},
'host': {'build_env': '',
'conf': {},
'options': {'foo': 'False'},
'package_settings': {},
'settings': {'compiler': 'gcc',
'compiler.version': '11',
'os': 'Linux'},
'tool_requires': {}}}


def test_profile_show_json():
c = TestClient()
c.save({"myprofilewin": "[settings]\nos=Windows",
"myprofilelinux": "[settings]\nos=Linux"})
c.run("profile show -pr:b=myprofilewin -pr:h=myprofilelinux --format=json")
profile = json.loads(c.stdout)
assert profile["build"]["settings"] == {"os": "Windows"}
assert profile["build"]["settings"] == {"os": "Windows"}
assert profile["host"]["settings"] == {"os": "Linux"}
6 changes: 5 additions & 1 deletion conans/test/unittests/cli/common_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,16 @@ def argparse_args():
return MagicMock(
profile_build=None,
profile_host=None,
profile_all=None,
settings_build=None,
settings_host=None,
settings_all=None,
options_build=None,
options_host=None,
options_all=None,
conf_build=None,
conf_host=None
conf_host=None,
conf_all=None,
)


Expand Down

0 comments on commit 91c5860

Please sign in to comment.