D7173: packaging: stage files and dynamically generate WiX installer

indygreg (Gregory Szorc) phabricator at mercurial-scm.org
Thu Oct 24 23:35:10 EDT 2019


indygreg updated this revision to Diff 17406.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D7173?vs=17394&id=17406

BRANCH
  stable

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D7173/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D7173

AFFECTED FILES
  contrib/packaging/hgpackaging/py2exe.py
  contrib/packaging/hgpackaging/wix.py
  contrib/packaging/wix/contrib.wxs
  contrib/packaging/wix/dist.wxs
  contrib/packaging/wix/doc.wxs
  contrib/packaging/wix/guids.wxi
  contrib/packaging/wix/help.wxs
  contrib/packaging/wix/locale.wxs
  contrib/packaging/wix/mercurial.wxs
  contrib/packaging/wix/templates.wxs
  contrib/win32/mercurial.ini
  tests/test-install.t

CHANGE DETAILS

diff --git a/tests/test-install.t b/tests/test-install.t
--- a/tests/test-install.t
+++ b/tests/test-install.t
@@ -162,80 +162,6 @@
     "fsmonitor-watchman": "false",
     "fsmonitor-watchman-error": "warning: Watchman unavailable: watchman exited with code 1",
 
-
-#if test-repo
-  $ . "$TESTDIR/helpers-testrepo.sh"
-
-  $ cat >> wixxml.py << EOF
-  > import os
-  > import subprocess
-  > import sys
-  > import xml.etree.ElementTree as ET
-  > from mercurial import pycompat
-  > 
-  > # MSYS mangles the path if it expands $TESTDIR
-  > testdir = os.environ['TESTDIR']
-  > ns = {'wix' : 'http://schemas.microsoft.com/wix/2006/wi'}
-  > 
-  > def directory(node, relpath):
-  >     '''generator of files in the xml node, rooted at relpath'''
-  >     dirs = node.findall('./{%(wix)s}Directory' % ns)
-  > 
-  >     for d in dirs:
-  >         for subfile in directory(d, relpath + d.attrib['Name'] + '/'):
-  >             yield subfile
-  > 
-  >     files = node.findall('./{%(wix)s}Component/{%(wix)s}File' % ns)
-  > 
-  >     for f in files:
-  >         yield pycompat.sysbytes(relpath + f.attrib['Name'])
-  > 
-  > def hgdirectory(relpath):
-  >     '''generator of tracked files, rooted at relpath'''
-  >     hgdir = "%s/../mercurial" % (testdir)
-  >     args = ['hg', '--cwd', hgdir, 'files', relpath]
-  >     proc = subprocess.Popen(args, stdout=subprocess.PIPE,
-  >                             stderr=subprocess.PIPE)
-  >     output = proc.communicate()[0]
-  > 
-  >     for line in output.splitlines():
-  >         if os.name == 'nt':
-  >             yield line.replace(pycompat.sysbytes(os.sep), b'/')
-  >         else:
-  >             yield line
-  > 
-  > tracked = [f for f in hgdirectory(sys.argv[1])]
-  > 
-  > xml = ET.parse("%s/../contrib/packaging/wix/%s.wxs" % (testdir, sys.argv[1]))
-  > root = xml.getroot()
-  > dir = root.find('.//{%(wix)s}DirectoryRef' % ns)
-  > 
-  > installed = [f for f in directory(dir, '')]
-  > 
-  > print('Not installed:')
-  > for f in sorted(set(tracked) - set(installed)):
-  >     print('  %s' % pycompat.sysstr(f))
-  > 
-  > print('Not tracked:')
-  > for f in sorted(set(installed) - set(tracked)):
-  >     print('  %s' % pycompat.sysstr(f))
-  > EOF
-
-  $ ( testrepohgenv; "$PYTHON" wixxml.py help )
-  Not installed:
-    help/common.txt
-    help/hg-ssh.8.txt
-    help/hg.1.txt
-    help/hgignore.5.txt
-    help/hgrc.5.txt
-  Not tracked:
-
-  $ ( testrepohgenv; "$PYTHON" wixxml.py templates )
-  Not installed:
-  Not tracked:
-
-#endif
-
 #if py3
   $ HGALLOWPYTHON3=1
   $ export HGALLOWPYTHON3
diff --git a/contrib/win32/mercurial.ini b/contrib/win32/mercurial.ini
--- a/contrib/win32/mercurial.ini
+++ b/contrib/win32/mercurial.ini
@@ -16,7 +16,7 @@
 
 [ui]
 ; editor used to enter commit logs, etc.  Most text editors will work.
-editor = notepad
+; editor = notepad
 ; show changed files and be a bit more verbose if True
 ; verbose = True
 ; colorize commands output
