tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

compute_build_timestamp.py (5467B)


      1 #!/usr/bin/env python3
      2 # Copyright 2018 The Chromium Authors
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 """Returns a timestamp that approximates the build date.
      6 
      7 build_type impacts the timestamp generated, both relative to the date of the
      8 last recent commit:
      9 - default: the build date is set to the most recent first Sunday of a month at
     10  5:00am. The reason is that it is a time where invalidating the build cache
     11  shouldn't have major repercussions (due to lower load).
     12 - official: the build date is set to the time of the most recent commit.
     13 Either way, it is guaranteed to be in the past and always in UTC.
     14 """
     15 
     16 # The requirements for the timestamp:
     17 # (1) for the purposes of continuous integration, longer duration
     18 #     between cache invalidation is better, but >=1mo is preferable.
     19 # (2) for security purposes, timebombs would ideally be as close to
     20 #     the actual time of the build as possible. It must be in the past.
     21 # (3) HSTS certificate pinning is valid for 70 days. To make CI builds enforce
     22 #     HTST pinning, <=1mo is preferable.
     23 #
     24 # On Windows, the timestamp is also written in the PE/COFF file header of
     25 # executables of dlls.  That timestamp and the executable's file size are
     26 # the only two pieces of information that identify a given executable on
     27 # the symbol server, so rarely changing timestamps can cause conflicts there
     28 # as well. We only upload symbols for official builds to the symbol server.
     29 
     30 
     31 import argparse
     32 import calendar
     33 import datetime
     34 import doctest
     35 import os
     36 import sys
     37 
     38 
     39 THIS_DIR = os.path.abspath(os.path.dirname(__file__))
     40 
     41 
     42 def GetFirstSundayOfMonth(year, month):
     43  """Returns the first sunday of the given month of the given year.
     44 
     45  >>> GetFirstSundayOfMonth(2016, 2)
     46  7
     47  >>> GetFirstSundayOfMonth(2016, 3)
     48  6
     49  >>> GetFirstSundayOfMonth(2000, 1)
     50  2
     51  """
     52  weeks = calendar.Calendar().monthdays2calendar(year, month)
     53  # Return the first day in the first week that is a Sunday.
     54  return [date_day[0] for date_day in weeks[0] if date_day[1] == 6][0]
     55 
     56 
     57 def GetUnofficialBuildDate(build_date):
     58  """Gets the approximate build date given the specific build type.
     59 
     60  >>> GetUnofficialBuildDate(datetime.datetime(2016, 2, 6, 1, 2, 3))
     61  datetime.datetime(2016, 1, 3, 5, 0)
     62  >>> GetUnofficialBuildDate(datetime.datetime(2016, 2, 7, 5))
     63  datetime.datetime(2016, 2, 7, 5, 0)
     64  >>> GetUnofficialBuildDate(datetime.datetime(2016, 2, 8, 5))
     65  datetime.datetime(2016, 2, 7, 5, 0)
     66  """
     67 
     68  if build_date.hour < 5:
     69    # The time is locked at 5:00 am in UTC to cause the build cache
     70    # invalidation to not happen exactly at midnight. Use the same calculation
     71    # as the day before.
     72    # See //base/build_time.cc.
     73    build_date = build_date - datetime.timedelta(days=1)
     74  build_date = datetime.datetime(build_date.year, build_date.month,
     75                                 build_date.day, 5, 0, 0)
     76 
     77  day = build_date.day
     78  month = build_date.month
     79  year = build_date.year
     80  first_sunday = GetFirstSundayOfMonth(year, month)
     81  # If our build is after the first Sunday, we've already refreshed our build
     82  # cache on a quiet day, so just use that day.
     83  # Otherwise, take the first Sunday of the previous month.
     84  if day >= first_sunday:
     85    day = first_sunday
     86  else:
     87    month -= 1
     88    if month == 0:
     89      month = 12
     90      year -= 1
     91    day = GetFirstSundayOfMonth(year, month)
     92  return datetime.datetime(
     93      year, month, day, build_date.hour, build_date.minute, build_date.second)
     94 
     95 
     96 def main():
     97  if doctest.testmod()[0]:
     98    return 1
     99  argument_parser = argparse.ArgumentParser()
    100  argument_parser.add_argument(
    101      'build_type', help='The type of build', choices=('official', 'default'))
    102  args = argument_parser.parse_args()
    103 
    104  # The mtime of the revision in build/util/LASTCHANGE is stored in a file
    105  # next to it. Read it, to get a deterministic time close to "now".
    106  # That date is then modified as described at the top of the file so that
    107  # it changes less frequently than with every commit.
    108  # This intentionally always uses build/util/LASTCHANGE's commit time even if
    109  # use_dummy_lastchange is set.
    110  lastchange_file = os.path.join(THIS_DIR, 'util', 'LASTCHANGE.committime')
    111  last_commit_timestamp = int(open(lastchange_file).read())
    112  build_date = datetime.datetime.fromtimestamp(last_commit_timestamp,
    113                                               datetime.timezone.utc)
    114 
    115  # For official builds we want full fidelity time stamps because official
    116  # builds are typically added to symbol servers and Windows symbol servers
    117  # use the link timestamp as the prime differentiator, but for unofficial
    118  # builds we do lots of quantization to avoid churn.
    119  offset = 0
    120  if args.build_type == 'official':
    121    if os.name == 'nt':
    122      version_path = os.path.join(THIS_DIR, os.pardir, 'chrome', 'VERSION')
    123      with open(version_path) as f:
    124        patch_line = f.readlines()[3].strip()
    125        # Use the patch number as an offset to the build date so that multiple
    126        # versions with different patch numbers built from the same source code
    127        # will get different build_date values. This is critical for Windows
    128        # symbol servers, to avoid collisions.
    129        assert patch_line.startswith('PATCH=')
    130        offset = int(patch_line[6:])
    131  else:
    132    build_date = GetUnofficialBuildDate(build_date)
    133  print(offset + int(calendar.timegm(build_date.utctimetuple())))
    134  return 0
    135 
    136 
    137 if __name__ == '__main__':
    138  sys.exit(main())