Skip to content
30 changes: 19 additions & 11 deletions conan/cli/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,21 @@ def install(conan_api, parser, *args):
help="Generation strategy for virtual environment files for the root")
args = parser.parse_args(*args)
validate_common_graph_args(args)
# basic paths
cwd = os.getcwd()

deps_graph, lockfile = run_install_command(conan_api, args, cwd)

# Update lockfile if necessary
lockfile = conan_api.lockfile.update_lockfile(lockfile, deps_graph, args.lockfile_packages,
clean=args.lockfile_clean)
conan_api.lockfile.save_lockfile(lockfile, args.lockfile_out, cwd)
return {"graph": deps_graph,
"conan_api": conan_api}


def run_install_command(conan_api, args, cwd):
# basic paths

path = conan_api.local.get_conanfile_path(args.path, cwd, py=None) if args.path else None
source_folder = os.path.dirname(path) if args.path else cwd
output_folder = make_abs_path(args.output_folder, cwd) if args.output_folder else None
Expand Down Expand Up @@ -75,14 +88,9 @@ def install(conan_api, parser, *args):
conan_api.install.install_binaries(deps_graph=deps_graph, remotes=remotes)
ConanOutput().title("Finalizing install (deploy, generators)")
conan_api.install.install_consumer(deps_graph, args.generator, source_folder, output_folder,
deploy=args.deployer, deploy_package=args.deployer_package,
deploy_folder=args.deployer_folder,
envs_generation=args.envs_generation)
deploy=getattr(args, "deployer", None),
deploy_package=getattr(args, "deployer_package", None),
deploy_folder=getattr(args,"deployer_folder", None),
envs_generation=getattr(args, "envs_generation", None))
ConanOutput().success("Install finished successfully")

# Update lockfile if necessary
lockfile = conan_api.lockfile.update_lockfile(lockfile, deps_graph, args.lockfile_packages,
clean=args.lockfile_clean)
conan_api.lockfile.save_lockfile(lockfile, args.lockfile_out, cwd)
return {"graph": deps_graph,
"conan_api": conan_api}
return deps_graph, lockfile
42 changes: 42 additions & 0 deletions conan/cli/commands/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import os

from conan.api.output import ConanOutput, LEVEL_WARNING, LEVEL_VERBOSE, LEVEL_STATUS
from conan.cli.args import common_graph_args, validate_common_graph_args
from conan.cli.command import conan_command
from conan.cli.commands.install import run_install_command


@conan_command(group="Consumer")
def run(conan_api, parser, *args):
"""
Run a command given a set of requirements from a recipe or from command line.
"""
common_graph_args(parser)
parser.add_argument("command", help="Command to run", nargs='+')
parser.add_argument("--context", help="Context to use, host or build",
choices=["host", "build"], default="build")
parser.add_argument("-g", "--generator", action="append", help='Generators to use')
parser.add_argument("-of", "--output-folder",
help='The root output folder for generated and build files')
parser.add_argument("--build-require", action='store_true', default=False,
help='Whether the provided path is a build-require')
args = parser.parse_args(*args)
validate_common_graph_args(args)
command = " ".join(args.command)
cwd = os.getcwd()

# If conanfile is provided, delegate output_folder definition to the conanfile layout
# Otherwise, use a hidden folder to avoid cluttering the workspace
if not args.path:
args.output_folder = ".conanrun"

previous_log_level = ConanOutput._conan_output_level
if previous_log_level == LEVEL_STATUS:
ConanOutput._conan_output_level = LEVEL_WARNING
deps_graph, lockfile = run_install_command(conan_api, args, cwd)
ConanOutput._conan_output_level = previous_log_level

# TODO:
# - Tests
scope = "run" if args.context == "host" else "build"
deps_graph.root.conanfile.run(command, cwd=cwd, scope=scope)
1 change: 1 addition & 0 deletions conan/internal/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ def load_virtual(self, requires=None, tool_requires=None, python_requires=None,
# If user don't specify namespace in options, assume that it is
# for the reference (keep compatibility)
conanfile = ConanFile(display_name="cli")
conanfile._conan_helpers = self._conanfile_helpers

if tool_requires:
for reference in tool_requires:
Expand Down
53 changes: 53 additions & 0 deletions test/integration/command/test_run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import textwrap
import pytest
import platform

from conan.test.assets.genconanfile import GenConanfile
from conan.test.utils.tools import TestClient


@pytest.mark.parametrize("context", ["host", "build"])
@pytest.mark.parametrize("use_conanfile", [True, False])
def test_run(context, use_conanfile):
tc = TestClient()
conanfile = textwrap.dedent("""
from conan import ConanFile
from conan.tools.files import save
import os

class Pkg(ConanFile):
name = "pkg"
version = "0.1"
# So that the requirement is run=True even for --requires
package_type = "application"

def package(self):
save(self, os.path.join(self.package_folder, "bin", "myapp.sh"), "echo Hello World!")
save(self, os.path.join(self.package_folder, "bin", "myapp.bat"), "echo Hello World!")
# Make it executable
os.chmod(os.path.join(self.package_folder, "bin", "myapp.sh"), 0o755)
os.chmod(os.path.join(self.package_folder, "bin", "myapp.bat"), 0o755)
""")

conanfile_consumer = GenConanfile("consumer", "1.0").with_settings("os")
if context == "host":
conanfile_consumer.with_requires("pkg/0.1")
else:
conanfile_consumer.with_tool_requires("pkg/0.1")

tc.save({"pkg/conanfile.py": conanfile, "conanfile.py": conanfile_consumer })
tc.run("create pkg")
requires = "requires" if context == "host" else "tool-requires"

if use_conanfile:
if platform.system() == "Windows":
tc.run(f"run . myapp.bat --context={context}")
else:
tc.run(f"run . myapp.sh --context={context}")
else:
if platform.system() == "Windows":
tc.run(f"run myapp.bat --{requires}=pkg/0.1 --context={context}")
else:
tc.run(f"run myapp.sh --{requires}=pkg/0.1 --context={context}")
assert "Hello World!" in tc.out

Loading