summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Tools/msi/buildrelease.bat9
-rw-r--r--Tools/msi/make_zip.proj37
-rw-r--r--Tools/msi/make_zip.py160
3 files changed, 206 insertions, 0 deletions
diff --git a/Tools/msi/buildrelease.bat b/Tools/msi/buildrelease.bat
index 25f4508..21ce863 100644
--- a/Tools/msi/buildrelease.bat
+++ b/Tools/msi/buildrelease.bat
@@ -64,6 +64,13 @@ for %%f in (%_DLLTOOL_PATH%) do set PATH=%PATH%;%%~dpf
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 @@ if errorlevel 1 exit /B
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
index 0000000..d47de88
--- /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="&quot;$(PythonExe)&quot; generate_md5.py @(UserFiles->'&quot;%(FullPath)&quot;',' ')" />
+ </Target>
+</Project>
diff --git a/Tools/msi/make_zip.py b/Tools/msi/make_zip.py
new file mode 100644
index 0000000..0032956
--- /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))