Repository: mesonbuild/meson

Issue:
Problems building gtk4 on windows with 'msi meson'
**Describe the bug**

We've got this bug report that describes a failure to build gtk4 on windows using meson from the msi installer:

https://gitlab.gnome.org/GNOME/gtk/-/issues/3701

**To Reproduce**

- git clone https://gitlab.gnome.org/GNOME/gtk.git
- cd gtk
- meson setup . ..\gtk-build

```
Traceback (most recent call last):
  File "mesonbuild\mesonmain.py", line 132, in run
  File "mesonbuild\mesonmain.py", line 100, in run_runpython_command
  File "runpy.py", line 268, in run_path
  File "runpy.py", line 97, in _run_module_code
  File "runpy.py", line 87, in _run_code
  File "C:\Users\Aleksandr\gtk\gdk\gen-gdk-gresources-xml.py", line 8, in <module>
    import filecmp
ModuleNotFoundError: No module named 'filecmp'
```

**Expected behavior**
successful build, as with pip installed meson


Discussion:
PyInstaller likes to cut down on the size of a distributed application by stripping out stdlib modules which it heuristically detects are not being used. The script to create the .msi already overrides and forces inclusion for a bunch of modules that those heuristics get wrong for meson itself. https://github.com/mesonbuild/meson/blob/b86ef3f85020eb8ed0e464a7653fa9e57cd527c2/packaging/createmsi.py#L121-L144

Unfortunately that means it's very... suboptimal... for the case where the PyInstaller-installed application intends to additionally ship a full python interpreter capable of running arbitrary external scripts. And meson does try to use its own python interpreter to run .py programs used in a project build.

I think this means the approach to packaging the .msi needs to be re-evaluated. Maybe there's some way to force PyInstaller to just bundle the entire python stdlib? Alternatively, pass a list of every single module in the stdlib to the current --hidden-imports code. Which does seem a bit meh for maintainability...

Resolving patch:
diff --git a/packaging/createmsi.py b/packaging/createmsi.py
--- a/packaging/createmsi.py
+++ b/packaging/createmsi.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 
-# Copyright 2017 The Meson development team
+# Copyright 2017-2021 The Meson development team
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -25,12 +25,14 @@
 import sys
 import os
 from glob import glob
-import platform
 import xml.etree.ElementTree as ET
 
 sys.path.append(os.getcwd())
 from mesonbuild import coredata
 
+# Elementtree does not support CDATA. So hack it.
+WINVER_CHECK = '<![CDATA[Installed OR (VersionNT64 > 602)]]>'
+
 def gen_guid():
     '''
        Generate guid
@@ -77,8 +79,7 @@ def __init__(self):
         self.update_guid = '141527EE-E28A-4D14-97A4-92E6075D28B2'
         self.main_xml = 'meson.wxs'
         self.main_o = 'meson.wixobj'
-        self.bytesize = 32 if '32' in platform.architecture()[0] else 64
-        self.final_output = 'meson-{}-{}.msi'.format(self.version, self.bytesize)
+        self.final_output = 'meson-{}-64.msi'.format(self.version)
         self.staging_dirs = ['dist', 'dist2']
         self.progfile_dir = 'ProgramFiles64Folder'
         redist_glob = 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Redist\\MSVC\\v*\\MergeModules\\Microsoft_VC142_CRT_x64.msm'
@@ -141,6 +142,7 @@ def get_more_modules():
                 'distutils.version',
                 'distutils.command.build_ext',
                 'distutils.command.build',
+                'filecmp',
                 ]
 
     def build_dist(self):
@@ -206,11 +208,13 @@ def generate_files(self):
             'SummaryCodepage': '1252',
         })
 
+        condition = ET.SubElement(product, 'Condition', {'Message': 'This application is only supported on Windows 10 or higher.'})
+
+        condition.text = 'X'*len(WINVER_CHECK)
         ET.SubElement(product, 'MajorUpgrade',
                       {'DowngradeErrorMessage': 'A newer version of Meson is already installed.'})
 
-        if self.bytesize == 64:
-            package.set('Platform', 'x64')
+        package.set('Platform', 'x64')
         ET.SubElement(product, 'Media', {
             'Id': '1',
             'Cabinet': 'meson.cab',
@@ -272,6 +276,12 @@ def generate_files(self):
         doc = xml.dom.minidom.parse(self.main_xml)
         with open(self.main_xml, 'w') as open_file:
             open_file.write(doc.toprettyxml())
+        # One last fix, add CDATA.
+        with open(self.main_xml, 'r') as open_file:
+            data = open_file.read()
+        data = data.replace('X'*len(WINVER_CHECK), WINVER_CHECK)
+        with open(self.main_xml, 'w') as open_file:
+            open_file.write(data)
 
     def build_features(self, top_feature, staging_dir):
         '''
@@ -295,8 +305,7 @@ def create_xml(self, nodes, current_dir, parent_xml_node, staging_dir):
                 'Guid': gen_guid(),
             })
             self.feature_components[staging_dir].append(component_id)
-            if self.bytesize == 64:
-                comp_xml_node.set('Win64', 'yes')
+            comp_xml_node.set('Win64', 'yes')
             if self.component_num == 0:
                 ET.SubElement(comp_xml_node, 'Environment', {
                     'Id': 'Environment',


Test patch: