Skip to content

Commit

Permalink
Merge pull request #122 from davidlatwe/feature/parent-environ
Browse files Browse the repository at this point in the history
Allow setting parent environment explicitly
  • Loading branch information
davidlatwe authored Nov 5, 2020
2 parents 43655aa + c82e2aa commit c4c10ab
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 28 deletions.
2 changes: 2 additions & 0 deletions allzpark/_rezapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from rez.package_repository import package_repository_manager
from rez.packages_ import Package
from rez.utils.formatting import PackageRequest
from rez.system import system
from rez.config import config
from rez.util import which
from rez import __version__ as version
Expand Down Expand Up @@ -62,6 +63,7 @@ def find_latest(name, range_=None, paths=None):
"project",
"copy_package",
"package_repository_manager",
"system",

# Classes
"Package",
Expand Down
17 changes: 17 additions & 0 deletions allzpark/allzparkconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,23 @@ def themes():
return []


def application_parent_environment():
"""Application's launching environment
You may want to set this so the application won't be inheriting current
environment which is used to launch Allzpark. E.g. when Allzaprk is
launched from a Rez resolved context.
But if using bleeding-rez, and `config.inherit_parent_environment` is
set to False, config will be respected and this will be ignored.
Returns:
dict
"""
return None


def subprocess_encoding():
"""Codec that should be used to decode subprocess stdout/stderr
Expand Down
18 changes: 17 additions & 1 deletion allzpark/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@ def _patch_allzparkconfig():
getattr(allzparkconfig, member))


def _get_application_parent_environ():
from rez.config import config
from rez.shells import create_shell

if not config.get("inherit_parent_environment", True):
# bleeding-rez, isolate enabled.
sh = create_shell()
return sh.environment()

return (allzparkconfig.application_parent_environment()
or os.environ.copy())


@contextlib.contextmanager
def timings(title, timing=True):
tell(title, newlines=0)
Expand Down Expand Up @@ -244,6 +257,9 @@ def main():

_backwards_compatibility()

with timings("- Loading application parent environment.. ") as msg:
parent_environ = _get_application_parent_environ()

# Allow the application to die on CTRL+C
signal.signal(signal.SIGINT, signal.SIG_DFL)

Expand Down Expand Up @@ -308,7 +324,7 @@ def main():
tell("-" * 30) # Add some space between boot messages, and upcoming log

app = QtWidgets.QApplication([])
ctrl = control.Controller(storage)
ctrl = control.Controller(storage, parent_environ)

# Handle stdio from within the application if necessary
if hasattr(allzparkconfig, "__noconsole__"):
Expand Down
100 changes: 78 additions & 22 deletions allzpark/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os
import sys
import time
import json
import errno
import shutil
import logging
Expand Down Expand Up @@ -44,7 +45,7 @@ class State(dict):
"""

