##// END OF EJS Templates
packaging: stage files and dynamically generate WiX installer...
packaging: stage files and dynamically generate WiX installer Like we did for Inno, we want to make the WiX installer "dumb" and simply consume source files from a directory tree rather than have to define every single file in installer files. This will greatly decrease the amount of effort required to maintain the WiX installer since we don't have to think that much about keeping files in sync. This commit changes the WiX packager to populate a staging directory as part of packaging. After it does so, it scans that directory and dynamically generates WiX XML defining the content within. The IDs and GUIDs being generated are deterministic. So, upgrades should work as expected in Windows Installer land. (WiX has a "heat" tool that can generate XML by walking the filesystem but it doesn't have this deterministic property, sadly.) As part of this change, GUIDs are now effectively reset. So the next upgrade should be a complete wipe and replace. This could potentially cause issues. But in my local testing, I was able to upgrade an existing 5.1.2 install without issue. Compared to the previous commit, the installed files differ in the following: * A ReleaseNotes.txt file is now included * A hgrc.d/editor.rc file is now generated (mercurial.rc has been updated to reflect this logical change to the content source) * All files are marked as read-only. Previously, only a subset of files were. This should help prevent unwanted tampering. Although we may want to consider use cases like modifying template files... This change also means that Inno and WiX are now using very similar code for managing the install layout. This means that on disk both packages are nearly identical. The differences in install layout are as follows: * Inno has a Copying.txt vs a COPYING.rtf for WiX. (The WiX installer wants to use RTF.) * Inno has a Mercurial.url file that is an internet shortcut to www.mercurial-scm.org. (This could potentially be removed.) * Inno includes msvc[mpr]90.dll files and WiX does not. (WiX installs the MSVC runtime via merge modules.) * Inno includes unins000.{dat,exe} files. (WiX's state is managed by Windows Installer, which places things elsewhere.) Because file lists are dynamically generated now, the test ensuring things remain in sync has been deleted. Good riddance. While this is a huge step towards unifying the Windows installers, there's still some improvements that can be made. But I think it is worth celebrating the milestone of getting both Inno and WiX to essentially share core packaging code and workflows. That should make it much easier to change the installers going forward. This will aid support of Python 3. Differential Revision: https://phab.mercurial-scm.org/D7173

File last commit:

r43812:2fe6121c default
r44022:94eac340 default
Show More
dateutil.py
351 lines | 10.6 KiB | text/x-python | PythonLexer
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 # util.py - Mercurial utility functions relative to dates
#
# Copyright 2018 Boris Feld <boris.feld@octobus.net>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import calendar
import datetime
import time
from ..i18n import _
from .. import (
encoding,
error,
pycompat,
)
# used by parsedate
defaultdateformats = (
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'%Y-%m-%dT%H:%M:%S', # the 'real' ISO8601
b'%Y-%m-%dT%H:%M', # without seconds
b'%Y-%m-%dT%H%M%S', # another awful but legal variant without :
b'%Y-%m-%dT%H%M', # without seconds
b'%Y-%m-%d %H:%M:%S', # our common legal variant
b'%Y-%m-%d %H:%M', # without seconds
b'%Y-%m-%d %H%M%S', # without :
b'%Y-%m-%d %H%M', # without seconds
b'%Y-%m-%d %I:%M:%S%p',
b'%Y-%m-%d %H:%M',
b'%Y-%m-%d %I:%M%p',
b'%Y-%m-%d',
b'%m-%d',
b'%m/%d',
b'%m/%d/%y',
b'%m/%d/%Y',
b'%a %b %d %H:%M:%S %Y',
b'%a %b %d %I:%M:%S%p %Y',
b'%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
b'%b %d %H:%M:%S %Y',
b'%b %d %I:%M:%S%p %Y',
b'%b %d %H:%M:%S',
b'%b %d %I:%M:%S%p',
b'%b %d %H:%M',
b'%b %d %I:%M%p',
b'%b %d %Y',
b'%b %d',
b'%H:%M:%S',
b'%I:%M:%S%p',
b'%H:%M',
b'%I:%M%p',
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 )
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 extendeddateformats = defaultdateformats + (b"%Y", b"%Y-%m", b"%b", b"%b %Y",)
Augie Fackler
formatting: blacken the codebase...
r43346
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625
def makedate(timestamp=None):
'''Return a unix timestamp (or the current time) as a (unixtime,
offset) tuple based off the local timezone.'''
if timestamp is None:
timestamp = time.time()
if timestamp < 0:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 hint = _(b"check your clock")
raise error.Abort(_(b"negative timestamp: %d") % timestamp, hint=hint)
Augie Fackler
formatting: blacken the codebase...
r43346 delta = datetime.datetime.utcfromtimestamp(
timestamp
) - datetime.datetime.fromtimestamp(timestamp)
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 tz = delta.days * 86400 + delta.seconds
return timestamp, tz
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 def datestr(date=None, format=b'%a %b %d %H:%M:%S %Y %1%2'):
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 """represent a (unixtime, offset) tuple as a localized time.
unixtime is seconds since the epoch, and offset is the time zone's
number of seconds away from UTC.
>>> datestr((0, 0))
'Thu Jan 01 00:00:00 1970 +0000'
>>> datestr((42, 0))
'Thu Jan 01 00:00:42 1970 +0000'
>>> datestr((-42, 0))
'Wed Dec 31 23:59:18 1969 +0000'
>>> datestr((0x7fffffff, 0))
'Tue Jan 19 03:14:07 2038 +0000'
>>> datestr((-0x80000000, 0))
'Fri Dec 13 20:45:52 1901 +0000'
"""
t, tz = date or makedate()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if b"%1" in format or b"%2" in format or b"%z" in format:
sign = (tz > 0) and b"-" or b"+"
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 minutes = abs(tz) // 60
q, r = divmod(minutes, 60)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 format = format.replace(b"%z", b"%1%2")
format = format.replace(b"%1", b"%c%02d" % (sign, q))
format = format.replace(b"%2", b"%02d" % r)
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 d = t - tz
Augie Fackler
formatting: blacken the codebase...
r43346 if d > 0x7FFFFFFF:
d = 0x7FFFFFFF
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 elif d < -0x80000000:
d = -0x80000000
# Never use time.gmtime() and datetime.datetime.fromtimestamp()
# because they use the gmtime() system call which is buggy on Windows
# for negative values.
t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
s = encoding.strtolocal(t.strftime(encoding.strfromlocal(format)))
return s
Augie Fackler
formatting: blacken the codebase...
r43346
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 def shortdate(date=None):
"""turn (timestamp, tzoff) tuple into iso 8631 date."""
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return datestr(date, format=b'%Y-%m-%d')
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625
Augie Fackler
formatting: blacken the codebase...
r43346
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 def parsetimezone(s):
"""find a trailing timezone, if any, in string, and return a
(offset, remainder) pair"""
s = pycompat.bytestr(s)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if s.endswith(b"GMT") or s.endswith(b"UTC"):
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 return 0, s[:-3].rstrip()
# Unix-style timezones [+-]hhmm
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if len(s) >= 5 and s[-5] in b"+-" and s[-4:].isdigit():
sign = (s[-5] == b"+") and 1 or -1
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 hours = int(s[-4:-2])
minutes = int(s[-2:])
return -sign * (hours * 60 + minutes) * 60, s[:-5].rstrip()
# ISO8601 trailing Z
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if s.endswith(b"Z") and s[-2:-1].isdigit():
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 return 0, s[:-1]
# ISO8601-style [+-]hh:mm
Augie Fackler
formatting: blacken the codebase...
r43346 if (
len(s) >= 6
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 and s[-6] in b"+-"
and s[-3] == b":"
Augie Fackler
formatting: blacken the codebase...
r43346 and s[-5:-3].isdigit()
and s[-2:].isdigit()
):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 sign = (s[-6] == b"+") and 1 or -1
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 hours = int(s[-5:-3])
minutes = int(s[-2:])
return -sign * (hours * 60 + minutes) * 60, s[:-6]
return None, s
Augie Fackler
formatting: blacken the codebase...
r43346
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 def strdate(string, format, defaults=None):
"""parse a localized time string and return a (unixtime, offset) tuple.
if the string cannot be parsed, ValueError is raised."""
if defaults is None:
defaults = {}
# NOTE: unixtime = localunixtime + offset
offset, date = parsetimezone(string)
# add missing elements from defaults
Augie Fackler
formatting: blacken the codebase...
r43346 usenow = False # default to using biased defaults
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 for part in (
b"S",
b"M",
b"HI",
b"d",
b"mb",
b"yY",
): # decreasing specificity
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 part = pycompat.bytestr(part)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 found = [True for p in part if (b"%" + p) in format]
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 if not found:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 date += b"@" + defaults[part][usenow]
format += b"@%" + part[0]
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 else:
# We've found a specific time element, less specific time
# elements are relative to today
usenow = True
Augie Fackler
formatting: blacken the codebase...
r43346 timetuple = time.strptime(
encoding.strfromlocal(date), encoding.strfromlocal(format)
)
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 localunixtime = int(calendar.timegm(timetuple))
if offset is None:
# local timezone
unixtime = int(time.mktime(timetuple))
offset = unixtime - localunixtime
else:
unixtime = localunixtime + offset
return unixtime, offset
Augie Fackler
formatting: blacken the codebase...
r43346
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 def parsedate(date, formats=None, bias=None):
"""parse a localized date/time and return a (unixtime, offset) tuple.
The date may be a "unixtime offset" string or in one of the specified
formats. If the date already is a (unixtime, offset) tuple, it is returned.
>>> parsedate(b' today ') == parsedate(
... datetime.date.today().strftime('%b %d').encode('ascii'))
True
>>> parsedate(b'yesterday ') == parsedate(
... (datetime.date.today() - datetime.timedelta(days=1)
... ).strftime('%b %d').encode('ascii'))
True
>>> now, tz = makedate()
>>> strnow, strtz = parsedate(b'now')
>>> (strnow - now) < 1
True
>>> tz == strtz
True
"""
if bias is None:
bias = {}
if not date:
return 0, 0
if isinstance(date, tuple) and len(date) == 2:
return date
if not formats:
formats = defaultdateformats
date = date.strip()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if date == b'now' or date == _(b'now'):
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 return makedate()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if date == b'today' or date == _(b'today'):
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 date = datetime.date.today().strftime('%b %d')
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 date = encoding.strtolocal(date)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif date == b'yesterday' or date == _(b'yesterday'):
Augie Fackler
formatting: blacken the codebase...
r43346 date = (datetime.date.today() - datetime.timedelta(days=1)).strftime(
r'%b %d'
)
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 date = encoding.strtolocal(date)
try:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 when, offset = map(int, date.split(b' '))
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 except ValueError:
# fill out defaults
now = makedate()
defaults = {}
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 for part in (b"d", b"mb", b"yY", b"HI", b"M", b"S"):
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 # this piece is for rounding the specific end of unknowns
b = bias.get(part)
if b is None:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if part[0:1] in b"HMS":
b = b"00"
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b = b"0"
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625
# this piece is for matching the generic end to today's date
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 n = datestr(now, b"%" + part[0:1])
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625
defaults[part] = (b, n)
for format in formats:
try:
when, offset = strdate(date, format, defaults)
except (ValueError, OverflowError):
pass
else:
break
else:
raise error.ParseError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _(b'invalid date: %r') % pycompat.bytestr(date)
Augie Fackler
formatting: blacken the codebase...
r43346 )
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 # validate explicit (probably user-specified) date and
# time zone offset. values must fit in signed 32 bits for
# current 32-bit linux runtimes. timezones go from UTC-12
# to UTC+14
Augie Fackler
formatting: blacken the codebase...
r43346 if when < -0x80000000 or when > 0x7FFFFFFF:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.ParseError(_(b'date exceeds 32 bits: %d') % when)
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 if offset < -50400 or offset > 43200:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.ParseError(_(b'impossible time zone offset: %d') % offset)
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 return when, offset
Augie Fackler
formatting: blacken the codebase...
r43346
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 def matchdate(date):
"""Return a function that matches a given date match specifier
Formats include:
'{date}' match a given date to the accuracy provided
'<{date}' on or before a given date
'>{date}' on or after a given date
>>> p1 = parsedate(b"10:29:59")
>>> p2 = parsedate(b"10:30:00")
>>> p3 = parsedate(b"10:30:59")
>>> p4 = parsedate(b"10:31:00")
>>> p5 = parsedate(b"Sep 15 10:30:00 1999")
>>> f = matchdate(b"10:30")
>>> f(p1[0])
False
>>> f(p2[0])
True
>>> f(p3[0])
True
>>> f(p4[0])
False
>>> f(p5[0])
False
"""
def lower(date):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 d = {b'mb': b"1", b'd': b"1"}
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 return parsedate(date, extendeddateformats, d)[0]
def upper(date):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 d = {b'mb': b"12", b'HI': b"23", b'M': b"59", b'S': b"59"}
for days in (b"31", b"30", b"29"):
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 try:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 d[b"d"] = days
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 return parsedate(date, extendeddateformats, d)[0]
except error.ParseError:
pass
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 d[b"d"] = b"28"
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 return parsedate(date, extendeddateformats, d)[0]
date = date.strip()
if not date:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.Abort(_(b"dates cannot consist entirely of whitespace"))
Mark Thomas
py3: fix test-parse-date.t...
r40292 elif date[0:1] == b"<":
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 if not date[1:]:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.Abort(_(b"invalid day spec, use '<DATE'"))
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 when = upper(date[1:])
return lambda x: x <= when
Mark Thomas
py3: fix test-parse-date.t...
r40292 elif date[0:1] == b">":
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 if not date[1:]:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.Abort(_(b"invalid day spec, use '>DATE'"))
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 when = lower(date[1:])
return lambda x: x >= when
Mark Thomas
py3: fix test-parse-date.t...
r40292 elif date[0:1] == b"-":
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 try:
days = int(date[1:])
except ValueError:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.Abort(_(b"invalid day spec: %s") % date[1:])
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 if days < 0:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.Abort(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _(b"%s must be nonnegative (see 'hg help dates')") % date[1:]
Augie Fackler
formatting: blacken the codebase...
r43346 )
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 when = makedate()[0] - days * 3600 * 24
return lambda x: x >= when
Mark Thomas
py3: fix test-parse-date.t...
r40292 elif b" to " in date:
a, b = date.split(b" to ")
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 start, stop = lower(a), upper(b)
return lambda x: x >= start and x <= stop
else:
start, stop = lower(date), upper(date)
return lambda x: x >= start and x <= stop