#!/usr/bin/env python # Copyright (c) 2016 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Writes a file that contains a define that approximates the build date. build_type impacts the timestamp generated: - default: the build date is set to the most recent first Sunday of a month at 5:00am. The reason is that it is a time where invalidating the build cache shouldn't have major reprecussions (due to lower load). - official: the build date is set to the current date at 5:00am, or the day before if the current time is before 5:00am. Either way, it is guaranteed to be in the past and always in UTC. It is also possible to explicitly set a build date to be used. """ import argparse import calendar import datetime import doctest import os import sys def GetFirstSundayOfMonth(year, month): """Returns the first sunday of the given month of the given year. >>> GetFirstSundayOfMonth(2016, 2) 7 >>> GetFirstSundayOfMonth(2016, 3) 6 >>> GetFirstSundayOfMonth(2000, 1) 2 """ weeks = calendar.Calendar().monthdays2calendar(year, month) # Return the first day in the first week that is a Sunday. return [date_day[0] for date_day in weeks[0] if date_day[1] == 6][0] def GetBuildDate(build_type, utc_now): """Gets the approximate build date given the specific build type. >>> GetBuildDate('default', datetime.datetime(2016, 2, 6, 1, 2, 3)) 'Jan 03 2016 01:02:03' >>> GetBuildDate('default', datetime.datetime(2016, 2, 7, 5)) 'Feb 07 2016 05:00:00' >>> GetBuildDate('default', datetime.datetime(2016, 2, 8, 5)) 'Feb 07 2016 05:00:00' """ day = utc_now.day month = utc_now.month year = utc_now.year if build_type != 'official': first_sunday = GetFirstSundayOfMonth(year, month) # If our build is after the first Sunday, we've already refreshed our build # cache on a quiet day, so just use that day. # Otherwise, take the first Sunday of the previous month. if day >= first_sunday: day = first_sunday else: month -= 1 if month == 0: month = 12 year -= 1 day = GetFirstSundayOfMonth(year, month) now = datetime.datetime( year, month, day, utc_now.hour, utc_now.minute, utc_now.second) return '{:%b %d %Y %H:%M:%S}'.format(now) def main(): if doctest.testmod()[0]: return 1 argument_parser = argparse.ArgumentParser( description=sys.modules[__name__].__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) argument_parser.add_argument('output_file', help='The file to write to') argument_parser.add_argument( 'build_type', help='The type of build', choices=('official', 'default')) argument_parser.add_argument( 'build_date_override', nargs='?', help='Optional override for the build date. Format must be ' '\'Mmm DD YYYY HH:MM:SS\'') args = argument_parser.parse_args() if args.build_date_override: # Format is expected to be "Mmm DD YYYY HH:MM:SS". build_date = args.build_date_override else: now = datetime.datetime.utcnow() if now.hour < 5: # The time is locked at 5:00 am in UTC to cause the build cache # invalidation to not happen exactly at midnight. Use the same calculation # as the day before. # See //base/build_time.cc. now = now - datetime.timedelta(days=1) now = datetime.datetime(now.year, now.month, now.day, 5, 0, 0) build_date = GetBuildDate(args.build_type, now) output = ('// Generated by //build/write_build_date_header.py\n' '#ifndef BUILD_DATE\n' '#define BUILD_DATE "{}"\n' '#endif // BUILD_DATE\n'.format(build_date)) current_contents = '' if os.path.isfile(args.output_file): with open(args.output_file, 'r') as current_file: current_contents = current_file.read() if current_contents != output: with open(args.output_file, 'w') as output_file: output_file.write(output) return 0 if __name__ == '__main__': sys.exit(main())