zforman | 08d91b7 | 2016-02-12 06:23:42 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # Copyright (c) 2016 The Chromium Authors. All rights reserved. |
| 3 | # Use of this source code is governed by a BSD-style license that can be |
| 4 | # found in the LICENSE file. |
| 5 | """Writes a file that contains a define that approximates the build date. |
| 6 | |
maruel | 1c9b0223 | 2016-04-04 20:21:41 | [diff] [blame] | 7 | build_type impacts the timestamp generated: |
| 8 | - default: the build date is set to the most recent first Sunday of a month at |
| 9 | 5:00am. The reason is that it is a time where invalidating the build cache |
| 10 | shouldn't have major reprecussions (due to lower load). |
| 11 | - official: the build date is set to the current date at 5:00am, or the day |
| 12 | before if the current time is before 5:00am. |
| 13 | Either way, it is guaranteed to be in the past and always in UTC. |
zforman | 08d91b7 | 2016-02-12 06:23:42 | [diff] [blame] | 14 | |
| 15 | It is also possible to explicitly set a build date to be used. |
zforman | 08d91b7 | 2016-02-12 06:23:42 | [diff] [blame] | 16 | """ |
| 17 | |
| 18 | import argparse |
| 19 | import calendar |
| 20 | import datetime |
maruel | 1c9b0223 | 2016-04-04 20:21:41 | [diff] [blame] | 21 | import doctest |
zforman | 08d91b7 | 2016-02-12 06:23:42 | [diff] [blame] | 22 | import os |
| 23 | import sys |
| 24 | |
| 25 | |
| 26 | def GetFirstSundayOfMonth(year, month): |
maruel | 1c9b0223 | 2016-04-04 20:21:41 | [diff] [blame] | 27 | """Returns the first sunday of the given month of the given year. |
| 28 | |
| 29 | >>> GetFirstSundayOfMonth(2016, 2) |
| 30 | 7 |
| 31 | >>> GetFirstSundayOfMonth(2016, 3) |
| 32 | 6 |
| 33 | >>> GetFirstSundayOfMonth(2000, 1) |
| 34 | 2 |
| 35 | """ |
zforman | 08d91b7 | 2016-02-12 06:23:42 | [diff] [blame] | 36 | weeks = calendar.Calendar().monthdays2calendar(year, month) |
| 37 | # Return the first day in the first week that is a Sunday. |
| 38 | return [date_day[0] for date_day in weeks[0] if date_day[1] == 6][0] |
| 39 | |
| 40 | |
zforman | 08d91b7 | 2016-02-12 06:23:42 | [diff] [blame] | 41 | def GetBuildDate(build_type, utc_now): |
maruel | 1c9b0223 | 2016-04-04 20:21:41 | [diff] [blame] | 42 | """Gets the approximate build date given the specific build type. |
| 43 | |
| 44 | >>> GetBuildDate('default', datetime.datetime(2016, 2, 6, 1, 2, 3)) |
| 45 | 'Jan 03 2016 01:02:03' |
| 46 | >>> GetBuildDate('default', datetime.datetime(2016, 2, 7, 5)) |
| 47 | 'Feb 07 2016 05:00:00' |
| 48 | >>> GetBuildDate('default', datetime.datetime(2016, 2, 8, 5)) |
| 49 | 'Feb 07 2016 05:00:00' |
| 50 | """ |
zforman | 08d91b7 | 2016-02-12 06:23:42 | [diff] [blame] | 51 | day = utc_now.day |
| 52 | month = utc_now.month |
| 53 | year = utc_now.year |
| 54 | if build_type != 'official': |
| 55 | first_sunday = GetFirstSundayOfMonth(year, month) |
| 56 | # If our build is after the first Sunday, we've already refreshed our build |
| 57 | # cache on a quiet day, so just use that day. |
| 58 | # Otherwise, take the first Sunday of the previous month. |
| 59 | if day >= first_sunday: |
| 60 | day = first_sunday |
| 61 | else: |
| 62 | month -= 1 |
| 63 | if month == 0: |
| 64 | month = 12 |
| 65 | year -= 1 |
| 66 | day = GetFirstSundayOfMonth(year, month) |
maruel | 1c9b0223 | 2016-04-04 20:21:41 | [diff] [blame] | 67 | now = datetime.datetime( |
| 68 | year, month, day, utc_now.hour, utc_now.minute, utc_now.second) |
| 69 | return '{:%b %d %Y %H:%M:%S}'.format(now) |
zforman | 08d91b7 | 2016-02-12 06:23:42 | [diff] [blame] | 70 | |
| 71 | |
| 72 | def main(): |
maruel | 1c9b0223 | 2016-04-04 20:21:41 | [diff] [blame] | 73 | if doctest.testmod()[0]: |
| 74 | return 1 |
| 75 | argument_parser = argparse.ArgumentParser( |
| 76 | description=sys.modules[__name__].__doc__, |
| 77 | formatter_class=argparse.RawDescriptionHelpFormatter) |
zforman | 08d91b7 | 2016-02-12 06:23:42 | [diff] [blame] | 78 | argument_parser.add_argument('output_file', help='The file to write to') |
maruel | 1c9b0223 | 2016-04-04 20:21:41 | [diff] [blame] | 79 | argument_parser.add_argument( |
| 80 | 'build_type', help='The type of build', choices=('official', 'default')) |
| 81 | argument_parser.add_argument( |
| 82 | 'build_date_override', nargs='?', |
| 83 | help='Optional override for the build date. Format must be ' |
| 84 | '\'Mmm DD YYYY HH:MM:SS\'') |
zforman | 08d91b7 | 2016-02-12 06:23:42 | [diff] [blame] | 85 | args = argument_parser.parse_args() |
| 86 | |
| 87 | if args.build_date_override: |
maruel | 1c9b0223 | 2016-04-04 20:21:41 | [diff] [blame] | 88 | # Format is expected to be "Mmm DD YYYY HH:MM:SS". |
zforman | 08d91b7 | 2016-02-12 06:23:42 | [diff] [blame] | 89 | build_date = args.build_date_override |
| 90 | else: |
maruel | 1c9b0223 | 2016-04-04 20:21:41 | [diff] [blame] | 91 | now = datetime.datetime.utcnow() |
| 92 | if now.hour < 5: |
| 93 | # The time is locked at 5:00 am in UTC to cause the build cache |
| 94 | # invalidation to not happen exactly at midnight. Use the same calculation |
| 95 | # as the day before. |
| 96 | # See //base/build_time.cc. |
Marc-Antoine Ruel | 031aebd | 2016-04-05 00:15:03 | [diff] [blame] | 97 | now = now - datetime.timedelta(days=1) |
maruel | 1c9b0223 | 2016-04-04 20:21:41 | [diff] [blame] | 98 | now = datetime.datetime(now.year, now.month, now.day, 5, 0, 0) |
| 99 | build_date = GetBuildDate(args.build_type, now) |
zforman | 08d91b7 | 2016-02-12 06:23:42 | [diff] [blame] | 100 | |
| 101 | output = ('// Generated by //build/write_build_date_header.py\n' |
| 102 | '#ifndef BUILD_DATE\n' |
| 103 | '#define BUILD_DATE "{}"\n' |
| 104 | '#endif // BUILD_DATE\n'.format(build_date)) |
| 105 | |
| 106 | current_contents = '' |
| 107 | if os.path.isfile(args.output_file): |
| 108 | with open(args.output_file, 'r') as current_file: |
| 109 | current_contents = current_file.read() |
| 110 | |
| 111 | if current_contents != output: |
| 112 | with open(args.output_file, 'w') as output_file: |
| 113 | output_file.write(output) |
| 114 | return 0 |
| 115 | |
| 116 | |
| 117 | if __name__ == '__main__': |
| 118 | sys.exit(main()) |