You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@buildstream.apache.org by no...@apache.org on 2020/12/29 12:39:49 UTC

[buildstream] 01/01: HACK: unsuccessful multi-venv experiment

This is an automated email from the ASF dual-hosted git repository.

not-in-ldap pushed a commit to branch aevri/plugin_venvs
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit c0fd176b901186295582f2afceb4c8194307141a
Author: Angelos Evripiotis <je...@bloomberg.net>
AuthorDate: Tue Apr 16 15:07:13 2019 +0100

    HACK: unsuccessful multi-venv experiment
    
    This is an experiment to test the feasability of supporting a venv per
    plugin, all running under the same interpreter.
    
    It shows:
    
      * Installing two separate venvs with different dependency versions
      * Running an interpretor which loads a plugin in both separate venvs
        (it could even be the same plugin, and need not be a "BuildStream"
        plugin, but some python module loaded on demand)
      * Prove that we infact have separation (perhaps by having the plugin
        just print the versions of it's dependencies).
    
    The approach taken in this experiment is to push all modules required by the
    plugin into a PluginBase space.
    
    There are major problems with this approach, see the README.md.
---
 .gitignore                                         |  1 +
 multivenv_experiment/README.md                     | 99 ++++++++++++++++++++++
 .../notbstalphaelement/bstplugin.py                | 10 +++
 multivenv_experiment/notbstalphaelement/setup.py   |  8 ++
 .../notbstbetaelement/bstplugin.py                 | 10 +++
 multivenv_experiment/notbstbetaelement/setup.py    |  8 ++
 .../notbstgammaelement/bstplugin.py                | 10 +++
 multivenv_experiment/notbstgammaelement/setup.py   |  8 ++
 .../notbuildstream/notbuildstream.py               | 74 ++++++++++++++++
 multivenv_experiment/notbuildstream/setup.py       | 12 +++
 10 files changed, 240 insertions(+)

diff --git a/.gitignore b/.gitignore
index 5c258fa..b02de5f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,3 +40,4 @@ doc/source/buildstream.rst
 doc/source/buildstream.*.rst
 doc/build/
 versioneer.pyc
+*.egg-info
diff --git a/multivenv_experiment/README.md b/multivenv_experiment/README.md
new file mode 100644
index 0000000..1ca97f1
--- /dev/null
+++ b/multivenv_experiment/README.md
@@ -0,0 +1,99 @@
+Multi-venv experiment
+=====================
+
+This is an experiment to test the feasability of supporting a venv per plugin,
+all running under the same interpreter.
+
+It shows:
+
+  * Installing two separate venvs with different dependency versions
+  * Running an interpretor which loads a plugin in both separate
+    venvs (it could even be the same plugin, and need not be a
+    "BuildStream" plugin, but some python module loaded on demand)
+  * Prove that we infact have separation (perhaps by having the plugin
+    just print the versions of it's dependencies).
+
+The approach taken in this experiment is to push all modules required by the
+plugin into a PluginBase space.
+
+Major problems discovered
+-------------------------
+
+- We don't override `sys.modules`, so this in jinja2 v2.10 won't work: `del
+  sys.modules['jinja2._identifier']`. A first attempt to override `sys.modules`
+  resulted in mysterious failures not detailed here.
+
+- Relative-imports work in a way that doesn't seem to be obvious, which is
+  incompatible with the hack to rewrite top-level imports. See the
+  `import_override` function for more details.
+
+- Global imports don't seem to work with pure PluginBase, e.g. jinja2
+  itself will do `from jinja2.environment import Environment, Template` in its
+  `__init__.py`, which fails. This is overcome with the `import_override`
+  hackery in the experiment.
+
+- the `jinja2.__version__` reported by the plugin is actually the version of
+  `pluginbase` instead. Interestingly the `jinja2.__version__` reported in the
+  main app is different.
+
+- the `jinja2.evalcontextfilter` accessed by the plugin is different from the
+  one accessed in the main app. That's probably for the same reason as the
+  above point.
+
+Setup
+-----
+
+Create a Python venv for each subdirectory, e.g.
+
+    python3 -m venv ~/PyVenv/notbuildstream
+    python3 -m venv ~/PyVenv/notbstalphaelement
+    python3 -m venv ~/PyVenv/notbstbetaelement
+    python3 -m venv ~/PyVenv/notbstgammaelement
+
+Then, for each directory:
+
+- Activate the appropriate venv. e.g. `. ~/PyVenv/notbuildstream/bin/activate`.
+- For each subdirectory, `pip install SUBDIRECTORY`. Don't use `-e`, as it
+  seems that `.egg-link`s are a separate case that need special consideration.
+
+Make sure that the reference to `lib/python3.7/site-packages` in
+`notbuildstream.py` is fixed up as appropriate for your venvs.
+
+Running
+-------
+
+Enter the venv for `notbuildstream`, and invoke it with the paths to the other
+venvs, e.g.
+
+    notbst ~/PyVenv/ ~/PyVenv/notbst{alpha,beta,gamma}element
+
+You should see somthing like:
+
+```
+venv: /Users/jevripiotis/PyVenv/notbstalphaelement
+Alpha
+jinja2.__version__: 1.0.0
+jinja2.evalcontextfilter: None
+main: jinja2: <module 'pluginbase._internalspace._sp5d362f8c6220a8c7f6ec3263825004f6.jinja2' from '/Users/jevripiotis/PyVenv/notbstalphaelement/lib/python3.7/site-packages/jinja2/__init__.py'>
+main: jinja2.__version__: 2.8
+main: jinja2.__file__: /Users/jevripiotis/PyVenv/notbstalphaelement/lib/python3.7/site-packages/jinja2/__init__.py
+main: Has evalcontextfilter: <function evalcontextfilter at 0x10b66c9d8>
+
+venv: /Users/jevripiotis/PyVenv/notbstbetaelement
+Beta
+jinja2.__version__: 1.0.0
+jinja2.evalcontextfilter: None
+main: jinja2: <module 'pluginbase._internalspace._sp97ff7e741eeaeaf9c282906561d1b456.jinja2' from '/Users/jevripiotis/PyVenv/notbstbetaelement/lib/python3.7/site-packages/jinja2/__init__.py'>
+main: jinja2.__version__: unknown
+main: jinja2.__file__: /Users/jevripiotis/PyVenv/notbstbetaelement/lib/python3.7/site-packages/jinja2/__init__.py
+main: Has evalcontextfilter: None
+
+venv: /Users/jevripiotis/PyVenv/notbstgammaelement
+Traceback (most recent call last):
+  File "/Users/jevripiotis/PyVenv/notbuildstream/bin/notbst", line 11, in <module>
+    load_entry_point('notbuildstream', 'console_scripts', 'notbst')()
+--- 8< --- snip long stacktrace --- 8< ---
+  File "/Users/jevripiotis/PyVenv/notbstgammaelement/lib/python3.7/site-packages/jinja2/lexer.py", line 50, in <module>
+    del sys.modules['jinja2._identifier']
+KeyError: 'jinja2._identifier'
+```
diff --git a/multivenv_experiment/notbstalphaelement/bstplugin.py b/multivenv_experiment/notbstalphaelement/bstplugin.py
new file mode 100644
index 0000000..e30671f
--- /dev/null
+++ b/multivenv_experiment/notbstalphaelement/bstplugin.py
@@ -0,0 +1,10 @@
+import sys
+
+import jinja2
+
+
+class Element:
+    def __init__(self, bst_context):
+        print("Alpha")
+        print(f"jinja2.__version__: {jinja2.__version__}")
+        print("jinja2.evalcontextfilter:", getattr(jinja2, "evalcontextfilter", None))
diff --git a/multivenv_experiment/notbstalphaelement/setup.py b/multivenv_experiment/notbstalphaelement/setup.py
new file mode 100644
index 0000000..cf758fb
--- /dev/null
+++ b/multivenv_experiment/notbstalphaelement/setup.py
@@ -0,0 +1,8 @@
+from setuptools import setup
+
+setup(
+    name="notbstalphaelement",
+    version="0.1",
+    py_modules=["bstplugin"],
+    install_requires=["Jinja2==2.8"],
+)
diff --git a/multivenv_experiment/notbstbetaelement/bstplugin.py b/multivenv_experiment/notbstbetaelement/bstplugin.py
new file mode 100644
index 0000000..5e41f75
--- /dev/null
+++ b/multivenv_experiment/notbstbetaelement/bstplugin.py
@@ -0,0 +1,10 @@
+import sys
+
+import jinja2
+
+
+class Element:
+    def __init__(self, bst_context):
+        print("Beta")
+        print(f"jinja2.__version__: {jinja2.__version__}")
+        print("jinja2.evalcontextfilter:", getattr(jinja2, "evalcontextfilter", None))
diff --git a/multivenv_experiment/notbstbetaelement/setup.py b/multivenv_experiment/notbstbetaelement/setup.py
new file mode 100644
index 0000000..da7c2d8
--- /dev/null
+++ b/multivenv_experiment/notbstbetaelement/setup.py
@@ -0,0 +1,8 @@
+from setuptools import setup
+
+setup(
+    name="notbstbetaelement",
+    version="0.1",
+    py_modules=["bstplugin"],
+    install_requires=["Jinja2==2.3"],
+)
diff --git a/multivenv_experiment/notbstgammaelement/bstplugin.py b/multivenv_experiment/notbstgammaelement/bstplugin.py
new file mode 100644
index 0000000..cfd41f2
--- /dev/null
+++ b/multivenv_experiment/notbstgammaelement/bstplugin.py
@@ -0,0 +1,10 @@
+import sys
+
+import jinja2
+
+
+class Element:
+    def __init__(self, bst_context):
+        print("Gamma")
+        print(f"jinja2.__version__: {jinja2.__version__}")
+        print("jinja2.evalcontextfilter:", getattr(jinja2, "evalcontextfilter", None))
diff --git a/multivenv_experiment/notbstgammaelement/setup.py b/multivenv_experiment/notbstgammaelement/setup.py
new file mode 100644
index 0000000..3bf3a2e
--- /dev/null
+++ b/multivenv_experiment/notbstgammaelement/setup.py
@@ -0,0 +1,8 @@
+from setuptools import setup
+
+setup(
+    name="notbstgammaelement",
+    version="0.1",
+    py_modules=["bstplugin"],
+    install_requires=["Jinja2==2.10"],
+)
diff --git a/multivenv_experiment/notbuildstream/notbuildstream.py b/multivenv_experiment/notbuildstream/notbuildstream.py
new file mode 100644
index 0000000..cce44d6
--- /dev/null
+++ b/multivenv_experiment/notbuildstream/notbuildstream.py
@@ -0,0 +1,74 @@
+import builtins
+import sys
+import contextlib
+
+import click
+
+import pluginbase
+
+
+# TODO: Make this work instead by overriding everything except BuildStream and
+# the standard library.
+@contextlib.contextmanager
+def import_override(*override_modules):
+    def myimport(name, globals_=None, locals_=None, fromlist=None, level=None):
+        # XXX: In the case of 'from . import A, B' we run into trouble if
+        # we remap the name. We should fully understand why and what
+        # guarantees there are before considering using any of this.
+        #
+        # When this is the case, it seems that 'fromlist' will be an empty
+        # list. Again, we need to know the guarantees here.
+        #
+        if fromlist != []:
+            for m in override_modules:
+                if name.startswith(m):
+                    name = "notbuildstream.plugins." + name
+        return builtins_import(name, globals_, locals_, fromlist, level)
+
+    builtins_import = builtins.__import__
+    try:
+        builtins.__import__ = myimport
+        yield
+    finally:
+        builtins.__import__ = builtins_import
+
+
+@click.command("notbuildstream")
+@click.argument(
+    "plugin_venvs",
+    nargs=-1,
+    metavar="PATH",
+    type=click.Path(exists=True, file_okay=False, dir_okay=True),
+)
+def cli(plugin_venvs):
+
+    pbase = pluginbase.PluginBase(package="notbuildstream.plugins")
+    psource_list = []
+
+    for venv in plugin_venvs:
+        print(f"venv: {venv}")
+
+        # XXX: We should determine this path using some standard mechanism.
+        search_path = [venv + "/lib/python3.7/site-packages", venv]
+
+        psource = pbase.make_plugin_source(searchpath=search_path, identifier=venv)
+        psource_list.append(psource)
+
+        with import_override("jinja2", "markupsafe"):
+            with psource:
+                # TODO: use entrypoints and lookup plugins with pkgconfig.
+                plugin = psource.load_plugin("bstplugin")
+                element = plugin.Element("a")
+                jinja2 = psource.load_plugin("jinja2")
+                print(f"main: jinja2: {jinja2}")
+                print(f"main: jinja2.__version__: {jinja2.__version__}")
+                print(f"main: jinja2.__file__: {jinja2.__file__}")
+                print(
+                    "main: Has evalcontextfilter:",
+                    getattr(jinja2, "evalcontextfilter", None),
+                )
+                print()
+
+
+if __name__ == "__main__":
+    sys.exit(cli())
diff --git a/multivenv_experiment/notbuildstream/setup.py b/multivenv_experiment/notbuildstream/setup.py
new file mode 100644
index 0000000..f0a7982
--- /dev/null
+++ b/multivenv_experiment/notbuildstream/setup.py
@@ -0,0 +1,12 @@
+from setuptools import setup
+
+setup(
+    name="notbuildstream",
+    version="0.1",
+    py_modules=["notbuildstream"],
+    install_requires=["Click", "pluginbase"],
+    entry_points="""
+        [console_scripts]
+        notbst=notbuildstream:cli
+    """,
+)