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