def __init__(self, ctrl, storage):
def __init__(self, ctrl, storage, parent_environ=None):
super(State, self).__init__({
"profileName": storage.value("startupProfile"),
"appRequest": storage.value("startupApplication"),
Expand All @@ -67,6 +68,12 @@ def __init__(self, ctrl, storage):
# Cache, for performance only
"rezEnvirons": {},

# Parent environment for all applications
"parentEnviron": parent_environ or {},

# Cache environment testing result
"testedEnvirons": {},

"rezApps": odict(),
"fullCommand": "rez env",
"serialisationMode": (
Expand Down Expand Up @@ -204,10 +211,16 @@ class Controller(QtCore.QObject):
_State("pkgnotfound", help="One or more packages was not found"),
]

def __init__(self, storage, stdio=None, stderr=None, parent=None):
def __init__(self,
storage,
parent_environ=None,
stdio=None,
stderr=None,
parent=None):

super(Controller, self).__init__(parent)

state = State(self, storage)
state = State(self, storage, parent_environ)

models = {
"apps": model.ApplicationModel(),
Expand All @@ -218,6 +231,8 @@ def __init__(self, storage, stdio=None, stderr=None, parent=None):
"packages": model.PackagesModel(self),
"context": model.ContextModel(),
"environment": model.EnvironmentModel(),
"parentenv": model.EnvironmentModel(),
"diagnose": model.EnvironmentModel(),
"commands": model.CommandsModel(),
}

Expand All @@ -228,6 +243,8 @@ def __init__(self, storage, stdio=None, stderr=None, parent=None):
timers["commandsPoller"].timeout.connect(self.on_tasks_polled)
timers["commandsPoller"].start(500)

models["parentenv"].load(state["parentEnviron"].copy())

# Initialize the state machine
self._machine = transitions.Machine(
model=state,
Expand Down Expand Up @@ -281,6 +298,18 @@ def current_tool(self):
def context(self, app_request):
return self._state["rezContexts"][app_request].to_dict()

def parent_environ(self):
environ = self._state["parentEnviron"].copy()
# Inject user environment
#
# NOTE: Rez takes precendence on environment, so a user
# cannot edit the environment in such a way that packages break.
# However it also means it cannot edit variables also edited
# by a package. Win some lose some
environ = dict(environ, **self._state.retrieve("userEnv", {}))

return environ

def environ(self, app_request):
"""Fetch the environment of a context
Expand All @@ -306,15 +335,18 @@ def environ(self, app_request):
return env[app_request]

except KeyError:
context = ctx[app_request]
parent_env = self.parent_environ()
try:
environ = ctx[app_request].get_environ()
env[app_request] = environ
return environ
environ = context.get_environ(parent_environ=parent_env)

except rez.ResolvedContextError:
return {
"error": "Failed context"
}
else:
env[app_request] = environ
return environ

def resolved_packages(self, app_request):
return self._state["rezContexts"][app_request].resolved_packages
Expand Down Expand Up @@ -467,11 +499,11 @@ def find(self, family, range_=None):

yield pkg

def env(self, request, use_filter=True):
def env(self, requests, use_filter=True):
"""Resolve context, relative Allzpark state
Arguments:
request (str): Fully formatted request, including any
requests (list): Fully formatted request, including any
number of packages. E.g. "six==1.2 PySide2"
use_filter (bool, optional): Whether or not to apply
the current package_filter
Expand All @@ -482,7 +514,7 @@ def env(self, request, use_filter=True):
paths = self._package_paths()

return rez.env(
request,
requests,
package_paths=paths,
package_filter=package_filter if use_filter else None
)
Expand Down Expand Up @@ -685,6 +717,8 @@ def do():
"command", app_model.data(app_index, "tool"))
is_detached = kwargs.get(
"detached", app_model.data(app_index, "detached"))
stdout = kwargs.get("stdout", self.info)
stderr = kwargs.get("stderr", self.error)

assert tool_name, (
"There should have been at least one tool name. "
Expand All @@ -693,7 +727,7 @@ def do():

overrides = self._models["packages"]._overrides
disabled = self._models["packages"]._disabled
environ = self._state.retrieve("userEnv", {})
environ = self.parent_environ()

self.debug(
"Launching %s%s.." % (
Expand All @@ -715,8 +749,8 @@ def on_error(error):
parent=self
)

cmd.stdout.connect(self.info)
cmd.stderr.connect(self.error)
cmd.stdout.connect(stdout)
cmd.stderr.connect(stderr)
cmd.error.connect(on_error)

cmd.execute()
Expand Down Expand Up @@ -832,11 +866,13 @@ def select_profile(self, profile_name, version_name=Latest):
self._models["apps"].reset()
self._models["context"].reset()
self._models["environment"].reset()
self._models["diagnose"].reset()
self._models["packages"].reset()
self._models["profileVersions"].setStringList([])

self._state["rezContexts"].clear()
self._state["rezEnvirons"].clear()
self._state["testedEnvirons"].clear()
self._state["rezApps"].clear()

def on_apps_found(apps):
Expand Down Expand Up @@ -924,16 +960,19 @@ def select_application(self, app_request):
context = self.context(app_request)
environ = self.environ(app_request)
packages = self.resolved_packages(app_request)
diagnose = self._state["testedEnvirons"].get(app_request, {})

except Exception:
self._models["packages"].reset()
self._models["context"].reset()
self._models["environment"].reset()
self._models["diagnose"].reset()
raise

self._models["packages"].reset(packages)
self._models["context"].load(context)
self._models["environment"].load(environ)
self._models["diagnose"].load(diagnose)

tools = self._models["apps"].find(app_request)["tools"]
self._state["tool"] = tools[0]
Expand Down Expand Up @@ -1124,6 +1163,32 @@ def graph(self):

return pixmap

def shell_code(self):
app_request = self._state["appRequest"]
context = self._state["rezContexts"][app_request]
parent_env = self.parent_environ()
return context.get_shell_code(parent_environ=parent_env)

def test_environment(self):
app_request = self._state["appRequest"]

command = (
'%s -c "'
'import os,sys,json;'
'sys.stdout.write(json.dumps(os.environ.copy(),ensure_ascii=0))"'
) % sys.executable

def load(message):
try:
env = json.loads(message)
except json.JSONDecodeError:
self.info(message) # regular messages during resolve
else:
self._state["testedEnvirons"][app_request] = env
self._models["diagnose"].load(env)

self.launch(command=command, stdout=load)


class Command(QtCore.QObject):
stdout = QtCore.Signal(str)
Expand Down Expand Up @@ -1191,7 +1256,7 @@ def _execute(self):
"command": self.cmd,
"stdout": subprocess.PIPE,
"stderr": subprocess.PIPE,
"parent_environ": None,
"parent_environ": self.environ or None,
"startupinfo": startupinfo
}
if rez.project == "rez":
Expand All @@ -1205,15 +1270,6 @@ def _execute(self):

context = self.context

if self.environ:
# Inject user environment
#
# NOTE: Rez takes precendence on environment, so a user
# cannot edit the environment in such a way that packages break.
# However it also means it cannot edit variables also edited
# by a package. Win some lose some
kwargs["parent_environ"] = dict(os.environ, **self.environ)

try:
self.popen = context.execute_shell(**kwargs)
except Exception as e:
Expand Down
Loading

0 comments on commit c4c10ab

Please sign in to comment.