##// END OF EJS Templates
setup: slight simplification
Martin Geisler -
r12501:98f21e4d default
parent child Browse files
Show More
@@ -1,343 +1,343 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # This is the mercurial setup script.
3 # This is the mercurial setup script.
4 #
4 #
5 # 'python setup.py install', or
5 # 'python setup.py install', or
6 # 'python setup.py --help' for more options
6 # 'python setup.py --help' for more options
7
7
8 import sys
8 import sys
9 if not hasattr(sys, 'version_info') or sys.version_info < (2, 4, 0, 'final'):
9 if not hasattr(sys, 'version_info') or sys.version_info < (2, 4, 0, 'final'):
10 raise SystemExit("Mercurial requires Python 2.4 or later.")
10 raise SystemExit("Mercurial requires Python 2.4 or later.")
11
11
12 if sys.version_info[0] >= 3:
12 if sys.version_info[0] >= 3:
13 def b(s):
13 def b(s):
14 '''A helper function to emulate 2.6+ bytes literals using string
14 '''A helper function to emulate 2.6+ bytes literals using string
15 literals.'''
15 literals.'''
16 return s.encode('latin1')
16 return s.encode('latin1')
17 else:
17 else:
18 def b(s):
18 def b(s):
19 '''A helper function to emulate 2.6+ bytes literals using string
19 '''A helper function to emulate 2.6+ bytes literals using string
20 literals.'''
20 literals.'''
21 return s
21 return s
22
22
23 # Solaris Python packaging brain damage
23 # Solaris Python packaging brain damage
24 try:
24 try:
25 import hashlib
25 import hashlib
26 sha = hashlib.sha1()
26 sha = hashlib.sha1()
27 except:
27 except:
28 try:
28 try:
29 import sha
29 import sha
30 except:
30 except:
31 raise SystemExit(
31 raise SystemExit(
32 "Couldn't import standard hashlib (incomplete Python install).")
32 "Couldn't import standard hashlib (incomplete Python install).")
33
33
34 try:
34 try:
35 import zlib
35 import zlib
36 except:
36 except:
37 raise SystemExit(
37 raise SystemExit(
38 "Couldn't import standard zlib (incomplete Python install).")
38 "Couldn't import standard zlib (incomplete Python install).")
39
39
40 try:
40 try:
41 import bz2
41 import bz2
42 except:
42 except:
43 raise SystemExit(
43 raise SystemExit(
44 "Couldn't import standard bz2 (incomplete Python install).")
44 "Couldn't import standard bz2 (incomplete Python install).")
45
45
46 import os, subprocess, time
46 import os, subprocess, time
47 import shutil
47 import shutil
48 import tempfile
48 import tempfile
49 from distutils import log
49 from distutils import log
50 from distutils.core import setup, Extension
50 from distutils.core import setup, Extension
51 from distutils.dist import Distribution
51 from distutils.dist import Distribution
52 from distutils.command.build import build
52 from distutils.command.build import build
53 from distutils.command.build_ext import build_ext
53 from distutils.command.build_ext import build_ext
54 from distutils.command.build_py import build_py
54 from distutils.command.build_py import build_py
55 from distutils.spawn import spawn, find_executable
55 from distutils.spawn import spawn, find_executable
56 from distutils.ccompiler import new_compiler
56 from distutils.ccompiler import new_compiler
57 from distutils.errors import CCompilerError
57 from distutils.errors import CCompilerError
58
58
59 scripts = ['hg']
59 scripts = ['hg']
60 if os.name == 'nt':
60 if os.name == 'nt':
61 scripts.append('contrib/win32/hg.bat')
61 scripts.append('contrib/win32/hg.bat')
62
62
63 # simplified version of distutils.ccompiler.CCompiler.has_function
63 # simplified version of distutils.ccompiler.CCompiler.has_function
64 # that actually removes its temporary files.
64 # that actually removes its temporary files.
65 def hasfunction(cc, funcname):
65 def hasfunction(cc, funcname):
66 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
66 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
67 devnull = oldstderr = None
67 devnull = oldstderr = None
68 try:
68 try:
69 try:
69 try:
70 fname = os.path.join(tmpdir, 'funcname.c')
70 fname = os.path.join(tmpdir, 'funcname.c')
71 f = open(fname, 'w')
71 f = open(fname, 'w')
72 f.write('int main(void) {\n')
72 f.write('int main(void) {\n')
73 f.write(' %s();\n' % funcname)
73 f.write(' %s();\n' % funcname)
74 f.write('}\n')
74 f.write('}\n')
75 f.close()
75 f.close()
76 # Redirect stderr to /dev/null to hide any error messages
76 # Redirect stderr to /dev/null to hide any error messages
77 # from the compiler.
77 # from the compiler.
78 # This will have to be changed if we ever have to check
78 # This will have to be changed if we ever have to check
79 # for a function on Windows.
79 # for a function on Windows.
80 devnull = open('/dev/null', 'w')
80 devnull = open('/dev/null', 'w')
81 oldstderr = os.dup(sys.stderr.fileno())
81 oldstderr = os.dup(sys.stderr.fileno())
82 os.dup2(devnull.fileno(), sys.stderr.fileno())
82 os.dup2(devnull.fileno(), sys.stderr.fileno())
83 objects = cc.compile([fname], output_dir=tmpdir)
83 objects = cc.compile([fname], output_dir=tmpdir)
84 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
84 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
85 except:
85 except:
86 return False
86 return False
87 return True
87 return True
88 finally:
88 finally:
89 if oldstderr is not None:
89 if oldstderr is not None:
90 os.dup2(oldstderr, sys.stderr.fileno())
90 os.dup2(oldstderr, sys.stderr.fileno())
91 if devnull is not None:
91 if devnull is not None:
92 devnull.close()
92 devnull.close()
93 shutil.rmtree(tmpdir)
93 shutil.rmtree(tmpdir)
94
94
95 # py2exe needs to be installed to work
95 # py2exe needs to be installed to work
96 try:
96 try:
97 import py2exe
97 import py2exe
98 py2exeloaded = True
98 py2exeloaded = True
99
99
100 # Help py2exe to find win32com.shell
100 # Help py2exe to find win32com.shell
101 try:
101 try:
102 import modulefinder
102 import modulefinder
103 import win32com
103 import win32com
104 for p in win32com.__path__[1:]: # Take the path to win32comext
104 for p in win32com.__path__[1:]: # Take the path to win32comext
105 modulefinder.AddPackagePath("win32com", p)
105 modulefinder.AddPackagePath("win32com", p)
106 pn = "win32com.shell"
106 pn = "win32com.shell"
107 __import__(pn)
107 __import__(pn)
108 m = sys.modules[pn]
108 m = sys.modules[pn]
109 for p in m.__path__[1:]:
109 for p in m.__path__[1:]:
110 modulefinder.AddPackagePath(pn, p)
110 modulefinder.AddPackagePath(pn, p)
111 except ImportError:
111 except ImportError:
112 pass
112 pass
113
113
114 except ImportError:
114 except ImportError:
115 py2exeloaded = False
115 py2exeloaded = False
116 pass
116 pass
117
117
118 def runcmd(cmd, env):
118 def runcmd(cmd, env):
119 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
119 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
120 stderr=subprocess.PIPE, env=env)
120 stderr=subprocess.PIPE, env=env)
121 out, err = p.communicate()
121 out, err = p.communicate()
122 # If root is executing setup.py, but the repository is owned by
122 # If root is executing setup.py, but the repository is owned by
123 # another user (as in "sudo python setup.py install") we will get
123 # another user (as in "sudo python setup.py install") we will get
124 # trust warnings since the .hg/hgrc file is untrusted. That is
124 # trust warnings since the .hg/hgrc file is untrusted. That is
125 # fine, we don't want to load it anyway. Python may warn about
125 # fine, we don't want to load it anyway. Python may warn about
126 # a missing __init__.py in mercurial/locale, we also ignore that.
126 # a missing __init__.py in mercurial/locale, we also ignore that.
127 err = [e for e in err.splitlines()
127 err = [e for e in err.splitlines()
128 if not e.startswith(b('Not trusting file')) \
128 if not e.startswith(b('Not trusting file')) \
129 and not e.startswith(b('warning: Not importing'))]
129 and not e.startswith(b('warning: Not importing'))]
130 if err:
130 if err:
131 return ''
131 return ''
132 return out
132 return out
133
133
134 version = ''
134 version = ''
135
135
136 if os.path.isdir('.hg'):
136 if os.path.isdir('.hg'):
137 # Execute hg out of this directory with a custom environment which
137 # Execute hg out of this directory with a custom environment which
138 # includes the pure Python modules in mercurial/pure. We also take
138 # includes the pure Python modules in mercurial/pure. We also take
139 # care to not use any hgrc files and do no localization.
139 # care to not use any hgrc files and do no localization.
140 pypath = ['mercurial', os.path.join('mercurial', 'pure')]
140 pypath = ['mercurial', os.path.join('mercurial', 'pure')]
141 env = {'PYTHONPATH': os.pathsep.join(pypath),
141 env = {'PYTHONPATH': os.pathsep.join(pypath),
142 'HGRCPATH': '',
142 'HGRCPATH': '',
143 'LANGUAGE': 'C'}
143 'LANGUAGE': 'C'}
144 if 'LD_LIBRARY_PATH' in os.environ:
144 if 'LD_LIBRARY_PATH' in os.environ:
145 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
145 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
146 if 'SystemRoot' in os.environ:
146 if 'SystemRoot' in os.environ:
147 # Copy SystemRoot into the custom environment for Python 2.6
147 # Copy SystemRoot into the custom environment for Python 2.6
148 # under Windows. Otherwise, the subprocess will fail with
148 # under Windows. Otherwise, the subprocess will fail with
149 # error 0xc0150004. See: http://bugs.python.org/issue3440
149 # error 0xc0150004. See: http://bugs.python.org/issue3440
150 env['SystemRoot'] = os.environ['SystemRoot']
150 env['SystemRoot'] = os.environ['SystemRoot']
151 cmd = [sys.executable, 'hg', 'id', '-i', '-t']
151 cmd = [sys.executable, 'hg', 'id', '-i', '-t']
152 l = runcmd(cmd, env).split()
152 l = runcmd(cmd, env).split()
153 while len(l) > 1 and l[-1][0].isalpha(): # remove non-numbered tags
153 while len(l) > 1 and l[-1][0].isalpha(): # remove non-numbered tags
154 l.pop()
154 l.pop()
155 if len(l) > 1: # tag found
155 if len(l) > 1: # tag found
156 version = l[-1]
156 version = l[-1]
157 if l[0].endswith('+'): # propagate the dirty status to the tag
157 if l[0].endswith('+'): # propagate the dirty status to the tag
158 version += '+'
158 version += '+'
159 elif len(l) == 1: # no tag found
159 elif len(l) == 1: # no tag found
160 cmd = [sys.executable, 'hg', 'parents', '--template',
160 cmd = [sys.executable, 'hg', 'parents', '--template',
161 '{latesttag}+{latesttagdistance}-']
161 '{latesttag}+{latesttagdistance}-']
162 version = runcmd(cmd, env) + l[0]
162 version = runcmd(cmd, env) + l[0]
163 if version.endswith('+'):
163 if version.endswith('+'):
164 version += time.strftime('%Y%m%d')
164 version += time.strftime('%Y%m%d')
165 elif os.path.exists('.hg_archival.txt'):
165 elif os.path.exists('.hg_archival.txt'):
166 kw = dict([[t.strip() for t in l.split(':', 1)]
166 kw = dict([[t.strip() for t in l.split(':', 1)]
167 for l in open('.hg_archival.txt')])
167 for l in open('.hg_archival.txt')])
168 if 'tag' in kw:
168 if 'tag' in kw:
169 version = kw['tag']
169 version = kw['tag']
170 elif 'latesttag' in kw:
170 elif 'latesttag' in kw:
171 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
171 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
172 else:
172 else:
173 version = kw.get('node', '')[:12]
173 version = kw.get('node', '')[:12]
174
174
175 if version:
175 if version:
176 f = open("mercurial/__version__.py", "w")
176 f = open("mercurial/__version__.py", "w")
177 f.write('# this file is autogenerated by setup.py\n')
177 f.write('# this file is autogenerated by setup.py\n')
178 f.write('version = "%s"\n' % version)
178 f.write('version = "%s"\n' % version)
179 f.close()
179 f.close()
180
180
181
181
182 try:
182 try:
183 from mercurial import __version__
183 from mercurial import __version__
184 version = __version__.version
184 version = __version__.version
185 except ImportError:
185 except ImportError:
186 version = 'unknown'
186 version = 'unknown'
187
187
188 class hgbuildmo(build):
188 class hgbuildmo(build):
189
189
190 description = "build translations (.mo files)"
190 description = "build translations (.mo files)"
191
191
192 def run(self):
192 def run(self):
193 if not find_executable('msgfmt'):
193 if not find_executable('msgfmt'):
194 self.warn("could not find msgfmt executable, no translations "
194 self.warn("could not find msgfmt executable, no translations "
195 "will be built")
195 "will be built")
196 return
196 return
197
197
198 podir = 'i18n'
198 podir = 'i18n'
199 if not os.path.isdir(podir):
199 if not os.path.isdir(podir):
200 self.warn("could not find %s/ directory" % podir)
200 self.warn("could not find %s/ directory" % podir)
201 return
201 return
202
202
203 join = os.path.join
203 join = os.path.join
204 for po in os.listdir(podir):
204 for po in os.listdir(podir):
205 if not po.endswith('.po'):
205 if not po.endswith('.po'):
206 continue
206 continue
207 pofile = join(podir, po)
207 pofile = join(podir, po)
208 modir = join('locale', po[:-3], 'LC_MESSAGES')
208 modir = join('locale', po[:-3], 'LC_MESSAGES')
209 mofile = join(modir, 'hg.mo')
209 mofile = join(modir, 'hg.mo')
210 mobuildfile = join('mercurial', mofile)
210 mobuildfile = join('mercurial', mofile)
211 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
211 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
212 if sys.platform != 'sunos5':
212 if sys.platform != 'sunos5':
213 # msgfmt on Solaris does not know about -c
213 # msgfmt on Solaris does not know about -c
214 cmd.append('-c')
214 cmd.append('-c')
215 self.mkpath(join('mercurial', modir))
215 self.mkpath(join('mercurial', modir))
216 self.make_file([pofile], mobuildfile, spawn, (cmd,))
216 self.make_file([pofile], mobuildfile, spawn, (cmd,))
217
217
218 # Insert hgbuildmo first so that files in mercurial/locale/ are found
218 # Insert hgbuildmo first so that files in mercurial/locale/ are found
219 # when build_py is run next.
219 # when build_py is run next.
220 build.sub_commands.insert(0, ('build_mo', None))
220 build.sub_commands.insert(0, ('build_mo', None))
221
221
222 Distribution.pure = 0
222 Distribution.pure = 0
223 Distribution.global_options.append(('pure', None, "use pure (slow) Python "
223 Distribution.global_options.append(('pure', None, "use pure (slow) Python "
224 "code instead of C extensions"))
224 "code instead of C extensions"))
225
225
226 class hgbuildext(build_ext):
226 class hgbuildext(build_ext):
227
227
228 def build_extension(self, ext):
228 def build_extension(self, ext):
229 try:
229 try:
230 build_ext.build_extension(self, ext)
230 build_ext.build_extension(self, ext)
231 except CCompilerError:
231 except CCompilerError:
232 if not hasattr(ext, 'optional') or not ext.optional:
232 if not getattr(ext, 'optional', False):
233 raise
233 raise
234 log.warn("Failed to build optional extension '%s' (skipping)",
234 log.warn("Failed to build optional extension '%s' (skipping)",
235 ext.name)
235 ext.name)
236
236
237 class hgbuildpy(build_py):
237 class hgbuildpy(build_py):
238
238
239 def finalize_options(self):
239 def finalize_options(self):
240 build_py.finalize_options(self)
240 build_py.finalize_options(self)
241
241
242 if self.distribution.pure:
242 if self.distribution.pure:
243 if self.py_modules is None:
243 if self.py_modules is None:
244 self.py_modules = []
244 self.py_modules = []
245 for ext in self.distribution.ext_modules:
245 for ext in self.distribution.ext_modules:
246 if ext.name.startswith("mercurial."):
246 if ext.name.startswith("mercurial."):
247 self.py_modules.append("mercurial.pure.%s" % ext.name[10:])
247 self.py_modules.append("mercurial.pure.%s" % ext.name[10:])
248 self.distribution.ext_modules = []
248 self.distribution.ext_modules = []
249
249
250 def find_modules(self):
250 def find_modules(self):
251 modules = build_py.find_modules(self)
251 modules = build_py.find_modules(self)
252 for module in modules:
252 for module in modules:
253 if module[0] == "mercurial.pure":
253 if module[0] == "mercurial.pure":
254 if module[1] != "__init__":
254 if module[1] != "__init__":
255 yield ("mercurial", module[1], module[2])
255 yield ("mercurial", module[1], module[2])
256 else:
256 else:
257 yield module
257 yield module
258
258
259 cmdclass = {'build_mo': hgbuildmo,
259 cmdclass = {'build_mo': hgbuildmo,
260 'build_ext': hgbuildext,
260 'build_ext': hgbuildext,
261 'build_py': hgbuildpy}
261 'build_py': hgbuildpy}
262
262
263 packages = ['mercurial', 'mercurial.hgweb', 'hgext', 'hgext.convert',
263 packages = ['mercurial', 'mercurial.hgweb', 'hgext', 'hgext.convert',
264 'hgext.highlight', 'hgext.zeroconf']
264 'hgext.highlight', 'hgext.zeroconf']
265
265
266 pymodules = []
266 pymodules = []
267
267
268 extmodules = [
268 extmodules = [
269 Extension('mercurial.base85', ['mercurial/base85.c']),
269 Extension('mercurial.base85', ['mercurial/base85.c']),
270 Extension('mercurial.bdiff', ['mercurial/bdiff.c']),
270 Extension('mercurial.bdiff', ['mercurial/bdiff.c']),
271 Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c']),
271 Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c']),
272 Extension('mercurial.mpatch', ['mercurial/mpatch.c']),
272 Extension('mercurial.mpatch', ['mercurial/mpatch.c']),
273 Extension('mercurial.parsers', ['mercurial/parsers.c']),
273 Extension('mercurial.parsers', ['mercurial/parsers.c']),
274 ]
274 ]
275
275
276 # disable osutil.c under windows + python 2.4 (issue1364)
276 # disable osutil.c under windows + python 2.4 (issue1364)
277 if sys.platform == 'win32' and sys.version_info < (2, 5, 0, 'final'):
277 if sys.platform == 'win32' and sys.version_info < (2, 5, 0, 'final'):
278 pymodules.append('mercurial.pure.osutil')
278 pymodules.append('mercurial.pure.osutil')
279 else:
279 else:
280 extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c']))
280 extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c']))
281
281
282 if sys.platform == 'linux2' and os.uname()[2] > '2.6':
282 if sys.platform == 'linux2' and os.uname()[2] > '2.6':
283 # The inotify extension is only usable with Linux 2.6 kernels.
283 # The inotify extension is only usable with Linux 2.6 kernels.
284 # You also need a reasonably recent C library.
284 # You also need a reasonably recent C library.
285 # In any case, if it fails to build the error will be skipped ('optional').
285 # In any case, if it fails to build the error will be skipped ('optional').
286 cc = new_compiler()
286 cc = new_compiler()
287 if hasfunction(cc, 'inotify_add_watch'):
287 if hasfunction(cc, 'inotify_add_watch'):
288 inotify = Extension('hgext.inotify.linux._inotify',
288 inotify = Extension('hgext.inotify.linux._inotify',
289 ['hgext/inotify/linux/_inotify.c'],
289 ['hgext/inotify/linux/_inotify.c'],
290 ['mercurial'])
290 ['mercurial'])
291 inotify.optional = True
291 inotify.optional = True
292 extmodules.append(inotify)
292 extmodules.append(inotify)
293 packages.extend(['hgext.inotify', 'hgext.inotify.linux'])
293 packages.extend(['hgext.inotify', 'hgext.inotify.linux'])
294
294
295 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
295 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
296 'help/*.txt']}
296 'help/*.txt']}
297
297
298 def ordinarypath(p):
298 def ordinarypath(p):
299 return p and p[0] != '.' and p[-1] != '~'
299 return p and p[0] != '.' and p[-1] != '~'
300
300
301 for root in ('templates',):
301 for root in ('templates',):
302 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
302 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
303 curdir = curdir.split(os.sep, 1)[1]
303 curdir = curdir.split(os.sep, 1)[1]
304 dirs[:] = filter(ordinarypath, dirs)
304 dirs[:] = filter(ordinarypath, dirs)
305 for f in filter(ordinarypath, files):
305 for f in filter(ordinarypath, files):
306 f = os.path.join(curdir, f)
306 f = os.path.join(curdir, f)
307 packagedata['mercurial'].append(f)
307 packagedata['mercurial'].append(f)
308
308
309 datafiles = []
309 datafiles = []
310 setupversion = version
310 setupversion = version
311 extra = {}
311 extra = {}
312
312
313 if py2exeloaded:
313 if py2exeloaded:
314 extra['console'] = [
314 extra['console'] = [
315 {'script':'hg',
315 {'script':'hg',
316 'copyright':'Copyright (C) 2005-2010 Matt Mackall and others',
316 'copyright':'Copyright (C) 2005-2010 Matt Mackall and others',
317 'product_version':version}]
317 'product_version':version}]
318
318
319 if os.name == 'nt':
319 if os.name == 'nt':
320 # Windows binary file versions for exe/dll files must have the
320 # Windows binary file versions for exe/dll files must have the
321 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
321 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
322 setupversion = version.split('+', 1)[0]
322 setupversion = version.split('+', 1)[0]
323
323
324 setup(name='mercurial',
324 setup(name='mercurial',
325 version=setupversion,
325 version=setupversion,
326 author='Matt Mackall',
326 author='Matt Mackall',
327 author_email='mpm@selenic.com',
327 author_email='mpm@selenic.com',
328 url='http://mercurial.selenic.com/',
328 url='http://mercurial.selenic.com/',
329 description='Scalable distributed SCM',
329 description='Scalable distributed SCM',
330 license='GNU GPLv2+',
330 license='GNU GPLv2+',
331 scripts=scripts,
331 scripts=scripts,
332 packages=packages,
332 packages=packages,
333 py_modules=pymodules,
333 py_modules=pymodules,
334 ext_modules=extmodules,
334 ext_modules=extmodules,
335 data_files=datafiles,
335 data_files=datafiles,
336 package_data=packagedata,
336 package_data=packagedata,
337 cmdclass=cmdclass,
337 cmdclass=cmdclass,
338 options=dict(py2exe=dict(packages=['hgext', 'email']),
338 options=dict(py2exe=dict(packages=['hgext', 'email']),
339 bdist_mpkg=dict(zipdist=True,
339 bdist_mpkg=dict(zipdist=True,
340 license='COPYING',
340 license='COPYING',
341 readme='contrib/macosx/Readme.html',
341 readme='contrib/macosx/Readme.html',
342 welcome='contrib/macosx/Welcome.html')),
342 welcome='contrib/macosx/Welcome.html')),
343 **extra)
343 **extra)
General Comments 0
You need to be logged in to leave comments. Login now