##// END OF EJS Templates
bdiff: add a xdiffblocks method...
Jun Wu -
r36693:430fdb71 default
parent child Browse files
Show More
@@ -1,291 +1,352 b''
1 1 /*
2 2 bdiff.c - efficient binary diff extension for Mercurial
3 3
4 4 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 5
6 6 This software may be used and distributed according to the terms of
7 7 the GNU General Public License, incorporated herein by reference.
8 8
9 9 Based roughly on Python difflib
10 10 */
11 11
12 12 #define PY_SSIZE_T_CLEAN
13 13 #include <Python.h>
14 14 #include <limits.h>
15 15 #include <stdlib.h>
16 16 #include <string.h>
17 17
18 18 #include "bdiff.h"
19 19 #include "bitmanipulation.h"
20 #include "thirdparty/xdiff/xdiff.h"
20 21 #include "util.h"
21 22
22 23 static PyObject *blocks(PyObject *self, PyObject *args)
23 24 {
24 25 PyObject *sa, *sb, *rl = NULL, *m;
25 26 struct bdiff_line *a, *b;
26 27 struct bdiff_hunk l, *h;
27 28 int an, bn, count, pos = 0;
28 29
29 30 l.next = NULL;
30 31
31 32 if (!PyArg_ParseTuple(args, "SS:bdiff", &sa, &sb))
32 33 return NULL;
33 34
34 35 an = bdiff_splitlines(PyBytes_AsString(sa), PyBytes_Size(sa), &a);
35 36 bn = bdiff_splitlines(PyBytes_AsString(sb), PyBytes_Size(sb), &b);
36 37
37 38 if (!a || !b)
38 39 goto nomem;
39 40
40 41 count = bdiff_diff(a, an, b, bn, &l);
41 42 if (count < 0)
42 43 goto nomem;
43 44
44 45 rl = PyList_New(count);
45 46 if (!rl)
46 47 goto nomem;
47 48
48 49 for (h = l.next; h; h = h->next) {
49 50 m = Py_BuildValue("iiii", h->a1, h->a2, h->b1, h->b2);
50 51 PyList_SetItem(rl, pos, m);
51 52 pos++;
52 53 }
53 54
54 55 nomem:
55 56 free(a);
56 57 free(b);
57 58 bdiff_freehunks(l.next);
58 59 return rl ? rl : PyErr_NoMemory();
59 60 }
60 61
61 62 static PyObject *bdiff(PyObject *self, PyObject *args)
62 63 {
63 64 Py_buffer ba, bb;
64 65 char *rb, *ia, *ib;
65 66 PyObject *result = NULL;
66 67 struct bdiff_line *al = NULL, *bl = NULL;
67 68 struct bdiff_hunk l, *h;
68 69 int an, bn, count;
69 70 Py_ssize_t len = 0, la, lb, li = 0, lcommon = 0, lmax;
70 71 PyThreadState *_save = NULL;
71 72
72 73 l.next = NULL;
73 74
74 75 if (!PyArg_ParseTuple(args, PY23("s*s*:bdiff", "y*y*:bdiff"), &ba, &bb))
75 76 return NULL;
76 77
77 78 if (!PyBuffer_IsContiguous(&ba, 'C') || ba.ndim > 1) {
78 79 PyErr_SetString(PyExc_ValueError, "bdiff input not contiguous");
79 80 goto cleanup;
80 81 }
81 82
82 83 if (!PyBuffer_IsContiguous(&bb, 'C') || bb.ndim > 1) {
83 84 PyErr_SetString(PyExc_ValueError, "bdiff input not contiguous");
84 85 goto cleanup;
85 86 }
86 87
87 88 la = ba.len;
88 89 lb = bb.len;
89 90
90 91 if (la > UINT_MAX || lb > UINT_MAX) {
91 92 PyErr_SetString(PyExc_ValueError, "bdiff inputs too large");
92 93 goto cleanup;
93 94 }
94 95
95 96 _save = PyEval_SaveThread();
96 97
97 98 lmax = la > lb ? lb : la;
98 99 for (ia = ba.buf, ib = bb.buf; li < lmax && *ia == *ib;
99 100 ++li, ++ia, ++ib) {
100 101 if (*ia == '\n')
101 102 lcommon = li + 1;
102 103 }
103 104 /* we can almost add: if (li == lmax) lcommon = li; */
104 105
105 106 an = bdiff_splitlines(ba.buf + lcommon, la - lcommon, &al);
106 107 bn = bdiff_splitlines(bb.buf + lcommon, lb - lcommon, &bl);
107 108 if (!al || !bl) {
108 109 PyErr_NoMemory();
109 110 goto cleanup;
110 111 }
111 112
112 113 count = bdiff_diff(al, an, bl, bn, &l);
113 114 if (count < 0) {
114 115 PyErr_NoMemory();
115 116 goto cleanup;
116 117 }
117 118
118 119 /* calculate length of output */
119 120 la = lb = 0;
120 121 for (h = l.next; h; h = h->next) {
121 122 if (h->a1 != la || h->b1 != lb)
122 123 len += 12 + bl[h->b1].l - bl[lb].l;
123 124 la = h->a2;
124 125 lb = h->b2;
125 126 }
126 127 PyEval_RestoreThread(_save);
127 128 _save = NULL;
128 129
129 130 result = PyBytes_FromStringAndSize(NULL, len);
130 131
131 132 if (!result)
132 133 goto cleanup;
133 134
134 135 /* build binary patch */
135 136 rb = PyBytes_AsString(result);
136 137 la = lb = 0;
137 138
138 139 for (h = l.next; h; h = h->next) {
139 140 if (h->a1 != la || h->b1 != lb) {
140 141 len = bl[h->b1].l - bl[lb].l;
141 142 putbe32((uint32_t)(al[la].l + lcommon - al->l), rb);
142 143 putbe32((uint32_t)(al[h->a1].l + lcommon - al->l),
143 144 rb + 4);
144 145 putbe32((uint32_t)len, rb + 8);
145 146 memcpy(rb + 12, bl[lb].l, len);
146 147 rb += 12 + len;
147 148 }
148 149 la = h->a2;
149 150 lb = h->b2;
150 151 }
151 152
152 153 cleanup:
153 154 if (_save)
154 155 PyEval_RestoreThread(_save);
155 156 PyBuffer_Release(&ba);
156 157 PyBuffer_Release(&bb);
157 158 if (al) {
158 159 free(al);
159 160 }
160 161 if (bl) {
161 162 free(bl);
162 163 }
163 164 if (l.next) {
164 165 bdiff_freehunks(l.next);
165 166 }
166 167 return result;
167 168 }
168 169
169 170 /*
170 171 * If allws != 0, remove all whitespace (' ', \t and \r). Otherwise,
171 172 * reduce whitespace sequences to a single space and trim remaining whitespace
172 173 * from end of lines.
173 174 */
174 175 static PyObject *fixws(PyObject *self, PyObject *args)
175 176 {
176 177 PyObject *s, *result = NULL;
177 178 char allws, c;
178 179 const char *r;
179 180 Py_ssize_t i, rlen, wlen = 0;
180 181 char *w;
181 182
182 183 if (!PyArg_ParseTuple(args, "Sb:fixws", &s, &allws))
183 184 return NULL;
184 185 r = PyBytes_AsString(s);
185 186 rlen = PyBytes_Size(s);
186 187
187 188 w = (char *)PyMem_Malloc(rlen ? rlen : 1);
188 189 if (!w)
189 190 goto nomem;
190 191
191 192 for (i = 0; i != rlen; i++) {
192 193 c = r[i];
193 194 if (c == ' ' || c == '\t' || c == '\r') {
194 195 if (!allws && (wlen == 0 || w[wlen - 1] != ' '))
195 196 w[wlen++] = ' ';
196 197 } else if (c == '\n' && !allws && wlen > 0 &&
197 198 w[wlen - 1] == ' ') {
198 199 w[wlen - 1] = '\n';
199 200 } else {
200 201 w[wlen++] = c;
201 202 }
202 203 }
203 204
204 205 result = PyBytes_FromStringAndSize(w, wlen);
205 206
206 207 nomem:
207 208 PyMem_Free(w);
208 209 return result ? result : PyErr_NoMemory();
209 210 }
210 211
211 212 static bool sliceintolist(PyObject *list, Py_ssize_t destidx,
212 213 const char *source, Py_ssize_t len)
213 214 {
214 215 PyObject *sliced = PyBytes_FromStringAndSize(source, len);
215 216 if (sliced == NULL)
216 217 return false;
217 218 PyList_SET_ITEM(list, destidx, sliced);
218 219 return true;
219 220 }
220 221
221 222 static PyObject *splitnewlines(PyObject *self, PyObject *args)
222 223 {
223 224 const char *text;
224 225 Py_ssize_t nelts = 0, size, i, start = 0;
225 226 PyObject *result = NULL;
226 227
227 228 if (!PyArg_ParseTuple(args, PY23("s#", "y#"), &text, &size)) {
228 229 goto abort;
229 230 }
230 231 if (!size) {
231 232 return PyList_New(0);
232 233 }
233 234 /* This loops to size-1 because if the last byte is a newline,
234 235 * we don't want to perform a split there. */
235 236 for (i = 0; i < size - 1; ++i) {
236 237 if (text[i] == '\n') {
237 238 ++nelts;
238 239 }
239 240 }
240 241 if ((result = PyList_New(nelts + 1)) == NULL)
241 242 goto abort;
242 243 nelts = 0;
243 244 for (i = 0; i < size - 1; ++i) {
244 245 if (text[i] == '\n') {
245 246 if (!sliceintolist(result, nelts++, text + start,
246 247 i - start + 1))
247 248 goto abort;
248 249 start = i + 1;
249 250 }
250 251 }
251 252 if (!sliceintolist(result, nelts++, text + start, size - start))
252 253 goto abort;
253 254 return result;
254 255 abort:
255 256 Py_XDECREF(result);
256 257 return NULL;
257 258 }
258 259
260 static int hunk_consumer(long a1, long a2, long b1, long b2, void *priv)
261 {
262 PyObject *rl = (PyObject *)priv;
263 PyObject *m = Py_BuildValue("llll", a1, a2, b1, b2);
264 if (!m)
265 return -1;
266 if (PyList_Append(rl, m) != 0) {
267 Py_DECREF(m);
268 return -1;
269 }
270 return 0;
271 }
272
273 static PyObject *xdiffblocks(PyObject *self, PyObject *args)
274 {
275 Py_ssize_t la, lb;
276 mmfile_t a, b;
277 PyObject *rl;
278
279 xpparam_t xpp = {
280 XDF_INDENT_HEURISTIC, /* flags */
281 NULL, /* anchors */
282 0, /* anchors_nr */
283 };
284 xdemitconf_t xecfg = {
285 0, /* ctxlen */
286 0, /* interhunkctxlen */
287 XDL_EMIT_BDIFFHUNK, /* flags */
288 NULL, /* find_func */
289 NULL, /* find_func_priv */
290 hunk_consumer, /* hunk_consume_func */
291 };
292 xdemitcb_t ecb = {
293 NULL, /* priv */
294 NULL, /* outf */
295 };
296
297 if (!PyArg_ParseTuple(args, PY23("s#s#", "y#y#"), &a.ptr, &la, &b.ptr,
298 &lb))
299 return NULL;
300
301 a.size = la;
302 b.size = lb;
303
304 rl = PyList_New(0);
305 if (!rl)
306 return PyErr_NoMemory();
307
308 ecb.priv = rl;
309
310 if (xdl_diff(&a, &b, &xpp, &xecfg, &ecb) != 0) {
311 Py_DECREF(rl);
312 return PyErr_NoMemory();
313 }
314
315 return rl;
316 }
317
259 318 static char mdiff_doc[] = "Efficient binary diff.";
260 319
261 320 static PyMethodDef methods[] = {
262 321 {"bdiff", bdiff, METH_VARARGS, "calculate a binary diff\n"},
263 322 {"blocks", blocks, METH_VARARGS, "find a list of matching lines\n"},
264 323 {"fixws", fixws, METH_VARARGS, "normalize diff whitespaces\n"},
265 324 {"splitnewlines", splitnewlines, METH_VARARGS,
266 325 "like str.splitlines, but only split on newlines\n"},
326 {"xdiffblocks", xdiffblocks, METH_VARARGS,
327 "find a list of matching lines using xdiff algorithm\n"},
267 328 {NULL, NULL},
268 329 };
269 330
270 static const int version = 2;
331 static const int version = 3;
271 332
272 333 #ifdef IS_PY3K
273 334 static struct PyModuleDef bdiff_module = {
274 335 PyModuleDef_HEAD_INIT, "bdiff", mdiff_doc, -1, methods,
275 336 };
276 337
277 338 PyMODINIT_FUNC PyInit_bdiff(void)
278 339 {
279 340 PyObject *m;
280 341 m = PyModule_Create(&bdiff_module);
281 342 PyModule_AddIntConstant(m, "version", version);
282 343 return m;
283 344 }
284 345 #else
285 346 PyMODINIT_FUNC initbdiff(void)
286 347 {
287 348 PyObject *m;
288 349 m = Py_InitModule3("bdiff", methods, mdiff_doc);
289 350 PyModule_AddIntConstant(m, "version", version);
290 351 }
291 352 #endif
@@ -1,111 +1,111 b''
1 1 # policy.py - module policy logic for Mercurial.
2 2 #
3 3 # Copyright 2015 Gregory Szorc <gregory.szorc@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import os
11 11 import sys
12 12
13 13 # Rules for how modules can be loaded. Values are:
14 14 #
15 15 # c - require C extensions
16 16 # allow - allow pure Python implementation when C loading fails
17 17 # cffi - required cffi versions (implemented within pure module)
18 18 # cffi-allow - allow pure Python implementation if cffi version is missing
19 19 # py - only load pure Python modules
20 20 #
21 21 # By default, fall back to the pure modules so the in-place build can
22 22 # run without recompiling the C extensions. This will be overridden by
23 23 # __modulepolicy__ generated by setup.py.
24 24 policy = b'allow'
25 25 _packageprefs = {
26 26 # policy: (versioned package, pure package)
27 27 b'c': (r'cext', None),
28 28 b'allow': (r'cext', r'pure'),
29 29 b'cffi': (r'cffi', None),
30 30 b'cffi-allow': (r'cffi', r'pure'),
31 31 b'py': (None, r'pure'),
32 32 }
33 33
34 34 try:
35 35 from . import __modulepolicy__
36 36 policy = __modulepolicy__.modulepolicy
37 37 except ImportError:
38 38 pass
39 39
40 40 # PyPy doesn't load C extensions.
41 41 #
42 42 # The canonical way to do this is to test platform.python_implementation().
43 43 # But we don't import platform and don't bloat for it here.
44 44 if r'__pypy__' in sys.builtin_module_names:
45 45 policy = b'cffi'
46 46
47 47 # Environment variable can always force settings.
48 48 if sys.version_info[0] >= 3:
49 49 if r'HGMODULEPOLICY' in os.environ:
50 50 policy = os.environ[r'HGMODULEPOLICY'].encode(r'utf-8')
51 51 else:
52 52 policy = os.environ.get(r'HGMODULEPOLICY', policy)
53 53
54 54 def _importfrom(pkgname, modname):
55 55 # from .<pkgname> import <modname> (where . is looked through this module)
56 56 fakelocals = {}
57 57 pkg = __import__(pkgname, globals(), fakelocals, [modname], level=1)
58 58 try:
59 59 fakelocals[modname] = mod = getattr(pkg, modname)
60 60 except AttributeError:
61 61 raise ImportError(r'cannot import name %s' % modname)
62 62 # force import; fakelocals[modname] may be replaced with the real module
63 63 getattr(mod, r'__doc__', None)
64 64 return fakelocals[modname]
65 65
66 66 # keep in sync with "version" in C modules
67 67 _cextversions = {
68 68 (r'cext', r'base85'): 1,
69 (r'cext', r'bdiff'): 2,
69 (r'cext', r'bdiff'): 3,
70 70 (r'cext', r'diffhelpers'): 1,
71 71 (r'cext', r'mpatch'): 1,
72 72 (r'cext', r'osutil'): 3,
73 73 (r'cext', r'parsers'): 4,
74 74 }
75 75
76 76 # map import request to other package or module
77 77 _modredirects = {
78 78 (r'cext', r'charencode'): (r'cext', r'parsers'),
79 79 (r'cffi', r'base85'): (r'pure', r'base85'),
80 80 (r'cffi', r'charencode'): (r'pure', r'charencode'),
81 81 (r'cffi', r'diffhelpers'): (r'pure', r'diffhelpers'),
82 82 (r'cffi', r'parsers'): (r'pure', r'parsers'),
83 83 }
84 84
85 85 def _checkmod(pkgname, modname, mod):
86 86 expected = _cextversions.get((pkgname, modname))
87 87 actual = getattr(mod, r'version', None)
88 88 if actual != expected:
89 89 raise ImportError(r'cannot import module %s.%s '
90 90 r'(expected version: %d, actual: %r)'
91 91 % (pkgname, modname, expected, actual))
92 92
93 93 def importmod(modname):
94 94 """Import module according to policy and check API version"""
95 95 try:
96 96 verpkg, purepkg = _packageprefs[policy]
97 97 except KeyError:
98 98 raise ImportError(r'invalid HGMODULEPOLICY %r' % policy)
99 99 assert verpkg or purepkg
100 100 if verpkg:
101 101 pn, mn = _modredirects.get((verpkg, modname), (verpkg, modname))
102 102 try:
103 103 mod = _importfrom(pn, mn)
104 104 if pn == verpkg:
105 105 _checkmod(pn, mn, mod)
106 106 return mod
107 107 except ImportError:
108 108 if not purepkg:
109 109 raise
110 110 pn, mn = _modredirects.get((purepkg, modname), (purepkg, modname))
111 111 return _importfrom(pn, mn)
@@ -1,1055 +1,1074 b''
1 1 #
2 2 # This is the mercurial setup script.
3 3 #
4 4 # 'python setup.py install', or
5 5 # 'python setup.py --help' for more options
6 6
7 7 import os
8 8
9 9 supportedpy = '~= 2.7'
10 10 if os.environ.get('HGALLOWPYTHON3', ''):
11 11 # Mercurial will never work on Python 3 before 3.5 due to a lack
12 12 # of % formatting on bytestrings, and can't work on 3.6.0 or 3.6.1
13 13 # due to a bug in % formatting in bytestrings.
14 14 #
15 15 # TODO: when we actually work on Python 3, use this string as the
16 16 # actual supportedpy string.
17 17 supportedpy = ','.join([
18 18 '>=2.7',
19 19 '!=3.0.*',
20 20 '!=3.1.*',
21 21 '!=3.2.*',
22 22 '!=3.3.*',
23 23 '!=3.4.*',
24 24 '!=3.6.0',
25 25 '!=3.6.1',
26 26 ])
27 27
28 28 import sys, platform
29 29 if sys.version_info[0] >= 3:
30 30 printf = eval('print')
31 31 libdir_escape = 'unicode_escape'
32 32 def sysstr(s):
33 33 return s.decode('latin-1')
34 34 else:
35 35 libdir_escape = 'string_escape'
36 36 def printf(*args, **kwargs):
37 37 f = kwargs.get('file', sys.stdout)
38 38 end = kwargs.get('end', '\n')
39 39 f.write(b' '.join(args) + end)
40 40 def sysstr(s):
41 41 return s
42 42
43 43 # Attempt to guide users to a modern pip - this means that 2.6 users
44 44 # should have a chance of getting a 4.2 release, and when we ratchet
45 45 # the version requirement forward again hopefully everyone will get
46 46 # something that works for them.
47 47 if sys.version_info < (2, 7, 0, 'final'):
48 48 pip_message = ('This may be due to an out of date pip. '
49 49 'Make sure you have pip >= 9.0.1.')
50 50 try:
51 51 import pip
52 52 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
53 53 if pip_version < (9, 0, 1) :
54 54 pip_message = (
55 55 'Your pip version is out of date, please install '
56 56 'pip >= 9.0.1. pip {} detected.'.format(pip.__version__))
57 57 else:
58 58 # pip is new enough - it must be something else
59 59 pip_message = ''
60 60 except Exception:
61 61 pass
62 62 error = """
63 63 Mercurial does not support Python older than 2.7.
64 64 Python {py} detected.
65 65 {pip}
66 66 """.format(py=sys.version_info, pip=pip_message)
67 67 printf(error, file=sys.stderr)
68 68 sys.exit(1)
69 69
70 70 # We don't yet officially support Python 3. But we want to allow developers to
71 71 # hack on. Detect and disallow running on Python 3 by default. But provide a
72 72 # backdoor to enable working on Python 3.
73 73 if sys.version_info[0] != 2:
74 74 badpython = True
75 75
76 76 # Allow Python 3 from source checkouts.
77 77 if os.path.isdir('.hg'):
78 78 badpython = False
79 79
80 80 if badpython:
81 81 error = """
82 82 Mercurial only supports Python 2.7.
83 83 Python {py} detected.
84 84 Please re-run with Python 2.7.
85 85 """.format(py=sys.version_info)
86 86
87 87 printf(error, file=sys.stderr)
88 88 sys.exit(1)
89 89
90 90 # Solaris Python packaging brain damage
91 91 try:
92 92 import hashlib
93 93 sha = hashlib.sha1()
94 94 except ImportError:
95 95 try:
96 96 import sha
97 97 sha.sha # silence unused import warning
98 98 except ImportError:
99 99 raise SystemExit(
100 100 "Couldn't import standard hashlib (incomplete Python install).")
101 101
102 102 try:
103 103 import zlib
104 104 zlib.compressobj # silence unused import warning
105 105 except ImportError:
106 106 raise SystemExit(
107 107 "Couldn't import standard zlib (incomplete Python install).")
108 108
109 109 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
110 110 isironpython = False
111 111 try:
112 112 isironpython = (platform.python_implementation()
113 113 .lower().find("ironpython") != -1)
114 114 except AttributeError:
115 115 pass
116 116
117 117 if isironpython:
118 118 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
119 119 else:
120 120 try:
121 121 import bz2
122 122 bz2.BZ2Compressor # silence unused import warning
123 123 except ImportError:
124 124 raise SystemExit(
125 125 "Couldn't import standard bz2 (incomplete Python install).")
126 126
127 127 ispypy = "PyPy" in sys.version
128 128
129 129 import ctypes
130 130 import stat, subprocess, time
131 131 import re
132 132 import shutil
133 133 import tempfile
134 134 from distutils import log
135 135 # We have issues with setuptools on some platforms and builders. Until
136 136 # those are resolved, setuptools is opt-in except for platforms where
137 137 # we don't have issues.
138 138 issetuptools = (os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ)
139 139 if issetuptools:
140 140 from setuptools import setup
141 141 else:
142 142 from distutils.core import setup
143 143 from distutils.ccompiler import new_compiler
144 144 from distutils.core import Command, Extension
145 145 from distutils.dist import Distribution
146 146 from distutils.command.build import build
147 147 from distutils.command.build_ext import build_ext
148 148 from distutils.command.build_py import build_py
149 149 from distutils.command.build_scripts import build_scripts
150 150 from distutils.command.install import install
151 151 from distutils.command.install_lib import install_lib
152 152 from distutils.command.install_scripts import install_scripts
153 153 from distutils.spawn import spawn, find_executable
154 154 from distutils import file_util
155 155 from distutils.errors import (
156 156 CCompilerError,
157 157 DistutilsError,
158 158 DistutilsExecError,
159 159 )
160 160 from distutils.sysconfig import get_python_inc, get_config_var
161 161 from distutils.version import StrictVersion
162 162
163 163 def write_if_changed(path, content):
164 164 """Write content to a file iff the content hasn't changed."""
165 165 if os.path.exists(path):
166 166 with open(path, 'rb') as fh:
167 167 current = fh.read()
168 168 else:
169 169 current = b''
170 170
171 171 if current != content:
172 172 with open(path, 'wb') as fh:
173 173 fh.write(content)
174 174
175 175 scripts = ['hg']
176 176 if os.name == 'nt':
177 177 # We remove hg.bat if we are able to build hg.exe.
178 178 scripts.append('contrib/win32/hg.bat')
179 179
180 180 def cancompile(cc, code):
181 181 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
182 182 devnull = oldstderr = None
183 183 try:
184 184 fname = os.path.join(tmpdir, 'testcomp.c')
185 185 f = open(fname, 'w')
186 186 f.write(code)
187 187 f.close()
188 188 # Redirect stderr to /dev/null to hide any error messages
189 189 # from the compiler.
190 190 # This will have to be changed if we ever have to check
191 191 # for a function on Windows.
192 192 devnull = open('/dev/null', 'w')
193 193 oldstderr = os.dup(sys.stderr.fileno())
194 194 os.dup2(devnull.fileno(), sys.stderr.fileno())
195 195 objects = cc.compile([fname], output_dir=tmpdir)
196 196 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
197 197 return True
198 198 except Exception:
199 199 return False
200 200 finally:
201 201 if oldstderr is not None:
202 202 os.dup2(oldstderr, sys.stderr.fileno())
203 203 if devnull is not None:
204 204 devnull.close()
205 205 shutil.rmtree(tmpdir)
206 206
207 207 # simplified version of distutils.ccompiler.CCompiler.has_function
208 208 # that actually removes its temporary files.
209 209 def hasfunction(cc, funcname):
210 210 code = 'int main(void) { %s(); }\n' % funcname
211 211 return cancompile(cc, code)
212 212
213 213 def hasheader(cc, headername):
214 214 code = '#include <%s>\nint main(void) { return 0; }\n' % headername
215 215 return cancompile(cc, code)
216 216
217 217 # py2exe needs to be installed to work
218 218 try:
219 219 import py2exe
220 220 py2exe.Distribution # silence unused import warning
221 221 py2exeloaded = True
222 222 # import py2exe's patched Distribution class
223 223 from distutils.core import Distribution
224 224 except ImportError:
225 225 py2exeloaded = False
226 226
227 227 def runcmd(cmd, env):
228 228 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
229 229 stderr=subprocess.PIPE, env=env)
230 230 out, err = p.communicate()
231 231 return p.returncode, out, err
232 232
233 233 class hgcommand(object):
234 234 def __init__(self, cmd, env):
235 235 self.cmd = cmd
236 236 self.env = env
237 237
238 238 def run(self, args):
239 239 cmd = self.cmd + args
240 240 returncode, out, err = runcmd(cmd, self.env)
241 241 err = filterhgerr(err)
242 242 if err or returncode != 0:
243 243 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
244 244 printf(err, file=sys.stderr)
245 245 return ''
246 246 return out
247 247
248 248 def filterhgerr(err):
249 249 # If root is executing setup.py, but the repository is owned by
250 250 # another user (as in "sudo python setup.py install") we will get
251 251 # trust warnings since the .hg/hgrc file is untrusted. That is
252 252 # fine, we don't want to load it anyway. Python may warn about
253 253 # a missing __init__.py in mercurial/locale, we also ignore that.
254 254 err = [e for e in err.splitlines()
255 255 if (not e.startswith(b'not trusting file')
256 256 and not e.startswith(b'warning: Not importing')
257 257 and not e.startswith(b'obsolete feature not enabled')
258 258 and not e.startswith(b'devel-warn:'))]
259 259 return b'\n'.join(b' ' + e for e in err)
260 260
261 261 def findhg():
262 262 """Try to figure out how we should invoke hg for examining the local
263 263 repository contents.
264 264
265 265 Returns an hgcommand object."""
266 266 # By default, prefer the "hg" command in the user's path. This was
267 267 # presumably the hg command that the user used to create this repository.
268 268 #
269 269 # This repository may require extensions or other settings that would not
270 270 # be enabled by running the hg script directly from this local repository.
271 271 hgenv = os.environ.copy()
272 272 # Use HGPLAIN to disable hgrc settings that would change output formatting,
273 273 # and disable localization for the same reasons.
274 274 hgenv['HGPLAIN'] = '1'
275 275 hgenv['LANGUAGE'] = 'C'
276 276 hgcmd = ['hg']
277 277 # Run a simple "hg log" command just to see if using hg from the user's
278 278 # path works and can successfully interact with this repository.
279 279 check_cmd = ['log', '-r.', '-Ttest']
280 280 try:
281 281 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
282 282 except EnvironmentError:
283 283 retcode = -1
284 284 if retcode == 0 and not filterhgerr(err):
285 285 return hgcommand(hgcmd, hgenv)
286 286
287 287 # Fall back to trying the local hg installation.
288 288 hgenv = localhgenv()
289 289 hgcmd = [sys.executable, 'hg']
290 290 try:
291 291 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
292 292 except EnvironmentError:
293 293 retcode = -1
294 294 if retcode == 0 and not filterhgerr(err):
295 295 return hgcommand(hgcmd, hgenv)
296 296
297 297 raise SystemExit('Unable to find a working hg binary to extract the '
298 298 'version from the repository tags')
299 299
300 300 def localhgenv():
301 301 """Get an environment dictionary to use for invoking or importing
302 302 mercurial from the local repository."""
303 303 # Execute hg out of this directory with a custom environment which takes
304 304 # care to not use any hgrc files and do no localization.
305 305 env = {'HGMODULEPOLICY': 'py',
306 306 'HGRCPATH': '',
307 307 'LANGUAGE': 'C',
308 308 'PATH': ''} # make pypi modules that use os.environ['PATH'] happy
309 309 if 'LD_LIBRARY_PATH' in os.environ:
310 310 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
311 311 if 'SystemRoot' in os.environ:
312 312 # SystemRoot is required by Windows to load various DLLs. See:
313 313 # https://bugs.python.org/issue13524#msg148850
314 314 env['SystemRoot'] = os.environ['SystemRoot']
315 315 return env
316 316
317 317 version = ''
318 318
319 319 if os.path.isdir('.hg'):
320 320 hg = findhg()
321 321 cmd = ['log', '-r', '.', '--template', '{tags}\n']
322 322 numerictags = [t for t in sysstr(hg.run(cmd)).split() if t[0:1].isdigit()]
323 323 hgid = sysstr(hg.run(['id', '-i'])).strip()
324 324 if not hgid:
325 325 # Bail out if hg is having problems interacting with this repository,
326 326 # rather than falling through and producing a bogus version number.
327 327 # Continuing with an invalid version number will break extensions
328 328 # that define minimumhgversion.
329 329 raise SystemExit('Unable to determine hg version from local repository')
330 330 if numerictags: # tag(s) found
331 331 version = numerictags[-1]
332 332 if hgid.endswith('+'): # propagate the dirty status to the tag
333 333 version += '+'
334 334 else: # no tag found
335 335 ltagcmd = ['parents', '--template', '{latesttag}']
336 336 ltag = sysstr(hg.run(ltagcmd))
337 337 changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag]
338 338 changessince = len(hg.run(changessincecmd).splitlines())
339 339 version = '%s+%s-%s' % (ltag, changessince, hgid)
340 340 if version.endswith('+'):
341 341 version += time.strftime('%Y%m%d')
342 342 elif os.path.exists('.hg_archival.txt'):
343 343 kw = dict([[t.strip() for t in l.split(':', 1)]
344 344 for l in open('.hg_archival.txt')])
345 345 if 'tag' in kw:
346 346 version = kw['tag']
347 347 elif 'latesttag' in kw:
348 348 if 'changessincelatesttag' in kw:
349 349 version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw
350 350 else:
351 351 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
352 352 else:
353 353 version = kw.get('node', '')[:12]
354 354
355 355 if version:
356 356 versionb = version
357 357 if not isinstance(versionb, bytes):
358 358 versionb = versionb.encode('ascii')
359 359
360 360 write_if_changed('mercurial/__version__.py', b''.join([
361 361 b'# this file is autogenerated by setup.py\n'
362 362 b'version = "%s"\n' % versionb,
363 363 ]))
364 364
365 365 try:
366 366 oldpolicy = os.environ.get('HGMODULEPOLICY', None)
367 367 os.environ['HGMODULEPOLICY'] = 'py'
368 368 from mercurial import __version__
369 369 version = __version__.version
370 370 except ImportError:
371 371 version = 'unknown'
372 372 finally:
373 373 if oldpolicy is None:
374 374 del os.environ['HGMODULEPOLICY']
375 375 else:
376 376 os.environ['HGMODULEPOLICY'] = oldpolicy
377 377
378 378 class hgbuild(build):
379 379 # Insert hgbuildmo first so that files in mercurial/locale/ are found
380 380 # when build_py is run next.
381 381 sub_commands = [('build_mo', None)] + build.sub_commands
382 382
383 383 class hgbuildmo(build):
384 384
385 385 description = "build translations (.mo files)"
386 386
387 387 def run(self):
388 388 if not find_executable('msgfmt'):
389 389 self.warn("could not find msgfmt executable, no translations "
390 390 "will be built")
391 391 return
392 392
393 393 podir = 'i18n'
394 394 if not os.path.isdir(podir):
395 395 self.warn("could not find %s/ directory" % podir)
396 396 return
397 397
398 398 join = os.path.join
399 399 for po in os.listdir(podir):
400 400 if not po.endswith('.po'):
401 401 continue
402 402 pofile = join(podir, po)
403 403 modir = join('locale', po[:-3], 'LC_MESSAGES')
404 404 mofile = join(modir, 'hg.mo')
405 405 mobuildfile = join('mercurial', mofile)
406 406 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
407 407 if sys.platform != 'sunos5':
408 408 # msgfmt on Solaris does not know about -c
409 409 cmd.append('-c')
410 410 self.mkpath(join('mercurial', modir))
411 411 self.make_file([pofile], mobuildfile, spawn, (cmd,))
412 412
413 413
414 414 class hgdist(Distribution):
415 415 pure = False
416 416 cffi = ispypy
417 417
418 418 global_options = Distribution.global_options + \
419 419 [('pure', None, "use pure (slow) Python "
420 420 "code instead of C extensions"),
421 421 ]
422 422
423 423 def has_ext_modules(self):
424 424 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
425 425 # too late for some cases
426 426 return not self.pure and Distribution.has_ext_modules(self)
427 427
428 428 # This is ugly as a one-liner. So use a variable.
429 429 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
430 430 buildextnegops['no-zstd'] = 'zstd'
431 431
432 432 class hgbuildext(build_ext):
433 433 user_options = build_ext.user_options + [
434 434 ('zstd', None, 'compile zstd bindings [default]'),
435 435 ('no-zstd', None, 'do not compile zstd bindings'),
436 436 ]
437 437
438 438 boolean_options = build_ext.boolean_options + ['zstd']
439 439 negative_opt = buildextnegops
440 440
441 441 def initialize_options(self):
442 442 self.zstd = True
443 443 return build_ext.initialize_options(self)
444 444
445 445 def build_extensions(self):
446 446 # Filter out zstd if disabled via argument.
447 447 if not self.zstd:
448 448 self.extensions = [e for e in self.extensions
449 449 if e.name != 'mercurial.zstd']
450 450
451 451 return build_ext.build_extensions(self)
452 452
453 453 def build_extension(self, ext):
454 454 try:
455 455 build_ext.build_extension(self, ext)
456 456 except CCompilerError:
457 457 if not getattr(ext, 'optional', False):
458 458 raise
459 459 log.warn("Failed to build optional extension '%s' (skipping)",
460 460 ext.name)
461 461
462 462 class hgbuildscripts(build_scripts):
463 463 def run(self):
464 464 if os.name != 'nt' or self.distribution.pure:
465 465 return build_scripts.run(self)
466 466
467 467 exebuilt = False
468 468 try:
469 469 self.run_command('build_hgexe')
470 470 exebuilt = True
471 471 except (DistutilsError, CCompilerError):
472 472 log.warn('failed to build optional hg.exe')
473 473
474 474 if exebuilt:
475 475 # Copying hg.exe to the scripts build directory ensures it is
476 476 # installed by the install_scripts command.
477 477 hgexecommand = self.get_finalized_command('build_hgexe')
478 478 dest = os.path.join(self.build_dir, 'hg.exe')
479 479 self.mkpath(self.build_dir)
480 480 self.copy_file(hgexecommand.hgexepath, dest)
481 481
482 482 # Remove hg.bat because it is redundant with hg.exe.
483 483 self.scripts.remove('contrib/win32/hg.bat')
484 484
485 485 return build_scripts.run(self)
486 486
487 487 class hgbuildpy(build_py):
488 488 def finalize_options(self):
489 489 build_py.finalize_options(self)
490 490
491 491 if self.distribution.pure:
492 492 self.distribution.ext_modules = []
493 493 elif self.distribution.cffi:
494 494 from mercurial.cffi import (
495 495 bdiffbuild,
496 496 mpatchbuild,
497 497 )
498 498 exts = [mpatchbuild.ffi.distutils_extension(),
499 499 bdiffbuild.ffi.distutils_extension()]
500 500 # cffi modules go here
501 501 if sys.platform == 'darwin':
502 502 from mercurial.cffi import osutilbuild
503 503 exts.append(osutilbuild.ffi.distutils_extension())
504 504 self.distribution.ext_modules = exts
505 505 else:
506 506 h = os.path.join(get_python_inc(), 'Python.h')
507 507 if not os.path.exists(h):
508 508 raise SystemExit('Python headers are required to build '
509 509 'Mercurial but weren\'t found in %s' % h)
510 510
511 511 def run(self):
512 512 basepath = os.path.join(self.build_lib, 'mercurial')
513 513 self.mkpath(basepath)
514 514
515 515 if self.distribution.pure:
516 516 modulepolicy = 'py'
517 517 elif self.build_lib == '.':
518 518 # in-place build should run without rebuilding C extensions
519 519 modulepolicy = 'allow'
520 520 else:
521 521 modulepolicy = 'c'
522 522
523 523 content = b''.join([
524 524 b'# this file is autogenerated by setup.py\n',
525 525 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
526 526 ])
527 527 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'),
528 528 content)
529 529
530 530 build_py.run(self)
531 531
532 532 class buildhgextindex(Command):
533 533 description = 'generate prebuilt index of hgext (for frozen package)'
534 534 user_options = []
535 535 _indexfilename = 'hgext/__index__.py'
536 536
537 537 def initialize_options(self):
538 538 pass
539 539
540 540 def finalize_options(self):
541 541 pass
542 542
543 543 def run(self):
544 544 if os.path.exists(self._indexfilename):
545 545 with open(self._indexfilename, 'w') as f:
546 546 f.write('# empty\n')
547 547
548 548 # here no extension enabled, disabled() lists up everything
549 549 code = ('import pprint; from mercurial import extensions; '
550 550 'pprint.pprint(extensions.disabled())')
551 551 returncode, out, err = runcmd([sys.executable, '-c', code],
552 552 localhgenv())
553 553 if err or returncode != 0:
554 554 raise DistutilsExecError(err)
555 555
556 556 with open(self._indexfilename, 'w') as f:
557 557 f.write('# this file is autogenerated by setup.py\n')
558 558 f.write('docs = ')
559 559 f.write(out)
560 560
561 561 class buildhgexe(build_ext):
562 562 description = 'compile hg.exe from mercurial/exewrapper.c'
563 563 user_options = build_ext.user_options + [
564 564 ('long-paths-support', None, 'enable support for long paths on '
565 565 'Windows (off by default and '
566 566 'experimental)'),
567 567 ]
568 568
569 569 LONG_PATHS_MANIFEST = """
570 570 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
571 571 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
572 572 <application>
573 573 <windowsSettings
574 574 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
575 575 <ws2:longPathAware>true</ws2:longPathAware>
576 576 </windowsSettings>
577 577 </application>
578 578 </assembly>"""
579 579
580 580 def initialize_options(self):
581 581 build_ext.initialize_options(self)
582 582 self.long_paths_support = False
583 583
584 584 def build_extensions(self):
585 585 if os.name != 'nt':
586 586 return
587 587 if isinstance(self.compiler, HackedMingw32CCompiler):
588 588 self.compiler.compiler_so = self.compiler.compiler # no -mdll
589 589 self.compiler.dll_libraries = [] # no -lmsrvc90
590 590
591 591 # Different Python installs can have different Python library
592 592 # names. e.g. the official CPython distribution uses pythonXY.dll
593 593 # and MinGW uses libpythonX.Y.dll.
594 594 _kernel32 = ctypes.windll.kernel32
595 595 _kernel32.GetModuleFileNameA.argtypes = [ctypes.c_void_p,
596 596 ctypes.c_void_p,
597 597 ctypes.c_ulong]
598 598 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
599 599 size = 1000
600 600 buf = ctypes.create_string_buffer(size + 1)
601 601 filelen = _kernel32.GetModuleFileNameA(sys.dllhandle, ctypes.byref(buf),
602 602 size)
603 603
604 604 if filelen > 0 and filelen != size:
605 605 dllbasename = os.path.basename(buf.value)
606 606 if not dllbasename.lower().endswith('.dll'):
607 607 raise SystemExit('Python DLL does not end with .dll: %s' %
608 608 dllbasename)
609 609 pythonlib = dllbasename[:-4]
610 610 else:
611 611 log.warn('could not determine Python DLL filename; '
612 612 'assuming pythonXY')
613 613
614 614 hv = sys.hexversion
615 615 pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xff)
616 616
617 617 log.info('using %s as Python library name' % pythonlib)
618 618 with open('mercurial/hgpythonlib.h', 'wb') as f:
619 619 f.write('/* this file is autogenerated by setup.py */\n')
620 620 f.write('#define HGPYTHONLIB "%s"\n' % pythonlib)
621 621 objects = self.compiler.compile(['mercurial/exewrapper.c'],
622 622 output_dir=self.build_temp)
623 623 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
624 624 self.hgtarget = os.path.join(dir, 'hg')
625 625 self.compiler.link_executable(objects, self.hgtarget,
626 626 libraries=[],
627 627 output_dir=self.build_temp)
628 628 if self.long_paths_support:
629 629 self.addlongpathsmanifest()
630 630
631 631 def addlongpathsmanifest(self):
632 632 """Add manifest pieces so that hg.exe understands long paths
633 633
634 634 This is an EXPERIMENTAL feature, use with care.
635 635 To enable long paths support, one needs to do two things:
636 636 - build Mercurial with --long-paths-support option
637 637 - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
638 638 LongPathsEnabled to have value 1.
639 639
640 640 Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
641 641 it happens because Mercurial uses mt.exe circa 2008, which is not
642 642 yet aware of long paths support in the manifest (I think so at least).
643 643 This does not stop mt.exe from embedding/merging the XML properly.
644 644
645 645 Why resource #1 should be used for .exe manifests? I don't know and
646 646 wasn't able to find an explanation for mortals. But it seems to work.
647 647 """
648 648 exefname = self.compiler.executable_filename(self.hgtarget)
649 649 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
650 650 os.close(fdauto)
651 651 with open(manfname, 'w') as f:
652 652 f.write(self.LONG_PATHS_MANIFEST)
653 653 log.info("long paths manifest is written to '%s'" % manfname)
654 654 inputresource = '-inputresource:%s;#1' % exefname
655 655 outputresource = '-outputresource:%s;#1' % exefname
656 656 log.info("running mt.exe to update hg.exe's manifest in-place")
657 657 # supplying both -manifest and -inputresource to mt.exe makes
658 658 # it merge the embedded and supplied manifests in the -outputresource
659 659 self.spawn(['mt.exe', '-nologo', '-manifest', manfname,
660 660 inputresource, outputresource])
661 661 log.info("done updating hg.exe's manifest")
662 662 os.remove(manfname)
663 663
664 664 @property
665 665 def hgexepath(self):
666 666 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
667 667 return os.path.join(self.build_temp, dir, 'hg.exe')
668 668
669 669 class hginstall(install):
670 670
671 671 user_options = install.user_options + [
672 672 ('old-and-unmanageable', None,
673 673 'noop, present for eggless setuptools compat'),
674 674 ('single-version-externally-managed', None,
675 675 'noop, present for eggless setuptools compat'),
676 676 ]
677 677
678 678 # Also helps setuptools not be sad while we refuse to create eggs.
679 679 single_version_externally_managed = True
680 680
681 681 def get_sub_commands(self):
682 682 # Screen out egg related commands to prevent egg generation. But allow
683 683 # mercurial.egg-info generation, since that is part of modern
684 684 # packaging.
685 685 excl = set(['bdist_egg'])
686 686 return filter(lambda x: x not in excl, install.get_sub_commands(self))
687 687
688 688 class hginstalllib(install_lib):
689 689 '''
690 690 This is a specialization of install_lib that replaces the copy_file used
691 691 there so that it supports setting the mode of files after copying them,
692 692 instead of just preserving the mode that the files originally had. If your
693 693 system has a umask of something like 027, preserving the permissions when
694 694 copying will lead to a broken install.
695 695
696 696 Note that just passing keep_permissions=False to copy_file would be
697 697 insufficient, as it might still be applying a umask.
698 698 '''
699 699
700 700 def run(self):
701 701 realcopyfile = file_util.copy_file
702 702 def copyfileandsetmode(*args, **kwargs):
703 703 src, dst = args[0], args[1]
704 704 dst, copied = realcopyfile(*args, **kwargs)
705 705 if copied:
706 706 st = os.stat(src)
707 707 # Persist executable bit (apply it to group and other if user
708 708 # has it)
709 709 if st[stat.ST_MODE] & stat.S_IXUSR:
710 710 setmode = int('0755', 8)
711 711 else:
712 712 setmode = int('0644', 8)
713 713 m = stat.S_IMODE(st[stat.ST_MODE])
714 714 m = (m & ~int('0777', 8)) | setmode
715 715 os.chmod(dst, m)
716 716 file_util.copy_file = copyfileandsetmode
717 717 try:
718 718 install_lib.run(self)
719 719 finally:
720 720 file_util.copy_file = realcopyfile
721 721
722 722 class hginstallscripts(install_scripts):
723 723 '''
724 724 This is a specialization of install_scripts that replaces the @LIBDIR@ with
725 725 the configured directory for modules. If possible, the path is made relative
726 726 to the directory for scripts.
727 727 '''
728 728
729 729 def initialize_options(self):
730 730 install_scripts.initialize_options(self)
731 731
732 732 self.install_lib = None
733 733
734 734 def finalize_options(self):
735 735 install_scripts.finalize_options(self)
736 736 self.set_undefined_options('install',
737 737 ('install_lib', 'install_lib'))
738 738
739 739 def run(self):
740 740 install_scripts.run(self)
741 741
742 742 # It only makes sense to replace @LIBDIR@ with the install path if
743 743 # the install path is known. For wheels, the logic below calculates
744 744 # the libdir to be "../..". This is because the internal layout of a
745 745 # wheel archive looks like:
746 746 #
747 747 # mercurial-3.6.1.data/scripts/hg
748 748 # mercurial/__init__.py
749 749 #
750 750 # When installing wheels, the subdirectories of the "<pkg>.data"
751 751 # directory are translated to system local paths and files therein
752 752 # are copied in place. The mercurial/* files are installed into the
753 753 # site-packages directory. However, the site-packages directory
754 754 # isn't known until wheel install time. This means we have no clue
755 755 # at wheel generation time what the installed site-packages directory
756 756 # will be. And, wheels don't appear to provide the ability to register
757 757 # custom code to run during wheel installation. This all means that
758 758 # we can't reliably set the libdir in wheels: the default behavior
759 759 # of looking in sys.path must do.
760 760
761 761 if (os.path.splitdrive(self.install_dir)[0] !=
762 762 os.path.splitdrive(self.install_lib)[0]):
763 763 # can't make relative paths from one drive to another, so use an
764 764 # absolute path instead
765 765 libdir = self.install_lib
766 766 else:
767 767 common = os.path.commonprefix((self.install_dir, self.install_lib))
768 768 rest = self.install_dir[len(common):]
769 769 uplevel = len([n for n in os.path.split(rest) if n])
770 770
771 771 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
772 772
773 773 for outfile in self.outfiles:
774 774 with open(outfile, 'rb') as fp:
775 775 data = fp.read()
776 776
777 777 # skip binary files
778 778 if b'\0' in data:
779 779 continue
780 780
781 781 # During local installs, the shebang will be rewritten to the final
782 782 # install path. During wheel packaging, the shebang has a special
783 783 # value.
784 784 if data.startswith(b'#!python'):
785 785 log.info('not rewriting @LIBDIR@ in %s because install path '
786 786 'not known' % outfile)
787 787 continue
788 788
789 789 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
790 790 with open(outfile, 'wb') as fp:
791 791 fp.write(data)
792 792
793 793 cmdclass = {'build': hgbuild,
794 794 'build_mo': hgbuildmo,
795 795 'build_ext': hgbuildext,
796 796 'build_py': hgbuildpy,
797 797 'build_scripts': hgbuildscripts,
798 798 'build_hgextindex': buildhgextindex,
799 799 'install': hginstall,
800 800 'install_lib': hginstalllib,
801 801 'install_scripts': hginstallscripts,
802 802 'build_hgexe': buildhgexe,
803 803 }
804 804
805 805 packages = ['mercurial',
806 806 'mercurial.cext',
807 807 'mercurial.cffi',
808 808 'mercurial.hgweb',
809 809 'mercurial.pure',
810 810 'mercurial.thirdparty',
811 811 'mercurial.thirdparty.attr',
812 812 'mercurial.utils',
813 813 'hgext', 'hgext.convert', 'hgext.fsmonitor',
814 814 'hgext.fsmonitor.pywatchman', 'hgext.highlight',
815 815 'hgext.largefiles', 'hgext.lfs', 'hgext.narrow',
816 816 'hgext.zeroconf', 'hgext3rd',
817 817 'hgdemandimport']
818 818
819 819 common_depends = ['mercurial/bitmanipulation.h',
820 820 'mercurial/compat.h',
821 821 'mercurial/cext/util.h']
822 822 common_include_dirs = ['mercurial']
823 823
824 824 osutil_cflags = []
825 825 osutil_ldflags = []
826 826
827 827 # platform specific macros
828 828 for plat, func in [('bsd', 'setproctitle')]:
829 829 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
830 830 osutil_cflags.append('-DHAVE_%s' % func.upper())
831 831
832 832 for plat, macro, code in [
833 833 ('bsd|darwin', 'BSD_STATFS', '''
834 834 #include <sys/param.h>
835 835 #include <sys/mount.h>
836 836 int main() { struct statfs s; return sizeof(s.f_fstypename); }
837 837 '''),
838 838 ('linux', 'LINUX_STATFS', '''
839 839 #include <linux/magic.h>
840 840 #include <sys/vfs.h>
841 841 int main() { struct statfs s; return sizeof(s.f_type); }
842 842 '''),
843 843 ]:
844 844 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
845 845 osutil_cflags.append('-DHAVE_%s' % macro)
846 846
847 847 if sys.platform == 'darwin':
848 848 osutil_ldflags += ['-framework', 'ApplicationServices']
849 849
850 xdiff_srcs = [
851 'mercurial/thirdparty/xdiff/xdiffi.c',
852 'mercurial/thirdparty/xdiff/xemit.c',
853 'mercurial/thirdparty/xdiff/xmerge.c',
854 'mercurial/thirdparty/xdiff/xprepare.c',
855 'mercurial/thirdparty/xdiff/xutils.c',
856 ]
857
858 xdiff_headers = [
859 'mercurial/thirdparty/xdiff/xdiff.h',
860 'mercurial/thirdparty/xdiff/xdiffi.h',
861 'mercurial/thirdparty/xdiff/xemit.h',
862 'mercurial/thirdparty/xdiff/xinclude.h',
863 'mercurial/thirdparty/xdiff/xmacros.h',
864 'mercurial/thirdparty/xdiff/xprepare.h',
865 'mercurial/thirdparty/xdiff/xtypes.h',
866 'mercurial/thirdparty/xdiff/xutils.h',
867 ]
868
850 869 extmodules = [
851 870 Extension('mercurial.cext.base85', ['mercurial/cext/base85.c'],
852 871 include_dirs=common_include_dirs,
853 872 depends=common_depends),
854 873 Extension('mercurial.cext.bdiff', ['mercurial/bdiff.c',
855 'mercurial/cext/bdiff.c'],
874 'mercurial/cext/bdiff.c'] + xdiff_srcs,
856 875 include_dirs=common_include_dirs,
857 depends=common_depends + ['mercurial/bdiff.h']),
876 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers),
858 877 Extension('mercurial.cext.diffhelpers', ['mercurial/cext/diffhelpers.c'],
859 878 include_dirs=common_include_dirs,
860 879 depends=common_depends),
861 880 Extension('mercurial.cext.mpatch', ['mercurial/mpatch.c',
862 881 'mercurial/cext/mpatch.c'],
863 882 include_dirs=common_include_dirs,
864 883 depends=common_depends),
865 884 Extension('mercurial.cext.parsers', ['mercurial/cext/charencode.c',
866 885 'mercurial/cext/dirs.c',
867 886 'mercurial/cext/manifest.c',
868 887 'mercurial/cext/parsers.c',
869 888 'mercurial/cext/pathencode.c',
870 889 'mercurial/cext/revlog.c'],
871 890 include_dirs=common_include_dirs,
872 891 depends=common_depends + ['mercurial/cext/charencode.h']),
873 892 Extension('mercurial.cext.osutil', ['mercurial/cext/osutil.c'],
874 893 include_dirs=common_include_dirs,
875 894 extra_compile_args=osutil_cflags,
876 895 extra_link_args=osutil_ldflags,
877 896 depends=common_depends),
878 897 Extension('hgext.fsmonitor.pywatchman.bser',
879 898 ['hgext/fsmonitor/pywatchman/bser.c']),
880 899 ]
881 900
882 901 sys.path.insert(0, 'contrib/python-zstandard')
883 902 import setup_zstd
884 903 extmodules.append(setup_zstd.get_c_extension(name='mercurial.zstd'))
885 904
886 905 try:
887 906 from distutils import cygwinccompiler
888 907
889 908 # the -mno-cygwin option has been deprecated for years
890 909 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
891 910
892 911 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
893 912 def __init__(self, *args, **kwargs):
894 913 mingw32compilerclass.__init__(self, *args, **kwargs)
895 914 for i in 'compiler compiler_so linker_exe linker_so'.split():
896 915 try:
897 916 getattr(self, i).remove('-mno-cygwin')
898 917 except ValueError:
899 918 pass
900 919
901 920 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
902 921 except ImportError:
903 922 # the cygwinccompiler package is not available on some Python
904 923 # distributions like the ones from the optware project for Synology
905 924 # DiskStation boxes
906 925 class HackedMingw32CCompiler(object):
907 926 pass
908 927
909 928 if os.name == 'nt':
910 929 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
911 930 # extra_link_args to distutils.extensions.Extension() doesn't have any
912 931 # effect.
913 932 from distutils import msvccompiler
914 933
915 934 msvccompilerclass = msvccompiler.MSVCCompiler
916 935
917 936 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
918 937 def initialize(self):
919 938 msvccompilerclass.initialize(self)
920 939 # "warning LNK4197: export 'func' specified multiple times"
921 940 self.ldflags_shared.append('/ignore:4197')
922 941 self.ldflags_shared_debug.append('/ignore:4197')
923 942
924 943 msvccompiler.MSVCCompiler = HackedMSVCCompiler
925 944
926 945 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
927 946 'help/*.txt',
928 947 'help/internals/*.txt',
929 948 'default.d/*.rc',
930 949 'dummycert.pem']}
931 950
932 951 def ordinarypath(p):
933 952 return p and p[0] != '.' and p[-1] != '~'
934 953
935 954 for root in ('templates',):
936 955 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
937 956 curdir = curdir.split(os.sep, 1)[1]
938 957 dirs[:] = filter(ordinarypath, dirs)
939 958 for f in filter(ordinarypath, files):
940 959 f = os.path.join(curdir, f)
941 960 packagedata['mercurial'].append(f)
942 961
943 962 datafiles = []
944 963
945 964 # distutils expects version to be str/unicode. Converting it to
946 965 # unicode on Python 2 still works because it won't contain any
947 966 # non-ascii bytes and will be implicitly converted back to bytes
948 967 # when operated on.
949 968 assert isinstance(version, bytes)
950 969 setupversion = version.decode('ascii')
951 970
952 971 extra = {}
953 972
954 973 if issetuptools:
955 974 extra['python_requires'] = supportedpy
956 975 if py2exeloaded:
957 976 extra['console'] = [
958 977 {'script':'hg',
959 978 'copyright':'Copyright (C) 2005-2018 Matt Mackall and others',
960 979 'product_version':version}]
961 980 # sub command of 'build' because 'py2exe' does not handle sub_commands
962 981 build.sub_commands.insert(0, ('build_hgextindex', None))
963 982 # put dlls in sub directory so that they won't pollute PATH
964 983 extra['zipfile'] = 'lib/library.zip'
965 984
966 985 if os.name == 'nt':
967 986 # Windows binary file versions for exe/dll files must have the
968 987 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
969 988 setupversion = version.split('+', 1)[0]
970 989
971 990 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
972 991 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
973 992 if version:
974 993 version = version[0]
975 994 if sys.version_info[0] == 3:
976 995 version = version.decode('utf-8')
977 996 xcode4 = (version.startswith('Xcode') and
978 997 StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
979 998 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
980 999 else:
981 1000 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
982 1001 # installed, but instead with only command-line tools. Assume
983 1002 # that only happens on >= Lion, thus no PPC support.
984 1003 xcode4 = True
985 1004 xcode51 = False
986 1005
987 1006 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
988 1007 # distutils.sysconfig
989 1008 if xcode4:
990 1009 os.environ['ARCHFLAGS'] = ''
991 1010
992 1011 # XCode 5.1 changes clang such that it now fails to compile if the
993 1012 # -mno-fused-madd flag is passed, but the version of Python shipped with
994 1013 # OS X 10.9 Mavericks includes this flag. This causes problems in all
995 1014 # C extension modules, and a bug has been filed upstream at
996 1015 # http://bugs.python.org/issue21244. We also need to patch this here
997 1016 # so Mercurial can continue to compile in the meantime.
998 1017 if xcode51:
999 1018 cflags = get_config_var('CFLAGS')
1000 1019 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1001 1020 os.environ['CFLAGS'] = (
1002 1021 os.environ.get('CFLAGS', '') + ' -Qunused-arguments')
1003 1022
1004 1023 setup(name='mercurial',
1005 1024 version=setupversion,
1006 1025 author='Matt Mackall and many others',
1007 1026 author_email='mercurial@mercurial-scm.org',
1008 1027 url='https://mercurial-scm.org/',
1009 1028 download_url='https://mercurial-scm.org/release/',
1010 1029 description=('Fast scalable distributed SCM (revision control, version '
1011 1030 'control) system'),
1012 1031 long_description=('Mercurial is a distributed SCM tool written in Python.'
1013 1032 ' It is used by a number of large projects that require'
1014 1033 ' fast, reliable distributed revision control, such as '
1015 1034 'Mozilla.'),
1016 1035 license='GNU GPLv2 or any later version',
1017 1036 classifiers=[
1018 1037 'Development Status :: 6 - Mature',
1019 1038 'Environment :: Console',
1020 1039 'Intended Audience :: Developers',
1021 1040 'Intended Audience :: System Administrators',
1022 1041 'License :: OSI Approved :: GNU General Public License (GPL)',
1023 1042 'Natural Language :: Danish',
1024 1043 'Natural Language :: English',
1025 1044 'Natural Language :: German',
1026 1045 'Natural Language :: Italian',
1027 1046 'Natural Language :: Japanese',
1028 1047 'Natural Language :: Portuguese (Brazilian)',
1029 1048 'Operating System :: Microsoft :: Windows',
1030 1049 'Operating System :: OS Independent',
1031 1050 'Operating System :: POSIX',
1032 1051 'Programming Language :: C',
1033 1052 'Programming Language :: Python',
1034 1053 'Topic :: Software Development :: Version Control',
1035 1054 ],
1036 1055 scripts=scripts,
1037 1056 packages=packages,
1038 1057 ext_modules=extmodules,
1039 1058 data_files=datafiles,
1040 1059 package_data=packagedata,
1041 1060 cmdclass=cmdclass,
1042 1061 distclass=hgdist,
1043 1062 options={'py2exe': {'packages': ['hgdemandimport', 'hgext', 'email',
1044 1063 # implicitly imported per module policy
1045 1064 # (cffi wouldn't be used as a frozen exe)
1046 1065 'mercurial.cext',
1047 1066 #'mercurial.cffi',
1048 1067 'mercurial.pure']},
1049 1068 'bdist_mpkg': {'zipdist': False,
1050 1069 'license': 'COPYING',
1051 1070 'readme': 'contrib/macosx/Readme.html',
1052 1071 'welcome': 'contrib/macosx/Welcome.html',
1053 1072 },
1054 1073 },
1055 1074 **extra)
General Comments 0
You need to be logged in to leave comments. Login now