##// END OF EJS Templates
bdiff: split bdiff into cpy-aware and cpy-agnostic part
Maciej Fijalkowski -
r29541:9631ff5e default
parent child Browse files
Show More
@@ -0,0 +1,21 b''
1 #ifndef _HG_BDIFF_H_
2 #define _HG_BDIFF_H_
3
4 struct bdiff_line {
5 int hash, n, e;
6 ssize_t len;
7 const char *l;
8 };
9
10 struct bdiff_hunk;
11 struct bdiff_hunk {
12 int a1, a2, b1, b2;
13 struct bdiff_hunk *next;
14 };
15
16 int bdiff_splitlines(const char *a, ssize_t len, struct bdiff_line **lr);
17 int bdiff_diff(struct bdiff_line *a, int an, struct bdiff_line *b, int bn,
18 struct bdiff_hunk *base);
19 void bdiff_freehunks(struct bdiff_hunk *l);
20
21 #endif
@@ -0,0 +1,203 b''
1 /*
2 bdiff.c - efficient binary diff extension for Mercurial
3
4 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5
6 This software may be used and distributed according to the terms of
7 the GNU General Public License, incorporated herein by reference.
8
9 Based roughly on Python difflib
10 */
11
12 #define PY_SSIZE_T_CLEAN
13 #include <Python.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <limits.h>
17
18 #include "bdiff.h"
19 #include "bitmanipulation.h"
20
21
22 static PyObject *blocks(PyObject *self, PyObject *args)
23 {
24 PyObject *sa, *sb, *rl = NULL, *m;
25 struct bdiff_line *a, *b;
26 struct bdiff_hunk l, *h;
27 int an, bn, count, pos = 0;
28
29 l.next = NULL;
30
31 if (!PyArg_ParseTuple(args, "SS:bdiff", &sa, &sb))
32 return NULL;
33
34 an = bdiff_splitlines(PyBytes_AsString(sa), PyBytes_Size(sa), &a);
35 bn = bdiff_splitlines(PyBytes_AsString(sb), PyBytes_Size(sb), &b);
36
37 if (!a || !b)
38 goto nomem;
39
40 count = bdiff_diff(a, an, b, bn, &l);
41 if (count < 0)
42 goto nomem;
43
44 rl = PyList_New(count);
45 if (!rl)
46 goto nomem;
47
48 for (h = l.next; h; h = h->next) {
49 m = Py_BuildValue("iiii", h->a1, h->a2, h->b1, h->b2);
50 PyList_SetItem(rl, pos, m);
51 pos++;
52 }
53
54 nomem:
55 free(a);
56 free(b);
57 bdiff_freehunks(l.next);
58 return rl ? rl : PyErr_NoMemory();
59 }
60
61 static PyObject *bdiff(PyObject *self, PyObject *args)
62 {
63 char *sa, *sb, *rb;
64 PyObject *result = NULL;
65 struct bdiff_line *al, *bl;
66 struct bdiff_hunk l, *h;
67 int an, bn, count;
68 Py_ssize_t len = 0, la, lb;
69 PyThreadState *_save;
70
71 l.next = NULL;
72
73 if (!PyArg_ParseTuple(args, "s#s#:bdiff", &sa, &la, &sb, &lb))
74 return NULL;
75
76 if (la > UINT_MAX || lb > UINT_MAX) {
77 PyErr_SetString(PyExc_ValueError, "bdiff inputs too large");
78 return NULL;
79 }
80
81 _save = PyEval_SaveThread();
82 an = bdiff_splitlines(sa, la, &al);
83 bn = bdiff_splitlines(sb, lb, &bl);
84 if (!al || !bl)
85 goto nomem;
86
87 count = bdiff_diff(al, an, bl, bn, &l);
88 if (count < 0)
89 goto nomem;
90
91 /* calculate length of output */
92 la = lb = 0;
93 for (h = l.next; h; h = h->next) {
94 if (h->a1 != la || h->b1 != lb)
95 len += 12 + bl[h->b1].l - bl[lb].l;
96 la = h->a2;
97 lb = h->b2;
98 }
99 PyEval_RestoreThread(_save);
100 _save = NULL;
101
102 result = PyBytes_FromStringAndSize(NULL, len);
103
104 if (!result)
105 goto nomem;
106
107 /* build binary patch */
108 rb = PyBytes_AsString(result);
109 la = lb = 0;
110
111 for (h = l.next; h; h = h->next) {
112 if (h->a1 != la || h->b1 != lb) {
113 len = bl[h->b1].l - bl[lb].l;
114 putbe32((uint32_t)(al[la].l - al->l), rb);
115 putbe32((uint32_t)(al[h->a1].l - al->l), rb + 4);
116 putbe32((uint32_t)len, rb + 8);
117 memcpy(rb + 12, bl[lb].l, len);
118 rb += 12 + len;
119 }
120 la = h->a2;
121 lb = h->b2;
122 }
123
124 nomem:
125 if (_save)
126 PyEval_RestoreThread(_save);
127 free(al);
128 free(bl);
129 bdiff_freehunks(l.next);
130 return result ? result : PyErr_NoMemory();
131 }
132
133 /*
134 * If allws != 0, remove all whitespace (' ', \t and \r). Otherwise,
135 * reduce whitespace sequences to a single space and trim remaining whitespace
136 * from end of lines.
137 */
138 static PyObject *fixws(PyObject *self, PyObject *args)
139 {
140 PyObject *s, *result = NULL;
141 char allws, c;
142 const char *r;
143 Py_ssize_t i, rlen, wlen = 0;
144 char *w;
145
146 if (!PyArg_ParseTuple(args, "Sb:fixws", &s, &allws))
147 return NULL;
148 r = PyBytes_AsString(s);
149 rlen = PyBytes_Size(s);
150
151 w = (char *)malloc(rlen ? rlen : 1);
152 if (!w)
153 goto nomem;
154
155 for (i = 0; i != rlen; i++) {
156 c = r[i];
157 if (c == ' ' || c == '\t' || c == '\r') {
158 if (!allws && (wlen == 0 || w[wlen - 1] != ' '))
159 w[wlen++] = ' ';
160 } else if (c == '\n' && !allws
161 && wlen > 0 && w[wlen - 1] == ' ') {
162 w[wlen - 1] = '\n';
163 } else {
164 w[wlen++] = c;
165 }
166 }
167
168 result = PyBytes_FromStringAndSize(w, wlen);
169
170 nomem:
171 free(w);
172 return result ? result : PyErr_NoMemory();
173 }
174
175
176 static char mdiff_doc[] = "Efficient binary diff.";
177
178 static PyMethodDef methods[] = {
179 {"bdiff", bdiff, METH_VARARGS, "calculate a binary diff\n"},
180 {"blocks", blocks, METH_VARARGS, "find a list of matching lines\n"},
181 {"fixws", fixws, METH_VARARGS, "normalize diff whitespaces\n"},
182 {NULL, NULL}
183 };
184
185 #ifdef IS_PY3K
186 static struct PyModuleDef bdiff_module = {
187 PyModuleDef_HEAD_INIT,
188 "bdiff",
189 mdiff_doc,
190 -1,
191 methods
192 };
193
194 PyMODINIT_FUNC PyInit_bdiff(void)
195 {
196 return PyModule_Create(&bdiff_module);
197 }
198 #else
199 PyMODINIT_FUNC initbdiff(void)
200 {
201 Py_InitModule3("bdiff", methods, mdiff_doc);
202 }
203 #endif
@@ -1,492 +1,296 b''
1 1 /*
2 2 bdiff.c - efficient binary diff extension for Mercurial
3 3
4 4 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 5
6 6 This software may be used and distributed according to the terms of
7 7 the GNU General Public License, incorporated herein by reference.
8 8
9 9 Based roughly on Python difflib
10 10 */
11 11
12 #define PY_SSIZE_T_CLEAN
13 #include <Python.h>
14 12 #include <stdlib.h>
15 13 #include <string.h>
16 14 #include <limits.h>
17 15
18 16 #include "compat.h"
19 #include "util.h"
20 17 #include "bitmanipulation.h"
21
22 struct bdiff_line {
23 int hash, n, e;
24 ssize_t len;
25 const char *l;
26 };
18 #include "bdiff.h"
27 19
28 20 struct pos {
29 21 int pos, len;
30 22 };
31 23
32 struct bdiff_hunk;
33 struct bdiff_hunk {
34 int a1, a2, b1, b2;
35 struct bdiff_hunk *next;
36 };
37
38 static int bdiff_splitlines(const char *a, ssize_t len, struct bdiff_line **lr)
24 int bdiff_splitlines(const char *a, ssize_t len, struct bdiff_line **lr)
39 25 {
40 26 unsigned hash;
41 27 int i;
42 28 const char *p, *b = a;
43 29 const char * const plast = a + len - 1;
44 30 struct bdiff_line *l;
45 31
46 32 /* count the lines */
47 33 i = 1; /* extra line for sentinel */
48 34 for (p = a; p < a + len; p++)
49 35 if (*p == '\n' || p == plast)
50 36 i++;
51 37
52 38 *lr = l = (struct bdiff_line *)malloc(sizeof(struct bdiff_line) * i);
53 39 if (!l)
54 40 return -1;
55 41
56 42 /* build the line array and calculate hashes */
57 43 hash = 0;
58 44 for (p = a; p < a + len; p++) {
59 45 /* Leonid Yuriev's hash */
60 46 hash = (hash * 1664525) + (unsigned char)*p + 1013904223;
61 47
62 48 if (*p == '\n' || p == plast) {
63 49 l->hash = hash;
64 50 hash = 0;
65 51 l->len = p - b + 1;
66 52 l->l = b;
67 53 l->n = INT_MAX;
68 54 l++;
69 55 b = p + 1;
70 56 }
71 57 }
72 58
73 59 /* set up a sentinel */
74 60 l->hash = 0;
75 61 l->len = 0;
76 62 l->l = a + len;
77 63 return i - 1;
78 64 }
79 65
80 66 static inline int cmp(struct bdiff_line *a, struct bdiff_line *b)
81 67 {
82 68 return a->hash != b->hash || a->len != b->len || memcmp(a->l, b->l, a->len);
83 69 }
84 70
85 71 static int equatelines(struct bdiff_line *a, int an, struct bdiff_line *b,
86 72 int bn)
87 73 {
88 74 int i, j, buckets = 1, t, scale;
89 75 struct pos *h = NULL;
90 76
91 77 /* build a hash table of the next highest power of 2 */
92 78 while (buckets < bn + 1)
93 79 buckets *= 2;
94 80
95 81 /* try to allocate a large hash table to avoid collisions */
96 82 for (scale = 4; scale; scale /= 2) {
97 83 h = (struct pos *)malloc(scale * buckets * sizeof(struct pos));
98 84 if (h)
99 85 break;
100 86 }
101 87
102 88 if (!h)
103 89 return 0;
104 90
105 91 buckets = buckets * scale - 1;
106 92
107 93 /* clear the hash table */
108 94 for (i = 0; i <= buckets; i++) {
109 95 h[i].pos = -1;
110 96 h[i].len = 0;
111 97 }
112 98
113 99 /* add lines to the hash table chains */
114 100 for (i = 0; i < bn; i++) {
115 101 /* find the equivalence class */
116 102 for (j = b[i].hash & buckets; h[j].pos != -1;
117 103 j = (j + 1) & buckets)
118 104 if (!cmp(b + i, b + h[j].pos))
119 105 break;
120 106
121 107 /* add to the head of the equivalence class */
122 108 b[i].n = h[j].pos;
123 109 b[i].e = j;
124 110 h[j].pos = i;
125 111 h[j].len++; /* keep track of popularity */
126 112 }
127 113
128 114 /* compute popularity threshold */
129 115 t = (bn >= 31000) ? bn / 1000 : 1000000 / (bn + 1);
130 116
131 117 /* match items in a to their equivalence class in b */
132 118 for (i = 0; i < an; i++) {
133 119 /* find the equivalence class */
134 120 for (j = a[i].hash & buckets; h[j].pos != -1;
135 121 j = (j + 1) & buckets)
136 122 if (!cmp(a + i, b + h[j].pos))
137 123 break;
138 124
139 125 a[i].e = j; /* use equivalence class for quick compare */
140 126 if (h[j].len <= t)
141 127 a[i].n = h[j].pos; /* point to head of match list */
142 128 else
143 129 a[i].n = -1; /* too popular */
144 130 }
145 131
146 132 /* discard hash tables */
147 133 free(h);
148 134 return 1;
149 135 }
150 136
151 137 static int longest_match(struct bdiff_line *a, struct bdiff_line *b,
152 138 struct pos *pos,
153 139 int a1, int a2, int b1, int b2, int *omi, int *omj)
154 140 {
155 141 int mi = a1, mj = b1, mk = 0, i, j, k, half;
156 142
157 143 /* window our search on large regions to better bound
158 144 worst-case performance. by choosing a window at the end, we
159 145 reduce skipping overhead on the b chains. */
160 146 if (a2 - a1 > 30000)
161 147 a1 = a2 - 30000;
162 148
163 149 half = (a1 + a2) / 2;
164 150
165 151 for (i = a1; i < a2; i++) {
166 152 /* skip all lines in b after the current block */
167 153 for (j = a[i].n; j >= b2; j = b[j].n)
168 154 ;
169 155
170 156 /* loop through all lines match a[i] in b */
171 157 for (; j >= b1; j = b[j].n) {
172 158 /* does this extend an earlier match? */
173 159 for (k = 1; j - k >= b1 && i - k >= a1; k++) {
174 160 /* reached an earlier match? */
175 161 if (pos[j - k].pos == i - k) {
176 162 k += pos[j - k].len;
177 163 break;
178 164 }
179 165 /* previous line mismatch? */
180 166 if (a[i - k].e != b[j - k].e)
181 167 break;
182 168 }
183 169
184 170 pos[j].pos = i;
185 171 pos[j].len = k;
186 172
187 173 /* best match so far? we prefer matches closer
188 174 to the middle to balance recursion */
189 175 if (k > mk || (k == mk && (i <= mi || i < half))) {
190 176 mi = i;
191 177 mj = j;
192 178 mk = k;
193 179 }
194 180 }
195 181 }
196 182
197 183 if (mk) {
198 184 mi = mi - mk + 1;
199 185 mj = mj - mk + 1;
200 186 }
201 187
202 188 /* expand match to include subsequent popular lines */
203 189 while (mi + mk < a2 && mj + mk < b2 &&
204 190 a[mi + mk].e == b[mj + mk].e)
205 191 mk++;
206 192
207 193 *omi = mi;
208 194 *omj = mj;
209 195
210 196 return mk;
211 197 }
212 198
213 199 static struct bdiff_hunk *recurse(struct bdiff_line *a, struct bdiff_line *b,
214 200 struct pos *pos,
215 201 int a1, int a2, int b1, int b2, struct bdiff_hunk *l)
216 202 {
217 203 int i, j, k;
218 204
219 205 while (1) {
220 206 /* find the longest match in this chunk */
221 207 k = longest_match(a, b, pos, a1, a2, b1, b2, &i, &j);
222 208 if (!k)
223 209 return l;
224 210
225 211 /* and recurse on the remaining chunks on either side */
226 212 l = recurse(a, b, pos, a1, i, b1, j, l);
227 213 if (!l)
228 214 return NULL;
229 215
230 216 l->next = (struct bdiff_hunk *)malloc(sizeof(struct bdiff_hunk));
231 217 if (!l->next)
232 218 return NULL;
233 219
234 220 l = l->next;
235 221 l->a1 = i;
236 222 l->a2 = i + k;
237 223 l->b1 = j;
238 224 l->b2 = j + k;
239 225 l->next = NULL;
240 226
241 227 /* tail-recursion didn't happen, so do equivalent iteration */
242 228 a1 = i + k;
243 229 b1 = j + k;
244 230 }
245 231 }
246 232
247 static int bdiff_diff(struct bdiff_line *a, int an, struct bdiff_line *b,
233 int bdiff_diff(struct bdiff_line *a, int an, struct bdiff_line *b,
248 234 int bn, struct bdiff_hunk *base)
249 235 {
250 236 struct bdiff_hunk *curr;
251 237 struct pos *pos;
252 238 int t, count = 0;
253 239
254 240 /* allocate and fill arrays */
255 241 t = equatelines(a, an, b, bn);
256 242 pos = (struct pos *)calloc(bn ? bn : 1, sizeof(struct pos));
257 243
258 244 if (pos && t) {
259 245 /* generate the matching block list */
260 246
261 247 curr = recurse(a, b, pos, 0, an, 0, bn, base);
262 248 if (!curr)
263 249 return -1;
264 250
265 251 /* sentinel end hunk */
266 252 curr->next = (struct bdiff_hunk *)malloc(sizeof(struct bdiff_hunk));
267 253 if (!curr->next)
268 254 return -1;
269 255 curr = curr->next;
270 256 curr->a1 = curr->a2 = an;
271 257 curr->b1 = curr->b2 = bn;
272 258 curr->next = NULL;
273 259 }
274 260
275 261 free(pos);
276 262
277 263 /* normalize the hunk list, try to push each hunk towards the end */
278 264 for (curr = base->next; curr; curr = curr->next) {
279 265 struct bdiff_hunk *next = curr->next;
280 266
281 267 if (!next)
282 268 break;
283 269
284 270 if (curr->a2 == next->a1 || curr->b2 == next->b1)
285 271 while (curr->a2 < an && curr->b2 < bn
286 272 && next->a1 < next->a2
287 273 && next->b1 < next->b2
288 274 && !cmp(a + curr->a2, b + curr->b2)) {
289 275 curr->a2++;
290 276 next->a1++;
291 277 curr->b2++;
292 278 next->b1++;
293 279 }
294 280 }
295 281
296 282 for (curr = base->next; curr; curr = curr->next)
297 283 count++;
298 284 return count;
299 285 }
300 286
301 static void bdiff_freehunks(struct bdiff_hunk *l)
287 void bdiff_freehunks(struct bdiff_hunk *l)
302 288 {
303 289 struct bdiff_hunk *n;
304 290 for (; l; l = n) {
305 291 n = l->next;
306 292 free(l);
307 293 }
308 294 }
309 295
310 static PyObject *blocks(PyObject *self, PyObject *args)
311 {
312 PyObject *sa, *sb, *rl = NULL, *m;
313 struct bdiff_line *a, *b;
314 struct bdiff_hunk l, *h;
315 int an, bn, count, pos = 0;
316 296
317 l.next = NULL;
318
319 if (!PyArg_ParseTuple(args, "SS:bdiff", &sa, &sb))
320 return NULL;
321
322 an = bdiff_splitlines(PyBytes_AsString(sa), PyBytes_Size(sa), &a);
323 bn = bdiff_splitlines(PyBytes_AsString(sb), PyBytes_Size(sb), &b);
324
325 if (!a || !b)
326 goto nomem;
327
328 count = bdiff_diff(a, an, b, bn, &l);
329 if (count < 0)
330 goto nomem;
331
332 rl = PyList_New(count);
333 if (!rl)
334 goto nomem;
335
336 for (h = l.next; h; h = h->next) {
337 m = Py_BuildValue("iiii", h->a1, h->a2, h->b1, h->b2);
338 PyList_SetItem(rl, pos, m);
339 pos++;
340 }
341
342 nomem:
343 free(a);
344 free(b);
345 bdiff_freehunks(l.next);
346 return rl ? rl : PyErr_NoMemory();
347 }
348
349 static PyObject *bdiff(PyObject *self, PyObject *args)
350 {
351 char *sa, *sb, *rb;
352 PyObject *result = NULL;
353 struct bdiff_line *al, *bl;
354 struct bdiff_hunk l, *h;
355 int an, bn, count;
356 Py_ssize_t len = 0, la, lb;
357 PyThreadState *_save;
358
359 l.next = NULL;
360
361 if (!PyArg_ParseTuple(args, "s#s#:bdiff", &sa, &la, &sb, &lb))
362 return NULL;
363
364 if (la > UINT_MAX || lb > UINT_MAX) {
365 PyErr_SetString(PyExc_ValueError, "bdiff inputs too large");
366 return NULL;
367 }
368
369 _save = PyEval_SaveThread();
370 an = bdiff_splitlines(sa, la, &al);
371 bn = bdiff_splitlines(sb, lb, &bl);
372 if (!al || !bl)
373 goto nomem;
374
375 count = bdiff_diff(al, an, bl, bn, &l);
376 if (count < 0)
377 goto nomem;
378
379 /* calculate length of output */
380 la = lb = 0;
381 for (h = l.next; h; h = h->next) {
382 if (h->a1 != la || h->b1 != lb)
383 len += 12 + bl[h->b1].l - bl[lb].l;
384 la = h->a2;
385 lb = h->b2;
386 }
387 PyEval_RestoreThread(_save);
388 _save = NULL;
389
390 result = PyBytes_FromStringAndSize(NULL, len);
391
392 if (!result)
393 goto nomem;
394
395 /* build binary patch */
396 rb = PyBytes_AsString(result);
397 la = lb = 0;
398
399 for (h = l.next; h; h = h->next) {
400 if (h->a1 != la || h->b1 != lb) {
401 len = bl[h->b1].l - bl[lb].l;
402 putbe32((uint32_t)(al[la].l - al->l), rb);
403 putbe32((uint32_t)(al[h->a1].l - al->l), rb + 4);
404 putbe32((uint32_t)len, rb + 8);
405 memcpy(rb + 12, bl[lb].l, len);
406 rb += 12 + len;
407 }
408 la = h->a2;
409 lb = h->b2;
410 }
411
412 nomem:
413 if (_save)
414 PyEval_RestoreThread(_save);
415 free(al);
416 free(bl);
417 bdiff_freehunks(l.next);
418 return result ? result : PyErr_NoMemory();
419 }
420
421 /*
422 * If allws != 0, remove all whitespace (' ', \t and \r). Otherwise,
423 * reduce whitespace sequences to a single space and trim remaining whitespace
424 * from end of lines.
425 */
426 static PyObject *fixws(PyObject *self, PyObject *args)
427 {
428 PyObject *s, *result = NULL;
429 char allws, c;
430 const char *r;
431 Py_ssize_t i, rlen, wlen = 0;
432 char *w;
433
434 if (!PyArg_ParseTuple(args, "Sb:fixws", &s, &allws))
435 return NULL;
436 r = PyBytes_AsString(s);
437 rlen = PyBytes_Size(s);
438
439 w = (char *)malloc(rlen ? rlen : 1);
440 if (!w)
441 goto nomem;
442
443 for (i = 0; i != rlen; i++) {
444 c = r[i];
445 if (c == ' ' || c == '\t' || c == '\r') {
446 if (!allws && (wlen == 0 || w[wlen - 1] != ' '))
447 w[wlen++] = ' ';
448 } else if (c == '\n' && !allws
449 && wlen > 0 && w[wlen - 1] == ' ') {
450 w[wlen - 1] = '\n';
451 } else {
452 w[wlen++] = c;
453 }
454 }
455
456 result = PyBytes_FromStringAndSize(w, wlen);
457
458 nomem:
459 free(w);
460 return result ? result : PyErr_NoMemory();
461 }
462
463
464 static char mdiff_doc[] = "Efficient binary diff.";
465
466 static PyMethodDef methods[] = {
467 {"bdiff", bdiff, METH_VARARGS, "calculate a binary diff\n"},
468 {"blocks", blocks, METH_VARARGS, "find a list of matching lines\n"},
469 {"fixws", fixws, METH_VARARGS, "normalize diff whitespaces\n"},
470 {NULL, NULL}
471 };
472
473 #ifdef IS_PY3K
474 static struct PyModuleDef bdiff_module = {
475 PyModuleDef_HEAD_INIT,
476 "bdiff",
477 mdiff_doc,
478 -1,
479 methods
480 };
481
482 PyMODINIT_FUNC PyInit_bdiff(void)
483 {
484 return PyModule_Create(&bdiff_module);
485 }
486 #else
487 PyMODINIT_FUNC initbdiff(void)
488 {
489 Py_InitModule3("bdiff", methods, mdiff_doc);
490 }
491 #endif
492
@@ -1,712 +1,713 b''
1 1 #
2 2 # This is the mercurial setup script.
3 3 #
4 4 # 'python setup.py install', or
5 5 # 'python setup.py --help' for more options
6 6
7 7 import sys, platform
8 8 if getattr(sys, 'version_info', (0, 0, 0)) < (2, 6, 0, 'final'):
9 9 raise SystemExit("Mercurial requires Python 2.6 or later.")
10 10
11 11 if sys.version_info[0] >= 3:
12 12 printf = eval('print')
13 13 libdir_escape = 'unicode_escape'
14 14 else:
15 15 libdir_escape = 'string_escape'
16 16 def printf(*args, **kwargs):
17 17 f = kwargs.get('file', sys.stdout)
18 18 end = kwargs.get('end', '\n')
19 19 f.write(b' '.join(args) + end)
20 20
21 21 # Solaris Python packaging brain damage
22 22 try:
23 23 import hashlib
24 24 sha = hashlib.sha1()
25 25 except ImportError:
26 26 try:
27 27 import sha
28 28 sha.sha # silence unused import warning
29 29 except ImportError:
30 30 raise SystemExit(
31 31 "Couldn't import standard hashlib (incomplete Python install).")
32 32
33 33 try:
34 34 import zlib
35 35 zlib.compressobj # silence unused import warning
36 36 except ImportError:
37 37 raise SystemExit(
38 38 "Couldn't import standard zlib (incomplete Python install).")
39 39
40 40 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
41 41 isironpython = False
42 42 try:
43 43 isironpython = (platform.python_implementation()
44 44 .lower().find("ironpython") != -1)
45 45 except AttributeError:
46 46 pass
47 47
48 48 if isironpython:
49 49 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
50 50 else:
51 51 try:
52 52 import bz2
53 53 bz2.BZ2Compressor # silence unused import warning
54 54 except ImportError:
55 55 raise SystemExit(
56 56 "Couldn't import standard bz2 (incomplete Python install).")
57 57
58 58 ispypy = "PyPy" in sys.version
59 59
60 60 import ctypes
61 61 import os, stat, subprocess, time
62 62 import re
63 63 import shutil
64 64 import tempfile
65 65 from distutils import log
66 66 if 'FORCE_SETUPTOOLS' in os.environ:
67 67 from setuptools import setup
68 68 else:
69 69 from distutils.core import setup
70 70 from distutils.core import Command, Extension
71 71 from distutils.dist import Distribution
72 72 from distutils.command.build import build
73 73 from distutils.command.build_ext import build_ext
74 74 from distutils.command.build_py import build_py
75 75 from distutils.command.build_scripts import build_scripts
76 76 from distutils.command.install_lib import install_lib
77 77 from distutils.command.install_scripts import install_scripts
78 78 from distutils.spawn import spawn, find_executable
79 79 from distutils import file_util
80 80 from distutils.errors import (
81 81 CCompilerError,
82 82 DistutilsError,
83 83 DistutilsExecError,
84 84 )
85 85 from distutils.sysconfig import get_python_inc, get_config_var
86 86 from distutils.version import StrictVersion
87 87
88 88 scripts = ['hg']
89 89 if os.name == 'nt':
90 90 # We remove hg.bat if we are able to build hg.exe.
91 91 scripts.append('contrib/win32/hg.bat')
92 92
93 93 # simplified version of distutils.ccompiler.CCompiler.has_function
94 94 # that actually removes its temporary files.
95 95 def hasfunction(cc, funcname):
96 96 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
97 97 devnull = oldstderr = None
98 98 try:
99 99 fname = os.path.join(tmpdir, 'funcname.c')
100 100 f = open(fname, 'w')
101 101 f.write('int main(void) {\n')
102 102 f.write(' %s();\n' % funcname)
103 103 f.write('}\n')
104 104 f.close()
105 105 # Redirect stderr to /dev/null to hide any error messages
106 106 # from the compiler.
107 107 # This will have to be changed if we ever have to check
108 108 # for a function on Windows.
109 109 devnull = open('/dev/null', 'w')
110 110 oldstderr = os.dup(sys.stderr.fileno())
111 111 os.dup2(devnull.fileno(), sys.stderr.fileno())
112 112 objects = cc.compile([fname], output_dir=tmpdir)
113 113 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
114 114 return True
115 115 except Exception:
116 116 return False
117 117 finally:
118 118 if oldstderr is not None:
119 119 os.dup2(oldstderr, sys.stderr.fileno())
120 120 if devnull is not None:
121 121 devnull.close()
122 122 shutil.rmtree(tmpdir)
123 123
124 124 # py2exe needs to be installed to work
125 125 try:
126 126 import py2exe
127 127 py2exe.Distribution # silence unused import warning
128 128 py2exeloaded = True
129 129 # import py2exe's patched Distribution class
130 130 from distutils.core import Distribution
131 131 except ImportError:
132 132 py2exeloaded = False
133 133
134 134 def runcmd(cmd, env):
135 135 if (sys.platform == 'plan9'
136 136 and (sys.version_info[0] == 2 and sys.version_info[1] < 7)):
137 137 # subprocess kludge to work around issues in half-baked Python
138 138 # ports, notably bichued/python:
139 139 _, out, err = os.popen3(cmd)
140 140 return str(out), str(err)
141 141 else:
142 142 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
143 143 stderr=subprocess.PIPE, env=env)
144 144 out, err = p.communicate()
145 145 return out, err
146 146
147 147 def runhg(cmd, env):
148 148 out, err = runcmd(cmd, env)
149 149 # If root is executing setup.py, but the repository is owned by
150 150 # another user (as in "sudo python setup.py install") we will get
151 151 # trust warnings since the .hg/hgrc file is untrusted. That is
152 152 # fine, we don't want to load it anyway. Python may warn about
153 153 # a missing __init__.py in mercurial/locale, we also ignore that.
154 154 err = [e for e in err.splitlines()
155 155 if not e.startswith(b'not trusting file') \
156 156 and not e.startswith(b'warning: Not importing') \
157 157 and not e.startswith(b'obsolete feature not enabled')]
158 158 if err:
159 159 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
160 160 printf(b'\n'.join([b' ' + e for e in err]), file=sys.stderr)
161 161 return ''
162 162 return out
163 163
164 164 version = ''
165 165
166 166 # Execute hg out of this directory with a custom environment which takes care
167 167 # to not use any hgrc files and do no localization.
168 168 env = {'HGMODULEPOLICY': 'py',
169 169 'HGRCPATH': '',
170 170 'LANGUAGE': 'C'}
171 171 if 'LD_LIBRARY_PATH' in os.environ:
172 172 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
173 173 if 'SystemRoot' in os.environ:
174 174 # Copy SystemRoot into the custom environment for Python 2.6
175 175 # under Windows. Otherwise, the subprocess will fail with
176 176 # error 0xc0150004. See: http://bugs.python.org/issue3440
177 177 env['SystemRoot'] = os.environ['SystemRoot']
178 178
179 179 if os.path.isdir('.hg'):
180 180 cmd = [sys.executable, 'hg', 'log', '-r', '.', '--template', '{tags}\n']
181 181 numerictags = [t for t in runhg(cmd, env).split() if t[0].isdigit()]
182 182 hgid = runhg([sys.executable, 'hg', 'id', '-i'], env).strip()
183 183 if numerictags: # tag(s) found
184 184 version = numerictags[-1]
185 185 if hgid.endswith('+'): # propagate the dirty status to the tag
186 186 version += '+'
187 187 else: # no tag found
188 188 ltagcmd = [sys.executable, 'hg', 'parents', '--template',
189 189 '{latesttag}']
190 190 ltag = runhg(ltagcmd, env)
191 191 changessincecmd = [sys.executable, 'hg', 'log', '-T', 'x\n', '-r',
192 192 "only(.,'%s')" % ltag]
193 193 changessince = len(runhg(changessincecmd, env).splitlines())
194 194 version = '%s+%s-%s' % (ltag, changessince, hgid)
195 195 if version.endswith('+'):
196 196 version += time.strftime('%Y%m%d')
197 197 elif os.path.exists('.hg_archival.txt'):
198 198 kw = dict([[t.strip() for t in l.split(':', 1)]
199 199 for l in open('.hg_archival.txt')])
200 200 if 'tag' in kw:
201 201 version = kw['tag']
202 202 elif 'latesttag' in kw:
203 203 if 'changessincelatesttag' in kw:
204 204 version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw
205 205 else:
206 206 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
207 207 else:
208 208 version = kw.get('node', '')[:12]
209 209
210 210 if version:
211 211 with open("mercurial/__version__.py", "w") as f:
212 212 f.write('# this file is autogenerated by setup.py\n')
213 213 f.write('version = "%s"\n' % version)
214 214
215 215 try:
216 216 oldpolicy = os.environ.get('HGMODULEPOLICY', None)
217 217 os.environ['HGMODULEPOLICY'] = 'py'
218 218 from mercurial import __version__
219 219 version = __version__.version
220 220 except ImportError:
221 221 version = 'unknown'
222 222 finally:
223 223 if oldpolicy is None:
224 224 del os.environ['HGMODULEPOLICY']
225 225 else:
226 226 os.environ['HGMODULEPOLICY'] = oldpolicy
227 227
228 228 class hgbuild(build):
229 229 # Insert hgbuildmo first so that files in mercurial/locale/ are found
230 230 # when build_py is run next.
231 231 sub_commands = [('build_mo', None)] + build.sub_commands
232 232
233 233 class hgbuildmo(build):
234 234
235 235 description = "build translations (.mo files)"
236 236
237 237 def run(self):
238 238 if not find_executable('msgfmt'):
239 239 self.warn("could not find msgfmt executable, no translations "
240 240 "will be built")
241 241 return
242 242
243 243 podir = 'i18n'
244 244 if not os.path.isdir(podir):
245 245 self.warn("could not find %s/ directory" % podir)
246 246 return
247 247
248 248 join = os.path.join
249 249 for po in os.listdir(podir):
250 250 if not po.endswith('.po'):
251 251 continue
252 252 pofile = join(podir, po)
253 253 modir = join('locale', po[:-3], 'LC_MESSAGES')
254 254 mofile = join(modir, 'hg.mo')
255 255 mobuildfile = join('mercurial', mofile)
256 256 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
257 257 if sys.platform != 'sunos5':
258 258 # msgfmt on Solaris does not know about -c
259 259 cmd.append('-c')
260 260 self.mkpath(join('mercurial', modir))
261 261 self.make_file([pofile], mobuildfile, spawn, (cmd,))
262 262
263 263
264 264 class hgdist(Distribution):
265 265 pure = False
266 266 cffi = ispypy
267 267
268 268 global_options = Distribution.global_options + \
269 269 [('pure', None, "use pure (slow) Python "
270 270 "code instead of C extensions"),
271 271 ]
272 272
273 273 def has_ext_modules(self):
274 274 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
275 275 # too late for some cases
276 276 return not self.pure and Distribution.has_ext_modules(self)
277 277
278 278 class hgbuildext(build_ext):
279 279
280 280 def build_extension(self, ext):
281 281 try:
282 282 build_ext.build_extension(self, ext)
283 283 except CCompilerError:
284 284 if not getattr(ext, 'optional', False):
285 285 raise
286 286 log.warn("Failed to build optional extension '%s' (skipping)",
287 287 ext.name)
288 288
289 289 class hgbuildscripts(build_scripts):
290 290 def run(self):
291 291 if os.name != 'nt' or self.distribution.pure:
292 292 return build_scripts.run(self)
293 293
294 294 exebuilt = False
295 295 try:
296 296 self.run_command('build_hgexe')
297 297 exebuilt = True
298 298 except (DistutilsError, CCompilerError):
299 299 log.warn('failed to build optional hg.exe')
300 300
301 301 if exebuilt:
302 302 # Copying hg.exe to the scripts build directory ensures it is
303 303 # installed by the install_scripts command.
304 304 hgexecommand = self.get_finalized_command('build_hgexe')
305 305 dest = os.path.join(self.build_dir, 'hg.exe')
306 306 self.mkpath(self.build_dir)
307 307 self.copy_file(hgexecommand.hgexepath, dest)
308 308
309 309 # Remove hg.bat because it is redundant with hg.exe.
310 310 self.scripts.remove('contrib/win32/hg.bat')
311 311
312 312 return build_scripts.run(self)
313 313
314 314 class hgbuildpy(build_py):
315 315 def finalize_options(self):
316 316 build_py.finalize_options(self)
317 317
318 318 if self.distribution.pure:
319 319 self.distribution.ext_modules = []
320 320 elif self.distribution.cffi:
321 321 exts = []
322 322 # cffi modules go here
323 323 self.distribution.ext_modules = exts
324 324 else:
325 325 h = os.path.join(get_python_inc(), 'Python.h')
326 326 if not os.path.exists(h):
327 327 raise SystemExit('Python headers are required to build '
328 328 'Mercurial but weren\'t found in %s' % h)
329 329
330 330 def run(self):
331 331 if self.distribution.pure:
332 332 modulepolicy = 'py'
333 333 else:
334 334 modulepolicy = 'c'
335 335 with open("mercurial/__modulepolicy__.py", "w") as f:
336 336 f.write('# this file is autogenerated by setup.py\n')
337 337 f.write('modulepolicy = "%s"\n' % modulepolicy)
338 338
339 339 build_py.run(self)
340 340
341 341 class buildhgextindex(Command):
342 342 description = 'generate prebuilt index of hgext (for frozen package)'
343 343 user_options = []
344 344 _indexfilename = 'hgext/__index__.py'
345 345
346 346 def initialize_options(self):
347 347 pass
348 348
349 349 def finalize_options(self):
350 350 pass
351 351
352 352 def run(self):
353 353 if os.path.exists(self._indexfilename):
354 354 with open(self._indexfilename, 'w') as f:
355 355 f.write('# empty\n')
356 356
357 357 # here no extension enabled, disabled() lists up everything
358 358 code = ('import pprint; from mercurial import extensions; '
359 359 'pprint.pprint(extensions.disabled())')
360 360 out, err = runcmd([sys.executable, '-c', code], env)
361 361 if err:
362 362 raise DistutilsExecError(err)
363 363
364 364 with open(self._indexfilename, 'w') as f:
365 365 f.write('# this file is autogenerated by setup.py\n')
366 366 f.write('docs = ')
367 367 f.write(out)
368 368
369 369 class buildhgexe(build_ext):
370 370 description = 'compile hg.exe from mercurial/exewrapper.c'
371 371
372 372 def build_extensions(self):
373 373 if os.name != 'nt':
374 374 return
375 375 if isinstance(self.compiler, HackedMingw32CCompiler):
376 376 self.compiler.compiler_so = self.compiler.compiler # no -mdll
377 377 self.compiler.dll_libraries = [] # no -lmsrvc90
378 378
379 379 # Different Python installs can have different Python library
380 380 # names. e.g. the official CPython distribution uses pythonXY.dll
381 381 # and MinGW uses libpythonX.Y.dll.
382 382 _kernel32 = ctypes.windll.kernel32
383 383 _kernel32.GetModuleFileNameA.argtypes = [ctypes.c_void_p,
384 384 ctypes.c_void_p,
385 385 ctypes.c_ulong]
386 386 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
387 387 size = 1000
388 388 buf = ctypes.create_string_buffer(size + 1)
389 389 filelen = _kernel32.GetModuleFileNameA(sys.dllhandle, ctypes.byref(buf),
390 390 size)
391 391
392 392 if filelen > 0 and filelen != size:
393 393 dllbasename = os.path.basename(buf.value)
394 394 if not dllbasename.lower().endswith('.dll'):
395 395 raise SystemExit('Python DLL does not end with .dll: %s' %
396 396 dllbasename)
397 397 pythonlib = dllbasename[:-4]
398 398 else:
399 399 log.warn('could not determine Python DLL filename; '
400 400 'assuming pythonXY')
401 401
402 402 hv = sys.hexversion
403 403 pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xff)
404 404
405 405 log.info('using %s as Python library name' % pythonlib)
406 406 with open('mercurial/hgpythonlib.h', 'wb') as f:
407 407 f.write('/* this file is autogenerated by setup.py */\n')
408 408 f.write('#define HGPYTHONLIB "%s"\n' % pythonlib)
409 409 objects = self.compiler.compile(['mercurial/exewrapper.c'],
410 410 output_dir=self.build_temp)
411 411 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
412 412 target = os.path.join(dir, 'hg')
413 413 self.compiler.link_executable(objects, target,
414 414 libraries=[],
415 415 output_dir=self.build_temp)
416 416
417 417 @property
418 418 def hgexepath(self):
419 419 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
420 420 return os.path.join(self.build_temp, dir, 'hg.exe')
421 421
422 422 class hginstalllib(install_lib):
423 423 '''
424 424 This is a specialization of install_lib that replaces the copy_file used
425 425 there so that it supports setting the mode of files after copying them,
426 426 instead of just preserving the mode that the files originally had. If your
427 427 system has a umask of something like 027, preserving the permissions when
428 428 copying will lead to a broken install.
429 429
430 430 Note that just passing keep_permissions=False to copy_file would be
431 431 insufficient, as it might still be applying a umask.
432 432 '''
433 433
434 434 def run(self):
435 435 realcopyfile = file_util.copy_file
436 436 def copyfileandsetmode(*args, **kwargs):
437 437 src, dst = args[0], args[1]
438 438 dst, copied = realcopyfile(*args, **kwargs)
439 439 if copied:
440 440 st = os.stat(src)
441 441 # Persist executable bit (apply it to group and other if user
442 442 # has it)
443 443 if st[stat.ST_MODE] & stat.S_IXUSR:
444 444 setmode = int('0755', 8)
445 445 else:
446 446 setmode = int('0644', 8)
447 447 m = stat.S_IMODE(st[stat.ST_MODE])
448 448 m = (m & ~int('0777', 8)) | setmode
449 449 os.chmod(dst, m)
450 450 file_util.copy_file = copyfileandsetmode
451 451 try:
452 452 install_lib.run(self)
453 453 finally:
454 454 file_util.copy_file = realcopyfile
455 455
456 456 class hginstallscripts(install_scripts):
457 457 '''
458 458 This is a specialization of install_scripts that replaces the @LIBDIR@ with
459 459 the configured directory for modules. If possible, the path is made relative
460 460 to the directory for scripts.
461 461 '''
462 462
463 463 def initialize_options(self):
464 464 install_scripts.initialize_options(self)
465 465
466 466 self.install_lib = None
467 467
468 468 def finalize_options(self):
469 469 install_scripts.finalize_options(self)
470 470 self.set_undefined_options('install',
471 471 ('install_lib', 'install_lib'))
472 472
473 473 def run(self):
474 474 install_scripts.run(self)
475 475
476 476 # It only makes sense to replace @LIBDIR@ with the install path if
477 477 # the install path is known. For wheels, the logic below calculates
478 478 # the libdir to be "../..". This is because the internal layout of a
479 479 # wheel archive looks like:
480 480 #
481 481 # mercurial-3.6.1.data/scripts/hg
482 482 # mercurial/__init__.py
483 483 #
484 484 # When installing wheels, the subdirectories of the "<pkg>.data"
485 485 # directory are translated to system local paths and files therein
486 486 # are copied in place. The mercurial/* files are installed into the
487 487 # site-packages directory. However, the site-packages directory
488 488 # isn't known until wheel install time. This means we have no clue
489 489 # at wheel generation time what the installed site-packages directory
490 490 # will be. And, wheels don't appear to provide the ability to register
491 491 # custom code to run during wheel installation. This all means that
492 492 # we can't reliably set the libdir in wheels: the default behavior
493 493 # of looking in sys.path must do.
494 494
495 495 if (os.path.splitdrive(self.install_dir)[0] !=
496 496 os.path.splitdrive(self.install_lib)[0]):
497 497 # can't make relative paths from one drive to another, so use an
498 498 # absolute path instead
499 499 libdir = self.install_lib
500 500 else:
501 501 common = os.path.commonprefix((self.install_dir, self.install_lib))
502 502 rest = self.install_dir[len(common):]
503 503 uplevel = len([n for n in os.path.split(rest) if n])
504 504
505 505 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
506 506
507 507 for outfile in self.outfiles:
508 508 with open(outfile, 'rb') as fp:
509 509 data = fp.read()
510 510
511 511 # skip binary files
512 512 if b'\0' in data:
513 513 continue
514 514
515 515 # During local installs, the shebang will be rewritten to the final
516 516 # install path. During wheel packaging, the shebang has a special
517 517 # value.
518 518 if data.startswith(b'#!python'):
519 519 log.info('not rewriting @LIBDIR@ in %s because install path '
520 520 'not known' % outfile)
521 521 continue
522 522
523 523 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
524 524 with open(outfile, 'wb') as fp:
525 525 fp.write(data)
526 526
527 527 cmdclass = {'build': hgbuild,
528 528 'build_mo': hgbuildmo,
529 529 'build_ext': hgbuildext,
530 530 'build_py': hgbuildpy,
531 531 'build_scripts': hgbuildscripts,
532 532 'build_hgextindex': buildhgextindex,
533 533 'install_lib': hginstalllib,
534 534 'install_scripts': hginstallscripts,
535 535 'build_hgexe': buildhgexe,
536 536 }
537 537
538 538 packages = ['mercurial', 'mercurial.hgweb', 'mercurial.httpclient',
539 539 'mercurial.pure',
540 540 'hgext', 'hgext.convert', 'hgext.fsmonitor',
541 541 'hgext.fsmonitor.pywatchman', 'hgext.highlight',
542 542 'hgext.largefiles', 'hgext.zeroconf', 'hgext3rd']
543 543
544 544 common_depends = ['mercurial/bitmanipulation.h',
545 545 'mercurial/compat.h',
546 546 'mercurial/util.h']
547 547
548 548 osutil_ldflags = []
549 549
550 550 if sys.platform == 'darwin':
551 551 osutil_ldflags += ['-framework', 'ApplicationServices']
552 552
553 553 extmodules = [
554 554 Extension('mercurial.base85', ['mercurial/base85.c'],
555 555 depends=common_depends),
556 Extension('mercurial.bdiff', ['mercurial/bdiff.c'],
557 depends=common_depends),
556 Extension('mercurial.bdiff', ['mercurial/bdiff.c',
557 'mercurial/bdiff_module.c'],
558 depends=common_depends + ['mercurial/bdiff.h']),
558 559 Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c'],
559 560 depends=common_depends),
560 561 Extension('mercurial.mpatch', ['mercurial/mpatch.c'],
561 562 depends=common_depends),
562 563 Extension('mercurial.parsers', ['mercurial/dirs.c',
563 564 'mercurial/manifest.c',
564 565 'mercurial/parsers.c',
565 566 'mercurial/pathencode.c'],
566 567 depends=common_depends),
567 568 Extension('mercurial.osutil', ['mercurial/osutil.c'],
568 569 extra_link_args=osutil_ldflags,
569 570 depends=common_depends),
570 571 Extension('hgext.fsmonitor.pywatchman.bser',
571 572 ['hgext/fsmonitor/pywatchman/bser.c']),
572 573 ]
573 574
574 575 try:
575 576 from distutils import cygwinccompiler
576 577
577 578 # the -mno-cygwin option has been deprecated for years
578 579 compiler = cygwinccompiler.Mingw32CCompiler
579 580
580 581 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
581 582 def __init__(self, *args, **kwargs):
582 583 compiler.__init__(self, *args, **kwargs)
583 584 for i in 'compiler compiler_so linker_exe linker_so'.split():
584 585 try:
585 586 getattr(self, i).remove('-mno-cygwin')
586 587 except ValueError:
587 588 pass
588 589
589 590 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
590 591 except ImportError:
591 592 # the cygwinccompiler package is not available on some Python
592 593 # distributions like the ones from the optware project for Synology
593 594 # DiskStation boxes
594 595 class HackedMingw32CCompiler(object):
595 596 pass
596 597
597 598 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
598 599 'help/*.txt',
599 600 'help/internals/*.txt',
600 601 'default.d/*.rc',
601 602 'dummycert.pem']}
602 603
603 604 def ordinarypath(p):
604 605 return p and p[0] != '.' and p[-1] != '~'
605 606
606 607 for root in ('templates',):
607 608 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
608 609 curdir = curdir.split(os.sep, 1)[1]
609 610 dirs[:] = filter(ordinarypath, dirs)
610 611 for f in filter(ordinarypath, files):
611 612 f = os.path.join(curdir, f)
612 613 packagedata['mercurial'].append(f)
613 614
614 615 datafiles = []
615 616 setupversion = version
616 617 extra = {}
617 618
618 619 if py2exeloaded:
619 620 extra['console'] = [
620 621 {'script':'hg',
621 622 'copyright':'Copyright (C) 2005-2016 Matt Mackall and others',
622 623 'product_version':version}]
623 624 # sub command of 'build' because 'py2exe' does not handle sub_commands
624 625 build.sub_commands.insert(0, ('build_hgextindex', None))
625 626 # put dlls in sub directory so that they won't pollute PATH
626 627 extra['zipfile'] = 'lib/library.zip'
627 628
628 629 if os.name == 'nt':
629 630 # Windows binary file versions for exe/dll files must have the
630 631 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
631 632 setupversion = version.split('+', 1)[0]
632 633
633 634 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
634 635 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[0].splitlines()
635 636 if version:
636 637 version = version[0]
637 638 if sys.version_info[0] == 3:
638 639 version = version.decode('utf-8')
639 640 xcode4 = (version.startswith('Xcode') and
640 641 StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
641 642 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
642 643 else:
643 644 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
644 645 # installed, but instead with only command-line tools. Assume
645 646 # that only happens on >= Lion, thus no PPC support.
646 647 xcode4 = True
647 648 xcode51 = False
648 649
649 650 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
650 651 # distutils.sysconfig
651 652 if xcode4:
652 653 os.environ['ARCHFLAGS'] = ''
653 654
654 655 # XCode 5.1 changes clang such that it now fails to compile if the
655 656 # -mno-fused-madd flag is passed, but the version of Python shipped with
656 657 # OS X 10.9 Mavericks includes this flag. This causes problems in all
657 658 # C extension modules, and a bug has been filed upstream at
658 659 # http://bugs.python.org/issue21244. We also need to patch this here
659 660 # so Mercurial can continue to compile in the meantime.
660 661 if xcode51:
661 662 cflags = get_config_var('CFLAGS')
662 663 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
663 664 os.environ['CFLAGS'] = (
664 665 os.environ.get('CFLAGS', '') + ' -Qunused-arguments')
665 666
666 667 setup(name='mercurial',
667 668 version=setupversion,
668 669 author='Matt Mackall and many others',
669 670 author_email='mercurial@selenic.com',
670 671 url='https://mercurial-scm.org/',
671 672 download_url='https://mercurial-scm.org/release/',
672 673 description=('Fast scalable distributed SCM (revision control, version '
673 674 'control) system'),
674 675 long_description=('Mercurial is a distributed SCM tool written in Python.'
675 676 ' It is used by a number of large projects that require'
676 677 ' fast, reliable distributed revision control, such as '
677 678 'Mozilla.'),
678 679 license='GNU GPLv2 or any later version',
679 680 classifiers=[
680 681 'Development Status :: 6 - Mature',
681 682 'Environment :: Console',
682 683 'Intended Audience :: Developers',
683 684 'Intended Audience :: System Administrators',
684 685 'License :: OSI Approved :: GNU General Public License (GPL)',
685 686 'Natural Language :: Danish',
686 687 'Natural Language :: English',
687 688 'Natural Language :: German',
688 689 'Natural Language :: Italian',
689 690 'Natural Language :: Japanese',
690 691 'Natural Language :: Portuguese (Brazilian)',
691 692 'Operating System :: Microsoft :: Windows',
692 693 'Operating System :: OS Independent',
693 694 'Operating System :: POSIX',
694 695 'Programming Language :: C',
695 696 'Programming Language :: Python',
696 697 'Topic :: Software Development :: Version Control',
697 698 ],
698 699 scripts=scripts,
699 700 packages=packages,
700 701 ext_modules=extmodules,
701 702 data_files=datafiles,
702 703 package_data=packagedata,
703 704 cmdclass=cmdclass,
704 705 distclass=hgdist,
705 706 options={'py2exe': {'packages': ['hgext', 'email']},
706 707 'bdist_mpkg': {'zipdist': False,
707 708 'license': 'COPYING',
708 709 'readme': 'contrib/macosx/Readme.html',
709 710 'welcome': 'contrib/macosx/Welcome.html',
710 711 },
711 712 },
712 713 **extra)
General Comments 0
You need to be logged in to leave comments. Login now