diff --git a/README.md b/README.md index 3d2146a..ce3edf4 100644 --- a/README.md +++ b/README.md @@ -430,7 +430,7 @@ for details on each item. | Feature | Description | Multiple values | |---|---|---| | cli | Used by the hab cli to add extra commands | All unique names are used. | -| launch_cli | Used as the default `cls` by `hab.parsers.Config.launch()` to launch aliases from inside of python. This should be a subclass of subprocess.Popen. | Only the first is used, the rest are discarded. | +| launch_cls | Used as the default `cls` by `hab.parsers.Config.launch()` to launch aliases from inside of python. This should be a subclass of subprocess.Popen. A [complex alias](#complex-aliases) may override this per alias. Defaults to [`hab.launcher.Launcher`](hab/launcher.py). [Example](tests/site/site_entry_point_a.json) | Only the first is used, the rest are discarded. | The name of each entry point is used to de-duplicate results from multiple site json files. This follows the general rule defined in [duplicate definitions](#duplicate-definitions). @@ -767,12 +767,19 @@ active hab config, but in this case prepends an additional value on `ALIASED_GLOBAL_A` will be set to `Global A`. However while you are using the alias `as_dict`, the variable will be set to `Local A Prepend;Global A`. -Complex Aliases have two keys: +Complex Aliases supports several keys: 1. `cmd` is the command to run. When list or str defined aliases are resolved, their value is stored under this key. 2. `environment`: A set of env var configuration options. For details on this format, see [Defining Environments](#defining-environments). This is not os_specific due to aliases already being defined per-platform. +3. `launch_cls`: If defined this entry_point is used instead of the Site defined +or default class specifically for launching this alias. +See [houdini](tests/distros/houdini19.5/19.5.493/.hab.json) for an example. + +Note: Plugins may add support for their own keys. +[Hab-gui](https://github.com/blurstudio/hab-gui#icons-and-labels) +adds icon and label for example. **Use Case:** You want to add a custom AssetResolver to USD for Maya, Houdini, and standalone usdview. To get this to work, you need to compile your plugin diff --git a/hab/parsers/config.py b/hab/parsers/config.py index 3a44be5..900eb79 100644 --- a/hab/parsers/config.py +++ b/hab/parsers/config.py @@ -66,22 +66,32 @@ def launch(self, alias_name, args=None, blocking=False, cls=None, **kwargs): Returns: The created subprocess.Popen instance. """ + # Construct the command line arguments to execute + if alias_name not in self.aliases: + raise HabError(f'"{alias_name}" is not a valid alias name') + alias = self.aliases[alias_name] + + # Get the subprocess.Popen like class to use to launch the alias if cls is None: - # Respect the site entry point if defined - eps = self.resolver.site.entry_points_for_group("launch_cls") + # Use the entry_point if defined on the alias + alias_cls = alias.get("launch_cls") + if alias_cls: + alias_cls = {"launch_cls": alias_cls} + eps = self.resolver.site.entry_points_for_group( + "launch_cls", entry_points=alias_cls + ) + else: + # Otherwise use the global definition from Site + eps = self.resolver.site.entry_points_for_group("launch_cls") + if eps: cls = eps[0].load() else: - # Otherwise, default to subprocess.Popen + # Default to subprocess.Popen if not defined elsewhere from hab.launcher import Launcher cls = Launcher - # Construct the command line arguments to execute - if alias_name not in self.aliases: - raise HabError(f'"{alias_name}" is not a valid alias name') - alias = self.aliases[alias_name] - try: cmd = alias["cmd"] except KeyError: diff --git a/hab/site.py b/hab/site.py index e06bc2b..eb6becd 100644 --- a/hab/site.py +++ b/hab/site.py @@ -83,7 +83,7 @@ def dump(self, verbosity=0, color=None): ret = "\n".join(ret) return utils.dump_title("Dump of Site", f"{site_ret}\n{ret}", color=color) - def entry_points_for_group(self, group, default=None): + def entry_points_for_group(self, group, default=None, entry_points=None): """Returns a list of importlib_metadata.EntryPoint objects enabled by this site config. To import and resolve the defined object call `ep.load()`. @@ -93,13 +93,17 @@ def entry_points_for_group(self, group, default=None): the entry points defined by this dictionary. This is the contents of the entry_points group, not the entire entry_points dict. For example: `{"gui": "hab_gui.cli:gui"}` + entry_points (dict, optional): Use this dictionary instead of the one + defined on this Site object. """ # Delay this import to when required. It's faster than pkg_resources but # no need to pay the import price for it if you are not using it. from importlib_metadata import EntryPoint ret = [] - entry_points = self.get("entry_points", {}) + # Use the site defined entry_points if an override dict wasn't provided + if entry_points is None: + entry_points = self.get("entry_points", {}) # Get the entry point definitions, falling back to default if provided if group in entry_points: diff --git a/tests/distros/houdini19.5/19.5.493/.hab.json b/tests/distros/houdini19.5/19.5.493/.hab.json index fd2b8f8..4f07e34 100644 --- a/tests/distros/houdini19.5/19.5.493/.hab.json +++ b/tests/distros/houdini19.5/19.5.493/.hab.json @@ -32,24 +32,29 @@ ], "windows": [ [ - "houdini", - "C:\\Program Files\\Side Effects Software\\Houdini 19.5.493\\bin\\houdini.exe" + "houdini",{ + "cmd": "C:\\Program Files\\Side Effects Software\\Houdini 19.5.493\\bin\\houdini.exe", + "launch_cls": {"subprocess": "subprocess:Popen"} + } ], [ "houdini19.5", { "cmd": "C:\\Program Files\\Side Effects Software\\Houdini 19.5.493\\bin\\houdini.exe", - "min_verbosity": {"global": 1} + "min_verbosity": {"global": 1}, + "launch_cls": {"subprocess": "subprocess:Popen"} } ], [ "houdinicore", { - "cmd": "C:\\Program Files\\Side Effects Software\\Houdini 19.5.493\\bin\\houdinicore.exe" + "cmd": "C:\\Program Files\\Side Effects Software\\Houdini 19.5.493\\bin\\houdinicore.exe", + "launch_cls": {"subprocess": "subprocess:Popen"} } ], [ "houdinicore19.5", { "cmd": "C:\\Program Files\\Side Effects Software\\Houdini 19.5.493\\bin\\houdinicore.exe", - "min_verbosity": {"global": 1} + "min_verbosity": {"global": 1}, + "launch_cls": {"subprocess": "subprocess:Popen"} } ], [ diff --git a/tests/test_launch.py b/tests/test_launch.py index 3b6ebd0..8b6c4b7 100644 --- a/tests/test_launch.py +++ b/tests/test_launch.py @@ -168,3 +168,27 @@ def test_cls_entry_point(config_root): # Check that specifying cls, overrides site config proc = cfg.launch("global", blocking=True, cls=Topen) assert type(proc) is Topen + + +def test_alias_entry_point(config_root): + """Check that if an entry point is defined on a complex alias, it is used.""" + site = Site( + [config_root / "site/site_entry_point_a.json", config_root / "site_main.json"] + ) + + resolver = Resolver(site=site) + cfg = resolver.resolve("app/aliased/mod") + + # NOTE: We need to compare the name of the classes because they are separate + # imports that don't compare equal using `is`. + + # Check that entry_point site config is respected + proc = cfg.launch("global", blocking=True) + assert type(proc).__name__ == "Popen" + + # Check that if the complex alias specifies launch_cls, it is used instead + # of the site defined or default class. + alias = cfg.frozen_data["aliases"][utils.Platform.name()]["global"] + alias["launch_cls"] = {"subprocess": "tests.test_launch:Topen"} + proc = cfg.launch("global", blocking=True) + assert type(proc).__name__ == "Topen"