/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to setup.py

  • Committer: Jelmer Vernooij
  • Date: 2017-06-08 23:30:31 UTC
  • mto: This revision was merged to the branch mainline in revision 6690.
  • Revision ID: jelmer@jelmer.uk-20170608233031-3qavls2o7a1pqllj
Update imports.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#! /usr/bin/env python3
 
1
#! /usr/bin/env python
2
2
 
3
3
"""Installation script for brz.
4
4
Run it with
12
12
import copy
13
13
import glob
14
14
 
15
 
if sys.version_info < (3, 5):
16
 
    sys.stderr.write("[ERROR] Not a supported Python version. Need 3.5+\n")
17
 
    sys.exit(1)
18
 
 
19
 
 
20
 
try:
21
 
    import setuptools
22
 
except ImportError:
23
 
    sys.stderr.write("[ERROR] Please install setuptools\n")
24
 
    sys.exit(1)
25
 
 
 
15
if sys.version_info < (2, 7):
 
16
    sys.stderr.write("[ERROR] Not a supported Python version. Need 2.7+\n")
 
17
    sys.exit(1)
26
18
 
27
19
# NOTE: The directory containing setup.py, whether run by 'python setup.py' or
28
20
# './setup.py' or the equivalent with another path, should always be at the
31
23
 
32
24
def get_long_description():
33
25
    dirname = os.path.dirname(__file__)
34
 
    readme = os.path.join(dirname, 'README.rst')
35
 
    with open(readme, 'r') as f:
 
26
    readme = os.path.join(dirname, 'README')
 
27
    f = open(readme, 'rb')
 
28
    try:
36
29
        return f.read()
 
30
    finally:
 
31
        f.close()
37
32
 
38
33
 
39
34
##
40
35
# META INFORMATION FOR SETUP
41
36
# see http://docs.python.org/dist/meta-data.html
42
37
META_INFO = {
43
 
    'name': 'breezy',
44
 
    'version': breezy.__version__,
45
 
    'maintainer': 'Breezy Developers',
46
 
    'maintainer_email': 'team@breezy-vcs.org',
47
 
    'url': 'https://www.breezy-vcs.org/',
48
 
    'description': 'Friendly distributed version control system',
49
 
    'license': 'GNU GPL v2',
 
38
    'name':         'brz',
 
39
    'version':      breezy.__version__,
 
40
    'author':       'Canonical Ltd',
 
41
    'author_email': 'bazaar@lists.canonical.com',
 
42
    'maintainer':   'Breezy Developers',
 
43
    'url':          'https://www.breezy-vcs.org/',
 
44
    'description':  'Friendly distributed version control system',
 
45
    'license':      'GNU GPL v2',
50
46
    'download_url': 'https://launchpad.net/brz/+download',
51
47
    'long_description': get_long_description(),
52
48
    'classifiers': [
62
58
        'Programming Language :: C',
63
59
        'Topic :: Software Development :: Version Control',
64
60
        ],
65
 
    'install_requires': [
66
 
        'configobj',
67
 
        'patiencediff',
68
 
        # Technically, Breezy works without these two dependencies too. But there's
69
 
        # no way to enable them by default and let users opt out.
70
 
        'dulwich>=0.19.12;python_version>="3.5"',
71
 
        'dulwich<0.20,>=0.19.12;python_version<"3.0"',
72
 
        ],
73
 
    'extras_require': {
74
 
        'fastimport': [],
75
 
        'git': [],
76
 
        'launchpad': ['launchpadlib>=1.6.3'],
77
 
        'workspace': ['pyinotify'],
78
 
        },
79
 
    'tests_require': [
80
 
        'testtools',
81
 
        'testtools<=2.4.0;python_version<"3.0"',
82
 
        'python-subunit',
83
 
    ],
84
 
}
 
61
    }
85
62
 
86
63
# The list of packages is automatically generated later. Add other things
87
 
# that are part of BREEZY here.
88
 
BREEZY = {}
 
64
# that are part of BZRLIB here.
 
65
BZRLIB = {}
89
66
 
90
 
PKG_DATA = {
91
 
    # install files from selftest suite
92
 
    'package_data': {'breezy': ['doc/api/*.txt',
93
 
                                'tests/test_patches_data/*',
94
 
                                'help_topics/en/*.txt',
95
 
                                'tests/ssl_certs/ca.crt',
96
 
                                'tests/ssl_certs/server_without_pass.key',
97
 
                                'tests/ssl_certs/server_with_pass.key',
98
 
                                'tests/ssl_certs/server.crt',
99
 
                                ]},
100
 
    }
 
67
PKG_DATA = {# install files from selftest suite
 
68
            'package_data': {'breezy': ['doc/api/*.txt',
 
69
                                        'tests/test_patches_data/*',
 
70
                                        'help_topics/en/*.txt',
 
71
                                        'tests/ssl_certs/ca.crt',
 
72
                                        'tests/ssl_certs/server_without_pass.key',
 
73
                                        'tests/ssl_certs/server_with_pass.key',
 
74
                                        'tests/ssl_certs/server.crt',
 
75
                                       ]},
 
76
           }
101
77
I18N_FILES = []
102
78
for filepath in glob.glob("breezy/locale/*/LC_MESSAGES/*.mo"):
103
79
    langfile = filepath[len("breezy/locale/"):]
119
95
            if not package_path:
120
96
                package_name = 'breezy'
121
97
            else:
122
 
                package_name = (
123
 
                    'breezy.' +
124
 
                    package_path.replace('/', '.').replace('\\', '.'))
 
98
                package_name = ('breezy.' +
 
99
                            package_path.replace('/', '.').replace('\\', '.'))
125
100
            packages.append(package_name)
126
101
    return sorted(packages)
127
102
 
128
103
 
129
 
BREEZY['packages'] = get_breezy_packages()
130
 
 
131
 
 
132
 
from setuptools import setup
 
104
BZRLIB['packages'] = get_breezy_packages()
 
105
 
 
106
 
 
107
from distutils import log
 
108
from distutils.core import setup
133
109
from distutils.version import LooseVersion
134
110
from distutils.command.install_scripts import install_scripts
135
111
from distutils.command.install_data import install_data
152
128
                script_path = self._quoted_path(os.path.join(scripts_dir,
153
129
                                                             "brz"))
154
130
                python_exe = self._quoted_path(sys.executable)
155
 
                batch_str = "@%s %s %%*" % (python_exe, script_path)
 
131
                args = self._win_batch_args()
 
132
                batch_str = "@%s %s %s" % (python_exe, script_path, args)
156
133
                batch_path = os.path.join(self.install_dir, "brz.bat")
157
 
                with open(batch_path, "w") as f:
158
 
                    f.write(batch_str)
 
134
                f = file(batch_path, "w")
 
135
                f.write(batch_str)
 
136
                f.close()
159
137
                print(("Created: %s" % batch_path))
160
138
            except Exception:
161
139
                e = sys.exc_info()[1]
166
144
            return '"' + path + '"'
167
145
        else:
168
146
            return path
 
147
 
 
148
    def _win_batch_args(self):
 
149
        from breezy.win32utils import winver
 
150
        if winver == 'Windows NT':
 
151
            return '%*'
 
152
        else:
 
153
            return '%1 %2 %3 %4 %5 %6 %7 %8 %9'
169
154
#/class my_install_scripts
170
155
 
171
156
 
175
160
    """
