##// END OF EJS Templates
rust: lower compile error on non-linux platforms to a warning...
Dan Villiom Podlaski Christiansen -
r47081:cabc5e93 default
parent child Browse files
Show More
@@ -1,196 +1,188
1 1 // Copyright 2018-2020 Georges Racinet <georges.racinet@octobus.net>
2 2 // and Mercurial contributors
3 3 //
4 4 // This software may be used and distributed according to the terms of the
5 5 // GNU General Public License version 2 or any later version.
6 6 mod ancestors;
7 7 pub mod dagops;
8 8 pub use ancestors::{AncestorsIterator, LazyAncestors, MissingAncestors};
9 9 mod dirstate;
10 10 pub mod discovery;
11 11 pub mod requirements;
12 12 pub mod testing; // unconditionally built, for use from integration tests
13 13 pub use dirstate::{
14 14 dirs_multiset::{DirsMultiset, DirsMultisetIter},
15 15 dirstate_map::DirstateMap,
16 16 parsers::{pack_dirstate, parse_dirstate, PARENT_SIZE},
17 17 status::{
18 18 status, BadMatch, BadType, DirstateStatus, StatusError, StatusOptions,
19 19 },
20 20 CopyMap, CopyMapIter, DirstateEntry, DirstateParents, EntryState,
21 21 StateMap, StateMapIter,
22 22 };
23 23 pub mod copy_tracing;
24 24 mod filepatterns;
25 25 pub mod matchers;
26 26 pub mod repo;
27 27 pub mod revlog;
28 28 pub use revlog::*;
29 29 pub mod config;
30 30 pub mod operations;
31 31 pub mod utils;
32 32
33 // Remove this to see (potential) non-artificial compile failures. MacOS
34 // *should* compile, but fail to compile tests for example as of 2020-03-06
35 #[cfg(not(target_os = "linux"))]
36 compile_error!(
37 "`hg-core` has only been tested on Linux and will most \
38 likely not behave correctly on other platforms."
39 );
40
41 33 use crate::utils::hg_path::{HgPathBuf, HgPathError};
42 34 pub use filepatterns::{
43 35 parse_pattern_syntax, read_pattern_file, IgnorePattern,
44 36 PatternFileWarning, PatternSyntax,
45 37 };
46 38 use std::collections::HashMap;
47 39 use twox_hash::RandomXxHashBuilder64;
48 40
49 41 /// This is a contract between the `micro-timer` crate and us, to expose
50 42 /// the `log` crate as `crate::log`.
51 43 use log;
52 44
53 45 pub type LineNumber = usize;
54 46
55 47 /// Rust's default hasher is too slow because it tries to prevent collision
56 48 /// attacks. We are not concerned about those: if an ill-minded person has
57 49 /// write access to your repository, you have other issues.
58 50 pub type FastHashMap<K, V> = HashMap<K, V, RandomXxHashBuilder64>;
59 51
60 52 #[derive(Clone, Debug, PartialEq)]
61 53 pub enum DirstateParseError {
62 54 TooLittleData,
63 55 Overflow,
64 56 // TODO refactor to use bytes instead of String
65 57 CorruptedEntry(String),
66 58 Damaged,
67 59 }
68 60
69 61 impl From<std::io::Error> for DirstateParseError {
70 62 fn from(e: std::io::Error) -> Self {
71 63 DirstateParseError::CorruptedEntry(e.to_string())
72 64 }
73 65 }
74 66
75 67 impl ToString for DirstateParseError {
76 68 fn to_string(&self) -> String {
77 69 use crate::DirstateParseError::*;
78 70 match self {
79 71 TooLittleData => "Too little data for dirstate.".to_string(),
80 72 Overflow => "Overflow in dirstate.".to_string(),
81 73 CorruptedEntry(e) => format!("Corrupted entry: {:?}.", e),
82 74 Damaged => "Dirstate appears to be damaged.".to_string(),
83 75 }
84 76 }
85 77 }
86 78
87 79 #[derive(Debug, PartialEq)]
88 80 pub enum DirstatePackError {
89 81 CorruptedEntry(String),
90 82 CorruptedParent,
91 83 BadSize(usize, usize),
92 84 }
93 85
94 86 impl From<std::io::Error> for DirstatePackError {
95 87 fn from(e: std::io::Error) -> Self {
96 88 DirstatePackError::CorruptedEntry(e.to_string())
97 89 }
98 90 }
99 91 #[derive(Debug, PartialEq)]
100 92 pub enum DirstateMapError {
101 93 PathNotFound(HgPathBuf),
102 94 EmptyPath,
103 95 InvalidPath(HgPathError),
104 96 }
105 97
106 98 impl ToString for DirstateMapError {
107 99 fn to_string(&self) -> String {
108 100 match self {
109 101 DirstateMapError::PathNotFound(_) => {
110 102 "expected a value, found none".to_string()
111 103 }
112 104 DirstateMapError::EmptyPath => "Overflow in dirstate.".to_string(),
113 105 DirstateMapError::InvalidPath(e) => e.to_string(),
114 106 }
115 107 }
116 108 }
117 109
118 110 #[derive(Debug)]
119 111 pub enum DirstateError {
120 112 Parse(DirstateParseError),
121 113 Pack(DirstatePackError),
122 114 Map(DirstateMapError),
123 115 IO(std::io::Error),
124 116 }
125 117
126 118 impl From<DirstateParseError> for DirstateError {
127 119 fn from(e: DirstateParseError) -> Self {
128 120 DirstateError::Parse(e)
129 121 }
130 122 }
131 123
132 124 impl From<DirstatePackError> for DirstateError {
133 125 fn from(e: DirstatePackError) -> Self {
134 126 DirstateError::Pack(e)
135 127 }
136 128 }
137 129
138 130 #[derive(Debug)]
139 131 pub enum PatternError {
140 132 Path(HgPathError),
141 133 UnsupportedSyntax(String),
142 134 UnsupportedSyntaxInFile(String, String, usize),
143 135 TooLong(usize),
144 136 IO(std::io::Error),
145 137 /// Needed a pattern that can be turned into a regex but got one that
146 138 /// can't. This should only happen through programmer error.
147 139 NonRegexPattern(IgnorePattern),
148 140 }
149 141
150 142 impl ToString for PatternError {
151 143 fn to_string(&self) -> String {
152 144 match self {
153 145 PatternError::UnsupportedSyntax(syntax) => {
154 146 format!("Unsupported syntax {}", syntax)
155 147 }
156 148 PatternError::UnsupportedSyntaxInFile(syntax, file_path, line) => {
157 149 format!(
158 150 "{}:{}: unsupported syntax {}",
159 151 file_path, line, syntax
160 152 )
161 153 }
162 154 PatternError::TooLong(size) => {
163 155 format!("matcher pattern is too long ({} bytes)", size)
164 156 }
165 157 PatternError::IO(e) => e.to_string(),
166 158 PatternError::Path(e) => e.to_string(),
167 159 PatternError::NonRegexPattern(pattern) => {
168 160 format!("'{:?}' cannot be turned into a regex", pattern)
169 161 }
170 162 }
171 163 }
172 164 }
173 165
174 166 impl From<DirstateMapError> for DirstateError {
175 167 fn from(e: DirstateMapError) -> Self {
176 168 DirstateError::Map(e)
177 169 }
178 170 }
179 171
180 172 impl From<std::io::Error> for DirstateError {
181 173 fn from(e: std::io::Error) -> Self {
182 174 DirstateError::IO(e)
183 175 }
184 176 }
185 177
186 178 impl From<std::io::Error> for PatternError {
187 179 fn from(e: std::io::Error) -> Self {
188 180 PatternError::IO(e)
189 181 }
190 182 }
191 183
192 184 impl From<HgPathError> for PatternError {
193 185 fn from(e: HgPathError) -> Self {
194 186 PatternError::Path(e)
195 187 }
196 188 }
@@ -1,1835 +1,1841
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 import os
7 7
8 8 # Mercurial will never work on Python 3 before 3.5 due to a lack
9 9 # of % formatting on bytestrings, and can't work on 3.6.0 or 3.6.1
10 10 # due to a bug in % formatting in bytestrings.
11 11 # We cannot support Python 3.5.0, 3.5.1, 3.5.2 because of bug in
12 12 # codecs.escape_encode() where it raises SystemError on empty bytestring
13 13 # bug link: https://bugs.python.org/issue25270
14 14 supportedpy = ','.join(
15 15 [
16 16 '>=2.7.4',
17 17 '!=3.0.*',
18 18 '!=3.1.*',
19 19 '!=3.2.*',
20 20 '!=3.3.*',
21 21 '!=3.4.*',
22 22 '!=3.5.0',
23 23 '!=3.5.1',
24 24 '!=3.5.2',
25 25 '!=3.6.0',
26 26 '!=3.6.1',
27 27 ]
28 28 )
29 29
30 30 import sys, platform
31 31 import sysconfig
32 32
33 33 if sys.version_info[0] >= 3:
34 34 printf = eval('print')
35 35 libdir_escape = 'unicode_escape'
36 36
37 37 def sysstr(s):
38 38 return s.decode('latin-1')
39 39
40 40
41 41 else:
42 42 libdir_escape = 'string_escape'
43 43
44 44 def printf(*args, **kwargs):
45 45 f = kwargs.get('file', sys.stdout)
46 46 end = kwargs.get('end', '\n')
47 47 f.write(b' '.join(args) + end)
48 48
49 49 def sysstr(s):
50 50 return s
51 51
52 52
53 53 # Attempt to guide users to a modern pip - this means that 2.6 users
54 54 # should have a chance of getting a 4.2 release, and when we ratchet
55 55 # the version requirement forward again hopefully everyone will get
56 56 # something that works for them.
57 57 if sys.version_info < (2, 7, 4, 'final'):
58 58 pip_message = (
59 59 'This may be due to an out of date pip. '
60 60 'Make sure you have pip >= 9.0.1.'
61 61 )
62 62 try:
63 63 import pip
64 64
65 65 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
66 66 if pip_version < (9, 0, 1):
67 67 pip_message = (
68 68 'Your pip version is out of date, please install '
69 69 'pip >= 9.0.1. pip {} detected.'.format(pip.__version__)
70 70 )
71 71 else:
72 72 # pip is new enough - it must be something else
73 73 pip_message = ''
74 74 except Exception:
75 75 pass
76 76 error = """
77 77 Mercurial does not support Python older than 2.7.4.
78 78 Python {py} detected.
79 79 {pip}
80 80 """.format(
81 81 py=sys.version_info, pip=pip_message
82 82 )
83 83 printf(error, file=sys.stderr)
84 84 sys.exit(1)
85 85
86 86 import ssl
87 87
88 88 try:
89 89 ssl.SSLContext
90 90 except AttributeError:
91 91 error = """
92 92 The `ssl` module does not have the `SSLContext` class. This indicates an old
93 93 Python version which does not support modern security features (which were
94 94 added to Python 2.7 as part of "PEP 466"). Please make sure you have installed
95 95 at least Python 2.7.9 or a Python version with backports of these security
96 96 features.
97 97 """
98 98 printf(error, file=sys.stderr)
99 99 sys.exit(1)
100 100
101 101 # ssl.HAS_TLSv1* are preferred to check support but they were added in Python
102 102 # 3.7. Prior to CPython commit 6e8cda91d92da72800d891b2fc2073ecbc134d98
103 103 # (backported to the 3.7 branch), ssl.PROTOCOL_TLSv1_1 / ssl.PROTOCOL_TLSv1_2
104 104 # were defined only if compiled against a OpenSSL version with TLS 1.1 / 1.2
105 105 # support. At the mentioned commit, they were unconditionally defined.
106 106 _notset = object()
107 107 has_tlsv1_1 = getattr(ssl, 'HAS_TLSv1_1', _notset)
108 108 if has_tlsv1_1 is _notset:
109 109 has_tlsv1_1 = getattr(ssl, 'PROTOCOL_TLSv1_1', _notset) is not _notset
110 110 has_tlsv1_2 = getattr(ssl, 'HAS_TLSv1_2', _notset)
111 111 if has_tlsv1_2 is _notset:
112 112 has_tlsv1_2 = getattr(ssl, 'PROTOCOL_TLSv1_2', _notset) is not _notset
113 113 if not (has_tlsv1_1 or has_tlsv1_2):
114 114 error = """
115 115 The `ssl` module does not advertise support for TLS 1.1 or TLS 1.2.
116 116 Please make sure that your Python installation was compiled against an OpenSSL
117 117 version enabling these features (likely this requires the OpenSSL version to
118 118 be at least 1.0.1).
119 119 """
120 120 printf(error, file=sys.stderr)
121 121 sys.exit(1)
122 122
123 123 if sys.version_info[0] >= 3:
124 124 DYLIB_SUFFIX = sysconfig.get_config_vars()['EXT_SUFFIX']
125 125 else:
126 126 # deprecated in Python 3
127 127 DYLIB_SUFFIX = sysconfig.get_config_vars()['SO']
128 128
129 129 # Solaris Python packaging brain damage
130 130 try:
131 131 import hashlib
132 132
133 133 sha = hashlib.sha1()
134 134 except ImportError:
135 135 try:
136 136 import sha
137 137
138 138 sha.sha # silence unused import warning
139 139 except ImportError:
140 140 raise SystemExit(
141 141 "Couldn't import standard hashlib (incomplete Python install)."
142 142 )
143 143
144 144 try:
145 145 import zlib
146 146
147 147 zlib.compressobj # silence unused import warning
148 148 except ImportError:
149 149 raise SystemExit(
150 150 "Couldn't import standard zlib (incomplete Python install)."
151 151 )
152 152
153 153 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
154 154 isironpython = False
155 155 try:
156 156 isironpython = (
157 157 platform.python_implementation().lower().find("ironpython") != -1
158 158 )
159 159 except AttributeError:
160 160 pass
161 161
162 162 if isironpython:
163 163 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
164 164 else:
165 165 try:
166 166 import bz2
167 167
168 168 bz2.BZ2Compressor # silence unused import warning
169 169 except ImportError:
170 170 raise SystemExit(
171 171 "Couldn't import standard bz2 (incomplete Python install)."
172 172 )
173 173
174 174 ispypy = "PyPy" in sys.version
175 175
176 176 import ctypes
177 177 import errno
178 178 import stat, subprocess, time
179 179 import re
180 180 import shutil
181 181 import tempfile
182 182
183 183 # We have issues with setuptools on some platforms and builders. Until
184 184 # those are resolved, setuptools is opt-in except for platforms where
185 185 # we don't have issues.
186 186 issetuptools = os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ
187 187 if issetuptools:
188 188 from setuptools import setup
189 189 else:
190 190 from distutils.core import setup
191 191 from distutils.ccompiler import new_compiler
192 192 from distutils.core import Command, Extension
193 193 from distutils.dist import Distribution
194 194 from distutils.command.build import build
195 195 from distutils.command.build_ext import build_ext
196 196 from distutils.command.build_py import build_py
197 197 from distutils.command.build_scripts import build_scripts
198 198 from distutils.command.install import install
199 199 from distutils.command.install_lib import install_lib
200 200 from distutils.command.install_scripts import install_scripts
201 201 from distutils import log
202 202 from distutils.spawn import spawn, find_executable
203 203 from distutils import file_util
204 204 from distutils.errors import (
205 205 CCompilerError,
206 206 DistutilsError,
207 207 DistutilsExecError,
208 208 )
209 209 from distutils.sysconfig import get_python_inc, get_config_var
210 210 from distutils.version import StrictVersion
211 211
212 212 # Explain to distutils.StrictVersion how our release candidates are versionned
213 213 StrictVersion.version_re = re.compile(r'^(\d+)\.(\d+)(\.(\d+))?-?(rc(\d+))?$')
214 214
215 215
216 216 def write_if_changed(path, content):
217 217 """Write content to a file iff the content hasn't changed."""
218 218 if os.path.exists(path):
219 219 with open(path, 'rb') as fh:
220 220 current = fh.read()
221 221 else:
222 222 current = b''
223 223
224 224 if current != content:
225 225 with open(path, 'wb') as fh:
226 226 fh.write(content)
227 227
228 228
229 229 scripts = ['hg']
230 230 if os.name == 'nt':
231 231 # We remove hg.bat if we are able to build hg.exe.
232 232 scripts.append('contrib/win32/hg.bat')
233 233
234 234
235 235 def cancompile(cc, code):
236 236 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
237 237 devnull = oldstderr = None
238 238 try:
239 239 fname = os.path.join(tmpdir, 'testcomp.c')
240 240 f = open(fname, 'w')
241 241 f.write(code)
242 242 f.close()
243 243 # Redirect stderr to /dev/null to hide any error messages
244 244 # from the compiler.
245 245 # This will have to be changed if we ever have to check
246 246 # for a function on Windows.
247 247 devnull = open('/dev/null', 'w')
248 248 oldstderr = os.dup(sys.stderr.fileno())
249 249 os.dup2(devnull.fileno(), sys.stderr.fileno())
250 250 objects = cc.compile([fname], output_dir=tmpdir)
251 251 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
252 252 return True
253 253 except Exception:
254 254 return False
255 255 finally:
256 256 if oldstderr is not None:
257 257 os.dup2(oldstderr, sys.stderr.fileno())
258 258 if devnull is not None:
259 259 devnull.close()
260 260 shutil.rmtree(tmpdir)
261 261
262 262
263 263 # simplified version of distutils.ccompiler.CCompiler.has_function
264 264 # that actually removes its temporary files.
265 265 def hasfunction(cc, funcname):
266 266 code = 'int main(void) { %s(); }\n' % funcname
267 267 return cancompile(cc, code)
268 268
269 269
270 270 def hasheader(cc, headername):
271 271 code = '#include <%s>\nint main(void) { return 0; }\n' % headername
272 272 return cancompile(cc, code)
273 273
274 274
275 275 # py2exe needs to be installed to work
276 276 try:
277 277 import py2exe
278 278
279 279 py2exe.Distribution # silence unused import warning
280 280 py2exeloaded = True
281 281 # import py2exe's patched Distribution class
282 282 from distutils.core import Distribution
283 283 except ImportError:
284 284 py2exeloaded = False
285 285
286 286
287 287 def runcmd(cmd, env, cwd=None):
288 288 p = subprocess.Popen(
289 289 cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, cwd=cwd
290 290 )
291 291 out, err = p.communicate()
292 292 return p.returncode, out, err
293 293
294 294
295 295 class hgcommand(object):
296 296 def __init__(self, cmd, env):
297 297 self.cmd = cmd
298 298 self.env = env
299 299
300 300 def run(self, args):
301 301 cmd = self.cmd + args
302 302 returncode, out, err = runcmd(cmd, self.env)
303 303 err = filterhgerr(err)
304 304 if err or returncode != 0:
305 305 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
306 306 printf(err, file=sys.stderr)
307 307 return b''
308 308 return out
309 309
310 310
311 311 def filterhgerr(err):
312 312 # If root is executing setup.py, but the repository is owned by
313 313 # another user (as in "sudo python setup.py install") we will get
314 314 # trust warnings since the .hg/hgrc file is untrusted. That is
315 315 # fine, we don't want to load it anyway. Python may warn about
316 316 # a missing __init__.py in mercurial/locale, we also ignore that.
317 317 err = [
318 318 e
319 319 for e in err.splitlines()
320 320 if (
321 321 not e.startswith(b'not trusting file')
322 322 and not e.startswith(b'warning: Not importing')
323 323 and not e.startswith(b'obsolete feature not enabled')
324 324 and not e.startswith(b'*** failed to import extension')
325 325 and not e.startswith(b'devel-warn:')
326 326 and not (
327 327 e.startswith(b'(third party extension')
328 328 and e.endswith(b'or newer of Mercurial; disabling)')
329 329 )
330 330 )
331 331 ]
332 332 return b'\n'.join(b' ' + e for e in err)
333 333
334 334
335 335 def findhg():
336 336 """Try to figure out how we should invoke hg for examining the local
337 337 repository contents.
338 338
339 339 Returns an hgcommand object."""
340 340 # By default, prefer the "hg" command in the user's path. This was
341 341 # presumably the hg command that the user used to create this repository.
342 342 #
343 343 # This repository may require extensions or other settings that would not
344 344 # be enabled by running the hg script directly from this local repository.
345 345 hgenv = os.environ.copy()
346 346 # Use HGPLAIN to disable hgrc settings that would change output formatting,
347 347 # and disable localization for the same reasons.
348 348 hgenv['HGPLAIN'] = '1'
349 349 hgenv['LANGUAGE'] = 'C'
350 350 hgcmd = ['hg']
351 351 # Run a simple "hg log" command just to see if using hg from the user's
352 352 # path works and can successfully interact with this repository. Windows
353 353 # gives precedence to hg.exe in the current directory, so fall back to the
354 354 # python invocation of local hg, where pythonXY.dll can always be found.
355 355 check_cmd = ['log', '-r.', '-Ttest']
356 356 if os.name != 'nt' or not os.path.exists("hg.exe"):
357 357 try:
358 358 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
359 359 except EnvironmentError:
360 360 retcode = -1
361 361 if retcode == 0 and not filterhgerr(err):
362 362 return hgcommand(hgcmd, hgenv)
363 363
364 364 # Fall back to trying the local hg installation.
365 365 hgenv = localhgenv()
366 366 hgcmd = [sys.executable, 'hg']
367 367 try:
368 368 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
369 369 except EnvironmentError:
370 370 retcode = -1
371 371 if retcode == 0 and not filterhgerr(err):
372 372 return hgcommand(hgcmd, hgenv)
373 373
374 374 raise SystemExit(
375 375 'Unable to find a working hg binary to extract the '
376 376 'version from the repository tags'
377 377 )
378 378
379 379
380 380 def localhgenv():
381 381 """Get an environment dictionary to use for invoking or importing
382 382 mercurial from the local repository."""
383 383 # Execute hg out of this directory with a custom environment which takes
384 384 # care to not use any hgrc files and do no localization.
385 385 env = {
386 386 'HGMODULEPOLICY': 'py',
387 387 'HGRCPATH': '',
388 388 'LANGUAGE': 'C',
389 389 'PATH': '',
390 390 } # make pypi modules that use os.environ['PATH'] happy
391 391 if 'LD_LIBRARY_PATH' in os.environ:
392 392 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
393 393 if 'SystemRoot' in os.environ:
394 394 # SystemRoot is required by Windows to load various DLLs. See:
395 395 # https://bugs.python.org/issue13524#msg148850
396 396 env['SystemRoot'] = os.environ['SystemRoot']
397 397 return env
398 398
399 399
400 400 version = ''
401 401
402 402 if os.path.isdir('.hg'):
403 403 hg = findhg()
404 404 cmd = ['log', '-r', '.', '--template', '{tags}\n']
405 405 numerictags = [t for t in sysstr(hg.run(cmd)).split() if t[0:1].isdigit()]
406 406 hgid = sysstr(hg.run(['id', '-i'])).strip()
407 407 if not hgid:
408 408 # Bail out if hg is having problems interacting with this repository,
409 409 # rather than falling through and producing a bogus version number.
410 410 # Continuing with an invalid version number will break extensions
411 411 # that define minimumhgversion.
412 412 raise SystemExit('Unable to determine hg version from local repository')
413 413 if numerictags: # tag(s) found
414 414 version = numerictags[-1]
415 415 if hgid.endswith('+'): # propagate the dirty status to the tag
416 416 version += '+'
417 417 else: # no tag found
418 418 ltagcmd = ['parents', '--template', '{latesttag}']
419 419 ltag = sysstr(hg.run(ltagcmd))
420 420 changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag]
421 421 changessince = len(hg.run(changessincecmd).splitlines())
422 422 version = '%s+%s-%s' % (ltag, changessince, hgid)
423 423 if version.endswith('+'):
424 424 version += time.strftime('%Y%m%d')
425 425 elif os.path.exists('.hg_archival.txt'):
426 426 kw = dict(
427 427 [[t.strip() for t in l.split(':', 1)] for l in open('.hg_archival.txt')]
428 428 )
429 429 if 'tag' in kw:
430 430 version = kw['tag']
431 431 elif 'latesttag' in kw:
432 432 if 'changessincelatesttag' in kw:
433 433 version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw
434 434 else:
435 435 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
436 436 else:
437 437 version = kw.get('node', '')[:12]
438 438
439 439 if version:
440 440 versionb = version
441 441 if not isinstance(versionb, bytes):
442 442 versionb = versionb.encode('ascii')
443 443
444 444 write_if_changed(
445 445 'mercurial/__version__.py',
446 446 b''.join(
447 447 [
448 448 b'# this file is autogenerated by setup.py\n'
449 449 b'version = b"%s"\n' % versionb,
450 450 ]
451 451 ),
452 452 )
453 453
454 454 try:
455 455 oldpolicy = os.environ.get('HGMODULEPOLICY', None)
456 456 os.environ['HGMODULEPOLICY'] = 'py'
457 457 from mercurial import __version__
458 458
459 459 version = __version__.version
460 460 except ImportError:
461 461 version = b'unknown'
462 462 finally:
463 463 if oldpolicy is None:
464 464 del os.environ['HGMODULEPOLICY']
465 465 else:
466 466 os.environ['HGMODULEPOLICY'] = oldpolicy
467 467
468 468
469 469 class hgbuild(build):
470 470 # Insert hgbuildmo first so that files in mercurial/locale/ are found
471 471 # when build_py is run next.
472 472 sub_commands = [('build_mo', None)] + build.sub_commands
473 473
474 474
475 475 class hgbuildmo(build):
476 476
477 477 description = "build translations (.mo files)"
478 478
479 479 def run(self):
480 480 if not find_executable('msgfmt'):
481 481 self.warn(
482 482 "could not find msgfmt executable, no translations "
483 483 "will be built"
484 484 )
485 485 return
486 486
487 487 podir = 'i18n'
488 488 if not os.path.isdir(podir):
489 489 self.warn("could not find %s/ directory" % podir)
490 490 return
491 491
492 492 join = os.path.join
493 493 for po in os.listdir(podir):
494 494 if not po.endswith('.po'):
495 495 continue
496 496 pofile = join(podir, po)
497 497 modir = join('locale', po[:-3], 'LC_MESSAGES')
498 498 mofile = join(modir, 'hg.mo')
499 499 mobuildfile = join('mercurial', mofile)
500 500 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
501 501 if sys.platform != 'sunos5':
502 502 # msgfmt on Solaris does not know about -c
503 503 cmd.append('-c')
504 504 self.mkpath(join('mercurial', modir))
505 505 self.make_file([pofile], mobuildfile, spawn, (cmd,))
506 506
507 507
508 508 class hgdist(Distribution):
509 509 pure = False
510 510 rust = False
511 511 no_rust = False
512 512 cffi = ispypy
513 513
514 514 global_options = Distribution.global_options + [
515 515 ('pure', None, "use pure (slow) Python code instead of C extensions"),
516 516 ('rust', None, "use Rust extensions additionally to C extensions"),
517 517 (
518 518 'no-rust',
519 519 None,
520 520 "do not use Rust extensions additionally to C extensions",
521 521 ),
522 522 ]
523 523
524 524 negative_opt = Distribution.negative_opt.copy()
525 525 boolean_options = ['pure', 'rust', 'no-rust']
526 526 negative_opt['no-rust'] = 'rust'
527 527
528 528 def _set_command_options(self, command_obj, option_dict=None):
529 529 # Not all distutils versions in the wild have boolean_options.
530 530 # This should be cleaned up when we're Python 3 only.
531 531 command_obj.boolean_options = (
532 532 getattr(command_obj, 'boolean_options', []) + self.boolean_options
533 533 )
534 534 return Distribution._set_command_options(
535 535 self, command_obj, option_dict=option_dict
536 536 )
537 537
538 538 def parse_command_line(self):
539 539 ret = Distribution.parse_command_line(self)
540 540 if not (self.rust or self.no_rust):
541 541 hgrustext = os.environ.get('HGWITHRUSTEXT')
542 542 # TODO record it for proper rebuild upon changes
543 543 # (see mercurial/__modulepolicy__.py)
544 544 if hgrustext != 'cpython' and hgrustext is not None:
545 545 if hgrustext:
546 546 msg = 'unkown HGWITHRUSTEXT value: %s' % hgrustext
547 547 printf(msg, file=sys.stderr)
548 548 hgrustext = None
549 549 self.rust = hgrustext is not None
550 550 self.no_rust = not self.rust
551 551 return ret
552 552
553 553 def has_ext_modules(self):
554 554 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
555 555 # too late for some cases
556 556 return not self.pure and Distribution.has_ext_modules(self)
557 557
558 558
559 559 # This is ugly as a one-liner. So use a variable.
560 560 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
561 561 buildextnegops['no-zstd'] = 'zstd'
562 562 buildextnegops['no-rust'] = 'rust'
563 563
564 564
565 565 class hgbuildext(build_ext):
566 566 user_options = build_ext.user_options + [
567 567 ('zstd', None, 'compile zstd bindings [default]'),
568 568 ('no-zstd', None, 'do not compile zstd bindings'),
569 569 (
570 570 'rust',
571 571 None,
572 572 'compile Rust extensions if they are in use '
573 573 '(requires Cargo) [default]',
574 574 ),
575 575 ('no-rust', None, 'do not compile Rust extensions'),
576 576 ]
577 577
578 578 boolean_options = build_ext.boolean_options + ['zstd', 'rust']
579 579 negative_opt = buildextnegops
580 580
581 581 def initialize_options(self):
582 582 self.zstd = True
583 583 self.rust = True
584 584
585 585 return build_ext.initialize_options(self)
586 586
587 587 def finalize_options(self):
588 588 # Unless overridden by the end user, build extensions in parallel.
589 589 # Only influences behavior on Python 3.5+.
590 590 if getattr(self, 'parallel', None) is None:
591 591 self.parallel = True
592 592
593 593 return build_ext.finalize_options(self)
594 594
595 595 def build_extensions(self):
596 596 ruststandalones = [
597 597 e for e in self.extensions if isinstance(e, RustStandaloneExtension)
598 598 ]
599 599 self.extensions = [
600 600 e for e in self.extensions if e not in ruststandalones
601 601 ]
602 602 # Filter out zstd if disabled via argument.
603 603 if not self.zstd:
604 604 self.extensions = [
605 605 e for e in self.extensions if e.name != 'mercurial.zstd'
606 606 ]
607 607
608 608 # Build Rust standalon extensions if it'll be used
609 609 # and its build is not explictely disabled (for external build
610 610 # as Linux distributions would do)
611 611 if self.distribution.rust and self.rust:
612 if not sys.platform.startswith('linux'):
613 self.warn(
614 "rust extensions have only been tested on Linux "
615 "and may not behave correctly on other platforms"
616 )
617
612 618 for rustext in ruststandalones:
613 619 rustext.build('' if self.inplace else self.build_lib)
614 620
615 621 return build_ext.build_extensions(self)
616 622
617 623 def build_extension(self, ext):
618 624 if (
619 625 self.distribution.rust
620 626 and self.rust
621 627 and isinstance(ext, RustExtension)
622 628 ):
623 629 ext.rustbuild()
624 630 try:
625 631 build_ext.build_extension(self, ext)
626 632 except CCompilerError:
627 633 if not getattr(ext, 'optional', False):
628 634 raise
629 635 log.warn(
630 636 "Failed to build optional extension '%s' (skipping)", ext.name
631 637 )
632 638
633 639
634 640 class hgbuildscripts(build_scripts):
635 641 def run(self):
636 642 if os.name != 'nt' or self.distribution.pure:
637 643 return build_scripts.run(self)
638 644
639 645 exebuilt = False
640 646 try:
641 647 self.run_command('build_hgexe')
642 648 exebuilt = True
643 649 except (DistutilsError, CCompilerError):
644 650 log.warn('failed to build optional hg.exe')
645 651
646 652 if exebuilt:
647 653 # Copying hg.exe to the scripts build directory ensures it is
648 654 # installed by the install_scripts command.
649 655 hgexecommand = self.get_finalized_command('build_hgexe')
650 656 dest = os.path.join(self.build_dir, 'hg.exe')
651 657 self.mkpath(self.build_dir)
652 658 self.copy_file(hgexecommand.hgexepath, dest)
653 659
654 660 # Remove hg.bat because it is redundant with hg.exe.
655 661 self.scripts.remove('contrib/win32/hg.bat')
656 662
657 663 return build_scripts.run(self)
658 664
659 665
660 666 class hgbuildpy(build_py):
661 667 def finalize_options(self):
662 668 build_py.finalize_options(self)
663 669
664 670 if self.distribution.pure:
665 671 self.distribution.ext_modules = []
666 672 elif self.distribution.cffi:
667 673 from mercurial.cffi import (
668 674 bdiffbuild,
669 675 mpatchbuild,
670 676 )
671 677
672 678 exts = [
673 679 mpatchbuild.ffi.distutils_extension(),
674 680 bdiffbuild.ffi.distutils_extension(),
675 681 ]
676 682 # cffi modules go here
677 683 if sys.platform == 'darwin':
678 684 from mercurial.cffi import osutilbuild
679 685
680 686 exts.append(osutilbuild.ffi.distutils_extension())
681 687 self.distribution.ext_modules = exts
682 688 else:
683 689 h = os.path.join(get_python_inc(), 'Python.h')
684 690 if not os.path.exists(h):
685 691 raise SystemExit(
686 692 'Python headers are required to build '
687 693 'Mercurial but weren\'t found in %s' % h
688 694 )
689 695
690 696 def run(self):
691 697 basepath = os.path.join(self.build_lib, 'mercurial')
692 698 self.mkpath(basepath)
693 699
694 700 rust = self.distribution.rust
695 701 if self.distribution.pure:
696 702 modulepolicy = 'py'
697 703 elif self.build_lib == '.':
698 704 # in-place build should run without rebuilding and Rust extensions
699 705 modulepolicy = 'rust+c-allow' if rust else 'allow'
700 706 else:
701 707 modulepolicy = 'rust+c' if rust else 'c'
702 708
703 709 content = b''.join(
704 710 [
705 711 b'# this file is autogenerated by setup.py\n',
706 712 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
707 713 ]
708 714 )
709 715 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'), content)
710 716
711 717 build_py.run(self)
712 718
713 719
714 720 class buildhgextindex(Command):
715 721 description = 'generate prebuilt index of hgext (for frozen package)'
716 722 user_options = []
717 723 _indexfilename = 'hgext/__index__.py'
718 724
719 725 def initialize_options(self):
720 726 pass
721 727
722 728 def finalize_options(self):
723 729 pass
724 730
725 731 def run(self):
726 732 if os.path.exists(self._indexfilename):
727 733 with open(self._indexfilename, 'w') as f:
728 734 f.write('# empty\n')
729 735
730 736 # here no extension enabled, disabled() lists up everything
731 737 code = (
732 738 'import pprint; from mercurial import extensions; '
733 739 'ext = extensions.disabled();'
734 740 'ext.pop("__index__", None);'
735 741 'pprint.pprint(ext)'
736 742 )
737 743 returncode, out, err = runcmd(
738 744 [sys.executable, '-c', code], localhgenv()
739 745 )
740 746 if err or returncode != 0:
741 747 raise DistutilsExecError(err)
742 748
743 749 with open(self._indexfilename, 'wb') as f:
744 750 f.write(b'# this file is autogenerated by setup.py\n')
745 751 f.write(b'docs = ')
746 752 f.write(out)
747 753
748 754
749 755 class buildhgexe(build_ext):
750 756 description = 'compile hg.exe from mercurial/exewrapper.c'
751 757 user_options = build_ext.user_options + [
752 758 (
753 759 'long-paths-support',
754 760 None,
755 761 'enable support for long paths on '
756 762 'Windows (off by default and '
757 763 'experimental)',
758 764 ),
759 765 ]
760 766
761 767 LONG_PATHS_MANIFEST = """
762 768 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
763 769 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
764 770 <application>
765 771 <windowsSettings
766 772 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
767 773 <ws2:longPathAware>true</ws2:longPathAware>
768 774 </windowsSettings>
769 775 </application>
770 776 </assembly>"""
771 777
772 778 def initialize_options(self):
773 779 build_ext.initialize_options(self)
774 780 self.long_paths_support = False
775 781
776 782 def build_extensions(self):
777 783 if os.name != 'nt':
778 784 return
779 785 if isinstance(self.compiler, HackedMingw32CCompiler):
780 786 self.compiler.compiler_so = self.compiler.compiler # no -mdll
781 787 self.compiler.dll_libraries = [] # no -lmsrvc90
782 788
783 789 pythonlib = None
784 790
785 791 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
786 792 self.hgtarget = os.path.join(dir, 'hg')
787 793
788 794 if getattr(sys, 'dllhandle', None):
789 795 # Different Python installs can have different Python library
790 796 # names. e.g. the official CPython distribution uses pythonXY.dll
791 797 # and MinGW uses libpythonX.Y.dll.
792 798 _kernel32 = ctypes.windll.kernel32
793 799 _kernel32.GetModuleFileNameA.argtypes = [
794 800 ctypes.c_void_p,
795 801 ctypes.c_void_p,
796 802 ctypes.c_ulong,
797 803 ]
798 804 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
799 805 size = 1000
800 806 buf = ctypes.create_string_buffer(size + 1)
801 807 filelen = _kernel32.GetModuleFileNameA(
802 808 sys.dllhandle, ctypes.byref(buf), size
803 809 )
804 810
805 811 if filelen > 0 and filelen != size:
806 812 dllbasename = os.path.basename(buf.value)
807 813 if not dllbasename.lower().endswith(b'.dll'):
808 814 raise SystemExit(
809 815 'Python DLL does not end with .dll: %s' % dllbasename
810 816 )
811 817 pythonlib = dllbasename[:-4]
812 818
813 819 # Copy the pythonXY.dll next to the binary so that it runs
814 820 # without tampering with PATH.
815 821 fsdecode = lambda x: x
816 822 if sys.version_info[0] >= 3:
817 823 fsdecode = os.fsdecode
818 824 dest = os.path.join(
819 825 os.path.dirname(self.hgtarget),
820 826 fsdecode(dllbasename),
821 827 )
822 828
823 829 if not os.path.exists(dest):
824 830 shutil.copy(buf.value, dest)
825 831
826 832 if not pythonlib:
827 833 log.warn(
828 834 'could not determine Python DLL filename; assuming pythonXY'
829 835 )
830 836
831 837 hv = sys.hexversion
832 838 pythonlib = b'python%d%d' % (hv >> 24, (hv >> 16) & 0xFF)
833 839
834 840 log.info('using %s as Python library name' % pythonlib)
835 841 with open('mercurial/hgpythonlib.h', 'wb') as f:
836 842 f.write(b'/* this file is autogenerated by setup.py */\n')
837 843 f.write(b'#define HGPYTHONLIB "%s"\n' % pythonlib)
838 844
839 845 macros = None
840 846 if sys.version_info[0] >= 3:
841 847 macros = [('_UNICODE', None), ('UNICODE', None)]
842 848
843 849 objects = self.compiler.compile(
844 850 ['mercurial/exewrapper.c'],
845 851 output_dir=self.build_temp,
846 852 macros=macros,
847 853 )
848 854 self.compiler.link_executable(
849 855 objects, self.hgtarget, libraries=[], output_dir=self.build_temp
850 856 )
851 857 if self.long_paths_support:
852 858 self.addlongpathsmanifest()
853 859
854 860 def addlongpathsmanifest(self):
855 861 r"""Add manifest pieces so that hg.exe understands long paths
856 862
857 863 This is an EXPERIMENTAL feature, use with care.
858 864 To enable long paths support, one needs to do two things:
859 865 - build Mercurial with --long-paths-support option
860 866 - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
861 867 LongPathsEnabled to have value 1.
862 868
863 869 Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
864 870 it happens because Mercurial uses mt.exe circa 2008, which is not
865 871 yet aware of long paths support in the manifest (I think so at least).
866 872 This does not stop mt.exe from embedding/merging the XML properly.
867 873
868 874 Why resource #1 should be used for .exe manifests? I don't know and
869 875 wasn't able to find an explanation for mortals. But it seems to work.
870 876 """
871 877 exefname = self.compiler.executable_filename(self.hgtarget)
872 878 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
873 879 os.close(fdauto)
874 880 with open(manfname, 'w') as f:
875 881 f.write(self.LONG_PATHS_MANIFEST)
876 882 log.info("long paths manifest is written to '%s'" % manfname)
877 883 inputresource = '-inputresource:%s;#1' % exefname
878 884 outputresource = '-outputresource:%s;#1' % exefname
879 885 log.info("running mt.exe to update hg.exe's manifest in-place")
880 886 # supplying both -manifest and -inputresource to mt.exe makes
881 887 # it merge the embedded and supplied manifests in the -outputresource
882 888 self.spawn(
883 889 [
884 890 'mt.exe',
885 891 '-nologo',
886 892 '-manifest',
887 893 manfname,
888 894 inputresource,
889 895 outputresource,
890 896 ]
891 897 )
892 898 log.info("done updating hg.exe's manifest")
893 899 os.remove(manfname)
894 900
895 901 @property
896 902 def hgexepath(self):
897 903 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
898 904 return os.path.join(self.build_temp, dir, 'hg.exe')
899 905
900 906
901 907 class hgbuilddoc(Command):
902 908 description = 'build documentation'
903 909 user_options = [
904 910 ('man', None, 'generate man pages'),
905 911 ('html', None, 'generate html pages'),
906 912 ]
907 913
908 914 def initialize_options(self):
909 915 self.man = None
910 916 self.html = None
911 917
912 918 def finalize_options(self):
913 919 # If --man or --html are set, only generate what we're told to.
914 920 # Otherwise generate everything.
915 921 have_subset = self.man is not None or self.html is not None
916 922
917 923 if have_subset:
918 924 self.man = True if self.man else False
919 925 self.html = True if self.html else False
920 926 else:
921 927 self.man = True
922 928 self.html = True
923 929
924 930 def run(self):
925 931 def normalizecrlf(p):
926 932 with open(p, 'rb') as fh:
927 933 orig = fh.read()
928 934
929 935 if b'\r\n' not in orig:
930 936 return
931 937
932 938 log.info('normalizing %s to LF line endings' % p)
933 939 with open(p, 'wb') as fh:
934 940 fh.write(orig.replace(b'\r\n', b'\n'))
935 941
936 942 def gentxt(root):
937 943 txt = 'doc/%s.txt' % root
938 944 log.info('generating %s' % txt)
939 945 res, out, err = runcmd(
940 946 [sys.executable, 'gendoc.py', root], os.environ, cwd='doc'
941 947 )
942 948 if res:
943 949 raise SystemExit(
944 950 'error running gendoc.py: %s'
945 951 % '\n'.join([sysstr(out), sysstr(err)])
946 952 )
947 953
948 954 with open(txt, 'wb') as fh:
949 955 fh.write(out)
950 956
951 957 def gengendoc(root):
952 958 gendoc = 'doc/%s.gendoc.txt' % root
953 959
954 960 log.info('generating %s' % gendoc)
955 961 res, out, err = runcmd(
956 962 [sys.executable, 'gendoc.py', '%s.gendoc' % root],
957 963 os.environ,
958 964 cwd='doc',
959 965 )
960 966 if res:
961 967 raise SystemExit(
962 968 'error running gendoc: %s'
963 969 % '\n'.join([sysstr(out), sysstr(err)])
964 970 )
965 971
966 972 with open(gendoc, 'wb') as fh:
967 973 fh.write(out)
968 974
969 975 def genman(root):
970 976 log.info('generating doc/%s' % root)
971 977 res, out, err = runcmd(
972 978 [
973 979 sys.executable,
974 980 'runrst',
975 981 'hgmanpage',
976 982 '--halt',
977 983 'warning',
978 984 '--strip-elements-with-class',
979 985 'htmlonly',
980 986 '%s.txt' % root,
981 987 root,
982 988 ],
983 989 os.environ,
984 990 cwd='doc',
985 991 )
986 992 if res:
987 993 raise SystemExit(
988 994 'error running runrst: %s'
989 995 % '\n'.join([sysstr(out), sysstr(err)])
990 996 )
991 997
992 998 normalizecrlf('doc/%s' % root)
993 999
994 1000 def genhtml(root):
995 1001 log.info('generating doc/%s.html' % root)
996 1002 res, out, err = runcmd(
997 1003 [
998 1004 sys.executable,
999 1005 'runrst',
1000 1006 'html',
1001 1007 '--halt',
1002 1008 'warning',
1003 1009 '--link-stylesheet',
1004 1010 '--stylesheet-path',
1005 1011 'style.css',
1006 1012 '%s.txt' % root,
1007 1013 '%s.html' % root,
1008 1014 ],
1009 1015 os.environ,
1010 1016 cwd='doc',
1011 1017 )
1012 1018 if res:
1013 1019 raise SystemExit(
1014 1020 'error running runrst: %s'
1015 1021 % '\n'.join([sysstr(out), sysstr(err)])
1016 1022 )
1017 1023
1018 1024 normalizecrlf('doc/%s.html' % root)
1019 1025
1020 1026 # This logic is duplicated in doc/Makefile.
1021 1027 sources = {
1022 1028 f
1023 1029 for f in os.listdir('mercurial/helptext')
1024 1030 if re.search(r'[0-9]\.txt$', f)
1025 1031 }
1026 1032
1027 1033 # common.txt is a one-off.
1028 1034 gentxt('common')
1029 1035
1030 1036 for source in sorted(sources):
1031 1037 assert source[-4:] == '.txt'
1032 1038 root = source[:-4]
1033 1039
1034 1040 gentxt(root)
1035 1041 gengendoc(root)
1036 1042
1037 1043 if self.man:
1038 1044 genman(root)
1039 1045 if self.html:
1040 1046 genhtml(root)
1041 1047
1042 1048
1043 1049 class hginstall(install):
1044 1050
1045 1051 user_options = install.user_options + [
1046 1052 (
1047 1053 'old-and-unmanageable',
1048 1054 None,
1049 1055 'noop, present for eggless setuptools compat',
1050 1056 ),
1051 1057 (
1052 1058 'single-version-externally-managed',
1053 1059 None,
1054 1060 'noop, present for eggless setuptools compat',
1055 1061 ),
1056 1062 ]
1057 1063
1058 1064 # Also helps setuptools not be sad while we refuse to create eggs.
1059 1065 single_version_externally_managed = True
1060 1066
1061 1067 def get_sub_commands(self):
1062 1068 # Screen out egg related commands to prevent egg generation. But allow
1063 1069 # mercurial.egg-info generation, since that is part of modern
1064 1070 # packaging.
1065 1071 excl = {'bdist_egg'}
1066 1072 return filter(lambda x: x not in excl, install.get_sub_commands(self))
1067 1073
1068 1074
1069 1075 class hginstalllib(install_lib):
1070 1076 """
1071 1077 This is a specialization of install_lib that replaces the copy_file used
1072 1078 there so that it supports setting the mode of files after copying them,
1073 1079 instead of just preserving the mode that the files originally had. If your
1074 1080 system has a umask of something like 027, preserving the permissions when
1075 1081 copying will lead to a broken install.
1076 1082
1077 1083 Note that just passing keep_permissions=False to copy_file would be
1078 1084 insufficient, as it might still be applying a umask.
1079 1085 """
1080 1086
1081 1087 def run(self):
1082 1088 realcopyfile = file_util.copy_file
1083 1089
1084 1090 def copyfileandsetmode(*args, **kwargs):
1085 1091 src, dst = args[0], args[1]
1086 1092 dst, copied = realcopyfile(*args, **kwargs)
1087 1093 if copied:
1088 1094 st = os.stat(src)
1089 1095 # Persist executable bit (apply it to group and other if user
1090 1096 # has it)
1091 1097 if st[stat.ST_MODE] & stat.S_IXUSR:
1092 1098 setmode = int('0755', 8)
1093 1099 else:
1094 1100 setmode = int('0644', 8)
1095 1101 m = stat.S_IMODE(st[stat.ST_MODE])
1096 1102 m = (m & ~int('0777', 8)) | setmode
1097 1103 os.chmod(dst, m)
1098 1104
1099 1105 file_util.copy_file = copyfileandsetmode
1100 1106 try:
1101 1107 install_lib.run(self)
1102 1108 finally:
1103 1109 file_util.copy_file = realcopyfile
1104 1110
1105 1111
1106 1112 class hginstallscripts(install_scripts):
1107 1113 """
1108 1114 This is a specialization of install_scripts that replaces the @LIBDIR@ with
1109 1115 the configured directory for modules. If possible, the path is made relative
1110 1116 to the directory for scripts.
1111 1117 """
1112 1118
1113 1119 def initialize_options(self):
1114 1120 install_scripts.initialize_options(self)
1115 1121
1116 1122 self.install_lib = None
1117 1123
1118 1124 def finalize_options(self):
1119 1125 install_scripts.finalize_options(self)
1120 1126 self.set_undefined_options('install', ('install_lib', 'install_lib'))
1121 1127
1122 1128 def run(self):
1123 1129 install_scripts.run(self)
1124 1130
1125 1131 # It only makes sense to replace @LIBDIR@ with the install path if
1126 1132 # the install path is known. For wheels, the logic below calculates
1127 1133 # the libdir to be "../..". This is because the internal layout of a
1128 1134 # wheel archive looks like:
1129 1135 #
1130 1136 # mercurial-3.6.1.data/scripts/hg
1131 1137 # mercurial/__init__.py
1132 1138 #
1133 1139 # When installing wheels, the subdirectories of the "<pkg>.data"
1134 1140 # directory are translated to system local paths and files therein
1135 1141 # are copied in place. The mercurial/* files are installed into the
1136 1142 # site-packages directory. However, the site-packages directory
1137 1143 # isn't known until wheel install time. This means we have no clue
1138 1144 # at wheel generation time what the installed site-packages directory
1139 1145 # will be. And, wheels don't appear to provide the ability to register
1140 1146 # custom code to run during wheel installation. This all means that
1141 1147 # we can't reliably set the libdir in wheels: the default behavior
1142 1148 # of looking in sys.path must do.
1143 1149
1144 1150 if (
1145 1151 os.path.splitdrive(self.install_dir)[0]
1146 1152 != os.path.splitdrive(self.install_lib)[0]
1147 1153 ):
1148 1154 # can't make relative paths from one drive to another, so use an
1149 1155 # absolute path instead
1150 1156 libdir = self.install_lib
1151 1157 else:
1152 1158 libdir = os.path.relpath(self.install_lib, self.install_dir)
1153 1159
1154 1160 for outfile in self.outfiles:
1155 1161 with open(outfile, 'rb') as fp:
1156 1162 data = fp.read()
1157 1163
1158 1164 # skip binary files
1159 1165 if b'\0' in data:
1160 1166 continue
1161 1167
1162 1168 # During local installs, the shebang will be rewritten to the final
1163 1169 # install path. During wheel packaging, the shebang has a special
1164 1170 # value.
1165 1171 if data.startswith(b'#!python'):
1166 1172 log.info(
1167 1173 'not rewriting @LIBDIR@ in %s because install path '
1168 1174 'not known' % outfile
1169 1175 )
1170 1176 continue
1171 1177
1172 1178 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
1173 1179 with open(outfile, 'wb') as fp:
1174 1180 fp.write(data)
1175 1181
1176 1182
1177 1183 # virtualenv installs custom distutils/__init__.py and
1178 1184 # distutils/distutils.cfg files which essentially proxy back to the
1179 1185 # "real" distutils in the main Python install. The presence of this
1180 1186 # directory causes py2exe to pick up the "hacked" distutils package
1181 1187 # from the virtualenv and "import distutils" will fail from the py2exe
1182 1188 # build because the "real" distutils files can't be located.
1183 1189 #
1184 1190 # We work around this by monkeypatching the py2exe code finding Python
1185 1191 # modules to replace the found virtualenv distutils modules with the
1186 1192 # original versions via filesystem scanning. This is a bit hacky. But
1187 1193 # it allows us to use virtualenvs for py2exe packaging, which is more
1188 1194 # deterministic and reproducible.
1189 1195 #
1190 1196 # It's worth noting that the common StackOverflow suggestions for this
1191 1197 # problem involve copying the original distutils files into the
1192 1198 # virtualenv or into the staging directory after setup() is invoked.
1193 1199 # The former is very brittle and can easily break setup(). Our hacking
1194 1200 # of the found modules routine has a similar result as copying the files
1195 1201 # manually. But it makes fewer assumptions about how py2exe works and
1196 1202 # is less brittle.
1197 1203
1198 1204 # This only catches virtualenvs made with virtualenv (as opposed to
1199 1205 # venv, which is likely what Python 3 uses).
1200 1206 py2exehacked = py2exeloaded and getattr(sys, 'real_prefix', None) is not None
1201 1207
1202 1208 if py2exehacked:
1203 1209 from distutils.command.py2exe import py2exe as buildpy2exe
1204 1210 from py2exe.mf import Module as py2exemodule
1205 1211
1206 1212 class hgbuildpy2exe(buildpy2exe):
1207 1213 def find_needed_modules(self, mf, files, modules):
1208 1214 res = buildpy2exe.find_needed_modules(self, mf, files, modules)
1209 1215
1210 1216 # Replace virtualenv's distutils modules with the real ones.
1211 1217 modules = {}
1212 1218 for k, v in res.modules.items():
1213 1219 if k != 'distutils' and not k.startswith('distutils.'):
1214 1220 modules[k] = v
1215 1221
1216 1222 res.modules = modules
1217 1223
1218 1224 import opcode
1219 1225
1220 1226 distutilsreal = os.path.join(
1221 1227 os.path.dirname(opcode.__file__), 'distutils'
1222 1228 )
1223 1229
1224 1230 for root, dirs, files in os.walk(distutilsreal):
1225 1231 for f in sorted(files):
1226 1232 if not f.endswith('.py'):
1227 1233 continue
1228 1234
1229 1235 full = os.path.join(root, f)
1230 1236
1231 1237 parents = ['distutils']
1232 1238
1233 1239 if root != distutilsreal:
1234 1240 rel = os.path.relpath(root, distutilsreal)
1235 1241 parents.extend(p for p in rel.split(os.sep))
1236 1242
1237 1243 modname = '%s.%s' % ('.'.join(parents), f[:-3])
1238 1244
1239 1245 if modname.startswith('distutils.tests.'):
1240 1246 continue
1241 1247
1242 1248 if modname.endswith('.__init__'):
1243 1249 modname = modname[: -len('.__init__')]
1244 1250 path = os.path.dirname(full)
1245 1251 else:
1246 1252 path = None
1247 1253
1248 1254 res.modules[modname] = py2exemodule(
1249 1255 modname, full, path=path
1250 1256 )
1251 1257
1252 1258 if 'distutils' not in res.modules:
1253 1259 raise SystemExit('could not find distutils modules')
1254 1260
1255 1261 return res
1256 1262
1257 1263
1258 1264 cmdclass = {
1259 1265 'build': hgbuild,
1260 1266 'build_doc': hgbuilddoc,
1261 1267 'build_mo': hgbuildmo,
1262 1268 'build_ext': hgbuildext,
1263 1269 'build_py': hgbuildpy,
1264 1270 'build_scripts': hgbuildscripts,
1265 1271 'build_hgextindex': buildhgextindex,
1266 1272 'install': hginstall,
1267 1273 'install_lib': hginstalllib,
1268 1274 'install_scripts': hginstallscripts,
1269 1275 'build_hgexe': buildhgexe,
1270 1276 }
1271 1277
1272 1278 if py2exehacked:
1273 1279 cmdclass['py2exe'] = hgbuildpy2exe
1274 1280
1275 1281 packages = [
1276 1282 'mercurial',
1277 1283 'mercurial.cext',
1278 1284 'mercurial.cffi',
1279 1285 'mercurial.defaultrc',
1280 1286 'mercurial.helptext',
1281 1287 'mercurial.helptext.internals',
1282 1288 'mercurial.hgweb',
1283 1289 'mercurial.interfaces',
1284 1290 'mercurial.pure',
1285 1291 'mercurial.templates',
1286 1292 'mercurial.thirdparty',
1287 1293 'mercurial.thirdparty.attr',
1288 1294 'mercurial.thirdparty.zope',
1289 1295 'mercurial.thirdparty.zope.interface',
1290 1296 'mercurial.upgrade_utils',
1291 1297 'mercurial.utils',
1292 1298 'mercurial.revlogutils',
1293 1299 'mercurial.testing',
1294 1300 'hgext',
1295 1301 'hgext.convert',
1296 1302 'hgext.fsmonitor',
1297 1303 'hgext.fastannotate',
1298 1304 'hgext.fsmonitor.pywatchman',
1299 1305 'hgext.git',
1300 1306 'hgext.highlight',
1301 1307 'hgext.hooklib',
1302 1308 'hgext.infinitepush',
1303 1309 'hgext.largefiles',
1304 1310 'hgext.lfs',
1305 1311 'hgext.narrow',
1306 1312 'hgext.remotefilelog',
1307 1313 'hgext.zeroconf',
1308 1314 'hgext3rd',
1309 1315 'hgdemandimport',
1310 1316 ]
1311 1317
1312 1318 # The pygit2 dependency dropped py2 support with the 1.0 release in Dec 2019.
1313 1319 # Prior releases do not build at all on Windows, because Visual Studio 2008
1314 1320 # doesn't understand C 11. Older Linux releases are buggy.
1315 1321 if sys.version_info[0] == 2:
1316 1322 packages.remove('hgext.git')
1317 1323
1318 1324
1319 1325 for name in os.listdir(os.path.join('mercurial', 'templates')):
1320 1326 if name != '__pycache__' and os.path.isdir(
1321 1327 os.path.join('mercurial', 'templates', name)
1322 1328 ):
1323 1329 packages.append('mercurial.templates.%s' % name)
1324 1330
1325 1331 if sys.version_info[0] == 2:
1326 1332 packages.extend(
1327 1333 [
1328 1334 'mercurial.thirdparty.concurrent',
1329 1335 'mercurial.thirdparty.concurrent.futures',
1330 1336 ]
1331 1337 )
1332 1338
1333 1339 if 'HG_PY2EXE_EXTRA_INSTALL_PACKAGES' in os.environ:
1334 1340 # py2exe can't cope with namespace packages very well, so we have to
1335 1341 # install any hgext3rd.* extensions that we want in the final py2exe
1336 1342 # image here. This is gross, but you gotta do what you gotta do.
1337 1343 packages.extend(os.environ['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'].split(' '))
1338 1344
1339 1345 common_depends = [
1340 1346 'mercurial/bitmanipulation.h',
1341 1347 'mercurial/compat.h',
1342 1348 'mercurial/cext/util.h',
1343 1349 ]
1344 1350 common_include_dirs = ['mercurial']
1345 1351
1346 1352 common_cflags = []
1347 1353
1348 1354 # MSVC 2008 still needs declarations at the top of the scope, but Python 3.9
1349 1355 # makes declarations not at the top of a scope in the headers.
1350 1356 if os.name != 'nt' and sys.version_info[1] < 9:
1351 1357 common_cflags = ['-Werror=declaration-after-statement']
1352 1358
1353 1359 osutil_cflags = []
1354 1360 osutil_ldflags = []
1355 1361
1356 1362 # platform specific macros
1357 1363 for plat, func in [('bsd', 'setproctitle')]:
1358 1364 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
1359 1365 osutil_cflags.append('-DHAVE_%s' % func.upper())
1360 1366
1361 1367 for plat, macro, code in [
1362 1368 (
1363 1369 'bsd|darwin',
1364 1370 'BSD_STATFS',
1365 1371 '''
1366 1372 #include <sys/param.h>
1367 1373 #include <sys/mount.h>
1368 1374 int main() { struct statfs s; return sizeof(s.f_fstypename); }
1369 1375 ''',
1370 1376 ),
1371 1377 (
1372 1378 'linux',
1373 1379 'LINUX_STATFS',
1374 1380 '''
1375 1381 #include <linux/magic.h>
1376 1382 #include <sys/vfs.h>
1377 1383 int main() { struct statfs s; return sizeof(s.f_type); }
1378 1384 ''',
1379 1385 ),
1380 1386 ]:
1381 1387 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
1382 1388 osutil_cflags.append('-DHAVE_%s' % macro)
1383 1389
1384 1390 if sys.platform == 'darwin':
1385 1391 osutil_ldflags += ['-framework', 'ApplicationServices']
1386 1392
1387 1393 if sys.platform == 'sunos5':
1388 1394 osutil_ldflags += ['-lsocket']
1389 1395
1390 1396 xdiff_srcs = [
1391 1397 'mercurial/thirdparty/xdiff/xdiffi.c',
1392 1398 'mercurial/thirdparty/xdiff/xprepare.c',
1393 1399 'mercurial/thirdparty/xdiff/xutils.c',
1394 1400 ]
1395 1401
1396 1402 xdiff_headers = [
1397 1403 'mercurial/thirdparty/xdiff/xdiff.h',
1398 1404 'mercurial/thirdparty/xdiff/xdiffi.h',
1399 1405 'mercurial/thirdparty/xdiff/xinclude.h',
1400 1406 'mercurial/thirdparty/xdiff/xmacros.h',
1401 1407 'mercurial/thirdparty/xdiff/xprepare.h',
1402 1408 'mercurial/thirdparty/xdiff/xtypes.h',
1403 1409 'mercurial/thirdparty/xdiff/xutils.h',
1404 1410 ]
1405 1411
1406 1412
1407 1413 class RustCompilationError(CCompilerError):
1408 1414 """Exception class for Rust compilation errors."""
1409 1415
1410 1416
1411 1417 class RustExtension(Extension):
1412 1418 """Base classes for concrete Rust Extension classes."""
1413 1419
1414 1420 rusttargetdir = os.path.join('rust', 'target', 'release')
1415 1421
1416 1422 def __init__(
1417 1423 self, mpath, sources, rustlibname, subcrate, py3_features=None, **kw
1418 1424 ):
1419 1425 Extension.__init__(self, mpath, sources, **kw)
1420 1426 srcdir = self.rustsrcdir = os.path.join('rust', subcrate)
1421 1427 self.py3_features = py3_features
1422 1428
1423 1429 # adding Rust source and control files to depends so that the extension
1424 1430 # gets rebuilt if they've changed
1425 1431 self.depends.append(os.path.join(srcdir, 'Cargo.toml'))
1426 1432 cargo_lock = os.path.join(srcdir, 'Cargo.lock')
1427 1433 if os.path.exists(cargo_lock):
1428 1434 self.depends.append(cargo_lock)
1429 1435 for dirpath, subdir, fnames in os.walk(os.path.join(srcdir, 'src')):
1430 1436 self.depends.extend(
1431 1437 os.path.join(dirpath, fname)
1432 1438 for fname in fnames
1433 1439 if os.path.splitext(fname)[1] == '.rs'
1434 1440 )
1435 1441
1436 1442 @staticmethod
1437 1443 def rustdylibsuffix():
1438 1444 """Return the suffix for shared libraries produced by rustc.
1439 1445
1440 1446 See also: https://doc.rust-lang.org/reference/linkage.html
1441 1447 """
1442 1448 if sys.platform == 'darwin':
1443 1449 return '.dylib'
1444 1450 elif os.name == 'nt':
1445 1451 return '.dll'
1446 1452 else:
1447 1453 return '.so'
1448 1454
1449 1455 def rustbuild(self):
1450 1456 env = os.environ.copy()
1451 1457 if 'HGTEST_RESTOREENV' in env:
1452 1458 # Mercurial tests change HOME to a temporary directory,
1453 1459 # but, if installed with rustup, the Rust toolchain needs
1454 1460 # HOME to be correct (otherwise the 'no default toolchain'
1455 1461 # error message is issued and the build fails).
1456 1462 # This happens currently with test-hghave.t, which does
1457 1463 # invoke this build.
1458 1464
1459 1465 # Unix only fix (os.path.expanduser not really reliable if
1460 1466 # HOME is shadowed like this)
1461 1467 import pwd
1462 1468
1463 1469 env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
1464 1470
1465 1471 cargocmd = ['cargo', 'rustc', '--release']
1466 1472
1467 1473 feature_flags = []
1468 1474
1469 1475 if sys.version_info[0] == 3 and self.py3_features is not None:
1470 1476 feature_flags.append(self.py3_features)
1471 1477 cargocmd.append('--no-default-features')
1472 1478
1473 1479 rust_features = env.get("HG_RUST_FEATURES")
1474 1480 if rust_features:
1475 1481 feature_flags.append(rust_features)
1476 1482
1477 1483 cargocmd.extend(('--features', " ".join(feature_flags)))
1478 1484
1479 1485 cargocmd.append('--')
1480 1486 if sys.platform == 'darwin':
1481 1487 cargocmd.extend(
1482 1488 ("-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup")
1483 1489 )
1484 1490 try:
1485 1491 subprocess.check_call(cargocmd, env=env, cwd=self.rustsrcdir)
1486 1492 except OSError as exc:
1487 1493 if exc.errno == errno.ENOENT:
1488 1494 raise RustCompilationError("Cargo not found")
1489 1495 elif exc.errno == errno.EACCES:
1490 1496 raise RustCompilationError(
1491 1497 "Cargo found, but permisssion to execute it is denied"
1492 1498 )
1493 1499 else:
1494 1500 raise
1495 1501 except subprocess.CalledProcessError:
1496 1502 raise RustCompilationError(
1497 1503 "Cargo failed. Working directory: %r, "
1498 1504 "command: %r, environment: %r"
1499 1505 % (self.rustsrcdir, cargocmd, env)
1500 1506 )
1501 1507
1502 1508
1503 1509 class RustStandaloneExtension(RustExtension):
1504 1510 def __init__(self, pydottedname, rustcrate, dylibname, **kw):
1505 1511 RustExtension.__init__(
1506 1512 self, pydottedname, [], dylibname, rustcrate, **kw
1507 1513 )
1508 1514 self.dylibname = dylibname
1509 1515
1510 1516 def build(self, target_dir):
1511 1517 self.rustbuild()
1512 1518 target = [target_dir]
1513 1519 target.extend(self.name.split('.'))
1514 1520 target[-1] += DYLIB_SUFFIX
1515 1521 shutil.copy2(
1516 1522 os.path.join(
1517 1523 self.rusttargetdir, self.dylibname + self.rustdylibsuffix()
1518 1524 ),
1519 1525 os.path.join(*target),
1520 1526 )
1521 1527
1522 1528
1523 1529 extmodules = [
1524 1530 Extension(
1525 1531 'mercurial.cext.base85',
1526 1532 ['mercurial/cext/base85.c'],
1527 1533 include_dirs=common_include_dirs,
1528 1534 extra_compile_args=common_cflags,
1529 1535 depends=common_depends,
1530 1536 ),
1531 1537 Extension(
1532 1538 'mercurial.cext.bdiff',
1533 1539 ['mercurial/bdiff.c', 'mercurial/cext/bdiff.c'] + xdiff_srcs,
1534 1540 include_dirs=common_include_dirs,
1535 1541 extra_compile_args=common_cflags,
1536 1542 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers,
1537 1543 ),
1538 1544 Extension(
1539 1545 'mercurial.cext.mpatch',
1540 1546 ['mercurial/mpatch.c', 'mercurial/cext/mpatch.c'],
1541 1547 include_dirs=common_include_dirs,
1542 1548 extra_compile_args=common_cflags,
1543 1549 depends=common_depends,
1544 1550 ),
1545 1551 Extension(
1546 1552 'mercurial.cext.parsers',
1547 1553 [
1548 1554 'mercurial/cext/charencode.c',
1549 1555 'mercurial/cext/dirs.c',
1550 1556 'mercurial/cext/manifest.c',
1551 1557 'mercurial/cext/parsers.c',
1552 1558 'mercurial/cext/pathencode.c',
1553 1559 'mercurial/cext/revlog.c',
1554 1560 ],
1555 1561 include_dirs=common_include_dirs,
1556 1562 extra_compile_args=common_cflags,
1557 1563 depends=common_depends
1558 1564 + [
1559 1565 'mercurial/cext/charencode.h',
1560 1566 'mercurial/cext/revlog.h',
1561 1567 ],
1562 1568 ),
1563 1569 Extension(
1564 1570 'mercurial.cext.osutil',
1565 1571 ['mercurial/cext/osutil.c'],
1566 1572 include_dirs=common_include_dirs,
1567 1573 extra_compile_args=common_cflags + osutil_cflags,
1568 1574 extra_link_args=osutil_ldflags,
1569 1575 depends=common_depends,
1570 1576 ),
1571 1577 Extension(
1572 1578 'mercurial.thirdparty.zope.interface._zope_interface_coptimizations',
1573 1579 [
1574 1580 'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c',
1575 1581 ],
1576 1582 extra_compile_args=common_cflags,
1577 1583 ),
1578 1584 Extension(
1579 1585 'mercurial.thirdparty.sha1dc',
1580 1586 [
1581 1587 'mercurial/thirdparty/sha1dc/cext.c',
1582 1588 'mercurial/thirdparty/sha1dc/lib/sha1.c',
1583 1589 'mercurial/thirdparty/sha1dc/lib/ubc_check.c',
1584 1590 ],
1585 1591 extra_compile_args=common_cflags,
1586 1592 ),
1587 1593 Extension(
1588 1594 'hgext.fsmonitor.pywatchman.bser',
1589 1595 ['hgext/fsmonitor/pywatchman/bser.c'],
1590 1596 extra_compile_args=common_cflags,
1591 1597 ),
1592 1598 RustStandaloneExtension(
1593 1599 'mercurial.rustext', 'hg-cpython', 'librusthg', py3_features='python3'
1594 1600 ),
1595 1601 ]
1596 1602
1597 1603
1598 1604 sys.path.insert(0, 'contrib/python-zstandard')
1599 1605 import setup_zstd
1600 1606
1601 1607 zstd = setup_zstd.get_c_extension(
1602 1608 name='mercurial.zstd', root=os.path.abspath(os.path.dirname(__file__))
1603 1609 )
1604 1610 zstd.extra_compile_args += common_cflags
1605 1611 extmodules.append(zstd)
1606 1612
1607 1613 try:
1608 1614 from distutils import cygwinccompiler
1609 1615
1610 1616 # the -mno-cygwin option has been deprecated for years
1611 1617 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
1612 1618
1613 1619 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
1614 1620 def __init__(self, *args, **kwargs):
1615 1621 mingw32compilerclass.__init__(self, *args, **kwargs)
1616 1622 for i in 'compiler compiler_so linker_exe linker_so'.split():
1617 1623 try:
1618 1624 getattr(self, i).remove('-mno-cygwin')
1619 1625 except ValueError:
1620 1626 pass
1621 1627
1622 1628 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
1623 1629 except ImportError:
1624 1630 # the cygwinccompiler package is not available on some Python
1625 1631 # distributions like the ones from the optware project for Synology
1626 1632 # DiskStation boxes
1627 1633 class HackedMingw32CCompiler(object):
1628 1634 pass
1629 1635
1630 1636
1631 1637 if os.name == 'nt':
1632 1638 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
1633 1639 # extra_link_args to distutils.extensions.Extension() doesn't have any
1634 1640 # effect.
1635 1641 from distutils import msvccompiler
1636 1642
1637 1643 msvccompilerclass = msvccompiler.MSVCCompiler
1638 1644
1639 1645 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
1640 1646 def initialize(self):
1641 1647 msvccompilerclass.initialize(self)
1642 1648 # "warning LNK4197: export 'func' specified multiple times"
1643 1649 self.ldflags_shared.append('/ignore:4197')
1644 1650 self.ldflags_shared_debug.append('/ignore:4197')
1645 1651
1646 1652 msvccompiler.MSVCCompiler = HackedMSVCCompiler
1647 1653
1648 1654 packagedata = {
1649 1655 'mercurial': [
1650 1656 'locale/*/LC_MESSAGES/hg.mo',
1651 1657 'dummycert.pem',
1652 1658 ],
1653 1659 'mercurial.defaultrc': [
1654 1660 '*.rc',
1655 1661 ],
1656 1662 'mercurial.helptext': [
1657 1663 '*.txt',
1658 1664 ],
1659 1665 'mercurial.helptext.internals': [
1660 1666 '*.txt',
1661 1667 ],
1662 1668 }
1663 1669
1664 1670
1665 1671 def ordinarypath(p):
1666 1672 return p and p[0] != '.' and p[-1] != '~'
1667 1673
1668 1674
1669 1675 for root in ('templates',):
1670 1676 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
1671 1677 packagename = curdir.replace(os.sep, '.')
1672 1678 packagedata[packagename] = list(filter(ordinarypath, files))
1673 1679
1674 1680 datafiles = []
1675 1681
1676 1682 # distutils expects version to be str/unicode. Converting it to
1677 1683 # unicode on Python 2 still works because it won't contain any
1678 1684 # non-ascii bytes and will be implicitly converted back to bytes
1679 1685 # when operated on.
1680 1686 assert isinstance(version, bytes)
1681 1687 setupversion = version.decode('ascii')
1682 1688
1683 1689 extra = {}
1684 1690
1685 1691 py2exepackages = [
1686 1692 'hgdemandimport',
1687 1693 'hgext3rd',
1688 1694 'hgext',
1689 1695 'email',
1690 1696 # implicitly imported per module policy
1691 1697 # (cffi wouldn't be used as a frozen exe)
1692 1698 'mercurial.cext',
1693 1699 #'mercurial.cffi',
1694 1700 'mercurial.pure',
1695 1701 ]
1696 1702
1697 1703 py2exeexcludes = []
1698 1704 py2exedllexcludes = ['crypt32.dll']
1699 1705
1700 1706 if issetuptools:
1701 1707 extra['python_requires'] = supportedpy
1702 1708
1703 1709 if py2exeloaded:
1704 1710 extra['console'] = [
1705 1711 {
1706 1712 'script': 'hg',
1707 1713 'copyright': 'Copyright (C) 2005-2020 Matt Mackall and others',
1708 1714 'product_version': version,
1709 1715 }
1710 1716 ]
1711 1717 # Sub command of 'build' because 'py2exe' does not handle sub_commands.
1712 1718 # Need to override hgbuild because it has a private copy of
1713 1719 # build.sub_commands.
1714 1720 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1715 1721 # put dlls in sub directory so that they won't pollute PATH
1716 1722 extra['zipfile'] = 'lib/library.zip'
1717 1723
1718 1724 # We allow some configuration to be supplemented via environment
1719 1725 # variables. This is better than setup.cfg files because it allows
1720 1726 # supplementing configs instead of replacing them.
1721 1727 extrapackages = os.environ.get('HG_PY2EXE_EXTRA_PACKAGES')
1722 1728 if extrapackages:
1723 1729 py2exepackages.extend(extrapackages.split(' '))
1724 1730
1725 1731 excludes = os.environ.get('HG_PY2EXE_EXTRA_EXCLUDES')
1726 1732 if excludes:
1727 1733 py2exeexcludes.extend(excludes.split(' '))
1728 1734
1729 1735 dllexcludes = os.environ.get('HG_PY2EXE_EXTRA_DLL_EXCLUDES')
1730 1736 if dllexcludes:
1731 1737 py2exedllexcludes.extend(dllexcludes.split(' '))
1732 1738
1733 1739 if os.environ.get('PYOXIDIZER'):
1734 1740 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1735 1741
1736 1742 if os.name == 'nt':
1737 1743 # Windows binary file versions for exe/dll files must have the
1738 1744 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
1739 1745 setupversion = setupversion.split(r'+', 1)[0]
1740 1746
1741 1747 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
1742 1748 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
1743 1749 if version:
1744 1750 version = version[0]
1745 1751 if sys.version_info[0] == 3:
1746 1752 version = version.decode('utf-8')
1747 1753 xcode4 = version.startswith('Xcode') and StrictVersion(
1748 1754 version.split()[1]
1749 1755 ) >= StrictVersion('4.0')
1750 1756 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
1751 1757 else:
1752 1758 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
1753 1759 # installed, but instead with only command-line tools. Assume
1754 1760 # that only happens on >= Lion, thus no PPC support.
1755 1761 xcode4 = True
1756 1762 xcode51 = False
1757 1763
1758 1764 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
1759 1765 # distutils.sysconfig
1760 1766 if xcode4:
1761 1767 os.environ['ARCHFLAGS'] = ''
1762 1768
1763 1769 # XCode 5.1 changes clang such that it now fails to compile if the
1764 1770 # -mno-fused-madd flag is passed, but the version of Python shipped with
1765 1771 # OS X 10.9 Mavericks includes this flag. This causes problems in all
1766 1772 # C extension modules, and a bug has been filed upstream at
1767 1773 # http://bugs.python.org/issue21244. We also need to patch this here
1768 1774 # so Mercurial can continue to compile in the meantime.
1769 1775 if xcode51:
1770 1776 cflags = get_config_var('CFLAGS')
1771 1777 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1772 1778 os.environ['CFLAGS'] = (
1773 1779 os.environ.get('CFLAGS', '') + ' -Qunused-arguments'
1774 1780 )
1775 1781
1776 1782 setup(
1777 1783 name='mercurial',
1778 1784 version=setupversion,
1779 1785 author='Matt Mackall and many others',
1780 1786 author_email='mercurial@mercurial-scm.org',
1781 1787 url='https://mercurial-scm.org/',
1782 1788 download_url='https://mercurial-scm.org/release/',
1783 1789 description=(
1784 1790 'Fast scalable distributed SCM (revision control, version '
1785 1791 'control) system'
1786 1792 ),
1787 1793 long_description=(
1788 1794 'Mercurial is a distributed SCM tool written in Python.'
1789 1795 ' It is used by a number of large projects that require'
1790 1796 ' fast, reliable distributed revision control, such as '
1791 1797 'Mozilla.'
1792 1798 ),
1793 1799 license='GNU GPLv2 or any later version',
1794 1800 classifiers=[
1795 1801 'Development Status :: 6 - Mature',
1796 1802 'Environment :: Console',
1797 1803 'Intended Audience :: Developers',
1798 1804 'Intended Audience :: System Administrators',
1799 1805 'License :: OSI Approved :: GNU General Public License (GPL)',
1800 1806 'Natural Language :: Danish',
1801 1807 'Natural Language :: English',
1802 1808 'Natural Language :: German',
1803 1809 'Natural Language :: Italian',
1804 1810 'Natural Language :: Japanese',
1805 1811 'Natural Language :: Portuguese (Brazilian)',
1806 1812 'Operating System :: Microsoft :: Windows',
1807 1813 'Operating System :: OS Independent',
1808 1814 'Operating System :: POSIX',
1809 1815 'Programming Language :: C',
1810 1816 'Programming Language :: Python',
1811 1817 'Topic :: Software Development :: Version Control',
1812 1818 ],
1813 1819 scripts=scripts,
1814 1820 packages=packages,
1815 1821 ext_modules=extmodules,
1816 1822 data_files=datafiles,
1817 1823 package_data=packagedata,
1818 1824 cmdclass=cmdclass,
1819 1825 distclass=hgdist,
1820 1826 options={
1821 1827 'py2exe': {
1822 1828 'bundle_files': 3,
1823 1829 'dll_excludes': py2exedllexcludes,
1824 1830 'excludes': py2exeexcludes,
1825 1831 'packages': py2exepackages,
1826 1832 },
1827 1833 'bdist_mpkg': {
1828 1834 'zipdist': False,
1829 1835 'license': 'COPYING',
1830 1836 'readme': 'contrib/packaging/macosx/Readme.html',
1831 1837 'welcome': 'contrib/packaging/macosx/Welcome.html',
1832 1838 },
1833 1839 },
1834 1840 **extra
1835 1841 )
General Comments 0
You need to be logged in to leave comments. Login now