diff --git a/contrib/packaging/wix/templates.wxs b/contrib/packaging/wix/templates.wxs
deleted file mode 100644
--- a/contrib/packaging/wix/templates.wxs
+++ /dev/null
@@ -1,251 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
-
-  <?include guids.wxi ?>
-  <?include defines.wxi ?>
-
-  <Fragment>
-    <ComponentGroup Id="templatesFolder">
-
-      <ComponentRef Id="templates.root" />
-
-      <ComponentRef Id="templates.atom" />
-      <ComponentRef Id="templates.coal" />
-      <ComponentRef Id="templates.gitweb" />
-      <ComponentRef Id="templates.json" />
-      <ComponentRef Id="templates.monoblue" />
-      <ComponentRef Id="templates.paper" />
-      <ComponentRef Id="templates.raw" />
-      <ComponentRef Id="templates.rss" />
-      <ComponentRef Id="templates.spartan" />
-      <ComponentRef Id="templates.static" />
-
-    </ComponentGroup>
-  </Fragment>
-
-  <Fragment>
-    <DirectoryRef Id="INSTALLDIR">
-
-      <Directory Id="templatesdir" Name="templates" FileSource="$(var.SourceDir)">
-
-        <Component Id="templates.root" Guid="$(var.templates.root.guid)" Win64='$(var.IsX64)'>
-          <File Name="map-cmdline.changelog" KeyPath="yes" />
-          <File Name="map-cmdline.compact" />
-          <File Name="map-cmdline.default" />
-          <File Name="map-cmdline.show" />
-          <File Name="map-cmdline.bisect" />
-          <File Name="map-cmdline.xml" />
-          <File Name="map-cmdline.status" />
-          <File Name="map-cmdline.phases" />
-        </Component>
-
-        <Directory Id="templates.jsondir" Name="json">
-          <Component Id="templates.json" Guid="$(var.templates.json.guid)" Win64='$(var.IsX64)'>
-            <File Id="json.changelist.tmpl" Name="changelist.tmpl" KeyPath="yes" />
-            <File Id="json.graph.tmpl"      Name="graph.tmpl" />
-            <File Id="json.map"             Name="map" />
-          </Component>
-        </Directory>
-
-        <Directory Id="templates.atomdir" Name="atom">
-          <Component Id="templates.atom" Guid="$(var.templates.atom.guid)" Win64='$(var.IsX64)'>
-            <File Id="atom.changelog.tmpl"      Name="changelog.tmpl" KeyPath="yes" />
-            <File Id="atom.changelogentry.tmpl" Name="changelogentry.tmpl" />
-            <File Id="atom.error.tmpl"          Name="error.tmpl" />
-            <File Id="atom.filelog.tmpl"        Name="filelog.tmpl" />
-            <File Id="atom.header.tmpl"         Name="header.tmpl" />
-            <File Id="atom.map"                 Name="map" />
-            <File Id="atom.tagentry.tmpl"       Name="tagentry.tmpl" />
-            <File Id="atom.tags.tmpl"           Name="tags.tmpl" />
-            <File Id="atom.branchentry.tmpl"    Name="branchentry.tmpl" />
-            <File Id="atom.branches.tmpl"       Name="branches.tmpl" />
-            <File Id="atom.bookmarks.tmpl"      Name="bookmarks.tmpl" />
-            <File Id="atom.bookmarkentry.tmpl"  Name="bookmarkentry.tmpl" />
-          </Component>
-        </Directory>
-
-        <Directory Id="templates.coaldir" Name="coal">
-          <Component Id="templates.coal" Guid="$(var.templates.coal.guid)" Win64='$(var.IsX64)'>
-            <File Id="coal.header.tmpl" Name="header.tmpl" KeyPath="yes" />
-            <File Id="coal.map"         Name="map" />
-          </Component>
-        </Directory>
-
-        <Directory Id="templates.gitwebdir" Name="gitweb">
-          <Component Id="templates.gitweb" Guid="$(var.templates.gitweb.guid)" Win64='$(var.IsX64)'>
-            <File Id="gitweb.branches.tmpl"       Name="branches.tmpl" KeyPath="yes" />
-            <File Id="gitweb.bookmarks.tmpl"      Name="bookmarks.tmpl" />
-            <File Id="gitweb.changelog.tmpl"      Name="changelog.tmpl" />
-            <File Id="gitweb.changelogentry.tmpl" Name="changelogentry.tmpl" />
-            <File Id="gitweb.changeset.tmpl"      Name="changeset.tmpl" />
-            <File Id="gitweb.error.tmpl"          Name="error.tmpl" />
-            <File Id="gitweb.fileannotate.tmpl"   Name="fileannotate.tmpl" />
-            <File Id="gitweb.filecomparison.tmpl" Name="filecomparison.tmpl" />
-            <File Id="gitweb.filediff.tmpl"       Name="filediff.tmpl" />
-            <File Id="gitweb.filelog.tmpl"        Name="filelog.tmpl" />
-            <File Id="gitweb.filerevision.tmpl"   Name="filerevision.tmpl" />
-            <File Id="gitweb.footer.tmpl"         Name="footer.tmpl" />
-            <File Id="gitweb.graph.tmpl"          Name="graph.tmpl" />
-            <File Id="gitweb.graphentry.tmpl"     Name="graphentry.tmpl" />
-            <File Id="gitweb.header.tmpl"         Name="header.tmpl" />
-            <File Id="gitweb.index.tmpl"          Name="index.tmpl" />
-            <File Id="gitweb.manifest.tmpl"       Name="manifest.tmpl" />
-            <File Id="gitweb.map"                 Name="map" />
-            <File Id="gitweb.notfound.tmpl"       Name="notfound.tmpl" />
-            <File Id="gitweb.search.tmpl"         Name="search.tmpl" />
-            <File Id="gitweb.shortlog.tmpl"       Name="shortlog.tmpl" />
-            <File Id="gitweb.summary.tmpl"        Name="summary.tmpl" />
-            <File Id="gitweb.tags.tmpl"           Name="tags.tmpl" />
-            <File Id="gitweb.help.tmpl"           Name="help.tmpl" />
-            <File Id="gitweb.helptopics.tmpl"     Name="helptopics.tmpl" />
-          </Component>
-        </Directory>
-
-        <Directory Id="templates.monobluedir" Name="monoblue">
-          <Component Id="templates.monoblue" Guid="$(var.templates.monoblue.guid)" Win64='$(var.IsX64)'>
-            <File Id="monoblue.branches.tmpl"       Name="branches.tmpl" KeyPath="yes" />
-            <File Id="monoblue.bookmarks.tmpl"      Name="bookmarks.tmpl" />
-            <File Id="monoblue.changelog.tmpl"      Name="changelog.tmpl" />
-            <File Id="monoblue.changelogentry.tmpl" Name="changelogentry.tmpl" />
-            <File Id="monoblue.changeset.tmpl"      Name="changeset.tmpl" />
-            <File Id="monoblue.error.tmpl"          Name="error.tmpl" />
-            <File Id="monoblue.fileannotate.tmpl"   Name="fileannotate.tmpl" />
-            <File Id="monoblue.filecomparison.tmpl" Name="filecomparison.tmpl" />
-            <File Id="monoblue.filediff.tmpl"       Name="filediff.tmpl" />
-            <File Id="monoblue.filelog.tmpl"        Name="filelog.tmpl" />
-            <File Id="monoblue.filerevision.tmpl"   Name="filerevision.tmpl" />
-            <File Id="monoblue.footer.tmpl"         Name="footer.tmpl" />
-            <File Id="monoblue.graph.tmpl"          Name="graph.tmpl" />
-            <File Id="monoblue.graphentry.tmpl"     Name="graphentry.tmpl" />
-            <File Id="monoblue.header.tmpl"         Name="header.tmpl" />
-            <File Id="monoblue.index.tmpl"          Name="index.tmpl" />
-            <File Id="monoblue.manifest.tmpl"       Name="manifest.tmpl" />
-            <File Id="monoblue.map"                 Name="map" />
-            <File Id="monoblue.notfound.tmpl"       Name="notfound.tmpl" />
-            <File Id="monoblue.search.tmpl"         Name="search.tmpl" />
-            <File Id="monoblue.shortlog.tmpl"       Name="shortlog.tmpl" />
-            <File Id="monoblue.summary.tmpl"        Name="summary.tmpl" />
-            <File Id="monoblue.tags.tmpl"           Name="tags.tmpl" />
-            <File Id="monoblue.help.tmpl"           Name="help.tmpl" />
-            <File Id="monoblue.helptopics.tmpl"     Name="helptopics.tmpl" />
-          </Component>
-        </Directory>
-
-        <Directory Id="templates.paperdir" Name="paper">
-          <Component Id="templates.paper" Guid="$(var.templates.paper.guid)" Win64='$(var.IsX64)'>
-            <File Id="paper.branches.tmpl"      Name="branches.tmpl" KeyPath="yes" />
-            <File Id="paper.bookmarks.tmpl"     Name="bookmarks.tmpl" />
-            <File Id="paper.changeset.tmpl"     Name="changeset.tmpl" />
-            <File Id="paper.diffstat.tmpl"      Name="diffstat.tmpl" />
-            <File Id="paper.error.tmpl"         Name="error.tmpl" />
-            <File Id="paper.fileannotate.tmpl"  Name="fileannotate.tmpl" />
-            <File Id="paper.filecomparison.tmpl" Name="filecomparison.tmpl" />
-            <File Id="paper.filediff.tmpl"      Name="filediff.tmpl" />
-            <File Id="paper.filelog.tmpl"       Name="filelog.tmpl" />
-            <File Id="paper.filelogentry.tmpl"  Name="filelogentry.tmpl" />
-            <File Id="paper.filerevision.tmpl"  Name="filerevision.tmpl" />
-            <File Id="paper.footer.tmpl"        Name="footer.tmpl" />
-            <File Id="paper.graph.tmpl"         Name="graph.tmpl" />
-            <File Id="paper.graphentry.tmpl"    Name="graphentry.tmpl" />
-            <File Id="paper.header.tmpl"        Name="header.tmpl" />
-            <File Id="paper.index.tmpl"         Name="index.tmpl" />
-            <File Id="paper.manifest.tmpl"      Name="manifest.tmpl" />
-            <File Id="paper.map"                Name="map" />
-            <File Id="paper.notfound.tmpl"      Name="notfound.tmpl" />
-            <File Id="paper.search.tmpl"        Name="search.tmpl" />
-            <File Id="paper.shortlog.tmpl"      Name="shortlog.tmpl" />
-            <File Id="paper.shortlogentry.tmpl" Name="shortlogentry.tmpl" />
-            <File Id="paper.tags.tmpl"          Name="tags.tmpl" />
-            <File Id="paper.help.tmpl"          Name="help.tmpl" />
-            <File Id="paper.helptopics.tmpl"    Name="helptopics.tmpl" />
-          </Component>
-        </Directory>
-
-        <Directory Id="templates.rawdir" Name="raw">
-          <Component Id="templates.raw" Guid="$(var.templates.raw.guid)" Win64='$(var.IsX64)'>
-            <File Id="raw.changeset.tmpl"    Name="changeset.tmpl" KeyPath="yes" />
-            <File Id="raw.error.tmpl"        Name="error.tmpl" />
-            <File Id="raw.fileannotate.tmpl" Name="fileannotate.tmpl" />
-            <File Id="raw.filediff.tmpl"     Name="filediff.tmpl" />
-            <File Id="raw.graph.tmpl"        Name="graph.tmpl" />
-            <File Id="raw.graphedge.tmpl"    Name="graphedge.tmpl" />
-            <File Id="raw.graphnode.tmpl"    Name="graphnode.tmpl" />
-            <File Id="raw.index.tmpl"        Name="index.tmpl" />
-            <File Id="raw.manifest.tmpl"     Name="manifest.tmpl" />
-            <File Id="raw.map"               Name="map" />
-            <File Id="raw.notfound.tmpl"     Name="notfound.tmpl" />
-            <File Id="raw.search.tmpl"       Name="search.tmpl" />
-            <File Id="raw.logentry.tmpl"     Name="logentry.tmpl" />
-            <File Id="raw.changelog.tmpl"    Name="changelog.tmpl" />
-          </Component>
-        </Directory>
-
-        <Directory Id="templates.rssdir" Name="rss">
-          <Component Id="templates.rss" Guid="$(var.templates.rss.guid)" Win64='$(var.IsX64)'>
-            <File Id="rss.changelog.tmpl"      Name="changelog.tmpl" KeyPath="yes" />
-            <File Id="rss.changelogentry.tmpl" Name="changelogentry.tmpl" />
-            <File Id="rss.error.tmpl"          Name="error.tmpl" />
-            <File Id="rss.filelog.tmpl"        Name="filelog.tmpl" />
-            <File Id="rss.filelogentry.tmpl"   Name="filelogentry.tmpl" />
-            <File Id="rss.header.tmpl"         Name="header.tmpl" />
-            <File Id="rss.map"                 Name="map" />
-            <File Id="rss.tagentry.tmpl"       Name="tagentry.tmpl" />
-            <File Id="rss.tags.tmpl"           Name="tags.tmpl" />
-            <File Id="rss.bookmarks.tmpl"      Name="bookmarks.tmpl" />
-            <File Id="rss.bookmarkentry.tmpl"  Name="bookmarkentry.tmpl" />
-            <File Id="rss.branchentry.tmpl"    Name="branchentry.tmpl" />
-            <File Id="rss.branches.tmpl"       Name="branches.tmpl" />
-          </Component>
-        </Directory>
-
-        <Directory Id="templates.spartandir" Name="spartan">
-          <Component Id="templates.spartan" Guid="$(var.templates.spartan.guid)" Win64='$(var.IsX64)'>
-            <File Id="spartan.branches.tmpl"       Name="branches.tmpl" KeyPath="yes" />
-            <File Id="spartan.changelog.tmpl"      Name="changelog.tmpl" />
-            <File Id="spartan.changelogentry.tmpl" Name="changelogentry.tmpl" />
-            <File Id="spartan.changeset.tmpl"      Name="changeset.tmpl" />
-            <File Id="spartan.error.tmpl"          Name="error.tmpl" />
-            <File Id="spartan.fileannotate.tmpl"   Name="fileannotate.tmpl" />
-            <File Id="spartan.filediff.tmpl"       Name="filediff.tmpl" />
-            <File Id="spartan.filelog.tmpl"        Name="filelog.tmpl" />
-            <File Id="spartan.filelogentry.tmpl"   Name="filelogentry.tmpl" />
-            <File Id="spartan.filerevision.tmpl"   Name="filerevision.tmpl" />
-            <File Id="spartan.footer.tmpl"         Name="footer.tmpl" />
-            <File Id="spartan.graph.tmpl"          Name="graph.tmpl" />
-            <File Id="spartan.graphentry.tmpl"     Name="graphentry.tmpl" />
-            <File Id="spartan.header.tmpl"         Name="header.tmpl" />
-            <File Id="spartan.index.tmpl"          Name="index.tmpl" />
-            <File Id="spartan.manifest.tmpl"       Name="manifest.tmpl" />
-            <File Id="spartan.map"                 Name="map" />
-            <File Id="spartan.notfound.tmpl"       Name="notfound.tmpl" />
-            <File Id="spartan.search.tmpl"         Name="search.tmpl" />
-            <File Id="spartan.shortlog.tmpl"       Name="shortlog.tmpl" />
-            <File Id="spartan.shortlogentry.tmpl"  Name="shortlogentry.tmpl" />
-            <File Id="spartan.tags.tmpl"           Name="tags.tmpl" />
-          </Component>
-        </Directory>
-
-        <Directory Id="templates.staticdir" Name="static">
-          <Component Id="templates.static" Guid="$(var.templates.static.guid)" Win64='$(var.IsX64)'>
-            <File Id="static.background.png"     Name="background.png" KeyPath="yes" />
-            <File Id="static.coal.file.png"      Name="coal-file.png" />
-            <File Id="static.coal.folder.png"    Name="coal-folder.png" />
-            <File Id="static.followlines.js"     Name="followlines.js" />
-            <File Id="static.mercurial.js"       Name="mercurial.js" />
-            <File Id="static.hgicon.png"         Name="hgicon.png" />
-            <File Id="static.hglogo.png"         Name="hglogo.png" />
-            <File Id="static.style.coal.css"     Name="style-extra-coal.css" />
-            <File Id="static.style.gitweb.css"   Name="style-gitweb.css" />
-            <File Id="static.style.monoblue.css" Name="style-monoblue.css" />
-            <File Id="static.style.paper.css"    Name="style-paper.css" />
-            <File Id="static.style.css"          Name="style.css" />
-            <File Id="static.feed.icon"          Name="feed-icon-14x14.png" />
-          </Component>
-        </Directory>
-
-      </Directory>
-
-    </DirectoryRef>
-  </Fragment>
-
- </Wix>
diff --git a/contrib/packaging/wix/mercurial.wxs b/contrib/packaging/wix/mercurial.wxs
--- a/contrib/packaging/wix/mercurial.wxs
+++ b/contrib/packaging/wix/mercurial.wxs
@@ -60,30 +60,10 @@
       <Directory Id='$(var.PFolder)' Name='PFiles'>
         <Directory Id='INSTALLDIR' Name='Mercurial'>
           <Component Id='MainExecutable' Guid='$(var.ComponentMainExecutableGUID)' Win64='$(var.IsX64)'>
