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