935 |
935 |
936 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape)) |
936 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape)) |
937 with open(outfile, 'wb') as fp: |
937 with open(outfile, 'wb') as fp: |
938 fp.write(data) |
938 fp.write(data) |
939 |
939 |
|
940 # virtualenv installs custom distutils/__init__.py and |
|
941 # distutils/distutils.cfg files which essentially proxy back to the |
|
942 # "real" distutils in the main Python install. The presence of this |
|
943 # directory causes py2exe to pick up the "hacked" distutils package |
|
944 # from the virtualenv and "import distutils" will fail from the py2exe |
|
945 # build because the "real" distutils files can't be located. |
|
946 # |
|
947 # We work around this by monkeypatching the py2exe code finding Python |
|
948 # modules to replace the found virtualenv distutils modules with the |
|
949 # original versions via filesystem scanning. This is a bit hacky. But |
|
950 # it allows us to use virtualenvs for py2exe packaging, which is more |
|
951 # deterministic and reproducible. |
|
952 # |
|
953 # It's worth noting that the common StackOverflow suggestions for this |
|
954 # problem involve copying the original distutils files into the |
|
955 # virtualenv or into the staging directory after setup() is invoked. |
|
956 # The former is very brittle and can easily break setup(). Our hacking |
|
957 # of the found modules routine has a similar result as copying the files |
|
958 # manually. But it makes fewer assumptions about how py2exe works and |
|
959 # is less brittle. |
|
960 |
|
961 # This only catches virtualenvs made with virtualenv (as opposed to |
|
962 # venv, which is likely what Python 3 uses). |
|
963 py2exehacked = py2exeloaded and getattr(sys, 'real_prefix', None) is not None |
|
964 |
|
965 if py2exehacked: |
|
966 from distutils.command.py2exe import py2exe as buildpy2exe |
|
967 from py2exe.mf import Module as py2exemodule |
|
968 |
|
969 class hgbuildpy2exe(buildpy2exe): |
|
970 def find_needed_modules(self, mf, files, modules): |
|
971 res = buildpy2exe.find_needed_modules(self, mf, files, modules) |
|
972 |
|
973 # Replace virtualenv's distutils modules with the real ones. |
|
974 res.modules = { |
|
975 k: v for k, v in res.modules.items() |
|
976 if k != 'distutils' and not k.startswith('distutils.')} |
|
977 |
|
978 import opcode |
|
979 distutilsreal = os.path.join(os.path.dirname(opcode.__file__), |
|
980 'distutils') |
|
981 |
|
982 for root, dirs, files in os.walk(distutilsreal): |
|
983 for f in sorted(files): |
|
984 if not f.endswith('.py'): |
|
985 continue |
|
986 |
|
987 full = os.path.join(root, f) |
|
988 |
|
989 parents = ['distutils'] |
|
990 |
|
991 if root != distutilsreal: |
|
992 rel = os.path.relpath(root, distutilsreal) |
|
993 parents.extend(p for p in rel.split(os.sep)) |
|
994 |
|
995 modname = '%s.%s' % ('.'.join(parents), f[:-3]) |
|
996 |
|
997 if modname.startswith('distutils.tests.'): |
|
998 continue |
|
999 |
|
1000 if modname.endswith('.__init__'): |
|
1001 modname = modname[:-len('.__init__')] |
|
1002 path = os.path.dirname(full) |
|
1003 else: |
|
1004 path = None |
|
1005 |
|
1006 res.modules[modname] = py2exemodule(modname, full, |
|
1007 path=path) |
|
1008 |
|
1009 if 'distutils' not in res.modules: |
|
1010 raise SystemExit('could not find distutils modules') |
|
1011 |
|
1012 return res |
|
1013 |
940 cmdclass = {'build': hgbuild, |
1014 cmdclass = {'build': hgbuild, |
941 'build_doc': hgbuilddoc, |
1015 'build_doc': hgbuilddoc, |
942 'build_mo': hgbuildmo, |
1016 'build_mo': hgbuildmo, |
943 'build_ext': hgbuildext, |
1017 'build_ext': hgbuildext, |
944 'build_py': hgbuildpy, |
1018 'build_py': hgbuildpy, |