← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:py3-reexec into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:py3-reexec into launchpad:master.

Commit message:
Re-exec scripts with env/bin/python if necessary

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/386486

Scripts have to have some kind of #! line, and with the current build system it needs to be an absolute path (perhaps eventually this should be refactored using entry_points).  This means we need to pick /usr/bin/python2 or /usr/bin/python3, which makes it difficult to deal with an incremental port to Python 3.

As an intermediate step, record the build-time major and minor version of Python in env/python_version, and make _pythonpath.py (which all our scripts import early) re-exec with env/bin/python if the current Python version doesn't match that.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:py3-reexec into launchpad:master.
diff --git a/_pythonpath.py b/_pythonpath.py
index c4a43fd..eab0d63 100644
--- a/_pythonpath.py
+++ b/_pythonpath.py
@@ -20,7 +20,20 @@ else:
 top = os.path.dirname(os.path.abspath(os.path.realpath(filename)))
 
 env = os.path.join(top, 'env')
-stdlib_dir = os.path.join(env, 'lib', 'python%s' % sys.version[:3])
+python_version_path = os.path.join(env, 'python_version')
+
+# If the current Python major/minor version doesn't match the virtualenv,
+# then re-exec.  This makes it practical to experiment with switching
+# between Python 2 and 3 without having to update the #! lines of all our
+# scripts to match.
+python_version = '%s.%s' % sys.version_info[:2]
+with open(python_version_path) as python_version_file:
+    env_python_version = python_version_file.readline().strip()
+if python_version != env_python_version:
+    env_python = os.path.join(env, 'bin', 'python')
+    os.execl(env_python, env_python, *sys.argv)
+
+stdlib_dir = os.path.join(env, 'lib', 'python%s' % python_version)
 
 if ('site' in sys.modules and
     not sys.modules['site'].__file__.startswith(
@@ -52,7 +65,7 @@ if not sys.executable.startswith(top + os.sep) or 'site' not in sys.modules:
     os.environ['PATH'] = (
         os.path.join(env, 'bin') + os.pathsep + os.environ.get('PATH', ''))
     site_packages = os.path.join(
-        env, 'lib', 'python%s' % sys.version[:3], 'site-packages')
+        env, 'lib', 'python%s' % python_version, 'site-packages')
     import site
     site.addsitedir(site_packages)
     if orig_disable_sitecustomize is not None:
diff --git a/setup.py b/setup.py
index 9a126c9..0f8d35f 100644
--- a/setup.py
+++ b/setup.py
@@ -126,6 +126,13 @@ class lp_develop(develop):
             with open(instance_name_path, "w") as instance_name_file:
                 print(os.environ["LPCONFIG"], file=instance_name_file)
 
+            # Write out the build-time Python major/minor version so that
+            # scripts run with /usr/bin/python2 know whether they need to
+            # re-exec.
+            python_version_path = os.path.join(env_top, "python_version")
+            with open(python_version_path, "w") as python_version_file:
+                print("%s.%s" % sys.version_info[:2], file=python_version_file)
+
 
 __version__ = '2.2.3'