packaging: stage installed files for Inno
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.
Differential Revision: https://phab.mercurial-scm.org/D7159
--- a/contrib/packaging/hgpackaging/inno.py Wed Oct 23 18:39:17 2019 -0700
+++ b/contrib/packaging/hgpackaging/inno.py Wed Oct 23 18:39:28 2019 -0700
@@ -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:
--- a/contrib/packaging/hgpackaging/py2exe.py Wed Oct 23 18:39:17 2019 -0700
+++ b/contrib/packaging/hgpackaging/py2exe.py Wed Oct 23 18:39:28 2019 -0700
@@ -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)
--- a/contrib/packaging/hgpackaging/util.py Wed Oct 23 18:39:17 2019 -0700
+++ b/contrib/packaging/hgpackaging/util.py Wed Oct 23 18:39:28 2019 -0700
@@ -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))
--- a/contrib/packaging/inno/mercurial.iss Wed Oct 23 18:39:17 2019 -0700
+++ b/contrib/packaging/inno/mercurial.iss Wed Oct 23 18:39:28 2019 -0700
@@ -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/