~jfb-tempo-consulting/unifield-web/US-1759

4680.3.4 by Xavier ALT
[AIO-45] win32: modidify package to not compile .py files
1
#!/usr/bin/python
2
# -*- coding: utf-8 -*-
3
#
4
# Usage for setup.py:
5
#
6
# from distutils.core import setup
7
# import py2exe
8
# from setup_py2exe_custom import custom_py2exe
9
#
10
# setup(...,
11
#       cmdclass={'py2exe': custom_py2exe},
12
#       options={'py2exe': {
13
#           skip_archive=1,   # mandatory overwith will be opt-out
14
#           compressed=0,     # do not compress - mandatory when skip_archive=1
15
#           bundle_files=3,   # keep pythonXX.dll out of exe
16
#           optimize=0,       # do not compile .py files
17
#           collected_libs_dir='libs',  # move all collected libs into this director
18
#           collected_libs_data_relocate='babel,pytz',  # force moving collected libs data into collected libs dir
19
#       }},
20
#       data_files=fixup_data_pytz_zoneinfo() # to add pytz zoneinfo files
21
#      )
22
#
23
#
24
25
import os
26
import tempfile
4831.1.1 by Jeff Allen
[IMP] US-2374 Make setup.py usable from Linux
27
4836.1.1 by Jeff Allen
Fixed failure while making AIOs.
28
if os.name == 'nt':
4831.1.1 by Jeff Allen
[IMP] US-2374 Make setup.py usable from Linux
29
    from py2exe.build_exe import py2exe as build_exe, fancy_split
30
else:
31
    # fake it for non-Windows, so that setup.py can be run for
32
    # installing dependencies.
33
    class _be(dict):
34
        def __init__(self, arg1,arg2,arg3):
35
            pass
36
        def __dir__(self):
37
            return tuple(self)
38
        def __getattribute__(self, name):
39
            if name == 'user_options':
40
                return []
41
            else:
42
                raise AttributeError(name)
43
    build_exe = _be(1, 2, 3)
44
    fancy_split = None
4680.3.4 by Xavier ALT
[AIO-45] win32: modidify package to not compile .py files
45
46
def fixup_data_pytz_zoneinfo():
47
    r = {}
48
    import pytz
4850.3.1 by Jeff Allen
[IMP] Fix character encoding of installer. Other cleanup as well.
49
    tzdir = os.path.relpath(os.path.dirname(pytz.__file__))
4680.3.4 by Xavier ALT
[AIO-45] win32: modidify package to not compile .py files
50
    for root, _, filenames in os.walk(os.path.join(tzdir, "zoneinfo")):
51
        base = os.path.join('pytz', root[len(tzdir) + 1:])
52
        r[base] = [os.path.join(root, f) for f in filenames]
53
    return r.items()
54
55
def byte_compile_noop(py_files, optimize=0, force=0,
4831.1.1 by Jeff Allen
[IMP] US-2374 Make setup.py usable from Linux
56
                      target_dir=None, verbose=1, dry_run=0,
57
                      direct=None):
4680.3.4 by Xavier ALT
[AIO-45] win32: modidify package to not compile .py files
58
59
    compiled_files = []
60
    from distutils.dir_util import mkpath
61
    from distutils.dep_util import newer
62
    from distutils.file_util import copy_file
63
64
    for file in py_files:
65
        # Terminology from the py_compile module:
66
        #   cfile - byte-compiled file
67
        #   dfile - purported source filename (same as 'file' by default)
68
        cfile = file.__name__.replace('.', '\\')
69
70
        if file.__path__:
71
            dfile = cfile + '\\__init__.py'
72
        else:
73
            dfile = cfile + '.py'
74
        if target_dir:
75
            cfile = os.path.join(target_dir, dfile)
76
77
        if force or newer(file.__file__, cfile):
78
            if verbose:
79
                print "fake-byte-compiling %s to %s" % (file.__file__, dfile)
80
            if not dry_run:
81
                mkpath(os.path.dirname(cfile))
82
                copy_file(file.__file__, cfile, preserve_mode=0)
83
        else:
84
            if verbose:
85
                print "skipping byte-compilation of %s to %s" % \
86
                      (file.__file__, dfile)
87
        compiled_files.append(dfile)
88
    return compiled_files
89
90
# byte_compile()
91
92
class custom_py2exe(build_exe):
93
    user_options = build_exe.user_options + [
94
        ("collected-libs-dir", None,
95
         "Place all collected libs under a specific sub-directory"),
96
        ("collected-libs-data-relocate", None,
97
         "List of prefix to rellocate under collected-libs-dir directory"),
98
        ("package-build-extra-dirs", None,
99
         "List extra packages dirs to check for - moving them to exe root dir"),
100
    ]