176
161
 
177
162
    sub_commands = build.sub_commands + [
178
 
        ('build_mo', lambda _: True),
179
 
        ]
 
163
            ('build_mo', lambda _: True),
 
164
            ]
180
165
 
181
166
    def run(self):
182
167
        build.run(self)
200
185
from distutils.extension import Extension
201
186
ext_modules = []
202
187
try:
203
 
    from Cython.Distutils import build_ext
204
 
    from Cython.Compiler.Version import version as cython_version
 
188
    try:
 
189
        from Cython.Distutils import build_ext
 
190
        from Cython.Compiler.Version import version as pyrex_version
 
191
    except ImportError:
 
192
        print("No Cython, trying Pyrex...")
 
193
        from Pyrex.Distutils import build_ext
 
194
        from Pyrex.Compiler.Version import version as pyrex_version
205
195
except ImportError:
206
 
    have_cython = False
 
196
    have_pyrex = False
207
197
    # try to build the extension from the prior generated source.
208
198
    print("")
209
 
    print("The python package 'Cython' is not available."
 
199
    print("The python package 'Pyrex' is not available."
210
200
          " If the .c files are available,")
211
201
    print("they will be built,"
212
202
          " but modifying the .pyx files will not rebuild them.")
213
203
    print("")
214
204
    from distutils.command.build_ext import build_ext
215
205
else:
216
 
    minimum_cython_version = '0.29'
217
 
    cython_version_info = LooseVersion(cython_version)
218
 
    if cython_version_info < LooseVersion(minimum_cython_version):
219
 
        print("Version of Cython is too old. "
220
 
              "Current is %s, need at least %s."
221
 
              % (cython_version, minimum_cython_version))
222
 
        print("If the .c files are available, they will be built,"
223
 
              " but modifying the .pyx files will not rebuild them.")
224
 
        have_cython = False
225
 
    else:
226
 
        have_cython = True
 
206
    have_pyrex = True
 
207
    pyrex_version_info = LooseVersion(pyrex_version)
227
208
 
228
209
 
229
210
class build_ext_if_possible(build_ext):
267
248
                     % (ext.name,))
