1#!/usr/bin/env python3
2# SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause)
3
4"""
5setup.py file for SWIG libfdt
6Copyright (C) 2017 Google, Inc.
7Written by Simon Glass <sjg@chromium.org>
8
9This script is modified from the upstream version, to fit in with the U-Boot
10build system.
11
12Files to be built into the extension are provided in SOURCES
13C flags to use are provided in CPPFLAGS
14Object file directory is provided in OBJDIR
15Version is provided in VERSION
16
17If these variables are not given they are parsed from the Makefiles. This
18allows this script to be run stand-alone, e.g.:
19
20    ./pylibfdt/setup.py install [--prefix=...]
21"""
22
23from setuptools import setup, Extension
24from setuptools.command.build_py import build_py as _build_py
25import os
26import re
27import sys
28
29try:
30    from setuptools import sic
31except ImportError:
32    pass
33
34srcdir = os.path.dirname(__file__)
35
36with open(os.path.join(srcdir, "../README"), "r") as fh:
37    long_description = fh.read()
38
39# Decodes a Makefile assignment line into key and value (and plus for +=)
40RE_KEY_VALUE = re.compile(r'(?P<key>\w+) *(?P<plus>[+])?= *(?P<value>.*)$')
41
42def get_top_builddir():
43    if '--top-builddir' in sys.argv:
44        index = sys.argv.index('--top-builddir')
45        sys.argv.pop(index)
46        return sys.argv.pop(index)
47    else:
48        return os.path.join(srcdir, '..')
49
50top_builddir = get_top_builddir()
51
52def ParseMakefile(fname):
53    """Parse a Makefile to obtain its variables.
54
55    This collects variable assigments of the form:
56
57        VAR = value
58        VAR += more
59
60    It does not pick out := assignments, as these are not needed here. It does
61    handle line continuation.
62
63    Returns a dict:
64        key: Variable name (e.g. 'VAR')
65        value: Variable value (e.g. 'value more')
66    """
67    makevars = {}
68    with open(fname) as fd:
69        prev_text = ''  # Continuation text from previous line(s)
70        for line in fd.read().splitlines():
71          if line and line[-1] == '\\':  # Deal with line continuation
72            prev_text += line[:-1]
73            continue
74          elif prev_text:
75            line = prev_text + line
76            prev_text = ''  # Continuation is now used up
77          m = RE_KEY_VALUE.match(line)
78          if m:
79            value = m.group('value') or ''
80            key = m.group('key')
81
82            # Appending to a variable inserts a space beforehand
83            if 'plus' in m.groupdict() and key in makevars:
84              makevars[key] += ' ' + value
85            else:
86              makevars[key] = value
87    return makevars
88
89def GetEnvFromMakefiles():
90    """Scan the Makefiles to obtain the settings we need.
91
92    This assumes that this script is being run from the top-level directory,
93    not the pylibfdt directory.
94
95    Returns:
96        Tuple with:
97            List of swig options
98            Version string
99            List of files to build
100            List of extra C preprocessor flags needed
101            Object directory to use (always '')
102    """
103    basedir = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0])))
104    swig_opts = ['-I%s' % basedir]
105    makevars = ParseMakefile(os.path.join(basedir, 'Makefile'))
106    version = '%s.%s.%s' % (makevars['VERSION'], makevars['PATCHLEVEL'],
107                            makevars['SUBLEVEL'])
108    makevars = ParseMakefile(os.path.join(basedir, 'libfdt', 'Makefile.libfdt'))
109    files = makevars['LIBFDT_SRCS'].split()
110    files = [os.path.join(basedir, 'libfdt', fname) for fname in files]
111    files.append('libfdt.i')
112    cflags = ['-I%s' % basedir, '-I%s/libfdt' % basedir]
113    objdir = ''
114    return swig_opts, version, files, cflags, objdir
115
116
117progname = sys.argv[0]
118files = os.environ.get('SOURCES', '').split()
119cflags = os.environ.get('CPPFLAGS', '').split()
120objdir = os.environ.get('OBJDIR')
121try:
122    version = sic(os.environ.get('VERSION'))
123except:
124    version = os.environ.get('VERSION')
125swig_opts = os.environ.get('SWIG_OPTS', '').split()
126
127# If we were called directly rather than through our Makefile (which is often
128# the case with Python module installation), read the settings from the
129# Makefile.
130if not all((swig_opts, version, files, cflags, objdir)):
131    swig_opts, version, files, cflags, objdir = GetEnvFromMakefiles()
132
133libfdt_module = Extension(
134    '_libfdt',
135    sources=files,
136    include_dirs=[os.path.join(srcdir, 'libfdt')],
137    library_dirs=[os.path.join(top_builddir, 'libfdt')],
138    swig_opts=swig_opts,
139)
140
141class build_py(_build_py):
142    def run(self):
143        self.run_command("build_ext")
144        return super().run()
145
146setup(
147    name='libfdt',
148    version=version,
149    cmdclass = {'build_py' : build_py},
150    author='Simon Glass',
151    author_email='sjg@chromium.org',
152    description='Python binding for libfdt',
153    ext_modules=[libfdt_module],
154    package_dir={'': objdir},
155    py_modules=['libfdt'],
156
157    long_description=long_description,
158    long_description_content_type="text/plain",
159    url="https://git.kernel.org/pub/scm/utils/dtc/dtc.git",
160    license="BSD",
161    license_files=["GPL", "BSD-2-Clause"],
162
163    classifiers=[
164        "Programming Language :: Python :: 3",
165        "License :: OSI Approved :: BSD License",
166        "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)",
167        "Operating System :: OS Independent",
168    ],
169
170)
171