101
102
    def initialize_options(self):
103
        build_exe.initialize_options(self)
104
        self.collected_libs_dir = '.'
105
        self.collected_libs_data_relocate = []
106
        self.package_build_extra_dirs = []
107
108
    def finalize_options(self):
109
        build_exe.finalize_options(self)
110
        self.collected_libs_data_relocate = fancy_split(self.collected_libs_data_relocate)
111
        self.package_build_extra_dirs = fancy_split(self.package_build_extra_dirs)
112
113
    def create_directories(self):
114
        build_exe.create_directories(self)
115
        self.lib_dir = os.path.join(self.lib_dir, self.collected_libs_dir)
116
        self.mkpath(self.lib_dir)
117
        self.boot_tmp_dir = tempfile.mkdtemp()
118
119
    def get_boot_script(self, boot_type):
120
        py2exe_boot_file = build_exe.get_boot_script(self, boot_type)
121
        custom_boot_file = os.path.join(self.boot_tmp_dir, os.path.basename(py2exe_boot_file))
122
        if not os.path.exists(custom_boot_file):
123
            cbootfile = open(custom_boot_file, 'wb')
124
            obootfile = open(py2exe_boot_file, 'rb')
125
126
            # copy original file
127
            cbootfile.write(obootfile.read())
128
            obootfile.close()
129
130
            # write special custom handlers
131
            if boot_type in ['common', 'service']:
132
                cbootfile.write("""
133
import sys, os
134
135
if hasattr(sys, 'frozen'):
136
    # executable is frozen, add executable directory to sys.path
137
    sys.path.append(os.path.dirname(sys.executable))
138
139
""")
140
            elif boot_type == 'service':
141
                pass
142
143
            cbootfile.close()
144
        return custom_boot_file
145
146
    def create_binaries(self, py_files, extensions, dlls):
147
        dist = self.distribution
148
149
        # Do not try compiling .py files for 'packages', we 
150
        # want them into the exe directory - and only collected
151
        # dependencies with 'collected libbs dir'
152
        src_build_cmd = dist.get_command_obj('build')
153
        src_build_cmd.ensure_finalized()
154
        build_lib = getattr(src_build_cmd, 'build_lib', None)
155
        if build_lib is None:
156
            raise Exception("Could not continue with no 'build_lib' set")
157
158
        dist_packages_py_files = []
159
        def is_forced_packages_files(m):
160
            if m.__file__:
161
                for build_dir in [build_lib] + self.package_build_extra_dirs:
162
                    if m.__file__.startswith(build_dir):
163
                        dist_packages_py_files.append((m.__file__, m.__file__[len(build_dir)+1:]))
164
                        return True
165
            return False
166
        py_files = [ m for m in py_files if not is_forced_packages_files(m) ]
167
168
        if dist_packages_py_files:
169
            print("*** copy package's python file to root directory ***")
170
            for (srcfile, relfile) in dist_packages_py_files:
171
                dstfile = os.path.join(self.exe_dir, *os.path.split(relfile))
172
                dstfile_dir = os.path.dirname(dstfile)
173
                self.mkpath(dstfile_dir)
174
                self.copy_file(srcfile, dstfile, preserve_mode=0)
175
176
        # Run fake compilation - just copy raw .py file into their
177
        # destination directory
178
        self.no_compiled_files = byte_compile_noop(py_files,
4831.1.1 by Jeff Allen
[IMP] US-2374 Make setup.py usable from Linux
179
                                                   target_dir=self.collect_dir,
180
                                                   optimize=self.optimize,
181
                                                   force=0,
182
                                                   verbose=self.verbose,
183
                                                   dry_run=self.dry_run)
4680.3.4 by Xavier ALT
[AIO-45] win32: modidify package to not compile .py files
184
185
        # Force relocate of specific packages data within collected libs dir
186
        def fixup_location(l):
187
            if isinstance(l, tuple) and any([ l[0].startswith(reloc_prefix)
188
                                              for reloc_prefix in self.collected_libs_data_relocate ]):
189
                return (os.path.join(self.collected_libs_dir, l[0]), l[1])
190
            return l
191
        if dist.has_data_files():
192
            dist.data_files = [ fixup_location(f) for f in dist.data_files ]
193
194
        # Call parent create_binaries() without any py_files, so that py2exe 
195
        # do no force their compilations
196
        return build_exe.create_binaries(self, [], extensions, dlls)
197
198
    def make_lib_archive(self, zip_filename, base_dir, files,
199
                         verbose=0, dry_run=0):
200
        allfiles = files + self.no_compiled_files
201
        return build_exe.make_lib_archive(self, zip_filename, base_dir, allfiles, verbose=verbose, dry_run=dry_run)
202