-            <File Id='hgEXE' Name='hg.exe' Source='dist\hg.exe' KeyPath='yes' />
+            <CreateFolder />
             <Environment Id="Environment" Name="PATH" Part="last" System="yes"
                          Permanent="no" Value="[INSTALLDIR]" Action="set" />
           </Component>
-          <Component Id='ReadMe' Guid='$(var.ReadMe.guid)' Win64='$(var.IsX64)'>
-              <File Id='ReadMe' Name='ReadMe.html' Source='contrib\win32\ReadMe.html'
-                    KeyPath='yes'/>
-          </Component>
-          <Component Id='COPYING' Guid='$(var.COPYING.guid)' Win64='$(var.IsX64)'>
-            <File Id='COPYING' Name='COPYING.rtf' Source='contrib\packaging\wix\COPYING.rtf'
-                  KeyPath='yes'/>
-          </Component>
-
-          <Directory Id='HGRCD' Name='hgrc.d'>
-            <Component Id='mercurial.rc' Guid='$(var.mercurial.rc.guid)' Win64='$(var.IsX64)'>
-              <File Id='mercurial.rc' Name='mercurial.rc' Source='contrib\win32\mercurial.ini'
-                    ReadOnly='yes' KeyPath='yes'/>
-            </Component>
-            <Component Id='mergetools.rc' Guid='$(var.mergetools.rc.guid)' Win64='$(var.IsX64)'>
-              <File Id='mergetools.rc' Name='mergetools.rc' Source='mercurial\default.d\mergetools.rc'
-                    ReadOnly='yes' KeyPath='yes'/>
-            </Component>
-          </Directory>
-
         </Directory>
       </Directory>
 
