From patchwork Thu Oct 24 01:56:15 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: D7159: packaging: stage installed files for Inno From: phabricator X-Patchwork-Id: 42548 Message-Id: To: Phabricator Cc: mercurial-devel@mercurial-scm.org Date: Thu, 24 Oct 2019 01:56:15 +0000 indygreg created this revision. Herald added a subscriber: mercurial-devel. Herald added a reviewer: hg-reviewers. REVISION SUMMARY Previously, the Inno installer maintained its own mapping of source files to install location. (We have to maintain a similar mapping in the WiX installer.) Managing the explicit file layout for Windows packages is cumbersome and redundant. Every time you want to change the layout you need to change N locations. We frequently forget to do this and we only find out when people install Mercurial from our packages at release time. This commit starts the process of consolidating and simplifying the logic for managing the install layout on Windows. We introduce a list of install layout rules. These are simply source filenames (which can contain wildcards) and destination paths. The Inno packaging code has been updated to assemble all files into a staging directory that mirrors the final install layout. The list of files to add to the installer is derived by walking this staging directory and dynamically emitting the proper entries for the Inno Setup script. I diffed the file layout before and after this commit and there is no difference. Another benefit of this change is that it facilitates easier testing of the Windows install layout. Before, in order to test the final install layout, you needed to build an installer and run it. Now, you can stage files into the final layout and test from there, without running the installer. This should cut down on overhead when changing Windows code. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D7159 AFFECTED FILES contrib/packaging/hgpackaging/inno.py contrib/packaging/hgpackaging/py2exe.py contrib/packaging/hgpackaging/util.py contrib/packaging/inno/mercurial.iss CHANGE DETAILS To: indygreg, #hg-reviewers Cc: mercurial-devel diff --git a/contrib/packaging/inno/mercurial.iss b/contrib/packaging/inno/mercurial.iss --- a/contrib/packaging/inno/mercurial.iss +++ b/contrib/packaging/inno/mercurial.iss @@ -33,8 +33,8 @@ AppVerName=Mercurial {#VERSION} OutputBaseFilename=Mercurial-{#VERSION} #endif -InfoAfterFile=contrib/win32/postinstall.txt -LicenseFile=COPYING +InfoAfterFile=../postinstall.txt +LicenseFile=Copying.txt ShowLanguageDialog=yes AppPublisher=Matt Mackall and others AppPublisherURL=https://mercurial-scm.org/ @@ -43,49 +43,23 @@ {{ 'AppID={{4B95A5F1-EF59-4B08-BED8-C891C46121B3}' }} AppContact=mercurial@mercurial-scm.org DefaultDirName={pf}\Mercurial -SourceDir=..\.. +SourceDir=stage VersionInfoDescription=Mercurial distributed SCM (version {#VERSION}) VersionInfoCopyright=Copyright 2005-2019 Matt Mackall and others VersionInfoCompany=Matt Mackall and others InternalCompressLevel=max SolidCompression=true -SetupIconFile=contrib\win32\mercurial.ico +SetupIconFile=../mercurial.ico AllowNoIcons=true DefaultGroupName=Mercurial PrivilegesRequired=none ChangesEnvironment=true [Files] -Source: contrib\mercurial.el; DestDir: {app}/Contrib -Source: contrib\vim\*.*; DestDir: {app}/Contrib/Vim -Source: contrib\zsh_completion; DestDir: {app}/Contrib -Source: contrib\bash_completion; DestDir: {app}/Contrib -Source: contrib\tcsh_completion; DestDir: {app}/Contrib -Source: contrib\tcsh_completion_build.sh; DestDir: {app}/Contrib -Source: contrib\hgk; DestDir: {app}/Contrib; DestName: hgk.tcl -Source: contrib\xml.rnc; DestDir: {app}/Contrib -Source: contrib\mercurial.el; DestDir: {app}/Contrib -Source: contrib\mq.el; DestDir: {app}/Contrib -Source: contrib\hgweb.fcgi; DestDir: {app}/Contrib -Source: contrib\hgweb.wsgi; DestDir: {app}/Contrib -Source: contrib\win32\ReadMe.html; DestDir: {app}; Flags: isreadme -Source: contrib\win32\postinstall.txt; DestDir: {app}; DestName: ReleaseNotes.txt -Source: dist\hg.exe; DestDir: {app}; AfterInstall: Touch('{app}\hg.exe.local') -Source: dist\lib\*.dll; Destdir: {app}\lib -Source: dist\lib\*.pyd; Destdir: {app}\lib -Source: dist\python*.dll; Destdir: {app}; Flags: skipifsourcedoesntexist -Source: dist\msvc*.dll; DestDir: {app}; Flags: skipifsourcedoesntexist -Source: dist\Microsoft.VC*.CRT.manifest; DestDir: {app}; Flags: skipifsourcedoesntexist -Source: dist\lib\library.zip; DestDir: {app}\lib -Source: doc\*.html; DestDir: {app}\Docs -Source: doc\style.css; DestDir: {app}\Docs -Source: mercurial\help\*.txt; DestDir: {app}\help -Source: mercurial\help\internals\*.txt; DestDir: {app}\help\internals -Source: mercurial\default.d\*.rc; DestDir: {app}\default.d -Source: mercurial\locale\*.*; DestDir: {app}\locale; Flags: recursesubdirs createallsubdirs skipifsourcedoesntexist -Source: mercurial\templates\*.*; DestDir: {app}\Templates; Flags: recursesubdirs createallsubdirs -Source: CONTRIBUTORS; DestDir: {app}; DestName: Contributors.txt -Source: COPYING; DestDir: {app}; DestName: Copying.txt +{% for entry in package_files -%} +Source: {{ entry.source }}; DestDir: {{ entry.dest_dir }} +{%- if entry.metadata %}; {{ entry.metadata }}{% endif %} +{% endfor %} [INI] Filename: {app}\Mercurial.url; Section: InternetShortcut; Key: URL; String: https://mercurial-scm.org/ diff --git a/contrib/packaging/hgpackaging/util.py b/contrib/packaging/hgpackaging/util.py --- a/contrib/packaging/hgpackaging/util.py +++ b/contrib/packaging/hgpackaging/util.py @@ -9,8 +9,10 @@ import distutils.version import getpass +import glob import os import pathlib +import shutil import subprocess import tarfile import zipfile @@ -164,3 +166,47 @@ 'version': version, 'py3': version >= distutils.version.LooseVersion('3'), } + + +def process_install_rules( + rules: list, source_dir: pathlib.Path, dest_dir: pathlib.Path +): + for source, dest in rules: + if '*' in source: + if not dest.endswith('/'): + raise ValueError('destination must end in / when globbing') + + # We strip off the source path component before the first glob + # character to construct the relative install path. + prefix_end_index = source[: source.index('*')].rindex('/') + relative_prefix = source_dir / source[0:prefix_end_index] + + for res in glob.glob(str(source_dir / source), recursive=True): + source_path = pathlib.Path(res) + + if source_path.is_dir(): + continue + + rel_path = source_path.relative_to(relative_prefix) + + dest_path = dest_dir / dest[:-1] / rel_path + + dest_path.parent.mkdir(parents=True, exist_ok=True) + print('copying %s to %s' % (source_path, dest_path)) + shutil.copy(source_path, dest_path) + + # Simple file case. + else: + source_path = pathlib.Path(source) + + if dest.endswith('/'): + dest_path = pathlib.Path(dest) / source_path.name + else: + dest_path = pathlib.Path(dest) + + full_source_path = source_dir / source_path + full_dest_path = dest_dir / dest_path + + full_dest_path.parent.mkdir(parents=True, exist_ok=True) + shutil.copy(full_source_path, full_dest_path) + print('copying %s to %s' % (full_source_path, full_dest_path)) diff --git a/contrib/packaging/hgpackaging/py2exe.py b/contrib/packaging/hgpackaging/py2exe.py --- a/contrib/packaging/hgpackaging/py2exe.py +++ b/contrib/packaging/hgpackaging/py2exe.py @@ -15,10 +15,43 @@ from .util import ( extract_tar_to_directory, extract_zip_to_directory, + process_install_rules, python_exe_info, ) +STAGING_RULES = [ + ('contrib/bash_completion', 'Contrib/'), + ('contrib/hgk', 'Contrib/hgk.tcl'), + ('contrib/hgweb.fcgi', 'Contrib/'), + ('contrib/hgweb.wsgi', 'Contrib/'), + ('contrib/mercurial.el', 'Contrib/'), + ('contrib/mq.el', 'Contrib/'), + ('contrib/tcsh_completion', 'Contrib/'), + ('contrib/tcsh_completion_build.sh', 'Contrib/'), + ('contrib/vim/*', 'Contrib/Vim/'), + ('contrib/win32/postinstall.txt', 'ReleaseNotes.txt'), + ('contrib/win32/ReadMe.html', 'ReadMe.html'), + ('contrib/xml.rnc', 'Contrib/'), + ('contrib/zsh_completion', 'Contrib/'), + ('dist/hg.exe', './'), + ('dist/lib/*.dll', 'lib/'), + ('dist/lib/*.pyd', 'lib/'), + ('dist/lib/library.zip', 'lib/'), + ('dist/Microsoft.VC*.CRT.manifest', './'), + ('dist/msvc*.dll', './'), + ('dist/python*.dll', './'), + ('doc/*.html', 'Docs/'), + ('doc/style.css', 'Docs/'), + ('mercurial/help/**/*.txt', 'help/'), + ('mercurial/default.d/*.rc', 'default.d/'), + ('mercurial/locale/**/*', 'locale/'), + ('mercurial/templates/**/*', 'Templates/'), + ('CONTRIBUTORS', 'Contributors.txt'), + ('COPYING', 'Copying.txt'), +] + + def build_py2exe( source_dir: pathlib.Path, build_dir: pathlib.Path, @@ -169,3 +202,12 @@ env=env, check=True, ) + + +def stage_install(source_dir: pathlib.Path, staging_dir: pathlib.Path): + """Copy all files to be installed to a directory. + + This allows packaging to simply walk a directory tree to find source + files. + """ + process_install_rules(STAGING_RULES, source_dir, staging_dir) diff --git a/contrib/packaging/hgpackaging/inno.py b/contrib/packaging/hgpackaging/inno.py --- a/contrib/packaging/hgpackaging/inno.py +++ b/contrib/packaging/hgpackaging/inno.py @@ -14,7 +14,10 @@ import jinja2 -from .py2exe import build_py2exe +from .py2exe import ( + build_py2exe, + stage_install, +) from .util import find_vc_runtime_files EXTRA_PACKAGES = { @@ -24,6 +27,11 @@ 'win32ctypes', } +PACKAGE_FILES_METADATA = { + 'ReadMe.html': 'Flags: isreadme', + 'hg.exe': "AfterInstall: Touch('{app}\\hg.exe.local')", +} + def build( source_dir: pathlib.Path, @@ -47,6 +55,7 @@ arch = 'x64' if vc_x64 else 'x86' inno_source_dir = source_dir / 'contrib' / 'packaging' / 'inno' inno_build_dir = build_dir / ('inno-%s' % arch) + staging_dir = inno_build_dir / 'stage' requirements_txt = ( source_dir / 'contrib' / 'packaging' / 'inno' / 'requirements.txt' @@ -63,6 +72,15 @@ extra_packages=EXTRA_PACKAGES, ) + # Purge the staging directory for every build so packaging is + # pristine. + if staging_dir.exists(): + print('purging %s' % staging_dir) + shutil.rmtree(staging_dir) + + # Now assemble all the packaged files into the staging directory. + stage_install(source_dir, staging_dir) + # hg.exe depends on VC9 runtime DLLs. Copy those into place. for f in find_vc_runtime_files(vc_x64): if f.name.endswith('.manifest'): @@ -70,11 +88,34 @@ else: basename = f.name - dest_path = source_dir / 'dist' / basename + dest_path = staging_dir / basename print('copying %s to %s' % (f, dest_path)) shutil.copyfile(f, dest_path) + # The final package layout is simply a mirror of the staging directory. + package_files = [] + for root, dirs, files in os.walk(staging_dir): + dirs.sort() + + root = pathlib.Path(root) + + for f in sorted(files): + full = root / f + rel = full.relative_to(staging_dir) + if str(rel.parent) == '.': + dest_dir = '{app}' + else: + dest_dir = '{app}\\%s' % rel.parent + + package_files.append( + { + 'source': rel, + 'dest_dir': dest_dir, + 'metadata': PACKAGE_FILES_METADATA.get(str(rel), None), + } + ) + print('creating installer') # Install Inno files by rendering a template. @@ -93,11 +134,17 @@ % (e.name, e.lineno, e.message,) ) - content = template.render() + content = template.render(package_files=package_files) with (inno_build_dir / 'mercurial.iss').open('w', encoding='utf-8') as fh: fh.write(content) + # Copy additional files used by Inno. + for p in ('mercurial.ico', 'postinstall.txt'): + shutil.copyfile( + source_dir / 'contrib' / 'win32' / p, inno_build_dir / p + ) + args = [str(iscc_exe)] if vc_x64: