浏览代码

Switch to using distlib command line exe le launchers

Thomas Kluyver 6 年之前
父节点
当前提交
674a4f8d23
共有 10 个文件被更改,包括 77 次插入64 次删除
  1. 0 1
      .coveragerc
  2. 1 1
      doc/pypi-requirements.txt
  3. 1 1
      nsist/__init__.py
  4. 33 0
      nsist/_assemble_launchers.py
  5. 0 36
      nsist/_rewrite_shebangs.py
  6. 21 11
      nsist/commands.py
  7. 2 1
      nsist/pyapp.nsi
  8. 17 11
      nsist/tests/test_commands.py
  9. 1 1
      pyproject.toml
  10. 1 1
      tox.ini

+ 0 - 1
.coveragerc

@@ -1,6 +1,5 @@
 [run]
 omit = 
     nsist/tests/*
-    nsist/_rewrite_shebangs.py
     nsist/_system_path.py
 

+ 1 - 1
doc/pypi-requirements.txt

@@ -1,6 +1,6 @@
 jinja2
 requests
-win_cli_launchers
+distlib
 yarg
 requests_download
 sphinxcontrib_github_alt

+ 1 - 1
nsist/__init__.py

@@ -354,7 +354,7 @@ if __name__ == '__main__':
         prepare_bin_directory(command_dir, self.commands, bitness=self.py_bitness)
         self.install_dirs.append((command_dir.name, '$INSTDIR'))
         self.extra_files.append((pjoin(_PKGDIR, '_system_path.py'), '$INSTDIR'))
-        self.extra_files.append((pjoin(_PKGDIR, '_rewrite_shebangs.py'), '$INSTDIR'))
+        self.extra_files.append((pjoin(_PKGDIR, '_assemble_launchers.py'), '$INSTDIR'))
 
     def copytree_ignore_callback(self, directory, files):
         """This is being called back by our shutil.copytree call to implement the

+ 33 - 0
nsist/_assemble_launchers.py

@@ -0,0 +1,33 @@
+"""This is run during installation to assemble command-line exe launchers
+
+Each launcher contains: exe base + shebang + zipped Python code
+"""
+import glob
+import os
+import sys
+
+b_shebang = '#!"{}"\r\n'.format(sys.executable).encode('utf-8')
+
+def assemble_exe(path, b_launcher):
+    exe_path = path[:-len('-append.zip')] + '.exe'
+
+    with open(exe_path, 'wb') as f:
+        f.write(b_launcher)
+        f.write(b_shebang)
+
+        with open(path, 'rb') as f2:
+            f.write(f2.read())
+
+def main(argv=None):
+    if argv is None:
+        argv = sys.argv
+    target_dir = argv[1]
+
+    with open(os.path.join(target_dir, 'launcher_exe.dat'), 'rb') as f:
+        b_launcher = f.read()
+
+    for path in glob.glob(os.path.join(target_dir, '*-append.zip')):
+        assemble_exe(path, b_launcher)
+
+if __name__ == '__main__':
+    main()

+ 0 - 36
nsist/_rewrite_shebangs.py

@@ -1,36 +0,0 @@
-"""This is run during installation to rewrite the shebang (#! headers) of script
-files.
-"""
-import glob
-import os.path
-import sys
-
-if sys.version_info[0] >= 3:
-    # What do we do if the path contains characters outside the system code page?!
-    b_python_exe = sys.executable.encode(sys.getfilesystemencoding())
-else:
-    b_python_exe = sys.executable
-
-def rewrite(path):
-    with open(path, 'rb') as f:
-        contents = f.readlines()
-
-    if not contents:
-        return
-    if contents[0].strip() != b'#!python':
-        return
-
-    contents[0] = b'#!"' + b_python_exe + b'"\n'
-
-    with open(path, 'wb') as f:
-        f.writelines(contents)
-
-def main(argv=None):
-    if argv is None:
-        argv = sys.argv
-    target_dir = argv[1]
-    for path in glob.glob(os.path.join(target_dir, '*-script.py')):
-        rewrite(path)
-
-if __name__ == '__main__':
-    main()

+ 21 - 11
nsist/commands.py

@@ -1,9 +1,11 @@
+import distlib.scripts
 import io
+import os.path as osp
 import shutil
-import win_cli_launchers
+from zipfile import ZipFile
 
 
-SCRIPT_TEMPLATE = u"""#!python
+SCRIPT_TEMPLATE = u"""# -*- coding: utf-8 -*-
 import sys, os
 import site
 installdir = os.path.dirname(os.path.dirname(__file__))
@@ -20,14 +22,20 @@ os.environ['PATH'] += ';' + os.path.dirname(sys.executable)
 
 if __name__ == '__main__':
     from {module} import {func}
-    {func}()
+    sys.exit({func}())
 """
 
+def find_exe(bitness=32):
+    distlib_dir = osp.dirname(distlib.scripts.__file__)
+    return osp.join(distlib_dir, 't%d.exe' % bitness)
+
 def prepare_bin_directory(target, commands, bitness=32):
-    exe_src = win_cli_launchers.find_exe('x64' if bitness == 64 else 'x86')
-    for name, command in commands.items():
-        shutil.copy(exe_src, str(target / (name+'.exe')))
+    # Give the base launcher a .dat extension so it doesn't show up as an
+    # executable command itself. During the installation it will be copied to
+    # each launcher name, and the necessary data appended to it.
+    shutil.copy(find_exe(bitness), str(target / 'launcher_exe.dat'))
 
+    for name, command in commands.items():
         specified_preamble = command.get('extra_preamble', None)
         if isinstance(specified_preamble, str):
             # Filename
@@ -38,8 +46,10 @@ def prepare_bin_directory(target, commands, bitness=32):
             # Passed a StringIO or similar object
             extra_preamble = specified_preamble
         module, func = command['entry_point'].split(':')
-        with (target / (name+'-script.py')).open('w') as f:
-            f.write(SCRIPT_TEMPLATE.format(
-                module=module, func=func,
-                extra_preamble=extra_preamble.read().rstrip(),
-            ))
+        script = SCRIPT_TEMPLATE.format(
+            module=module, func=func,
+            extra_preamble=extra_preamble.read().rstrip(),
+        )
+
+        with ZipFile(str(target / (name + '-append.zip')), 'w') as zf:
+            zf.writestr('__main__.py', script.encode('utf-8'))

+ 2 - 1
nsist/pyapp.nsi

@@ -106,7 +106,8 @@ Section "!${PRODUCT_NAME}" sec_app
 
   [% block install_commands %]
   [% if has_commands %]
-    nsExec::ExecToLog '[[ python ]] -Es "$INSTDIR\_rewrite_shebangs.py" "$INSTDIR\bin"'
+    DetailPrint "Setting up command-line launchers..."
+    nsExec::ExecToLog '[[ python ]] -Es "$INSTDIR\_assemble_launchers.py" "$INSTDIR\bin"'
     nsExec::ExecToLog '[[ python ]] -Es "$INSTDIR\_system_path.py" add "$INSTDIR\bin"'
   [% endif %]
   [% endblock install_commands %]

+ 17 - 11
nsist/tests/test_commands.py

@@ -1,23 +1,29 @@
 import io
-from testpath import assert_isfile
+from testpath import assert_isfile, assert_not_path_exists
+from zipfile import ZipFile
 
-from nsist import commands, _rewrite_shebangs
+from nsist import commands, _assemble_launchers
 
 cmds = {'acommand': {'entry_point': 'somemod:somefunc',
                      'extra_preamble': io.StringIO(u'import extra')}}
 
 def test_prepare_bin_dir(tmpdir):
     commands.prepare_bin_directory(tmpdir, cmds)
-    assert_isfile(str(tmpdir / 'acommand.exe'))
-    script_file = tmpdir / 'acommand-script.py'
-    assert_isfile(str(script_file))
 
-    with script_file.open() as f:
-        script_contents = f.read()
-    assert script_contents.startswith("#!python")
+    zip_file = tmpdir / 'acommand-append.zip'
+    exe_file = tmpdir / 'acommand.exe'
+    assert_isfile(zip_file)
+    assert_not_path_exists(exe_file)  # Created by _assemble_launchers
+
+    with ZipFile(str(zip_file)) as zf:
+        assert zf.testzip() is None
+        script_contents = zf.read('__main__.py').decode('utf-8')
     assert 'import extra' in script_contents
     assert 'somefunc()' in script_contents
 
-    _rewrite_shebangs.main(['_rewrite_shebangs.py', str(tmpdir)])
-    with script_file.open() as f:
-        assert f.read().startswith('#!"')
+    _assemble_launchers.main(['_assemble_launchers.py', str(tmpdir)])
+
+    assert_isfile(exe_file)
+    with ZipFile(str(exe_file)) as zf:
+        assert zf.testzip() is None
+        assert zf.read('__main__.py').decode('utf-8') == script_contents

+ 1 - 1
pyproject.toml

@@ -15,7 +15,7 @@ requires = [
     "requests_download",
     "jinja2",
     "yarg",
-    "win_cli_launchers"
+    "distlib"
 ]
 classifiers = [
     "License :: OSI Approved :: MIT License",

+ 1 - 1
tox.ini

@@ -6,7 +6,7 @@ envlist = python
 deps = pytest
        requests 
        requests_download
-       win_cli_launchers
+       distlib
        jinja2
        yarg
        testpath