@@ -117,15 +97,12 @@
       <Feature Id='MainProgram' Title='Program' Description='Mercurial command line app'
              Level='1' Absent='disallow' >
         <ComponentRef Id='MainExecutable' />
-        <ComponentRef Id='distOutput' />
-        <ComponentRef Id='libOutput' />
         <ComponentRef Id='ProgramMenuDir' />
-        <ComponentRef Id='ReadMe' />
-        <ComponentRef Id='COPYING' />
-        <ComponentRef Id='mercurial.rc' />
-        <ComponentRef Id='mergetools.rc' />
-        <ComponentGroupRef Id='helpFolder' />
-        <ComponentGroupRef Id='templatesFolder' />
+        <ComponentGroupRef Id="hg.group.ROOT" />
+        <ComponentGroupRef Id="hg.group.hgrc.d" />
+        <ComponentGroupRef Id="hg.group.help" />
+        <ComponentGroupRef Id="hg.group.lib" />
+        <ComponentGroupRef Id="hg.group.templates" />
         <MergeRef Id='VCRuntime' />
         <MergeRef Id='VCRuntimePolicy' />
       </Feature>
@@ -135,13 +112,13 @@
         <?endforeach?>
       <?endif?>
       <Feature Id='Locales' Title='Translations' Description='Translations' Level='1'>
-        <ComponentGroupRef Id='localeFolder' />
+        <ComponentGroupRef Id="hg.group.locale" />
       </Feature>
       <Feature Id='Documentation' Title='Documentation' Description='HTML man pages' Level='1'>
-        <ComponentGroupRef Id='docFolder' />
+        <ComponentGroupRef Id="hg.group.doc" />
       </Feature>
       <Feature Id='Misc' Title='Miscellaneous' Description='Contributed scripts' Level='1'>
-        <ComponentGroupRef Id='contribFolder' />
+        <ComponentGroupRef Id="hg.group.contrib" />
       </Feature>
     </Feature>
 
