44import sys
55import fnmatch
66import subprocess
7+ import tarfile
8+ import shutil
9+ import stat
710import re
811
12+ try :
13+ from urllib .request import urlretrieve
14+ except ImportError :
15+ from urllib import urlretrieve
16+
917from setuptools import setup
1018from distutils .core import Extension
1119from distutils .sysconfig import get_config_var as get_python_config
1220from distutils .sysconfig import get_python_lib
1321
14- # Compile all available source files.
22+ # Before anything else, this setup.py uses various tricks to install
23+ # precompiled Apache binaries for the Heroku and OpenShift environments.
24+ # Once they are installed, then the installation of the mod_wsgi package
25+ # itself will be triggered, ensuring that it can be built against the
26+ # precompiled Apache binaries which were installed.
27+ #
28+ # We therefore first need to work out whether we are actually running on
29+ # either Heroku of OpenShift. If we are, then we identify the set of
30+ # precompiled binaries we are to use and copy it into the Python
31+ # installation.
32+
33+ PREFIX = 'https://s3.amazonaws.com'
34+ BUCKET = os .environ .get ('MOD_WSGI_REMOTE_S3_BUCKET_NAME' , 'modwsgi.org' )
35+
36+ REMOTE_TARBALL_NAME = os .environ .get ('MOD_WSGI_REMOTE_PACKAGES_NAME' )
37+
38+ TGZ_OPENSHIFT = 'mod_wsgi-packages-openshift-centos6-apache-2.4.10-1.tar.gz'
39+ TGZ_HEROKU = 'mod_wsgi-packages-heroku-cedar14-apache-2.4.10-1.tar.gz'
40+
41+ if not REMOTE_TARBALL_NAME :
42+ if os .environ .get ('OPENSHIFT_HOMEDIR' ):
43+ REMOTE_TARBALL_NAME = TGZ_OPENSHIFT
44+ elif os .path .isdir ('/app/.heroku' ):
45+ REMOTE_TARBALL_NAME = TGZ_HEROKU
46+
47+ LOCAL_TARBALL_FILE = os .environ .get ('MOD_WSGI_LOCAL_PACKAGES_FILE' )
48+
49+ REMOTE_TARBALL_URL = None
50+
51+ if LOCAL_TARBALL_FILE is None and REMOTE_TARBALL_NAME :
52+ REMOTE_TARBALL_URL = '%s/%s/%s' % (PREFIX , BUCKET , REMOTE_TARBALL_NAME )
53+
54+ # Work out if we are actually performing an install as we don't want to
55+ # download any binaries or try and install them if we aren't. To
56+ # determine this, we need to scan through the arguments, skipping any
57+ # global options being passed to distutils and then look for 'install'.
58+ # Note that older versions of pip appear to use the 'egg_info' command
59+ # and not the 'install' command to somehow trigger installations.
60+
61+ WITH_PACKAGES = False
62+ SETUP_COMMAND = None
63+
64+ for arg in sys .argv [1 :]:
65+ if arg .startswith ('-' ):
66+ continue
67+
68+ SETUP_COMMAND = arg
69+
70+ break
71+
72+ if REMOTE_TARBALL_URL or LOCAL_TARBALL_FILE :
73+ if arg in ('install' , 'egg_info' ):
74+ WITH_PACKAGES = True
75+
76+ # If we are doing an install, download the tarball and unpack it into
77+ # the 'packages' subdirectory. We will then add everything in that
78+ # directory as package data so that it will be installed into the Python
79+ # installation.
80+
81+ if WITH_PACKAGES :
82+ if REMOTE_TARBALL_URL :
83+ if not os .path .isfile (REMOTE_TARBALL_NAME ):
84+ print ('Downloading' , REMOTE_TARBALL_URL )
85+ urlretrieve (REMOTE_TARBALL_URL , REMOTE_TARBALL_NAME + '.download' )
86+ os .rename (REMOTE_TARBALL_NAME + '.download' , REMOTE_TARBALL_NAME )
87+ LOCAL_TARBALL_FILE = REMOTE_TARBALL_NAME
88+
89+ shutil .rmtree ('src/packages' , ignore_errors = True )
90+
91+ tar = tarfile .open (LOCAL_TARBALL_FILE )
92+ tar .extractall ('src/packages' )
93+ tar .close ()
94+
95+ open ('src/packages/__init__.py' , 'a' ).close ()
96+
97+ package_files = []
98+
99+ for root , dirs , files in os .walk ('src/packages' , topdown = False ):
100+ for name in files :
101+ path = os .path .join (root , name ).split ('/' , 1 )[1 ]
102+ package_files .append (path )
103+ print ('adding ' , path )
104+
105+ print ('Running setup for Apache' )
106+
107+ setup (name = 'mod_wsgi-packages' ,
108+ version = '1.0.0' ,
109+ packages = ['mod_wsgi' , 'mod_wsgi.packages' ],
110+ package_dir = {'mod_wsgi' : 'src' },
111+ package_data = {'mod_wsgi' : package_files },
112+ entry_points = { 'console_scripts' :
113+ ['mod_wsgi-apxs = mod_wsgi.packages.apxs:main' ],},
114+ )
115+
116+ # From this point on we will now actually install mod_wsgi. First we need
117+ # to work out what all the available source code files are that should be
118+ # compiled.
15119
16120source_files = [os .path .join ('src/server' , name ) for name in
17121 os .listdir (os .path .join (os .path .dirname (os .path .abspath (__file__ )),
18122 'src/server' )) if fnmatch .fnmatch (name , '*.c' )]
19123
20- # Work out all the Apache specific compilation flags.
124+ # Work out all the Apache specific compilation flags. This is done using
125+ # the standard Apache apxs command unless we are installing our own build
126+ # of Apache. In that case we use Python code to do the equivalent of apxs
127+ # as apxs will not work due to paths not matching where it was installed.
21128
22129def find_program (names , default = None , paths = []):
23130 for name in names :
@@ -34,19 +141,102 @@ def find_program(names, default=None, paths=[]):
34141elif not os .path .isabs (APXS ):
35142 APXS = find_program ([APXS ], APXS , ['/usr/sbin' , os .getcwd ()])
36143
37- if not os .path .isabs (APXS ) or not os .access (APXS , os .X_OK ):
38- raise RuntimeError ('The %r command appears not to be installed or is '
39- 'not executable. Please check the list of prerequisites in the '
40- 'documentation for this package and install any missing '
41- 'Apache httpd server packages.' % APXS )
144+ if not WITH_PACKAGES :
145+ if not os .path .isabs (APXS ) or not os .access (APXS , os .X_OK ):
146+ raise RuntimeError ('The %r command appears not to be installed or '
147+ 'is not executable. Please check the list of prerequisites '
148+ 'in the documentation for this package and install any '
149+ 'missing Apache httpd server packages.' % APXS )
150+
151+ if WITH_PACKAGES :
152+ SCRIPT_DIR = os .path .join (os .path .dirname (__file__ ), 'src' , 'packages' )
153+
154+ CONFIG_FILE = os .path .join (SCRIPT_DIR , 'apache/build/config_vars.mk' )
155+
156+ CONFIG = {}
157+
158+ with open (CONFIG_FILE ) as fp :
159+ for line in fp .readlines ():
160+ name , value = line .split ('=' , 1 )
161+ name = name .strip ()
162+ value = value .strip ()
163+ CONFIG [name ] = value
164+
165+ _varprog = re .compile (r'\$(\w+|(?:\{[^}]*\}|\([^)]*\)))' )
166+
167+ def expand_vars (value ):
168+ if '$' not in value :
169+ return value
170+
171+ i = 0
172+ while True :
173+ m = _varprog .search (value , i )
174+ if not m :
175+ break
176+ i , j = m .span (0 )
177+ name = m .group (1 )
178+ if name .startswith ('{' ) and name .endswith ('}' ):
179+ name = name [1 :- 1 ]
180+ elif name .startswith ('(' ) and name .endswith (')' ):
181+ name = name [1 :- 1 ]
182+ if name in CONFIG :
183+ tail = value [j :]
184+ value = value [:i ] + CONFIG .get (name , '' )
185+ i = len (value )
186+ value += tail
187+ else :
188+ i = j
189+
190+ return value
191+
192+ def get_apxs_config (name ):
193+ value = CONFIG .get (name , '' )
194+ sub_value = expand_vars (value )
195+ while value != sub_value :
196+ value = sub_value
197+ sub_value = expand_vars (value )
198+ return sub_value .replace ('/mod_wsgi-packages/' , SCRIPT_DIR + '/' )
199+
200+ CONFIG ['PREFIX' ] = get_apxs_config ('prefix' )
201+ CONFIG ['TARGET' ] = get_apxs_config ('target' )
202+ CONFIG ['SYSCONFDIR' ] = get_apxs_config ('sysconfdir' )
203+ CONFIG ['INCLUDEDIR' ] = get_apxs_config ('includedir' )
204+ CONFIG ['LIBEXECDIR' ] = get_apxs_config ('libexecdir' )
205+ CONFIG ['BINDIR' ] = get_apxs_config ('bindir' )
206+ CONFIG ['SBINDIR' ] = get_apxs_config ('sbindir' )
207+ CONFIG ['PROGNAME' ] = get_apxs_config ('progname' )
208+
209+ _CFLAGS_NAMES = ['SHLTCFLAGS' , 'CFLAGS' , 'NOTEST_CPPFLAGS' ,
210+ 'EXTRA_CPPFLAGS' , 'EXTRA_CFLAGS' ]
211+
212+ _CFLAGS_VALUES = []
213+
214+ for name in _CFLAGS_NAMES :
215+ value = get_apxs_config (name )
216+
217+ # Heroku doesn't appear to run the same version of gcc
218+ # that a standard Ubuntu installation does and which was
219+ # used to originally build the Apache binaries. We need
220+ # therefore to strip out flags that the Heroku gcc may
221+ # not understand.
42222
43- def get_apxs_config (query ):
44- p = subprocess .Popen ([APXS , '-q' , query ],
45- stdout = subprocess .PIPE , stderr = subprocess .PIPE )
46- out , err = p .communicate ()
47- if isinstance (out , bytes ):
48- out = out .decode ('UTF-8' )
49- return out .strip ()
223+ if value :
224+ if os .path .isdir ('/app/.heroku' ):
225+ value = value .replace ('-prefer-pic' , '' )
226+
227+ if value :
228+ _CFLAGS_VALUES .append (value )
229+
230+ CONFIG ['CFLAGS' ] = ' ' .join (_CFLAGS_VALUES )
231+
232+ else :
233+ def get_apxs_config (query ):
234+ p = subprocess .Popen ([APXS , '-q' , query ],
235+ stdout = subprocess .PIPE , stderr = subprocess .PIPE )
236+ out , err = p .communicate ()
237+ if isinstance (out , bytes ):
238+ out = out .decode ('UTF-8' )
239+ return out .strip ()
50240
51241INCLUDEDIR = get_apxs_config ('INCLUDEDIR' )
52242CPPFLAGS = get_apxs_config ('CPPFLAGS' ).split ()
@@ -56,8 +246,12 @@ def get_apxs_config(query):
56246EXTRA_CPPFLAGS = get_apxs_config ('EXTRA_CPPFLAGS' ).split ()
57247EXTRA_CFLAGS = get_apxs_config ('EXTRA_CFLAGS' ).split ()
58248
59- # Write out apxs_config.py which caches various configuration
60- # related to Apache.
249+ # Write out apxs_config.py which caches various configuration related to
250+ # Apache. For the case of using our own Apache build, this needs to
251+ # calculate values dynamically based on where binaries were installed.
252+ # This is necessary as on OpenShift the virtual environment gets copied
253+ # for each gear to a different path. We can't therefore rely on a hard
254+ # coded path.
61255
62256BINDIR = get_apxs_config ('BINDIR' )
63257SBINDIR = get_apxs_config ('SBINDIR' )
@@ -68,6 +262,26 @@ def get_apxs_config(query):
68262LIBEXECDIR = get_apxs_config ('LIBEXECDIR' )
69263SHLIBPATH_VAR = get_apxs_config ('SHLIBPATH_VAR' )
70264
265+ APXS_CONFIG_TEMPLATE = """
266+ import os
267+
268+ WITH_PACKAGES = %(WITH_PACKAGES)r
269+
270+ if WITH_PACKAGES:
271+ import mod_wsgi.packages
272+ PACKAGES = os.path.join(os.path.dirname(mod_wsgi.packages.__file__))
273+ BINDIR = os.path.join(PACKAGES, 'apache', 'bin')
274+ SBINDIR = BINDIR
275+ LIBEXECDIR = os.path.join(PACKAGES, 'apache', 'modules')
276+ else:
277+ BINDIR = '%(BINDIR)s'
278+ SBINDIR = '%(SBINDIR)s'
279+ LIBEXECDIR = '%(LIBEXECDIR)s'
280+
281+ MPM_NAME = '%(MPM_NAME)s'
282+ PROGNAME = '%(PROGNAME)s'
283+ SHLIBPATH_VAR = '%(SHLIBPATH_VAR)s'
284+
71285if os.path.exists(os.path.join(SBINDIR, PROGNAME)):
72286 HTTPD = os.path.join(SBINDIR, PROGNAME)
73287elif os.path.exists(os.path.join(BINDIR, PROGNAME)):
@@ -81,17 +295,14 @@ def get_apxs_config(query):
81295 ROTATELOGS = os.path.join(BINDIR, 'rotatelogs')
82296else:
83297 ROTATELOGS = 'rotatelogs'
298+ """
84299
85300with open (os .path .join (os .path .dirname (__file__ ),
86301 'src/server/apxs_config.py' ), 'w' ) as fp :
87- print ('HTTPD = "%s"' % HTTPD , file = fp )
88- print ('ROTATELOGS = "%s"' % ROTATELOGS , file = fp )
89- print ('BINDIR = "%s"' % BINDIR , file = fp )
90- print ('SBINDIR = "%s"' % SBINDIR , file = fp )
91- print ('PROGNAME = "%s"' % PROGNAME , file = fp )
92- print ('MPM_NAME = "%s"' % MPM_NAME , file = fp )
93- print ('LIBEXECDIR = "%s"' % LIBEXECDIR , file = fp )
94- print ('SHLIBPATH_VAR = "%s"' % SHLIBPATH_VAR , file = fp )
302+ print (APXS_CONFIG_TEMPLATE % dict (WITH_PACKAGES = WITH_PACKAGES ,
303+ BINDIR = BINDIR , SBINDIR = SBINDIR , LIBEXECDIR = LIBEXECDIR ,
304+ MPM_NAME = MPM_NAME , PROGNAME = PROGNAME ,
305+ SHLIBPATH_VAR = SHLIBPATH_VAR ), file = fp )
95306
96307# Work out location of Python library and how to link it.
97308
@@ -235,5 +446,5 @@ def _version():
235446 ext_modules = [extension ],
236447 entry_points = { 'console_scripts' :
237448 ['mod_wsgi-express = mod_wsgi.server:main' ],},
238- install_requires = ['mod_wsgi-metrics >= 1.0.0' ],
449+ install_requires = ['mod_wsgi-metrics >= 1.0.0' ],
239450)
0 commit comments