[Python-checkins] cpython: Generates and signs a self-extracting executable containing an embedded Python.

steve.dower python-checkins at python.org
Wed Apr 15 00:40:18 CEST 2015


https://hg.python.org/cpython/rev/3da709cc631d
changeset:   95667:3da709cc631d
user:        Steve Dower <steve.dower at microsoft.com>
date:        Tue Apr 14 18:34:04 2015 -0400
summary:
  Generates and signs a self-extracting executable containing an embedded Python.

files:
  Tools/msi/buildrelease.bat |    9 +
  Tools/msi/make_zip.proj    |   37 +++++
  Tools/msi/make_zip.py      |  160 +++++++++++++++++++++++++
  3 files changed, 206 insertions(+), 0 deletions(-)


diff --git a/Tools/msi/buildrelease.bat b/Tools/msi/buildrelease.bat
--- a/Tools/msi/buildrelease.bat
+++ b/Tools/msi/buildrelease.bat
@@ -64,6 +64,13 @@
 set _DLLTOOL_PATH=
 :skipdlltoolsearch
 
+where rar /q && goto skiprarsearch
+set _RAR_PATH=
+where /R "%ProgramFiles%\WinRAR" rar > "%TEMP%\rar.loc" 2> nul && set /P _RAR_PATH= < "%TEMP%\rar.loc" & del "%TEMP%\rar.loc" 
+where /R "%ProgramFiles(x86)%\WinRAR" rar > "%TEMP%\rar.loc" 2> nul && set /P _RAR_PATH= < "%TEMP%\rar.loc" & del "%TEMP%\rar.loc" 
+if not exist "%_RAR_PATH%" echo Cannot find WinRAR on PATH or in external && pause
+:skiprarsearch
+
 if defined BUILDX86 (
     call :build x86
     if errorlevel 1 exit /B
@@ -135,6 +142,8 @@
 msbuild "%D%bundle\releaseweb.wixproj" /t:Rebuild %BUILDOPTS% %CERTOPTS% /p:RebuildAll=false
 if errorlevel 1 exit /B
 
+if defined _RAR_PATH msbuild "%D%make_zip.proj" /t:Build %BUILDOPTS% %CERTOPTS% "/p:RAR=%_RAR_PATH%"
+
 if not "%OUTDIR%" EQU "" (
     mkdir "%OUTDIR%\%OUTDIR_PLAT%"
     copy /Y "%BUILD%en-us\*.cab" "%OUTDIR%\%OUTDIR_PLAT%"
diff --git a/Tools/msi/make_zip.proj b/Tools/msi/make_zip.proj
new file mode 100644
--- /dev/null
+++ b/Tools/msi/make_zip.proj
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+    <PropertyGroup>
+        <ProjectGuid>{10487945-15D1-4092-A214-338395C4116B}</ProjectGuid>
+        <OutputName>python</OutputName>
+        <OutputSuffix></OutputSuffix>
+    </PropertyGroup>
+
+    <Import Project="msi.props" />
+
+    <PropertyGroup>
+        <TargetName>python-$(PythonVersion)-embed-$(ArchName)</TargetName>
+        <TargetExt>.exe</TargetExt>
+        <TargetPath>$(OutputPath)\en-us\$(TargetName)$(TargetExt)</TargetPath>
+        <Arguments>"$(PythonExe)" "$(MSBuildThisFileDirectory)\make_zip.py"</Arguments>
+        <Arguments>$(Arguments) -e -o "$(TargetPath)" -t "$(IntermediateOutputPath)\zip_$(ArchName)" -a $(ArchName)</Arguments>
+        <Arguments Condition="Exists('$(RAR)')">$(Arguments) --rar "$(RAR)"</Arguments>
+        <Environment>set DOC_FILENAME=python$(PythonVersion).chm</Environment>
+    </PropertyGroup>
+
+    <Target Name="_Build">
+        <Exec Command="setlocal
+$(Environment)
+$(Arguments)" />
+    </Target>
+
+    <Target Name="AfterBuild" />
+    <Target Name="Build" DependsOnTargets="_Build;AfterBuild" />
+
+    <Target Name="ShowHashes">
+        <ItemGroup>
+            <UserFiles Include="@(File)" Condition="'%(File.CopyTo)' == '$(EXETarget)'" />
+        </ItemGroup>
+
+        <Exec Command=""$(PythonExe)" generate_md5.py @(UserFiles->'"%(FullPath)"',' ')" />
+    </Target>
+</Project>
diff --git a/Tools/msi/make_zip.py b/Tools/msi/make_zip.py
new file mode 100644
--- /dev/null
+++ b/Tools/msi/make_zip.py
@@ -0,0 +1,160 @@
+import argparse
+import re
+import sys
+import shutil
+import os
+import tempfile
+
+from pathlib import Path
+from zipfile import ZipFile, ZIP_DEFLATED
+import subprocess
+
+TKTCL_RE = re.compile(r'^(_?tk|tcl).+\.(pyd|dll)', re.IGNORECASE)
+DEBUG_RE = re.compile(r'_d\.(pyd|dll|exe)$', re.IGNORECASE)
+PYTHON_DLL_RE = re.compile(r'python\d\d?\.dll$', re.IGNORECASE)
+
+def is_not_debug(p):
+    return not DEBUG_RE.search(p.name) and not TKTCL_RE.search(p.name)
+
+def is_not_debug_or_python(p):
+    return is_not_debug(p) and not PYTHON_DLL_RE.search(p.name)
+
+def include_in_lib(p):
+    name = p.name.lower()
+    if p.is_dir():
+        if name in {'__pycache__', 'ensurepip', 'idlelib', 'pydoc_data', 'tkinter', 'turtledemo'}:
+            return False
+        if name.startswith('plat-'):
+            return False
+        if name == 'test' and p.parts[-2].lower() == 'lib':
+            return False
+        return True
+
+    if name in {'_ctypes_test.pyd', '_testbuffer.pyd', '_testcapi.pyd', '_testimportmultiple.pyd', 'xxlimited.pyd'}:
+        return False
+    return p.suffix.lower() not in {'.pyc', '.pyo'}
+
+def include_in_tools(p):
+    if p.is_dir() and p.name.lower() in {'scripts', 'i18n', 'pynche', 'demo', 'parser'}:
+        return True
+
+    return p.suffix.lower() in {'.py', '.pyw', '.txt'}
+
+FULL_LAYOUT = [
+    ('/', 'PCBuild/$arch', 'python*.exe', is_not_debug),
+    ('/', 'PCBuild/$arch', 'python*.dll', is_not_debug),
+    ('DLLs/', 'PCBuild/$arch', '*.pyd', is_not_debug),
+    ('DLLs/', 'PCBuild/$arch', '*.dll', is_not_debug),
+    ('include/', 'include', '*.h', None),
+    ('include/', 'PC', 'pyconfig.h', None),
+    ('Lib/', 'Lib', '**/*', include_in_lib),
+    ('Tools/', 'Tools', '**/*', include_in_tools),
+]
+
+if os.getenv('DOC_FILENAME'):
+    FULL_LAYOUT.append(('Doc/', 'Doc/build/htmlhelp', os.getenv('DOC_FILENAME'), None))
+
+EMBED_LAYOUT = [
+    ('/', 'PCBuild/$arch', 'python*.exe', is_not_debug),
+    ('/', 'PCBuild/$arch', '*.pyd', is_not_debug),
+    ('/', 'PCBuild/$arch', '*.dll', is_not_debug),
+    ('python35.zip', 'Lib', '**/*', include_in_lib),
+]
+
+def copy_to_layout(target, source, rel_sources):
+    count = 0
+
+    if target.suffix.lower() == '.zip':
+        if target.exists():
+            target.unlink()
+
+        with ZipFile(str(target), 'w', ZIP_DEFLATED) as f:
+            for s, rel in rel_sources:
+                f.write(str(s), str(rel))
+                count += 1
+
+    else:
+        for s, rel in rel_sources:
+            try:
+                (target / rel).parent.mkdir(parents=True)
+            except FileExistsError:
+                pass
+            shutil.copy(str(s), str(target / rel))
+            count += 1
+
+    return count
+
+def rglob(root, pattern, condition):
+    dirs = [root]
+    recurse = pattern[:3] in {'**/', '**\\'}
+    while dirs:
+        d = dirs.pop(0)
+        for f in d.glob(pattern[3:] if recurse else pattern):
+            if recurse and f.is_dir() and (not condition or condition(f)):
+                dirs.append(f)
+            elif f.is_file() and (not condition or condition(f)):
+                yield f, f.relative_to(root)
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument('-s', '--source', metavar='dir', help='The directory containing the repository root', type=Path)
+    parser.add_argument('-o', '--out', metavar='file', help='The name of the output self-extracting archive', type=Path, required=True)
+    parser.add_argument('-t', '--temp', metavar='dir', help='A directory to temporarily extract files into', type=Path, default=None)
+    parser.add_argument('-e', '--embed', help='Create an embedding layout', action='store_true', default=False)
+    parser.add_argument('-a', '--arch', help='Specify the architecture to use (win32/amd64)', type=str, default="win32")
+    parser.add_argument('--rar', help='Full path to WinRAR compressor (rar.exe)', type=Path, default=Path("rar.exe"))
+    ns = parser.parse_args()
+
+    source = ns.source or (Path(__file__).parent.parent.parent)
+    out = ns.out
+    arch = ns.arch
+    rar = getattr(ns, 'rar')
+    assert isinstance(source, Path)
+    assert isinstance(out, Path)
+    assert isinstance(arch, str)
+    assert isinstance(rar, Path)
+
+    if ns.temp:
+        temp = ns.temp
+        delete_temp = False
+    else:
+        temp = Path(tempfile.mkdtemp())
+        delete_temp = True
+
+    try:
+        out.parent.mkdir(parents=True)
+    except FileExistsError:
+        pass
+    try:
+        temp.mkdir(parents=True)
+    except FileExistsError:
+        pass
+
+    layout = EMBED_LAYOUT if ns.embed else FULL_LAYOUT
+
+    try:
+        for t, s, p, c in layout:
+            s = source / s.replace("$arch", arch)
+            copied = copy_to_layout(
+                temp / t.rstrip('/'),
+                source,
+                rglob(s, p, c)
+            )
+            print('Copied {} files'.format(copied))
+
+        if rar and rar.is_file():
+            subprocess.check_call([
+                str(rar),
+                "a",
+                "-m5", "-ed", "-ep1", "-s", "-r",
+                "-sfxwincon.sfx",
+                str(out),
+                str(temp / '*')
+            ])
+    finally:
+        if delete_temp:
+            shutil.rmtree(temp, True)
+
+
+if __name__ == "__main__":
+    sys.exit(int(main() or 0))

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list