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