diff --git a/contrib/packaging/wix/locale.wxs b/contrib/packaging/wix/locale.wxs
deleted file mode 100644
--- a/contrib/packaging/wix/locale.wxs
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
-
-  <?include defines.wxi ?>
-
-  <?define hglocales =
-    da;de;el;fr;it;ja;pt_BR;ro;ru;sv;zh_CN;zh_TW
-  ?>
-
-  <Fragment>
-    <ComponentGroup Id="localeFolder">
-      <?foreach LOC in $(var.hglocales) ?>
-        <ComponentRef Id="hg.locale.$(var.LOC)"/>
-      <?endforeach?>
-    </ComponentGroup>
-  </Fragment>
-
-  <Fragment>
-    <DirectoryRef Id="INSTALLDIR">
-      <Directory Id="localedir" Name="locale" FileSource="$(var.SourceDir)">
-        <?foreach LOC in $(var.hglocales) ?>
-          <Directory Id="hg.locale.$(var.LOC)" Name="$(var.LOC)">
-            <Directory Id="hg.locale.$(var.LOC).LC_MESSAGES" Name="LC_MESSAGES">
-              <Component Id="hg.locale.$(var.LOC)" Guid="*" Win64='$(var.IsX64)'>
-                <File Id="hg.mo.$(var.LOC)" Name="hg.mo" KeyPath="yes" />
-              </Component>
-            </Directory>
-          </Directory>
-        <?endforeach?>
-      </Directory>
-    </DirectoryRef>
-  </Fragment>
-
-</Wix>
diff --git a/contrib/packaging/wix/help.wxs b/contrib/packaging/wix/help.wxs
deleted file mode 100644
--- a/contrib/packaging/wix/help.wxs
+++ /dev/null
@@ -1,70 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
-
-  <?include guids.wxi ?>
-  <?include defines.wxi ?>
-
-  <Fragment>
-    <ComponentGroup Id='helpFolder'>
-      <ComponentRef Id='help.root' />
-      <ComponentRef Id='help.internals' />
-    </ComponentGroup>
-  </Fragment>
-
-  <Fragment>
-    <DirectoryRef Id="INSTALLDIR">
-      <Directory Id="helpdir" Name="help" FileSource="$(var.SourceDir)">
-        <Component Id="help.root" Guid="$(var.help.root.guid)" Win64='$(var.IsX64)'>
-          <File Name="bundlespec.txt" />
-          <File Name="color.txt" />
-          <File Name="common.txt" />
-          <File Name="config.txt" KeyPath="yes" />
-          <File Name="dates.txt" />
-          <File Name="deprecated.txt" />
-          <File Name="diffs.txt" />
-          <File Name="environment.txt" />
-          <File Name="extensions.txt" />
-          <File Name="filesets.txt" />
-          <File Name="flags.txt" />
-          <File Name="glossary.txt" />
-          <File Name="hg-ssh.8.txt" />
-          <File Name="hg.1.txt" />
-          <File Name="hgignore.5.txt" />
-          <File Name="hgignore.txt" />
-          <File Name="hgrc.5.txt" />
-          <File Name="hgweb.txt" />
-          <File Name="merge-tools.txt" />
-          <File Name="pager.txt" />
-          <File Name="patterns.txt" />
-          <File Name="phases.txt" />
-          <File Name="revisions.txt" />
-          <File Name="scripting.txt" />
-          <File Name="subrepos.txt" />
-          <File Name="templates.txt" />
-          <File Name="urls.txt" />
-        </Component>
-
-        <Directory Id="help.internaldir" Name="internals">
-          <Component Id="help.internals" Guid="$(var.help.internals.guid)" Win64='$(var.IsX64)'>
-            <File Id="internals.bundle2.txt"      Name="bundle2.txt" />
-            <File Id="internals.bundles.txt"      Name="bundles.txt" KeyPath="yes" />
-            <File Id="internals.cbor.txt"         Name="cbor.txt" />
-            <File Id="internals.censor.txt"       Name="censor.txt" />
-            <File Id="internals.changegroups.txt" Name="changegroups.txt" />
-            <File Id="internals.config.txt"       Name="config.txt" />
-            <File Id="internals.extensions.txt"   Name="extensions.txt" />
-            <File Id="internals.linelog.txt"      Name="linelog.txt" />
-            <File Id="internals.mergestate.txt"   Name="mergestate.txt" />
-            <File Id="internals.requirements.txt" Name="requirements.txt" />
-            <File Id="internals.revlogs.txt"      Name="revlogs.txt" />
-            <File Id="internals.wireprotocol.txt" Name="wireprotocol.txt" />
-            <File Id="internals.wireprotocolrpc.txt" Name="wireprotocolrpc.txt" />
-            <File Id="internals.wireprotocolv2.txt" Name="wireprotocolv2.txt" />
-          </Component>
-        </Directory>
-
-      </Directory>
-    </DirectoryRef>
-  </Fragment>
-
-</Wix>
diff --git a/contrib/packaging/wix/guids.wxi b/contrib/packaging/wix/guids.wxi
--- a/contrib/packaging/wix/guids.wxi
+++ b/contrib/packaging/wix/guids.wxi
@@ -4,46 +4,9 @@
        and replace 'Mercurial' in this notice with the name of
        your project. Component GUIDs have global namespace!      -->
 
-  <!-- contrib.wxs -->
-  <?define contrib.guid = {4E11FFC2-E2F7-482A-8460-9394B5489F02} ?>
-  <?define contrib.vim.guid = {BB04903A-652D-4C4F-9590-2BD07A2304F2} ?>
-
-  <!-- dist.wxs -->
-  <?define dist.guid = {CE405FE6-CD1E-4873-9C9A-7683AE5A3D90} ?>
-  <?define lib.guid = {877633b5-0b7e-4b46-8f1c-224a61733297} ?>
-
-  <!-- doc.wxs -->
-  <?define doc.hg.1.html.guid = {AAAA3FDA-EDC5-4220-B59D-D342722358A2} ?>
-  <?define doc.hgignore.5.html.guid = {AA9118C4-F3A0-4429-A5F4-5A1906B2D67F} ?>
-  <?define doc.hgrc.5.html = {E0CEA1EB-FA01-408c-844B-EE5965165BAE} ?>
-  <?define doc.style.css = {172F8262-98E0-4711-BD39-4DAE0D77EF05} ?>
-
-  <!-- help.wxs -->
-  <?define help.root.guid = {9FA957DB-6DFE-44f2-AD03-293B2791CF17} ?>
-  <?define help.internals.guid = {2DD7669D-0DB8-4C39-9806-78E6475E7ACC} ?>
-
-  <!-- templates.wxs -->
-  <?define templates.root.guid = {437FD55C-7756-4EA0-87E5-FDBE75DC8595} ?>
-  <?define templates.atom.guid = {D30E14A5-8AF0-4268-8B00-00BEE9E09E39} ?>
-  <?define templates.coal.guid = {B63CCAAB-4EAF-43b4-901E-4BD13F5B78FC} ?>
-  <?define templates.gitweb.guid = {827334AF-1EFD-421B-962C-5660A068F612} ?>
-  <?define templates.json.guid = {F535BE7A-EC34-46E0-B9BE-013F3DBAFB19} ?>
-  <?define templates.monoblue.guid = {8060A1E4-BD4C-453E-92CB-9536DC44A9E3} ?>
-  <?define templates.paper.guid = {61AB1DE9-645F-46ED-8AF8-0CF02267FFBB} ?>
-  <?define templates.raw.guid = {834DF8D7-9784-43A6-851D-A96CE1B3575B} ?>
-  <?define templates.rss.guid = {9338FA09-E128-4B1C-B723-1142DBD09E14} ?>
-  <?define templates.spartan.guid = {80222625-FA8F-44b1-86CE-1781EF375D09} ?>
-  <?define templates.static.guid = {6B3D7C24-98DA-4B67-9F18-35F77357B0B4} ?>
-
   <!-- mercurial.wxs -->
   <?define ProductUpgradeCode = {A1CC6134-E945-4399-BE36-EB0017FDF7CF} ?>