268
249
 
269
250
 
270
 
# Override the build_ext if we have Cython available
 
251
# Override the build_ext if we have Pyrex available
271
252
command_classes['build_ext'] = build_ext_if_possible
272
253
unavailable_files = []
273
254
 
274
255
 
275
 
def add_cython_extension(module_name, libraries=None, extra_source=[]):
276
 
    """Add a cython module to build.
 
256
def add_pyrex_extension(module_name, libraries=None, extra_source=[]):
 
257
    """Add a pyrex module to build.
277
258
 
278
 
    This will use Cython to auto-generate the .c file if it is available.
 
259
    This will use Pyrex to auto-generate the .c file if it is available.
279
260
    Otherwise it will fall back on the .c file. If the .c file is not
280
261
    available, it will warn, and not add anything.
281
262
 
286
267
        determine the .pyx and .c files to use.
287
268
    """
288
269
    path = module_name.replace('.', '/')
289
 
    cython_name = path + '.pyx'
 
270
    pyrex_name = path + '.pyx'
290
271
    c_name = path + '.c'
291
272
    define_macros = []
292
273
    if sys.platform == 'win32':
293
 
        # cython uses the macro WIN32 to detect the platform, even though it
 
274
        # pyrex uses the macro WIN32 to detect the platform, even though it
294
275
        # should be using something like _WIN32 or MS_WINDOWS, oh well, we can
295
276
        # give it the right value.
296
277
        define_macros.append(('WIN32', None))
297
 
    if have_cython:
298
 
        source = [cython_name]
 
278
    if have_pyrex:
 
279
        source = [pyrex_name]
299
280
    else:
300
281
        if not os.path.isfile(c_name):
301
282
            unavailable_files.append(c_name)
303
284
        else:
304
285
            source = [c_name]
305
286
    source.extend(extra_source)
306
 
    include_dirs = ['breezy']
307
 
    ext_modules.append(
308
 
        Extension(
309
 
            module_name, source, define_macros=define_macros,
310
 
            libraries=libraries, include_dirs=include_dirs))
311
 
 
312
 
 
313
 
add_cython_extension('breezy._simple_set_pyx')
314
 
ext_modules.append(Extension('breezy._static_tuple_c',
315
 
                             ['breezy/_static_tuple_c.c']))
316
 
add_cython_extension('breezy._annotator_pyx')
317
 
add_cython_extension('breezy._bencode_pyx')
318
 
add_cython_extension('breezy._chunks_to_lines_pyx')
319
 
add_cython_extension('breezy.bzr._groupcompress_pyx',
320
 
                     extra_source=['breezy/bzr/diff-delta.c'])
321
 
add_cython_extension('breezy.bzr._knit_load_data_pyx')
322
 
add_cython_extension('breezy._known_graph_pyx')
323
 
add_cython_extension('breezy._rio_pyx')
 
287
    ext_modules.append(Extension(module_name, source,
 
288
        define_macros=define_macros, libraries=libraries))
 
289
 
 
290
 
 
291
add_pyrex_extension('breezy._annotator_pyx')
 
292
add_pyrex_extension('breezy._bencode_pyx')
 
293
add_pyrex_extension('breezy._chunks_to_lines_pyx')
 
294
add_pyrex_extension('breezy.bzr._groupcompress_pyx',
 
295
                    extra_source=['breezy/bzr/diff-delta.c'])
 
296
add_pyrex_extension('breezy.bzr._knit_load_data_pyx')
 
297
add_pyrex_extension('breezy._known_graph_pyx')
 
298
add_pyrex_extension('breezy._rio_pyx')
324
299
if sys.platform == 'win32':
325
 
    add_cython_extension('breezy.bzr._dirstate_helpers_pyx',
326
 
                         libraries=['Ws2_32'])
327
 
    add_cython_extension('breezy._walkdirs_win32')
328
 
else:
329
 
    add_cython_extension('breezy.bzr._dirstate_helpers_pyx')
330
 
    add_cython_extension('breezy._readdir_pyx')
331
 
add_cython_extension('breezy.bzr._chk_map_pyx')
332
 
add_cython_extension('breezy.bzr._btree_serializer_pyx')
 
300
    add_pyrex_extension('breezy.bzr._dirstate_helpers_pyx',
 
301
                        libraries=['Ws2_32'])
 
302
    add_pyrex_extension('breezy._walkdirs_win32')
 
303
else:
 
304
    if have_pyrex and pyrex_version_info == LooseVersion("0.9.4.1"):
 
305
        # Pyrex 0.9.4.1 fails to compile this extension correctly
 
306
        # The code it generates re-uses a "local" pointer and
 
307
        # calls "PY_DECREF" after having set it to NULL. (It mixes PY_XDECREF
 
308
        # which is NULL safe with PY_DECREF which is not.)
 
309
        # <https://bugs.launchpad.net/bzr/+bug/449372>
 
310
        # <https://bugs.launchpad.net/bzr/+bug/276868>
 
311
        print('Cannot build extension "breezy.bzr._dirstate_helpers_pyx" using')
 
312
        print(('your version of pyrex "%s". Please upgrade your pyrex'
 
313
              % (pyrex_version,)))
 
314
        print('install. For now, the non-compiled (python) version will')
 
315
        print('be used instead.')
 
316
    else:
 
317
        add_pyrex_extension('breezy.bzr._dirstate_helpers_pyx')
 
318
    add_pyrex_extension('breezy._readdir_pyx')
 
319
add_pyrex_extension('breezy.bzr._chk_map_pyx')
 
320
ext_modules.append(Extension('breezy._patiencediff_c',
 
321
                             ['breezy/_patiencediff_c.c']))
 
322
if have_pyrex and pyrex_version_info < LooseVersion("0.9.6.3"):
 
323
    print("")
 
324
    print(('Your Pyrex/Cython version %s is too old to build the simple_set' % (
 
325
        pyrex_version)))
 
326
    print('and static_tuple extensions.')
 
327
    print('Please upgrade to at least Pyrex 0.9.6.3')
 
328
    print("")
 
329
    # TODO: Should this be a fatal error?
 
330
else:
 
331
    # We only need 0.9.6.3 to build _simple_set_pyx, but static_tuple depends
 
332
    # on simple_set
 
333
    add_pyrex_extension('breezy._simple_set_pyx')
 
334
    ext_modules.append(Extension('breezy._static_tuple_c',
 
335
                                 ['breezy/_static_tuple_c.c']))
 
336
add_pyrex_extension('breezy.bzr._btree_serializer_pyx')
333
337
 
334
338
 
335
339
if unavailable_files:
371
375
    # First always brz's icon and its in the root of the brz tree.
372
376
    icos.append(('', 'brz.ico'))
373
377
    for root, dirs, files in os.walk(ico_root):
374
 
        icos.extend([(ico_root, os.path.join(root, f)[len(ico_root) + 1:])
 
378
        icos.extend([(ico_root, os.path.join(root, f)[len(ico_root)+1:])
375
379
                     for f in files if f.endswith('.ico')])
376
380
    # allocate an icon ID for each file and the full path to the ico
377
381
    icon_resources = [(rid, os.path.join(ico_dir, ico_name))
384
388
                 for rid, (_, f) in enumerate(icos)]
385
389
    ico_map = dict(map_items)
386
390
    # Create a new resource type of 'ICON_MAP', and use ID=1
387
 
    other_resources = [("ICON_MAP", 1, pickle.dumps(ico_map))]
 
391
    other_resources = [ ("ICON_MAP", 1, pickle.dumps(ico_map))]
388
392
 
389
393
    excludes.extend("""pywin pywin.dialogs pywin.dialogs.list
