Patchwork D7159: packaging: stage installed files for Inno

login
register
mail settings
Submitter phabricator
Date Oct. 24, 2019, 1:56 a.m.
Message ID <differential-rev-PHID-DREV-u26bnm5u3q224d4pdalm-req@mercurial-scm.org>
Download mbox | patch
Permalink /patch/42548/
State Superseded
Headers show

Comments

phabricator - Oct. 24, 2019, 1:56 a.m.
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

Patch

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: