Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions docs/rez_sphinxext.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import rez.cli._main
import rez.cli._util
import rez.config
import rez.rezconfig
import docutils.nodes
import sphinx.util.nodes
Expand Down Expand Up @@ -262,9 +263,46 @@ def convert_rez_config_to_rst() -> list[str]:
rst.append('')

envvar = f'REZ_{varname.upper()}'
rst.append(f' .. envvar:: {envvar}')
setting_type = rez.config.config_schema._schema.get(varname)

if varname == 'plugins':
# Plugins don't have a schema like the normal settings
# have, so let's not document environment variables since
# they effectively don't support them.
assert setting_type is None
continue

assert setting_type is not None

json_envvar = f'{envvar}_JSON'
rst.append(' Environment variables:')
rst.append('')
if not setting_type.env_var_json_only:
rst.append(f' .. envvar:: {envvar}')
rst.append(f' .. envvar:: {json_envvar}')
rst.append('')
rst.append(f' The ``{envvar}`` environment variable can also be used to configure this.')

if setting_type.env_var_json_only:
rst.append(
f' The non-JSON ``{envvar}`` environment variable is not supported.'
)
rst.append('')
continue

if issubclass(setting_type, rez.config.PathList):
rst.append(
f' Values in ``{envvar}`` must be separated with ``:`` on Unix-like '
'systems and ``;`` on Windows.'
)
elif issubclass(setting_type, rez.config.StrList):
rst.append(
f' Values in ``{envvar}`` can be separated by commas or whitespace.'
)
elif issubclass(setting_type, rez.config.Dict):
rst.append(
f' Dictionary values in ``{envvar}`` must use the format '
'``k1:v1,k2:v2,...kN:vN``.'
)
rst.append('')

return rst
Expand Down
4 changes: 3 additions & 1 deletion docs/source/configuring_rez.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ Settings are determined in the following way:
:envvar:`REZ_CONFIG_FILE` environment variable. This can also be a path-like variable, to read from
multiple configuration files;
- The setting is further overridden if it is present in ``$HOME/.rezconfig`` or ``$HOME/.rezconfig.py``;
- The setting is overridden again if the environment variable :envvar:`REZ_XXX_JSON` is present, where ``XXX``
is the uppercase version of the setting key. Its value must be JSON-encoded;
- The setting is overridden again if the environment variable :envvar:`REZ_XXX` is present, where ``XXX`` is
the uppercase version of the setting key. For example, :data:`.image_viewer` will be overridden by
:envvar:`REZ_IMAGE_VIEWER`.
:envvar:`REZ_IMAGE_VIEWER`. This form takes precedence if both environment variables are present;
- This is a special case applied only during a package build or release. In this case, if the
package definition file contains a "config" section, settings in this section will override all
others. See :ref:`configuring-rez-package-overrides`.
Expand Down
3 changes: 3 additions & 0 deletions docs/source/environment.rst
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ operation of rez.

Path to a rez configuration file.

.. _config-environment-variable-overrides:

.. envvar:: REZ_XXX

For any given rez config entry (see ``rezconfig.py``),
Expand All @@ -235,6 +237,7 @@ operation of rez.
Same as :envvar:`REZ_XXX`, except that the format
is a JSON string. This means that some more complex settings can be overridden,
that aren't supported in the non-JSON case (:data:`package_filter` is an example).
If both forms are present, :envvar:`REZ_XXX` takes precedence.

.. envvar:: REZ_DISABLE_HOME_CONFIG

Expand Down
15 changes: 14 additions & 1 deletion src/rez/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ class Setting(object):
"""
schema: Validatable = Schema(object)

# Only set to True in subclasses when the non-JSON
# env var cannot be used by users.
env_var_json_only = False

def __init__(self, config, key) -> None:
self.config = config
self.key = key
Expand Down Expand Up @@ -96,7 +100,8 @@ def _validate(self, data):

if not self.config.locked:

# next, env-var
# next, env-var. Note that all settings support _JSON
# but not all support the non-JSON variant.
value = os.getenv(self._env_var_name)
if value is not None:
if self.key in _deprecated_settings:
Expand All @@ -108,6 +113,12 @@ def _validate(self, data):
pre_formatted=True,
filename=self._env_var_name,
)
if self.env_var_json_only:
raise ConfigurationError(
"The setting %r doesn't support the environment variable $%s, "
"use $%s_JSON instead."
% (self.key, self._env_var_name, self._env_var_name)
)
return self._parse_env_var(value)

# next, JSON-encoded env-var
Expand Down Expand Up @@ -180,6 +191,7 @@ class PipInstallRemaps(Setting):
KEYS = ["record_path", "pip_install", "rez_install"]

schema = Schema([{key: And(str, len) for key in KEYS}])
env_var_json_only = True

def validate(self, data: list) -> list:
"""Extended to substitute regex-escaped path tokens."""
Expand Down Expand Up @@ -302,6 +314,7 @@ class OptionalDictOrDictList(Setting):
schema = Or(And(None, Use(lambda x: [])),
And(dict, Use(lambda x: [x])),
[dict])
env_var_json_only = True


class SuiteVisibility_(Str):
Expand Down
10 changes: 6 additions & 4 deletions src/rez/rezconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@
files are supported, separated by os.pathsep;
3) The setting is further overriden if it is present in $HOME/.rezconfig,
UNLESS $REZ_DISABLE_HOME_CONFIG is 1;
4) The setting is overridden again if the environment variable $REZ_XXX is
4) The setting can also be overridden by the environment variable
$REZ_XXX_JSON, and in this case the string is expected to be a JSON-encoded
value;
5) The setting is overridden again if the environment variable $REZ_XXX is
present, where XXX is the uppercase version of the setting key. For example,
"image_viewer" will be overriden by $REZ_IMAGE_VIEWER. List values can be
separated either with "," or blank space. Dict values are in the form
"k1:v1,k2:v2,kn:vn";
5) The setting can also be overriden by the environment variable $REZ_XXX_JSON,
and in this case the string is expected to be a JSON-encoded value;
"k1:v1,k2:v2,kn:vn". This form takes precedence if both environment
variables are present;
6) This is a special case applied only during a package build or release. In
this case, if the package definition file contains a "config" section,
settings in this section will override all others.
Expand Down
27 changes: 27 additions & 0 deletions src/rez/tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,33 @@ def test_8(self):
print(error.stdout)
raise

def test_9_json_only_environment_variable(self) -> None:
"""Test the error when a JSON-only setting uses its plain env var."""
for key in ("package_orderers", "pip_install_remaps"):
with self.subTest(key=key), restore_os_environ():
env_var = "REZ_%s" % key.upper()
os.environ[env_var] = "invalid"
config = Config([self.root_config_file], locked=False)

with self.assertRaises(ConfigurationError) as error:
getattr(config, key)

self.assertEqual(
str(error.exception),
"The setting %r doesn't support the environment variable $%s, "
"use $%s_JSON instead."
% (key, env_var, env_var),
)

def test_10_environment_variable_precedence(self) -> None:
"""Test that the plain environment variable takes precedence."""
with restore_os_environ():
os.environ["REZ_IMAGE_VIEWER"] = "plain"
os.environ["REZ_IMAGE_VIEWER_JSON"] = '"json"'
config = Config([self.root_config_file], locked=False)

self.assertEqual(config.image_viewer, "plain")


class TestDeprecations(TestBase, TempdirMixin):
@classmethod
Expand Down
Loading