Skip to content

Commit

Permalink
Add distro name and version information to each alias dict
Browse files Browse the repository at this point in the history
This lets you look up version information for the alias the current hab
configuration is using.
  • Loading branch information
MHendricks committed Mar 13, 2024
1 parent 53dc2eb commit 1e5efcc
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 14 deletions.
22 changes: 18 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -933,12 +933,26 @@ defined as a two part list where the first item is the name of the created alias
command. The second argument is the actual command to run and configuration
definition.

Ultimately all alias definitions are turned into dictionaries like `as_dict`, but
you can also define aliases as lists of strings or a single string. It's
recommended that you use a list of strings for any commands that require multiple
arguments, for more details see args documentation in
Ultimately all alias definitions are turned into [dictionaries](#complex-aliases)
like `as_dict`, but you can also define aliases as lists of strings or a single
string. It's recommended that you use a list of strings for any commands that
require multiple arguments, for more details see args documentation in
[subprocess.Popen](https://docs.python.org/3/library/subprocess.html#subprocess.Popen).

When the aliases are converted to dicts, they automatically get a "distro" key
added to them. This contains a two item tuple containing the `distro_name` and
`version`. This is preserved when frozen. You can use this to track down what
distro created a [duplicate alias](#duplicate-definitions). This is also useful
if you need to tie a farm job to a specific version of a dcc based on the active
hab setup.
```py
import hab
import os

resolver = hab.Resolver.instance()
cfg = resolver.resolve(os.environ["HAB_URI"])
version = cfg.aliases["houdinicore"]["distro"][1]
```

#### Complex Aliases

Expand Down
38 changes: 33 additions & 5 deletions hab/parsers/distro_version.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from packaging.version import InvalidVersion, Version

from .. import NotSet
from ..errors import InvalidVersionError, _IgnoredVersionError
from ..errors import HabError, InvalidVersionError, _IgnoredVersionError
from .distro import Distro
from .hab_base import HabBase
from .meta import hab_property
Expand Down Expand Up @@ -114,18 +114,46 @@ def load(self, filename):
# Fill in the DistroVersion specific settings before calling super
data = self._load(filename)

self.aliases = data.get("aliases", NotSet)
# Store any alias_mods, they will be processed later when flattening
self._alias_mods = data.get("alias_mods", NotSet)

# The name should be the version == specifier.
self.distro_name = data.get("name")
self.name = "{}=={}".format(self.distro_name, self.version)

self.aliases = self.standardize_aliases(data.get("aliases", NotSet))
# Store any alias_mods, they will be processed later when flattening
self._alias_mods = data.get("alias_mods", NotSet)

data = super().load(filename, data=data)

return data

def standardize_aliases(self, aliases):
"""Process a raw aliases dict adding distro information.
Converts any non-dict alias definitions into dicts and adds the "distro"
tuple containing `(distro_name, version)`. Does nothing if passed NotSet.
Returns:
dict: The same aliases object that was passed in. If it was a dict
the original dict's contents are modified.
"""
if aliases is NotSet:
return aliases

version_info = (self.distro_name, str(self.version))
for platform in aliases.values():
for alias in platform:
# Ensure that we always have a dictionary for aliases
if not isinstance(alias[1], dict):
alias[1] = dict(cmd=alias[1])
if "distro" in alias[1]:
raise HabError(
'The "distro" value on an alias dict is reserved. You '
"can not set this manually."
)
# Store the distro information on each alias dict.
alias[1]["distro"] = version_info
return aliases

@hab_property()
def version(self):
return super().version
Expand Down
4 changes: 1 addition & 3 deletions hab/parsers/flat_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,8 @@ def _process_version(self, version, existing=None):
continue

host_platform = utils.Platform.name()
# Ensure that we always have a dictionary for aliases
# Ensure the aliases are formatted and variables expanded
data = version.format_environment_value(aliases[i])
if not isinstance(data, dict):
data = dict(cmd=data)

mods = self._alias_mods.get(alias_name, [])
if "environment" not in data and not mods:
Expand Down
13 changes: 11 additions & 2 deletions hab/parsers/hab_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,10 +441,13 @@ def filename(self, filename):
self._dirname = self._filename.parent

def format_environment_value(self, value, ext=None, platform=None):
"""Apply standard formatting to environment variable values.
"""Apply standard formatting to string values.
If passed a list, tuple, dict, recursively calls this function on them
converting any strings found. Any bool or int values are not modified.
Args:
value (str): The string to format
value: The string to format.
ext (str, optional): Language passed to ``hab.formatter.Formatter``
for special formatters. In most cases this should not be used.
platform (str, optional): Convert path values from the current
Expand All @@ -463,6 +466,12 @@ def format_environment_value(self, value, ext=None, platform=None):
self.format_environment_value(v, ext=ext, platform=platform)
for v in value
]
elif isinstance(value, tuple):
# Format the individual items if a tuple of args is used.
return tuple(
self.format_environment_value(v, ext=ext, platform=platform)
for v in value
)
elif isinstance(value, dict):
# Format the values each dictionary pair
return {
Expand Down
22 changes: 22 additions & 0 deletions tests/distros/all_settings/0.1.0.dev1/invalid.hab.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "all_settings",
"version": "0.1.0.dev1",
"aliases": {
"windows": [
[
"maya",
{
"cmd": "C:\\Program Files\\Autodesk\\Maya2020\\bin\\maya.exe",
"distro": ["all_settings", "0.1.0"]
}
],
[
"mayapy",
{
"cmd": "C:\\Program Files\\Autodesk\\Maya2020\\bin\\mayapy.exe",
"distro": ["all_settings", "0.1.0"]
}
]
]
}
}
16 changes: 16 additions & 0 deletions tests/test_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from hab import NotSet, utils
from hab.errors import (
DuplicateJsonError,
HabError,
InvalidVersionError,
ReservedVariableNameError,
_IgnoredVersionError,
Expand All @@ -26,6 +27,9 @@ def test_distro_parse(config_root, resolver):
path = config_root / "distros" / "all_settings" / "0.1.0.dev1" / ".hab.json"
app.load(path)
check = json.load(path.open())
# Add dynamic alias settings like "distro" to the testing reference.
# That should never be defined in the raw alias json data.
app.standardize_aliases(check["aliases"])

assert "{name}=={version}".format(**check) == app.name
assert Version(check["version"]) == app.version
Expand All @@ -48,6 +52,18 @@ def test_distro_parse(config_root, resolver):
assert app.version == Version("2020.0")


def test_distro_exceptions(config_root, uncached_resolver):
"""Check that a exception is raised if you define "distro" on an alias."""
forest = {}
app = DistroVersion(forest, uncached_resolver)
# This file is used to test this feature and otherwise should be ignored.
path = config_root / "distros" / "all_settings" / "0.1.0.dev1" / "invalid.hab.json"
with pytest.raises(
HabError, match=r'The "distro" value on an alias dict is reserved.'
):
app.load(path)


def test_distro_version_resolve(config_root, resolver, helpers, monkeypatch, tmpdir):
"""Check the various methods for DistroVersion.version to be populated."""

Expand Down

0 comments on commit 1e5efcc

Please sign in to comment.