390
394
                       win32ui crawler.Crawler""".split())
392
396
    # tbzrcache executables - a "console" version for debugging and a
393
397
    # GUI version that is generally used.
394
398
    tbzrcache = dict(
395
 
        script=os.path.join(tbzr_root, "scripts", "tbzrcache.py"),
396
 
        icon_resources=icon_resources,
397
 
        other_resources=other_resources,
 
399
        script = os.path.join(tbzr_root, "scripts", "tbzrcache.py"),
 
400
        icon_resources = icon_resources,
 
401
        other_resources = other_resources,
398
402
    )
399
403
    console_targets.append(tbzrcache)
400
404
 
401
405
    # Make a windows version which is the same except for the base name.
402
406
    tbzrcachew = tbzrcache.copy()
403
 
    tbzrcachew["dest_base"] = "tbzrcachew"
 
407
    tbzrcachew["dest_base"]="tbzrcachew"
404
408
    gui_targets.append(tbzrcachew)
405
409
 
406
410
    # ditto for the tbzrcommand tool
407
411
    tbzrcommand = dict(
408
 
        script=os.path.join(tbzr_root, "scripts", "tbzrcommand.py"),
409
 
        icon_resources=icon_resources,
410
 
        other_resources=other_resources,
 
412
        script = os.path.join(tbzr_root, "scripts", "tbzrcommand.py"),
 
413
        icon_resources = icon_resources,
 
414
        other_resources = other_resources,
411
415
    )
412
416
    console_targets.append(tbzrcommand)
413
417
    tbzrcommandw = tbzrcommand.copy()
414
 
    tbzrcommandw["dest_base"] = "tbzrcommandw"
 
418
    tbzrcommandw["dest_base"]="tbzrcommandw"
415
419
    gui_targets.append(tbzrcommandw)
416
 
 
 
420
    
417
421
    # A utility to see python output from both C++ and Python based shell
418
422
    # extensions
419
423
    tracer = dict(script=os.path.join(tbzr_root, "scripts", "tbzrtrace.py"))
497
501
        for root, dirs, files in os.walk('doc'):
498
502
            r = []
499
503
            for f in files:
500
 
                if (os.path.splitext(f)[1] in ('.html', '.css', '.png', '.pdf')
501
 
                        or f == 'quick-start-summary.svg'):
 
504
                if (os.path.splitext(f)[1] in ('.html','.css','.png','.pdf')
 
505
                    or f == 'quick-start-summary.svg'):
502
506
                    r.append(os.path.join(root, f))
503
507
            if r:
504
508
                relative = root[4:]
514
518
            'ext_modules': ext_modules,
515
519
            # help pages
516
520
            'data_files': find_docs(),
517
 
            # for building cython extensions
 
521
            # for building pyrex extensions
518
522
            'cmdclass': command_classes,
519
 
            }
 
523
           }
520
524
 
521
525
    ARGS.update(META_INFO)
522
 
    ARGS.update(BREEZY)
 
526
    ARGS.update(BZRLIB)
523
527
    PKG_DATA['package_data']['breezy'].append('locale/*/LC_MESSAGES/*.mo')
524
528
    ARGS.update(PKG_DATA)
525
529
 
571
575
            self.outfiles.extend([f + 'o' for f in compile_names])
572
576
    # end of class install_data_with_bytecompile
573
577
 
574
 
    target = py2exe.build_exe.Target(
575
 
        script="brz",
576
 
        dest_base="brz",
577
 
        icon_resources=[(0, 'brz.ico')],
578
 
        name=META_INFO['name'],
579
 
        version=version_str,
580
 
        description=META_INFO['description'],
581
 
        author=META_INFO['author'],
582
 
        copyright="(c) Canonical Ltd, 2005-2010",
583
 
        company_name="Canonical Ltd.",
584
 
        comments=META_INFO['description'],
585
 
    )
 
578
    target = py2exe.build_exe.Target(script = "brz",
 
579
                                     dest_base = "brz",
 
580
                                     icon_resources = [(0,'brz.ico')],
 
581
                                     name = META_INFO['name'],
 
582
                                     version = version_str,
 
583
                                     description = META_INFO['description'],
 
584
                                     author = META_INFO['author'],
 
585
                                     copyright = "(c) Canonical Ltd, 2005-2010",
 
586
                                     company_name = "Canonical Ltd.",
 
587
                                     comments = META_INFO['description'],
 
588
                                    )
586
589
    gui_target = copy.copy(target)
587
590
    gui_target.dest_base = "bzrw"
588
591
 
589
 
    packages = BREEZY['packages']
 
592
    packages = BZRLIB['packages']
590
593
    packages.remove('breezy')
591
594
    packages = [i for i in packages if not i.startswith('breezy.plugins')]
592
595
    includes = []
597
600
        includes.append(module)
598
601
 
599
602
    additional_packages = set()
 
603
    if sys.version.startswith('2.4'):
 
604
        # adding elementtree package
 
605
        additional_packages.add('elementtree')
 
606
    elif sys.version.startswith('2.6') or sys.version.startswith('2.5'):
 
607
        additional_packages.add('xml.etree')
 
608
    else:
 
609
        import warnings
 
610
        warnings.warn('Unknown Python version.\n'
 
611
                      'Please check setup.py script for compatibility.')
600
612
 
601
613
    # Although we currently can't enforce it, we consider it an error for
602
614
    # py2exe to report any files are "missing".  Such modules we know aren't
604
616
    excludes = """Tkinter psyco ElementPath r_hmac