-
   <?define ComponentMainExecutableGUID = {D102B8FA-059B-4ACC-9FA3-8C78C3B58EEF} ?>
-
-  <?define ReadMe.guid = {56A8E372-991D-4DCA-B91D-93D775974CF5} ?>
-  <?define COPYING.guid = {B7801DBA-1C49-4BF4-91AD-33C65F5C7895} ?>
-  <?define mercurial.rc.guid = {52BBF223-58F6-47F5-9353-8FDEA88D9B8D} ?>
-  <?define mergetools.rc.guid = {5AF0430B-2B76-4BC6-91EE-E771B74A0214} ?>
   <?define ProgramMenuDir.guid = {D5A63320-1238-489B-B68B-CF053E9577CA} ?>
 
 </Include>
diff --git a/contrib/packaging/wix/doc.wxs b/contrib/packaging/wix/doc.wxs
deleted file mode 100644
--- a/contrib/packaging/wix/doc.wxs
+++ /dev/null
@@ -1,50 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
-
-  <?include guids.wxi ?>
-  <?include defines.wxi ?>
-
-  <Fragment>
-    <ComponentGroup Id="docFolder">
-      <ComponentRef Id="doc.hg.1.html" />
-      <ComponentRef Id="doc.hgignore.5.html" />
-      <ComponentRef Id="doc.hgrc.5.html" />
-      <ComponentRef Id="doc.style.css" />
-    </ComponentGroup>
-  </Fragment>
-
-  <Fragment>
-    <DirectoryRef Id="INSTALLDIR">
-      <Directory Id="docdir" Name="doc" FileSource="$(var.SourceDir)">
-        <Component Id="doc.hg.1.html" Guid="$(var.doc.hg.1.html.guid)" Win64='$(var.IsX64)'>
-          <File Name="hg.1.html" KeyPath="yes">
-            <Shortcut Id="hg1StartMenu" Directory="ProgramMenuDir"
-                      Name="Mercurial Command Reference"
-                      Icon="hgIcon.ico" IconIndex="0" Advertise="yes"
-            />
-          </File>
-        </Component>
-        <Component Id="doc.hgignore.5.html" Guid="$(var.doc.hgignore.5.html.guid)" Win64='$(var.IsX64)'>
-          <File Name="hgignore.5.html" KeyPath="yes">
-            <Shortcut Id="hgignore5StartMenu" Directory="ProgramMenuDir"
-                      Name="Mercurial Ignore Files"
-                      Icon="hgIcon.ico" IconIndex="0" Advertise="yes"
-            />
-          </File>
-        </Component>
-        <Component Id="doc.hgrc.5.html" Guid="$(var.doc.hgrc.5.html)" Win64='$(var.IsX64)'>
-          <File Name="hgrc.5.html" KeyPath="yes">
-            <Shortcut Id="hgrc5StartMenu" Directory="ProgramMenuDir"
-                      Name="Mercurial Configuration Files"
-                      Icon="hgIcon.ico" IconIndex="0" Advertise="yes"
-            />
-          </File>
-        </Component>
-        <Component Id="doc.style.css" Guid="$(var.doc.style.css)" Win64='$(var.IsX64)'>
-          <File Name="style.css" KeyPath="yes" />
-        </Component>
-      </Directory>
-    </DirectoryRef>
-  </Fragment>
-
-</Wix>
diff --git a/contrib/packaging/wix/dist.wxs b/contrib/packaging/wix/dist.wxs
deleted file mode 100644
--- a/contrib/packaging/wix/dist.wxs
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
-
-  <?include guids.wxi ?>
-  <?include defines.wxi ?>
-
-  <Fragment>
-    <DirectoryRef Id="INSTALLDIR" FileSource="$(var.SourceDir)">
-      <Component Id="distOutput" Guid="$(var.dist.guid)" Win64='$(var.IsX64)'>
-        <File Name="python27.dll" KeyPath="yes" />
-      </Component>
-    </DirectoryRef>
-  </Fragment>
-
-</Wix>
diff --git a/contrib/packaging/wix/contrib.wxs b/contrib/packaging/wix/contrib.wxs
deleted file mode 100644
--- a/contrib/packaging/wix/contrib.wxs
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
-
-  <?include guids.wxi ?>
-  <?include defines.wxi ?>
-
-  <Fragment>
-    <ComponentGroup Id="contribFolder">
-      <ComponentRef Id="contrib" />
-      <ComponentRef Id="contrib.vim" />
-    </ComponentGroup>
-  </Fragment>
-
-  <Fragment>
-    <DirectoryRef Id="INSTALLDIR">
-      <Directory Id="contribdir" Name="contrib" FileSource="$(var.SourceDir)">
-        <Component Id="contrib" Guid="$(var.contrib.guid)" Win64='$(var.IsX64)'>
-          <File Name="bash_completion" KeyPath="yes" />
-          <File Name="hgk.tcl" Source="$(var.SourceDir)/hgk" />
-          <File Name="hgweb.fcgi" />
-          <File Name="hgweb.wsgi" />
-          <File Name="logo-droplets.svg" />
-          <File Name="mercurial.el" />
-          <File Name="mq.el" />
-          <File Name="tcsh_completion" />
-          <File Name="tcsh_completion_build.sh" />
-          <File Name="xml.rnc" />
-          <File Name="zsh_completion" />
-        </Component>
-        <Directory Id="vimdir" Name="vim">
-          <Component Id="contrib.vim" Guid="$(var.contrib.vim.guid)" Win64='$(var.IsX64)'>
-            <File Name="hg-menu.vim" KeyPath="yes" />
-            <File Name="HGAnnotate.vim" />
-            <File Name="hgcommand.vim" />
-            <File Name="patchreview.txt" />
-            <File Name="patchreview.vim" />
-            <File Name="hgtest.vim" />
-          </Component>
-        </Directory>
-      </Directory>
-    </DirectoryRef>
-  </Fragment>
-
-</Wix>
diff --git a/contrib/packaging/hgpackaging/wix.py b/contrib/packaging/hgpackaging/wix.py
--- a/contrib/packaging/hgpackaging/wix.py
+++ b/contrib/packaging/hgpackaging/wix.py
@@ -7,38 +7,60 @@
 
 # no-check-code because Python 3 native.
 
+import collections
 import os
 import pathlib
 import re
+import shutil
 import subprocess
-import tempfile
 import typing
+import uuid
 import xml.dom.minidom
 
 from .downloads import download_entry
-from .py2exe import build_py2exe
+from .py2exe import (
+    build_py2exe,
+    stage_install,
+)
 from .util import (
     extract_zip_to_directory,
+    process_install_rules,
     sign_with_signtool,
 )
 
 
