Last active
February 9, 2025 22:04
-
-
Save davidfraser/6555916 to your computer and use it in GitHub Desktop.
Script to link Python system package into virtual environment from Nick Coghlan at https://bitbucket.org/ncoghlan/misc/raw/44341af0b2e4ac7f9580d5f907d8f49c7013b3b9/venv_link.py
Modified to use system python to find the module, and pip to decide the link location
This file contains hidden or 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
#!/usr/bin/env python | |
# symlink a system package or module into the current virtual env | |
import logging | |
import optparse | |
import os | |
import sys | |
import shutil | |
import subprocess | |
import pickle | |
import pip.locations | |
pyver_str = "%d.%d" % sys.version_info[:2] | |
def find_sys_python(): | |
return "/usr/bin/python%s" % pyver_str | |
def sys_python_find_module(python_exe, name): | |
cmd = "import imp, pickle, sys ; f, real_path, mod_info = imp.find_module('%s') ; f and f.close() ; pickle.dump((real_path, mod_info), sys.stdout)" % name | |
p = subprocess.check_output([python_exe, "-c", cmd]) | |
t = pickle.loads(p) | |
real_path, mod_info = t | |
return real_path, mod_info | |
def find_files(base, dir_name): | |
file_list = [] | |
for file_name in os.listdir(os.path.join(base, dir_name)): | |
file_list.append(os.path.join(dir_name, file_name)) | |
if os.path.isdir(os.path.join(base, dir_name, file_name)): | |
file_list.extend(find_files(base, os.path.join(dir_name, file_name))) | |
return file_list | |
def confirm_remove(target_path): | |
"""removes the given target path after obtaining user confirmation""" | |
remove = raw_input("Target %s already exists - remove? (yes/no) " % target_path).lower() == "yes" | |
if remove: | |
if os.path.isdir(target_path) and not os.path.islink(target_path): | |
shutil.rmtree(target_path) | |
else: | |
os.remove(target_path) | |
def install_link(names, action=None, egg_info=None, egg_version=None, sys_python=None): | |
"""'Install' a module or package by linking to system version""" | |
virt_prefix = sys.prefix | |
real_prefix = getattr(sys, "real_prefix", None) | |
if real_prefix is None or real_prefix == virt_prefix: | |
raise Exception("Not in a virtual environment") | |
virt_dir = pip.locations.site_packages | |
installed_files = [] | |
python_exe = find_sys_python() if sys_python is None else sys_python | |
for name in names: | |
# We want to see what the system Python would import... | |
real_path, mod_info = sys_python_find_module(python_exe, name) | |
mod_suffix, fmode, mod_type = mod_info | |
file_name = os.path.basename(real_path) | |
target_path = os.path.join(virt_dir, file_name) | |
if os.path.exists(target_path): | |
confirm_remove(target_path) | |
if action == "link": | |
print "{} -> {}".format(target_path, real_path) | |
os.symlink(real_path, target_path) | |
elif action == "copy": | |
print "copying {} -> {}".format(real_path, target_path) | |
if os.path.isdir(real_path): | |
shutil.copytree(real_path, target_path) | |
tree_files = find_files(virt_dir, file_name) | |
installed_files.extend([os.path.join("..", file_name) for file_name in tree_files]) | |
else: | |
shutil.copy2(real_path, target_path) | |
installed_files.append(os.path.join("..", file_name)) | |
if egg_info: | |
if action == "link": | |
logging.warning("Linked files will not be listed under egg_info as pip uninstall attempts to remove the targets rather than the links") | |
egg_info_dir = os.path.join(virt_dir, "%s-%s.egg-info" % (egg_info, egg_version if egg_version else "py%s" % pyver_str)) | |
if os.path.exists(egg_info_dir): | |
confirm_remove(egg_info_dir) | |
print "creating {}".format(egg_info_dir) | |
os.mkdir(egg_info_dir) | |
installed_lines = [installed_file + "\n" for installed_file in installed_files] | |
with open(os.path.join(egg_info_dir, 'installed-files.txt'), 'w') as f: | |
f.writelines(installed_lines) | |
if egg_version: | |
with open(os.path.join(egg_info_dir, 'PKG-INFO'), 'w') as f: | |
f.writelines(["Metadata-Version: 1.1\n", "Name: %s\n" % egg_info, "Version: %s\n" % egg_version]) | |
if __name__ == "__main__": | |
parser = optparse.OptionParser() | |
parser.add_option("-e", "--egg-info", dest="egg_info", help="Create egg-info entry for the given package name", default=None) | |
parser.add_option("-v", "--egg-version", dest="egg_version", help="Set the version of the egg info to this", default=None) | |
parser.add_option("-p", "--python", dest="sys_python", help="Use the given python executable as source for the modules", default=None) | |
parser.add_option("-c", "--copy", dest="action", action="store_const", const="copy", help="Copy modules into virtualenv (default)", default="copy") | |
parser.add_option("-l", "--link", dest="action", action="store_const", const="link", help="Link to modules rather than copying") | |
options, names = parser.parse_args() | |
install_link(names, action=options.action, egg_info=options.egg_info, egg_version=options.egg_version, sys_python=options.sys_python) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment