forked from ansible/ansible
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
transparent downstream vendoring (ansible#69850)
* builtin downstream vendoring support * allows downstream packagers to install packages to `ansible/_vendor` that will automatically be added to head of sys.path during `ansible` package load * tests * sort conflicting package names in warning text * sanity fixes * skip unnecessary comparison
- Loading branch information
1 parent
7641d32
commit de63cba
Showing
4 changed files
with
116 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
minor_changes: | ||
- downstream packagers may install packages under ansible._vendor, which will be added to head of sys.path at ansible package load |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
# (c) 2020 Ansible Project | ||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
|
||
from __future__ import (absolute_import, division, print_function) | ||
__metaclass__ = type | ||
|
||
import os | ||
import pkgutil | ||
import sys | ||
import warnings | ||
|
||
# This package exists to host vendored top-level Python packages for downstream packaging. Any Python packages | ||
# installed beneath this one will be masked from the Ansible loader, and available from the front of sys.path. | ||
# It is expected that the vendored packages will be loaded very early, so a warning will be fired on import of | ||
# the top-level ansible package if any packages beneath this are already loaded at that point. | ||
# | ||
# Python packages may be installed here during downstream packaging using something like: | ||
# pip install --upgrade -t (path to this dir) cryptography pyyaml packaging jinja2 | ||
|
||
# mask vendored content below this package from being accessed as an ansible subpackage | ||
__path__ = [] | ||
|
||
|
||
def _ensure_vendored_path_entry(): | ||
""" | ||
Ensure that any downstream-bundled content beneath this package is available at the top of sys.path | ||
""" | ||
# patch our vendored dir onto sys.path | ||
vendored_path_entry = os.path.dirname(__file__) | ||
vendored_module_names = set(m[1] for m in pkgutil.iter_modules([vendored_path_entry], '')) # m[1] == m.name | ||
|
||
if vendored_module_names: | ||
# patch us early to load vendored deps transparently | ||
if vendored_path_entry in sys.path: | ||
# handle reload case by removing the existing entry, wherever it might be | ||
sys.path.remove(vendored_path_entry) | ||
sys.path.insert(0, vendored_path_entry) | ||
|
||
already_loaded_vendored_modules = set(sys.modules.keys()).intersection(vendored_module_names) | ||
|
||
if already_loaded_vendored_modules: | ||
warnings.warn('One or more Python packages bundled by this ansible-base distribution were already ' | ||
'loaded ({0}). This may result in undefined behavior.'.format(', '.join(sorted(already_loaded_vendored_modules)))) | ||
|
||
|
||
_ensure_vendored_path_entry() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
# (c) 2020 Ansible Project | ||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
|
||
from __future__ import (absolute_import, division, print_function) | ||
__metaclass__ = type | ||
|
||
import os | ||
import pkgutil | ||
import pytest | ||
import sys | ||
|
||
from units.compat.mock import MagicMock, NonCallableMagicMock, patch | ||
|
||
|
||
def reset_internal_vendor_package(): | ||
import ansible | ||
ansible_vendor_path = os.path.join(os.path.dirname(ansible.__file__), '_vendor') | ||
|
||
if ansible_vendor_path in sys.path: | ||
sys.path.remove(ansible_vendor_path) | ||
|
||
for pkg in ['ansible._vendor', 'ansible']: | ||
if pkg in sys.modules: | ||
del sys.modules[pkg] | ||
|
||
|
||
def test_package_path_masking(): | ||
from ansible import _vendor | ||
|
||
assert hasattr(_vendor, '__path__') and _vendor.__path__ == [] | ||
|
||
|
||
def test_no_vendored(): | ||
reset_internal_vendor_package() | ||
with patch.object(pkgutil, 'iter_modules', return_value=[]): | ||
previous_path = list(sys.path) | ||
import ansible | ||
ansible_vendor_path = os.path.join(os.path.dirname(ansible.__file__), '_vendor') | ||
|
||
assert ansible_vendor_path not in sys.path | ||
assert sys.path == previous_path | ||
|
||
|
||
def test_vendored(vendored_pkg_names=None): | ||
if not vendored_pkg_names: | ||
vendored_pkg_names = ['boguspkg'] | ||
reset_internal_vendor_package() | ||
with patch.object(pkgutil, 'iter_modules', return_value=list((None, p, None) for p in vendored_pkg_names)): | ||
previous_path = list(sys.path) | ||
import ansible | ||
ansible_vendor_path = os.path.join(os.path.dirname(ansible.__file__), '_vendor') | ||
assert sys.path[0] == ansible_vendor_path | ||
|
||
if ansible_vendor_path in previous_path: | ||
previous_path.remove(ansible_vendor_path) | ||
|
||
assert sys.path[1:] == previous_path | ||
|
||
|
||
def test_vendored_conflict(): | ||
with pytest.warns(UserWarning) as w: | ||
import pkgutil | ||
import sys | ||
test_vendored(vendored_pkg_names=['sys', 'pkgutil']) # pass a real package we know is already loaded | ||
assert 'pkgutil, sys' in str(w[0].message) # ensure both conflicting modules are listed and sorted |