-SUPPORT_WXS = [
-    ('contrib.wxs', r'contrib'),
-    ('dist.wxs', r'dist'),
-    ('doc.wxs', r'doc'),
-    ('help.wxs', r'mercurial\help'),
-    ('locale.wxs', r'mercurial\locale'),
-    ('templates.wxs', r'mercurial\templates'),
-]
-
-
 EXTRA_PACKAGES = {
     'distutils',
     'pygments',
 }
 
 
+EXTRA_INSTALL_RULES = [
+    ('contrib/packaging/wix/COPYING.rtf', 'COPYING.rtf'),
+    ('contrib/win32/mercurial.ini', 'hgrc.d/mercurial.rc'),
+]
+
+STAGING_REMOVE_FILES = [
+    # We use the RTF variant.
+    'copying.txt',
+]
+
+SHORTCUTS = {
+    # hg.1.html'
+    'hg.file.5d3e441c_28d9_5542_afd0_cdd4234f12d5': {
+        'Name': 'Mercurial Command Reference',
+    },
+    # hgignore.5.html
+    'hg.file.5757d8e0_f207_5e10_a2ec_3ba0a062f431': {
+        'Name': 'Mercurial Ignore Files',
+    },
+    # hgrc.5.html
+    'hg.file.92e605fd_1d1a_5dc6_9fc0_5d2998eb8f5e': {
+        'Name': 'Mercurial Configuration Files',
+    },
+}
+
+
 def find_version(source_dir: pathlib.Path):
     version_py = source_dir / 'mercurial' / '__version__.py'
 
@@ -147,49 +169,165 @@
     return post_build_sign
 
 
-LIBRARIES_XML = '''
-<?xml version="1.0" encoding="utf-8"?>
-<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
-
-  <?include {wix_dir}/guids.wxi ?>
-  <?include {wix_dir}/defines.wxi ?>
+def make_files_xml(staging_dir: pathlib.Path, is_x64) -> str:
+    """Create XML string listing every file to be installed."""
 
-  <Fragment>
-    <DirectoryRef Id="INSTALLDIR" FileSource="$(var.SourceDir)">
-      <Directory Id="libdir" Name="lib" FileSource="$(var.SourceDir)/lib">
-        <Component Id="libOutput" Guid="$(var.lib.guid)" Win64='$(var.IsX64)'>
-        </Component>
-      </Directory>
-    </DirectoryRef>
-  </Fragment>
-</Wix>
-'''.lstrip()
+    # We derive GUIDs from a deterministic file path identifier.
+    # We shoehorn the name into something that looks like a URL because
+    # the UUID namespaces are supposed to work that way (even though
+    # the input data probably is never validated).
 
-
-def make_libraries_xml(wix_dir: pathlib.Path, dist_dir: pathlib.Path):
-    """Make XML data for library components WXS."""
-    # We can't use ElementTree because it doesn't handle the
-    # <?include ?> directives.
     doc = xml.dom.minidom.parseString(
-        LIBRARIES_XML.format(wix_dir=str(wix_dir))
+        '<?xml version="1.0" encoding="utf-8"?>'
+        '<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">'
+        '</Wix>'
     )
 
-    component = doc.getElementsByTagName('Component')[0]
+    # Assemble the install layout by directory. This makes it easier to
+    # emit XML, since each directory has separate entities.
+    manifest = collections.defaultdict(dict)
+
+    for root, dirs, files in os.walk(staging_dir):
+        dirs.sort()
+
+        root = pathlib.Path(root)
+        rel_dir = root.relative_to(staging_dir)
+
+        for i in range(len(rel_dir.parts)):
+            parent = '/'.join(rel_dir.parts[0 : i + 1])
+            manifest.setdefault(parent, {})
+
+        for f in sorted(files):
+            full = root / f
+            manifest[str(rel_dir).replace('\\', '/')][full.name] = full
+
+    component_groups = collections.defaultdict(list)
+
+    # Now emit a <Fragment> for each directory.
+    # Each directory is composed of a <DirectoryRef> pointing to its parent
+    # and defines child <Directory>'s and a <Component> with all the files.
+    for dir_name, entries in sorted(manifest.items()):
+        # The directory id is derived from the path. But the root directory
+        # is special.
+        if dir_name == '.':
+            parent_directory_id = 'INSTALLDIR'
+        else:
+            parent_directory_id = 'hg.dir.%s' % dir_name.replace('/', '.')
 
-    f = doc.createElement('File')
-    f.setAttribute('Name', 'library.zip')
-    f.setAttribute('KeyPath', 'yes')
-    component.appendChild(f)
+        fragment = doc.createElement('Fragment')
+        directory_ref = doc.createElement('DirectoryRef')
+        directory_ref.setAttribute('Id', parent_directory_id)
+
+        # Add <Directory> entries for immediate children directories.
+        for possible_child in sorted(manifest.keys()):
+            if (
+                dir_name == '.'
+                and '/' not in possible_child
+                and possible_child != '.'
+            ):
+                child_directory_id = 'hg.dir.%s' % possible_child
+                name = possible_child
+            else:
+                if not possible_child.startswith('%s/' % dir_name):
+                    continue
+                name = possible_child[len(dir_name) + 1 :]
+                if '/' in name:
+                    continue
+
+                child_directory_id = 'hg.dir.%s' % possible_child.replace(
+                    '/', '.'
+                )
+
+            directory = doc.createElement('Directory')
+            directory.setAttribute('Id', child_directory_id)
+            directory.setAttribute('Name', name)
+            directory_ref.appendChild(directory)
+
+        # Add <Component>s for files in this directory.
+        for rel, source_path in sorted(entries.items()):
+            if dir_name == '.':
+                full_rel = rel
+            else:
+                full_rel = '%s/%s' % (dir_name, rel)
 
-    lib_dir = dist_dir / 'lib'
+            component_unique_id = (
+                'https://www.mercurial-scm.org/wix-installer/0/component/%s'
+                % full_rel
+            )
+            component_guid = uuid.uuid5(uuid.NAMESPACE_URL, component_unique_id)
+            component_id = 'hg.component.%s' % str(component_guid).replace(
+                '-', '_'
+            )
+
+            component = doc.createElement('Component')
+
+            component.setAttribute('Id', component_id)
+            component.setAttribute('Guid', str(component_guid).upper())
+            component.setAttribute('Win64', 'yes' if is_x64 else 'no')
+
+            # Assign this component to a top-level group.
+            if dir_name == '.':
+                component_groups['ROOT'].append(component_id)
+            elif '/' in dir_name:
+                component_groups[dir_name[0 : dir_name.index('/')]].append(
+                    component_id
+                )
+            else:
+                component_groups[dir_name].append(component_id)
+
+            unique_id = (
+                'https://www.mercurial-scm.org/wix-installer/0/%s' % full_rel
+            )
+            file_guid = uuid.uuid5(uuid.NAMESPACE_URL, unique_id)
+
+            # IDs have length limits. So use GUID to derive them.
+            file_guid_normalized = str(file_guid).replace('-', '_')
+            file_id = 'hg.file.%s' % file_guid_normalized
 