605
617
                  ImaginaryModule cElementTree elementtree.ElementTree
606
618
                  Crypto.PublicKey._fastmath
 
619
                  medusa medusa.filesys medusa.ftp_server
607
620
                  tools
608
621
                  resource validate""".split()
609
622
    dll_excludes = []
705
718
        # at build time.  Also to stdout so it appears in the log
706
719
        for f in (sys.stderr, sys.stdout):
707
720
            f.write("Skipping TBZR binaries - "
708
 
                    "please set TBZR to a directory to enable\n")
 
721
                "please set TBZR to a directory to enable\n")
709
722
 
710
723
    # MSWSOCK.dll is a system-specific library, which py2exe accidentally pulls
711
724
    # in on Vista.
721
734
                               "dist_dir": "win32_bzr.exe",
722
735
                               "optimize": 2,
723
736
                               "custom_boot_script":
724
 
                                   "tools/win32/py2exe_boot_common.py",
725
 
                               },
726
 
                    }
 
737
                                        "tools/win32/py2exe_boot_common.py",
 
738
                              },
 
739
                   }
727
740
 
728
741
    # We want the libaray.zip to have optimize = 2, but the exe to have
729
742
    # optimize = 1, so that .py files that get compilied at run time
748
761
else:
749
762
    # ad-hoc for easy_install
750
763
    DATA_FILES = []
751
 
    if 'bdist_egg' not in sys.argv:
 
764
    if not 'bdist_egg' in sys.argv:
752
765
        # generate and install brz.1 only with plain install, not the
753
766
        # easy_install one
754
 
        DATA_FILES = [('man/man1', ['brz.1', 'breezy/git/git-remote-bzr.1'])]
 
767
        DATA_FILES = [('man/man1', ['brz.1'])]
755
768
 
756
769
    DATA_FILES = DATA_FILES + I18N_FILES
757
770
    # std setup
758
 
    ARGS = {'scripts': ['brz',
759
 
                        # TODO(jelmer): Only install the git scripts if
760
 
                        # Dulwich was found.
761
 
                        'breezy/git/git-remote-bzr',
762
 
                        'breezy/git/bzr-receive-pack',
763
 
                        'breezy/git/bzr-upload-pack'],
 
771
    ARGS = {'scripts': ['brz'],
764
772
            'data_files': DATA_FILES,
765
773
            'cmdclass': command_classes,
766
774
            'ext_modules': ext_modules,
767
 
            }
 
775
           }
768
776
 
769
777
    ARGS.update(META_INFO)
770
 
    ARGS.update(BREEZY)
 
778
    ARGS.update(BZRLIB)
771
779
    ARGS.update(PKG_DATA)
772
780
 
773
781
    if __name__ == '__main__':