• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1
2NAME = 'PyYAML'
3VERSION = '6.0'
4DESCRIPTION = "YAML parser and emitter for Python"
5LONG_DESCRIPTION = """\
6YAML is a data serialization format designed for human readability
7and interaction with scripting languages.  PyYAML is a YAML parser
8and emitter for Python.
9
10PyYAML features a complete YAML 1.1 parser, Unicode support, pickle
11support, capable extension API, and sensible error messages.  PyYAML
12supports standard YAML tags and provides Python-specific tags that
13allow to represent an arbitrary Python object.
14
15PyYAML is applicable for a broad range of tasks from complex
16configuration files to object serialization and persistence."""
17AUTHOR = "Kirill Simonov"
18AUTHOR_EMAIL = 'xi@resolvent.net'
19LICENSE = "MIT"
20PLATFORMS = "Any"
21URL = "https://pyyaml.org/"
22DOWNLOAD_URL = "https://pypi.org/project/PyYAML/"
23CLASSIFIERS = [
24    "Development Status :: 5 - Production/Stable",
25    "Intended Audience :: Developers",
26    "License :: OSI Approved :: MIT License",
27    "Operating System :: OS Independent",
28    "Programming Language :: Cython",
29    "Programming Language :: Python",
30    "Programming Language :: Python :: 3",
31    "Programming Language :: Python :: 3.6",
32    "Programming Language :: Python :: 3.7",
33    "Programming Language :: Python :: 3.8",
34    "Programming Language :: Python :: 3.9",
35    "Programming Language :: Python :: 3.10",
36    "Programming Language :: Python :: Implementation :: CPython",
37    "Programming Language :: Python :: Implementation :: PyPy",
38    "Topic :: Software Development :: Libraries :: Python Modules",
39    "Topic :: Text Processing :: Markup",
40]
41PROJECT_URLS = {
42   'Bug Tracker': 'https://github.com/yaml/pyyaml/issues',
43   'CI': 'https://github.com/yaml/pyyaml/actions',
44   'Documentation': 'https://pyyaml.org/wiki/PyYAMLDocumentation',
45   'Mailing lists': 'http://lists.sourceforge.net/lists/listinfo/yaml-core',
46   'Source Code': 'https://github.com/yaml/pyyaml',
47}
48
49LIBYAML_CHECK = """
50#include <yaml.h>
51
52int main(void) {
53    yaml_parser_t parser;
54    yaml_emitter_t emitter;
55
56    yaml_parser_initialize(&parser);
57    yaml_parser_delete(&parser);
58
59    yaml_emitter_initialize(&emitter);
60    yaml_emitter_delete(&emitter);
61
62    return 0;
63}
64"""
65
66
67import sys, os, os.path, pathlib, platform, shutil, tempfile, warnings
68
69# for newer setuptools, enable the embedded distutils before importing setuptools/distutils to avoid warnings
70os.environ['SETUPTOOLS_USE_DISTUTILS'] = 'local'
71
72from setuptools import setup, Command, Distribution as _Distribution, Extension as _Extension
73from setuptools.command.build_ext import build_ext as _build_ext
74# NB: distutils imports must remain below setuptools to ensure we use the embedded version
75from distutils import log
76from distutils.errors import DistutilsError, CompileError, LinkError, DistutilsPlatformError
77
78with_cython = False
79if 'sdist' in sys.argv or os.environ.get('PYYAML_FORCE_CYTHON') == '1':
80    # we need cython here
81    with_cython = True
82try:
83    from Cython.Distutils.extension import Extension as _Extension
84    from Cython.Distutils import build_ext as _build_ext
85    with_cython = True
86except ImportError:
87    if with_cython:
88        raise
89
90try:
91    from wheel.bdist_wheel import bdist_wheel
92except ImportError:
93    bdist_wheel = None
94
95
96# on Windows, disable wheel generation warning noise
97windows_ignore_warnings = [
98"Unknown distribution option: 'python_requires'",
99"Config variable 'Py_DEBUG' is unset",
100"Config variable 'WITH_PYMALLOC' is unset",
101"Config variable 'Py_UNICODE_SIZE' is unset",
102"Cython directive 'language_level' not set"
103]
104
105if platform.system() == 'Windows':
106    for w in windows_ignore_warnings:
107        warnings.filterwarnings('ignore', w)
108
109
110class Distribution(_Distribution):
111    def __init__(self, attrs=None):
112        _Distribution.__init__(self, attrs)
113        if not self.ext_modules:
114            return
115        for idx in range(len(self.ext_modules)-1, -1, -1):
116            ext = self.ext_modules[idx]
117            if not isinstance(ext, Extension):
118                continue
119            setattr(self, ext.attr_name, None)
120            self.global_options = [
121                    (ext.option_name, None,
122                        "include %s (default if %s is available)"
123                        % (ext.feature_description, ext.feature_name)),
124                    (ext.neg_option_name, None,
125                        "exclude %s" % ext.feature_description),
126            ] + self.global_options
127            self.negative_opt = self.negative_opt.copy()
128            self.negative_opt[ext.neg_option_name] = ext.option_name
129
130    def has_ext_modules(self):
131        if not self.ext_modules:
132            return False
133        for ext in self.ext_modules:
134            with_ext = self.ext_status(ext)
135            if with_ext is None or with_ext:
136                return True
137        return False
138
139    def ext_status(self, ext):
140        implementation = platform.python_implementation()
141        if implementation not in ['CPython', 'PyPy']:
142            return False
143        if isinstance(ext, Extension):
144            # the "build by default" behavior is implemented by this returning None
145            with_ext = getattr(self, ext.attr_name) or os.environ.get('PYYAML_FORCE_{0}'.format(ext.feature_name.upper()))
146            try:
147                with_ext = int(with_ext)  # attempt coerce envvar to int
148            except TypeError:
149                pass
150            return with_ext
151        else:
152            return True
153
154
155class Extension(_Extension):
156
157    def __init__(self, name, sources, feature_name, feature_description,
158            feature_check, **kwds):
159        if not with_cython:
160            for filename in sources[:]:
161                base, ext = os.path.splitext(filename)
162                if ext == '.pyx':
163                    sources.remove(filename)
164                    sources.append('%s.c' % base)
165        _Extension.__init__(self, name, sources, **kwds)
166        self.feature_name = feature_name
167        self.feature_description = feature_description
168        self.feature_check = feature_check
169        self.attr_name = 'with_' + feature_name.replace('-', '_')
170        self.option_name = 'with-' + feature_name
171        self.neg_option_name = 'without-' + feature_name
172
173
174class build_ext(_build_ext):
175
176    def run(self):
177        optional = True
178        disabled = True
179        for ext in self.extensions:
180            with_ext = self.distribution.ext_status(ext)
181            if with_ext is None:
182                disabled = False
183            elif with_ext:
184                optional = False
185                disabled = False
186                break
187        if disabled:
188            return
189        try:
190            _build_ext.run(self)
191        except DistutilsPlatformError:
192            exc = sys.exc_info()[1]
193            if optional:
194                log.warn(str(exc))
195                log.warn("skipping build_ext")
196            else:
197                raise
198
199    def get_source_files(self):
200        self.check_extensions_list(self.extensions)
201        filenames = []
202        for ext in self.extensions:
203            if with_cython:
204                self.cython_sources(ext.sources, ext)
205            for filename in ext.sources:
206                filenames.append(filename)
207                base = os.path.splitext(filename)[0]
208                for ext in ['c', 'h', 'pyx', 'pxd']:
209                    filename = '%s.%s' % (base, ext)
210                    if filename not in filenames and os.path.isfile(filename):
211                        filenames.append(filename)
212        return filenames
213
214    def get_outputs(self):
215        self.check_extensions_list(self.extensions)
216        outputs = []
217        for ext in self.extensions:
218            fullname = self.get_ext_fullname(ext.name)
219            filename = os.path.join(self.build_lib,
220                                    self.get_ext_filename(fullname))
221            if os.path.isfile(filename):
222                outputs.append(filename)
223        return outputs
224
225    def build_extensions(self):
226        self.check_extensions_list(self.extensions)
227        for ext in self.extensions:
228            with_ext = self.distribution.ext_status(ext)
229            if with_ext is not None and not with_ext:
230                continue
231            if with_cython:
232                ext.sources = self.cython_sources(ext.sources, ext)
233            try:
234                self.build_extension(ext)
235            except (CompileError, LinkError):
236                if with_ext is not None:
237                    raise
238                log.warn("Error compiling module, falling back to pure Python")
239
240
241class test(Command):
242
243    user_options = []
244
245    def initialize_options(self):
246        pass
247
248    def finalize_options(self):
249        pass
250
251    def run(self):
252        build_cmd = self.get_finalized_command('build')
253        build_cmd.run()
254
255        # running the tests this way can pollute the post-MANIFEST build sources
256        # (see https://github.com/yaml/pyyaml/issues/527#issuecomment-921058344)
257        # until we remove the test command, run tests from an ephemeral copy of the intermediate build sources
258        tempdir = tempfile.TemporaryDirectory(prefix='test_pyyaml')
259
260        try:
261            # have to create a subdir since we don't get dir_exists_ok on copytree until 3.8
262            temp_test_path = pathlib.Path(tempdir.name) / 'pyyaml'
263            shutil.copytree(build_cmd.build_lib, temp_test_path)
264            sys.path.insert(0, str(temp_test_path))
265            sys.path.insert(0, 'tests/lib')
266
267            import test_all
268            if not test_all.main([]):
269                raise DistutilsError("Tests failed")
270        finally:
271            try:
272                # this can fail under Windows; best-effort cleanup
273                tempdir.cleanup()
274            except Exception:
275                pass
276
277
278cmdclass = {
279    'build_ext': build_ext,
280    'test': test,
281}
282if bdist_wheel:
283    cmdclass['bdist_wheel'] = bdist_wheel
284
285
286if __name__ == '__main__':
287
288    setup(
289        name=NAME,
290        version=VERSION,
291        description=DESCRIPTION,
292        long_description=LONG_DESCRIPTION,
293        author=AUTHOR,
294        author_email=AUTHOR_EMAIL,
295        license=LICENSE,
296        platforms=PLATFORMS,
297        url=URL,
298        download_url=DOWNLOAD_URL,
299        classifiers=CLASSIFIERS,
300        project_urls=PROJECT_URLS,
301
302        package_dir={'': 'lib'},
303        packages=['yaml', '_yaml'],
304        ext_modules=[
305            Extension('yaml._yaml', ['yaml/_yaml.pyx'],
306                'libyaml', "LibYAML bindings", LIBYAML_CHECK,
307                libraries=['yaml']),
308        ],
309
310        distclass=Distribution,
311        cmdclass=cmdclass,
312        python_requires='>=3.6',
313    )
314