-    for p in sorted(lib_dir.iterdir()):
-        if not p.name.endswith(('.dll', '.pyd')):
-            continue
+            file_element = doc.createElement('File')
+            file_element.setAttribute('Id', file_id)
+            file_element.setAttribute('Source', str(source_path))
+            file_element.setAttribute('KeyPath', 'yes')
+            file_element.setAttribute('ReadOnly', 'yes')
+
+            component.appendChild(file_element)
+            directory_ref.appendChild(component)
+
+        fragment.appendChild(directory_ref)
+        doc.documentElement.appendChild(fragment)
+
+    for group, component_ids in sorted(component_groups.items()):
+        fragment = doc.createElement('Fragment')
+        component_group = doc.createElement('ComponentGroup')
+        component_group.setAttribute('Id', 'hg.group.%s' % group)
+
+        for component_id in component_ids:
+            component_ref = doc.createElement('ComponentRef')
+            component_ref.setAttribute('Id', component_id)
+            component_group.appendChild(component_ref)
 
-        f = doc.createElement('File')
-        f.setAttribute('Name', p.name)
-        component.appendChild(f)
+        fragment.appendChild(component_group)
+        doc.documentElement.appendChild(fragment)
+
+    # Add <Shortcut> to files that have it defined.
+    for file_id, metadata in sorted(SHORTCUTS.items()):
+        els = doc.getElementsByTagName('File')
+        els = [el for el in els if el.getAttribute('Id') == file_id]
+
+        if not els:
+            raise Exception('could not find File[Id=%s]' % file_id)
+
+        for el in els:
+            shortcut = doc.createElement('Shortcut')
+            shortcut.setAttribute('Id', 'hg.shortcut.%s' % file_id)
+            shortcut.setAttribute('Directory', 'ProgramMenuDir')
+            shortcut.setAttribute('Icon', 'hgIcon.ico')
+            shortcut.setAttribute('IconIndex', '0')
+            shortcut.setAttribute('Advertise', 'yes')
+            for k, v in sorted(metadata.items()):
+                shortcut.setAttribute(k, v)
+
+            el.appendChild(shortcut)
 
     return doc.toprettyxml()
 
@@ -248,9 +386,27 @@
         post_build_fn(source_dir, hg_build_dir, dist_dir, version)
 
     build_dir = hg_build_dir / ('wix-%s' % arch)
+    staging_dir = build_dir / 'stage'
 
     build_dir.mkdir(exist_ok=True)
 
+    # Purge the staging directory for every build so packaging is pristine.
+    if staging_dir.exists():
+        print('purging %s' % staging_dir)
+        shutil.rmtree(staging_dir)
+
+    stage_install(source_dir, staging_dir, lower_case=True)
+
+    # We also install some extra files.
+    process_install_rules(EXTRA_INSTALL_RULES, source_dir, staging_dir)
+
+    # And remove some files we don't want.
+    for f in STAGING_REMOVE_FILES:
+        p = staging_dir / f
+        if p.exists():
+            print('removing %s' % p)
+            p.unlink()
+
     wix_pkg, wix_entry = download_entry('wix', hg_build_dir)
     wix_path = hg_build_dir / ('wix-%s' % wix_entry['version'])
 
@@ -263,25 +419,16 @@
 
     defines = {'Platform': arch}
 
-    for wxs, rel_path in SUPPORT_WXS:
-        wxs = wix_dir / wxs
-        wxs_source_dir = source_dir / rel_path
-        run_candle(wix_path, build_dir, wxs, wxs_source_dir, defines=defines)
+    # Derive a .wxs file with the staged files.
+    manifest_wxs = build_dir / 'stage.wxs'
+    with manifest_wxs.open('w', encoding='utf-8') as fh:
+        fh.write(make_files_xml(staging_dir, is_x64=arch == 'x64'))
+
+    run_candle(wix_path, build_dir, manifest_wxs, staging_dir, defines=defines)
 
     for source, rel_path in sorted((extra_wxs or {}).items()):
         run_candle(wix_path, build_dir, source, rel_path, defines=defines)
 
-    # candle.exe doesn't like when we have an open handle on the file.
-    # So use TemporaryDirectory() instead of NamedTemporaryFile().
-    with tempfile.TemporaryDirectory() as td:
-        td = pathlib.Path(td)
-
-        tf = td / 'library.wxs'
-        with tf.open('w') as fh:
-            fh.write(make_libraries_xml(wix_dir, dist_dir))
-
-        run_candle(wix_path, build_dir, tf, dist_dir, defines=defines)
-
     source = wix_dir / 'mercurial.wxs'
     defines['Version'] = version
     defines['Comments'] = 'Installs Mercurial version %s' % version
@@ -307,20 +454,13 @@
         str(msi_path),
     ]
 
-    for source, rel_path in SUPPORT_WXS:
-        assert source.endswith('.wxs')
-        args.append(str(build_dir / ('%s.wixobj' % source[:-4])))
-
     for source, rel_path in sorted((extra_wxs or {}).items()):
         assert source.endswith('.wxs')
         source = os.path.basename(source)
         args.append(str(build_dir / ('%s.wixobj' % source[:-4])))
 
     args.extend(
-        [
-            str(build_dir / 'library.wixobj'),
-            str(build_dir / 'mercurial.wixobj'),
-        ]
+        [str(build_dir / 'stage.wixobj'), str(build_dir / 'mercurial.wixobj'),]
     )
 
     subprocess.run(args, cwd=str(source_dir), check=True)
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
@@ -209,13 +209,26 @@
     )
 
 
-def stage_install(source_dir: pathlib.Path, staging_dir: pathlib.Path):
+def stage_install(
+    source_dir: pathlib.Path, staging_dir: pathlib.Path, lower_case=False
+):
     """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)
+    if lower_case:
+        rules = []
+        for source, dest in STAGING_RULES:
+            # Only lower directory names.
+            if '/' in dest:
+                parent, leaf = dest.rsplit('/', 1)
+                dest = '%s/%s' % (parent.lower(), leaf)
+            rules.append((source, dest))
+    else:
+        rules = STAGING_RULES
+
+    process_install_rules(rules, source_dir, staging_dir)
 
     # Write out a default editor.rc file to configure notepad as the
     # default editor.



To: indygreg, #hg-reviewers
Cc: mercurial-devel


More information about the Mercurial-devel mailing list