|
2 | 2 | import sys |
3 | 3 | import subprocess |
4 | 4 | from os import path |
| 5 | +from collections import defaultdict |
5 | 6 | from distutils.core import setup |
6 | 7 | from distutils.extension import Extension |
7 | | -from Cython.Distutils import build_ext |
| 8 | +from Cython import Distutils |
8 | 9 |
|
9 | 10 |
|
10 | | -# |
11 | | -# HACK |
12 | | -# |
| 11 | +PKGCONFIG_TOKEN_MAP = { |
| 12 | + '-D': 'define_macros', |
| 13 | + '-I': 'include_dirs', |
| 14 | + '-L': 'library_dirs', |
| 15 | + '-l': 'libraries' |
| 16 | +} |
13 | 17 |
|
14 | | -def getoutput(command): |
| 18 | +def pkgconfig(*packages): |
| 19 | + """ |
| 20 | + Run the `pkg-config` utility to determine locations of includes, |
| 21 | + libraries, etc. for dependencies. |
| 22 | + """ |
| 23 | + config = defaultdict(set) |
| 24 | + |
| 25 | + # Execute the command in a subprocess and communicate the output. |
| 26 | + command = "pkg-config --libs --cflags %s" % ' '.join(packages) |
15 | 27 | process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) |
16 | | - out, err = process.communicate() |
17 | | - return out.decode('utf8') |
18 | | - |
19 | | - |
20 | | -def get_lxml_include_dirs(): |
21 | | - from os import environ |
22 | | - lxml_home = environ.get("LXML_HOME") |
23 | | - if lxml_home is None: |
24 | | - # `LXML_HOME` not specified -- derive from installed `lxml` |
25 | | - import lxml |
26 | | - lxml_home = path.dirname(lxml.__file__) |
27 | | - else: |
28 | | - if not exists(lxml_home): |
29 | | - sys.exit("The directory specified via envvar `LXML_HOME` does not exist") |
30 | | - lxml_home = path.join(lxml_home, "src", "lxml") |
31 | | - # check that it contains what is needed |
32 | | - lxml_include = path.join(lxml_home, "includes") |
33 | | - if not (path.exists(path.join(lxml_home, "etreepublic.pxd")) \ |
34 | | - or path.exists(path.join(lxml_include, "etreepublic.pxd"))): |
35 | | - sys.exit("The lxml installation lacks the mandatory `etreepublic.pxd`. You may need to install `lxml` manually or set envvar `LXML_HOME` to an `lxml` installation with `etreepublic.pxd`") |
36 | | - return [lxml_home, lxml_include] |
| 28 | + out, _ = process.communicate() |
| 29 | + |
| 30 | + # Clean the output. |
| 31 | + out = out.decode('utf8') |
| 32 | + out = out.replace('\\\"', "") |
| 33 | + |
| 34 | + # Iterate throught the tokens of the output. |
| 35 | + for token in out.split(): |
| 36 | + key = PKGCONFIG_TOKEN_MAP.get(token[:2]) |
| 37 | + if key: |
| 38 | + config[key].add(token[2:].strip()) |
| 39 | + |
| 40 | + # Convert sets to lists. |
| 41 | + for name in config: |
| 42 | + config[name] = list(config[name]) |
| 43 | + |
| 44 | + # Iterate and resolve define macros. |
| 45 | + macros = [] |
| 46 | + for declaration in config['define_macros']: |
| 47 | + macros.append(tuple(declaration.split('='))) |
| 48 | + |
| 49 | + config['define_macros'] = macros |
| 50 | + |
| 51 | + # Return discovered configuration. |
| 52 | + return config |
| 53 | + |
37 | 54 |
|
38 | 55 | # we must extend our cflags once `lxml` is installed. |
39 | 56 | # To this end, we override `Extension` |
40 | | -class Extension(Extension, object): |
41 | | - lxml_extended = False |
42 | | - |
43 | | - def get_include_dirs(self): |
44 | | - ids = self.__dict__["include_dirs"] |
45 | | - if self.lxml_extended: return ids |
46 | | - # ensure `lxml` headers come before ours |
47 | | - # this should make sure to use its headers rather than our old copy |
48 | | - # ids.extend(get_lxml_include_dirs()) |
49 | | - ids[0:0] = get_lxml_include_dirs() |
50 | | - self.lxml_extended = True |
51 | | - return ids |
52 | | - |
53 | | - def set_include_dirs(self, ids): self.__dict__["include_dirs"] = ids |
54 | | - |
55 | | - include_dirs = property(get_include_dirs, set_include_dirs) |
56 | | - |
57 | | - |
58 | | -define_macros = [] |
59 | | -include_dirs = ['src'] |
60 | | -library_dirs = [] |
61 | | -libraries = [] |
62 | | - |
63 | | -def extract_cflags(cflags): |
64 | | - global define_macros, include_dirs |
65 | | - list = cflags.split(' ') |
66 | | - for flag in list: |
67 | | - if flag == '': |
68 | | - continue |
69 | | - flag = flag.replace("\\\"", "") |
70 | | - if flag[:2] == "-I": |
71 | | - if flag[2:] not in include_dirs: |
72 | | - include_dirs.append(flag[2:]) |
73 | | - elif flag[:2] == "-D": |
74 | | - t = tuple(flag[2:].split('=')) |
75 | | - if len(t) == 1: |
76 | | - t = (t[0], None) |
77 | | - if t not in define_macros: |
78 | | - define_macros.append(t) |
79 | | - else: |
80 | | - print("Warning : cflag %s skipped" % flag) |
81 | | - |
82 | | -def extract_libs(libs): |
83 | | - global library_dirs, libraries |
84 | | - list = libs.split(' ') |
85 | | - for flag in list: |
86 | | - if flag == '': |
87 | | - continue |
88 | | - if flag[:2] == "-l": |
89 | | - if flag[2:] not in libraries: |
90 | | - libraries.append(flag[2:]) |
91 | | - elif flag[:2] == "-L": |
92 | | - if flag[2:] not in library_dirs: |
93 | | - library_dirs.append(flag[2:]) |
94 | | - else: |
95 | | - print("Warning : linker flag %s skipped" % flag) |
96 | | - |
97 | | - |
98 | | -libxml2_cflags = getoutput('pkg-config libxml-2.0 --cflags') |
99 | | -if libxml2_cflags[:2] not in ["-I", "-D"]: |
100 | | - libxml2_cflags = getoutput('xml2-config --cflags') |
101 | | -if libxml2_cflags[:2] not in ["-I", "-D"]: |
102 | | - print("Error : cannot get LibXML2 pre-processor and compiler flags") |
103 | | - |
104 | | -libxml2_libs = getoutput('pkg-config libxml-2.0 --libs') |
105 | | -if libxml2_libs[:2] not in ["-l", "-L"]: |
106 | | - libxml2_libs = getoutput('xml2-config --libs') |
107 | | -if libxml2_libs[:2] not in ["-l", "-L"]: |
108 | | - print("Error : cannot get LibXML2 linker flags") |
109 | | - |
110 | | -xmlsec1_crypto = 'openssl' |
111 | | -cmd = 'pkg-config xmlsec1-%s --cflags' % xmlsec1_crypto |
112 | | -xmlsec1_cflags = getoutput(cmd) |
113 | | -if xmlsec1_cflags[:2] not in ["-I", "-D"]: |
114 | | - cmd = 'xmlsec1-config --cflags --crypto=%s' % xmlsec1_crypto |
115 | | - xmlsec1_cflags = getoutput(cmd) |
116 | | -if xmlsec1_cflags[:2] not in ["-I", "-D"]: |
117 | | - print("Error : cannot get XMLSec1 pre-processor and compiler flags") |
118 | | - |
119 | | -cmd = 'pkg-config xmlsec1-%s --libs' % xmlsec1_crypto |
120 | | -xmlsec1_libs = getoutput(cmd) |
121 | | -if xmlsec1_libs[:2] not in ["-l", "-L"]: |
122 | | - cmd = 'xmlsec1-config --libs --crypto=%s' % xmlsec1_crypto |
123 | | - xmlsec1_libs = getoutput(cmd) |
124 | | -if xmlsec1_libs[:2] not in ["-l", "-L"]: |
125 | | - print("Error : cannot get XMLSec1 linker flags") |
126 | | - |
127 | | -#print(libxml2_cflags) |
128 | | -#print libxml2_libs |
129 | | -#print xmlsec1_cflags |
130 | | -#print xmlsec1_libs |
131 | | - |
132 | | -extract_cflags(libxml2_cflags) |
133 | | -extract_libs(libxml2_libs) |
134 | | - |
135 | | -extract_cflags(xmlsec1_cflags) |
136 | | -extract_libs(xmlsec1_libs) |
137 | | - |
138 | | -# |
139 | | -# END HACK |
140 | | -# |
| 57 | +class Extension(Extension): |
| 58 | + |
| 59 | + lxml_extended = False |
| 60 | + |
| 61 | + @property |
| 62 | + def include_dirs(self): |
| 63 | + dirs = self.__dict__['include_dirs'] |
| 64 | + if self.lxml_extended: |
| 65 | + return dirs |
| 66 | + |
| 67 | + # Resolve lxml include directories. |
| 68 | + import lxml |
| 69 | + lxml_base = path.dirname(lxml.__file__) |
| 70 | + lxml_include = path.join(lxml_base, 'includes') |
| 71 | + |
| 72 | + dirs.insert(0, lxml_include) |
| 73 | + dirs.insert(0, lxml_base) |
| 74 | + |
| 75 | + self.lxml_extended = True |
| 76 | + return dirs |
| 77 | + |
| 78 | + @include_dirs.setter |
| 79 | + def include_dirs(self, dirs): |
| 80 | + self.__dict__['include_dirs'] = dirs |
| 81 | + |
| 82 | + |
| 83 | +# Declare the crypto implementation. |
| 84 | +XMLSEC_CRYPTO = 'openssl' |
| 85 | + |
| 86 | +# Process the `pkg-config` utility and discover include and library |
| 87 | +# directories. |
| 88 | +config = pkgconfig('libxml-2.0', 'xmlsec1-%s' % XMLSEC_CRYPTO) |
| 89 | +config['include_dirs'].insert(0, 'src') # Prepend 'src' as an include dir. |
| 90 | + |
141 | 91 |
|
142 | 92 | setup( |
143 | 93 | name='xmlsec', |
144 | 94 | version='0.1.0', |
145 | 95 | description='Python bindings for the XML Security Library.', |
146 | | - setup_requires=["lxml >= 3.0",], |
147 | | - install_requires=[ |
148 | | - "lxml >= 3.0" |
| 96 | + setup_requires=[ |
| 97 | + 'lxml >= 3.0', |
149 | 98 | ], |
150 | | - cmdclass={'build_ext': build_ext}, |
151 | | - ext_modules=[ |
152 | | - Extension( |
153 | | - 'xmlsec', ['xmlsec.pyx'], |
154 | | - define_macros=define_macros, |
155 | | - include_dirs=include_dirs, |
156 | | - library_dirs=library_dirs, |
157 | | - libraries=libraries, |
158 | | - depends=["src/" + f for f in "cxmlsec.pxd cxmlsec.h lxml.etree.h lxml-version.h lxml.etree_api.h".split()] |
159 | | - ) |
| 99 | + install_requires=[ |
| 100 | + 'lxml >= 3.0', |
160 | 101 | ], |
| 102 | + cmdclass = {'build_ext': Distutils.build_ext}, |
| 103 | + ext_modules=[Extension('xmlsec', ['src/xmlsec.pyx'], **config)] |
161 | 104 | ) |
0 commit comments