1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
|
import os
import sys
# Provide a bunch of custom components that make it possible to build and
# install non-.py files into the package destinations.
from distutils import dir_util
from distutils.command.build import build as buildcmd
from distutils.command.install_lib import install_lib as installcmd
from distutils.core import setup
from distutils.dist import Distribution
from distutils.extension import Extension
from lxmldistutils import build_ext
# We have to snoop for file types that distutils doesn't copy correctly when
# doing a non-build-in-place.
EXTS = ['.conf', '.css', '.dtd', '.gif', '.jpg', '.html',
'.js', '.mo', '.png', '.pt', '.stx', '.ref',
'.txt', '.xml', '.zcml', '.mar', '.in', '.sample',
]
IGNORE_NAMES = ['.svn']
# This class serves multiple purposes. It walks the file system looking for
# auxiliary files that distutils doesn't install properly, and it actually
# copies those files (when hooked into by distutils). It also walks the file
# system looking for candidate packages for distutils to install as normal.
# The key here is that the package must have an __init__.py file.
class Finder:
def __init__(self, exts, prefix):
self._files = []
self._pkgs = {}
self._exts = exts
# We're finding packages in lib/python in the source dir, but we're
# copying them directly under build/lib.<plat>. So we need to lop off
# the prefix when calculating the package names from the file names.
self._plen = len(prefix)
def visit(self, ignore, dir, files):
for name in IGNORE_NAMES:
if name in files:
files.remove(name)
for file in files:
# First see if this is one of the packages we want to add, or if
# we're really skipping this package.
if '__init__.py' in files:
aspkg = dir[self._plen:].replace(os.sep, '.')
self._pkgs[aspkg] = True
# Add any extra files we're interested in
base, ext = os.path.splitext(file)
if ext in self._exts:
self._files.append(os.path.join(dir, file))
def copy_files(self, cmd, outputbase):
for file in self._files:
dest = os.path.join(outputbase, file[self._plen:])
# Make sure the destination directory exists
dir = os.path.dirname(dest)
if not os.path.exists(dir):
dir_util.mkpath(dir)
cmd.copy_file(file, dest)
def get_packages(self):
return self._pkgs.keys()
def remove_stale_bytecode(arg, dirname, names):
names = map(os.path.normcase, names)
for name in names:
if name.endswith(".pyc") or name.endswith(".pyo"):
srcname = name[:-1]
if srcname not in names:
fullname = os.path.join(dirname, name)
print "Removing stale bytecode file", fullname
os.unlink(fullname)
# Create the finder instance, which will be used in lots of places. `finder'
# is the global we're most interested in.
basedir = 'src/'
finder = Finder(EXTS, basedir)
os.path.walk(basedir, finder.visit, None)
packages = finder.get_packages()
# Distutils hook classes
class MyBuilder(buildcmd):
def run(self):
os.path.walk(os.curdir, remove_stale_bytecode, None)
buildcmd.run(self)
finder.copy_files(self, self.build_lib)
class MyExtBuilder(build_ext):
# Override the default build_ext to remove stale bytecodes.
# Technically, removing bytecode has nothing to do with
# building extensions, but the build_ext -i variant
# is used to build lxml in place.
def run(self):
os.path.walk(os.curdir, remove_stale_bytecode, None)
build_ext.run(self)
def get_pxd_include_paths(self):
"""lxml specific pxd paths.
"""
return ['src/lxml']
class MyLibInstaller(installcmd):
def run(self):
installcmd.run(self)
finder.copy_files(self, self.install_dir)
class MyDistribution(Distribution):
# To control the selection of MyLibInstaller and MyPyBuilder, we
# have to set it into the cmdclass instance variable, set in
# Distribution.__init__().
def __init__(self, *attrs):
Distribution.__init__(self, *attrs)
self.cmdclass['build'] = MyBuilder
self.cmdclass['build_ext'] = MyExtBuilder
self.cmdclass['install_lib'] = MyLibInstaller
class Error(Exception):
pass
def guess_dirs(xml2config_flags, flag):
wf, rf, ef = os.popen3('xml2-config %s' % xml2config_flags)
flags = rf.read()
error = ef.read()
if error:
# cannot find it, just refuse to guess
raise Error, "Cannot guess libxml2 dirs. Try configuring it manually."
# get all returned flags and return them
parts = flags.split()
result = []
for part in parts:
if part.startswith(flag):
result.append(part[2:])
return result
# if you want to configure include and library dir manually, you can do
# so here, for instance:
# include_dirs = ['/usr/include/libxml2']
# library_dirs = ['/usr/lib']
include_dirs = guess_dirs('--cflags', '-I')
library_dirs = guess_dirs('--libs', '-L')
ext_modules = [
Extension('lxml.etree',
sources=['src/lxml/etree.pyx'],
include_dirs=include_dirs,
library_dirs=library_dirs,
libraries=['xml2', 'xslt'],
extra_compile_args=['-w'])
]
# if compilation does not work for some reason, try the following:
# change these to your local installation of libxml2
# libxml2_include_dir = '/home/faassen/tmp/local/include/libxml2'
# libxslt_include_dir = '/home/faassen/tmp/local/include/'
# include_dirs = [libxml2_include_dir, libxslt_include_dir]
# library_dirs = ['/home/faassen/tmp/local/lib']
# libraries = ['xml2', 'xslt']
# runtime_library_dirs = ['/home/faassen/tmp/local/lib']
# extra_compile_args = ['-w']
# Extension('lxml.etree',
# sources=['src/lxml/etree.pyx'],
# include_dirs=include_dirs,
# runtime_library_dirs=runtime_library_dirs,
# library_dirs=library_dirs,
# libraries=libraries,
# extra_compile_args = extra_compile_args
# ),
# ]
f = open('version.txt', 'r')
version = f.read().strip()
f.close()
setup(name="lxml",
version=version,
maintainer="Infrae",
maintainer_email="faassen@infrae.com",
ext_modules = ext_modules,
platforms = ["any"],
packages = packages,
package_dir = {'': 'src'},
distclass = MyDistribution,
)
|