Skip to content

Commit

Permalink
[dev] Added env variable to append other custom command location (#15436
Browse files Browse the repository at this point in the history
)

* Added internal env variable _CONAN_INTERNAL_CUSTOM_COMMANDS_PATH to append custom commands location to the Conan one

* Added extra comments
  • Loading branch information
franramirez688 authored Jan 10, 2024
1 parent 96e6dda commit 40c3d99
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 24 deletions.
58 changes: 34 additions & 24 deletions conan/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,35 +47,43 @@ def _add_commands(self):
for k, v in self._commands.items(): # Fill groups data too
self._groups[v.group].append(k)

custom_commands_path = HomePaths(self._conan_api.cache_folder).custom_commands_path
if not os.path.isdir(custom_commands_path):
return
conan_custom_commands_path = HomePaths(self._conan_api.cache_folder).custom_commands_path
# Important! This variable should be only used for testing/debugging purpose
developer_custom_commands_path = os.getenv("_CONAN_INTERNAL_CUSTOM_COMMANDS_PATH")
# Notice that in case of having same custom commands file names, the developer one has
# preference over the Conan default location because of the sys.path.append(xxxx)
custom_commands_folders = [developer_custom_commands_path, conan_custom_commands_path] \
if developer_custom_commands_path else [conan_custom_commands_path]

for custom_commands_path in custom_commands_folders:
if not os.path.isdir(custom_commands_path):
return

sys.path.append(custom_commands_path)
for module in pkgutil.iter_modules([custom_commands_path]):
module_name = module[1]
if module_name.startswith("cmd_"):
try:
self._add_command(module_name, module_name.replace("cmd_", ""))
except Exception as e:
ConanOutput().error(f"Error loading custom command '{module_name}.py': {e}",
error_type="exception")
# layers
for folder in os.listdir(custom_commands_path):
layer_folder = os.path.join(custom_commands_path, folder)
sys.path.append(layer_folder)
if not os.path.isdir(layer_folder):
continue
for module in pkgutil.iter_modules([layer_folder]):
sys.path.append(custom_commands_path)
for module in pkgutil.iter_modules([custom_commands_path]):
module_name = module[1]
if module_name.startswith("cmd_"):
module_path = f"{folder}.{module_name}"
try:
self._add_command(module_path, module_name.replace("cmd_", ""),
package=folder)
self._add_command(module_name, module_name.replace("cmd_", ""))
except Exception as e:
ConanOutput().error(f"Error loading custom command {module_path}: {e}",
ConanOutput().error(f"Error loading custom command '{module_name}.py': {e}",
error_type="exception")
# layers
for folder in os.listdir(custom_commands_path):
layer_folder = os.path.join(custom_commands_path, folder)
sys.path.append(layer_folder)
if not os.path.isdir(layer_folder):
continue
for module in pkgutil.iter_modules([layer_folder]):
module_name = module[1]
if module_name.startswith("cmd_"):
module_path = f"{folder}.{module_name}"
try:
self._add_command(module_path, module_name.replace("cmd_", ""),
package=folder)
except Exception as e:
ConanOutput().error(f"Error loading custom command {module_path}: {e}",
error_type="exception")

def _add_command(self, import_path, method_name, package=None):
try:
Expand All @@ -84,7 +92,9 @@ def _add_command(self, import_path, method_name, package=None):
if command_wrapper.doc:
name = f"{package}:{command_wrapper.name}" if package else command_wrapper.name
self._commands[name] = command_wrapper
self._groups[command_wrapper.group].append(name)
# Avoiding duplicated command help messages
if name not in self._groups[command_wrapper.group]:
self._groups[command_wrapper.group].append(name)
for name, value in getmembers(imported_module):
if isinstance(value, ConanSubCommand):
if name.startswith("{}_".format(method_name)):
Expand Down
32 changes: 32 additions & 0 deletions conans/test/integration/command_v2/custom_commands_test.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import os
import textwrap

from conans.test.utils.test_files import temp_folder
from conans.test.utils.tools import TestClient
from conans.util.env import environment_update


class TestCustomCommands:
Expand Down Expand Up @@ -254,3 +256,33 @@ def mycommand(conan_api, parser, *args, **kwargs):
client.run("danimtb:mycommand")
foldername = os.path.basename(client.cache_folder)
assert f'Conan cache folder from cmd_mycode: {foldername}' in client.out

def test_custom_command_from_other_location(self):
"""
Tests that setting developer env variable ``_CONAN_INTERNAL_CUSTOM_COMMANDS_PATH``
will append that folder to the Conan custom command default location.
"""
myhello = textwrap.dedent("""
from conan.api.output import cli_out_write
from conan.cli.command import conan_command
@conan_command(group="custom commands")
def hello(conan_api, parser, *args, **kwargs):
'''
My Hello doc
'''
cli_out_write("Hello {}!")
""")

client = TestClient()
my_local_layer_path = temp_folder(path_with_spaces=False)
layer_path = os.path.join(client.cache_folder, 'extensions', 'commands')
client.save({os.path.join(layer_path, 'cmd_hello.py'): myhello.format("world")})
client.save({"cmd_hello.py": myhello.format("Overridden")}, path=my_local_layer_path)
with environment_update({"_CONAN_INTERNAL_CUSTOM_COMMANDS_PATH": my_local_layer_path}):
client.run("hello")
# Local commands have preference over Conan custom ones if they collide
assert "Hello Overridden!" in client.out
# Without the variable it only loads the default custom commands location
client.run("hello")
assert "Hello world!" in client.out

0 comments on commit 40c3d99

Please sign in to comment.