| #!/usr/bin/env python3 | 
 | # | 
 | # Copyright 2023 The Dawn & Tint Authors | 
 | # | 
 | # Redistribution and use in source and binary forms, with or without | 
 | # modification, are permitted provided that the following conditions are met: | 
 | # | 
 | # 1. Redistributions of source code must retain the above copyright notice, this | 
 | #    list of conditions and the following disclaimer. | 
 | # | 
 | # 2. Redistributions in binary form must reproduce the above copyright notice, | 
 | #    this list of conditions and the following disclaimer in the documentation | 
 | #    and/or other materials provided with the distribution. | 
 | # | 
 | # 3. Neither the name of the copyright holder nor the names of its | 
 | #    contributors may be used to endorse or promote products derived from | 
 | #    this software without specific prior written permission. | 
 | # | 
 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | 
 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 
 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | 
 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | 
 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | 
 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | 
 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | 
 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | 
 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
 | """Script for updating the active milestones for the Dawn project. | 
 |  | 
 | This is largely based on the Chromium equivalent in | 
 | chromium/src/infra/config/scripts/milestones.py. | 
 |  | 
 | To activate a new branch, run the following from the root of the repo (where MMM | 
 | is the milestone number and BBBB is the branch number): | 
 | ``` | 
 | infra/config/scripts/milestones.py activate --milestone MMM --branch BBBB | 
 | infra/config/global/main.star | 
 | ``` | 
 |  | 
 | To deactivate a branch, run the following from the root of the repo (where MMM | 
 | is the milestone number): | 
 | ``` | 
 | infra/config/scripts/milestones.py deactivate --milestone MMM | 
 | infra/config/global/main.star | 
 | ``` | 
 | """ | 
 |  | 
 | import argparse | 
 | import json | 
 | import os | 
 |  | 
 | INFRA_CONFIG_GLOBAL_DIR = os.path.realpath( | 
 |     os.path.join(os.path.dirname(__file__), '..', 'global')) | 
 |  | 
 |  | 
 | def parse_args(): | 
 |     parser = argparse.ArgumentParser( | 
 |         description='Update the active milestones for the Dawn project') | 
 |     parser.set_defaults(func=None) | 
 |     parser.add_argument('--milestones-json', | 
 |                         default=os.path.join(INFRA_CONFIG_GLOBAL_DIR, | 
 |                                              'milestones.json'), | 
 |                         help='Path to the milestones.json file to modify') | 
 |  | 
 |     subparsers = parser.add_subparsers() | 
 |  | 
 |     activate_parser = subparsers.add_parser( | 
 |         'activate', help='Add an additional active milestone.') | 
 |     activate_parser.set_defaults(func=cmd_activate) | 
 |     activate_parser.add_argument( | 
 |         '--milestone', | 
 |         required=True, | 
 |         type=int, | 
 |         help='The milestone identifier/release channel number') | 
 |     activate_parser.add_argument( | 
 |         '--branch', | 
 |         required=True, | 
 |         help='The branch name, which should correspond to a ref in refs/heads') | 
 |  | 
 |     deactivate_parser = subparsers.add_parser( | 
 |         'deactivate', help='Remove an active milestone') | 
 |     deactivate_parser.set_defaults(func=cmd_deactivate) | 
 |     deactivate_parser.add_argument( | 
 |         '--milestone', | 
 |         required=True, | 
 |         type=int, | 
 |         help='The milestone identifier/release channel number') | 
 |  | 
 |     args = parser.parse_args() | 
 |     if not args.func: | 
 |         parser.error('No sub-command specified') | 
 |     return args | 
 |  | 
 |  | 
 | def _sort_milestones(milestones): | 
 |     # We have to manually sort here instead of relying on | 
 |     # json.dump(..., sort_keys=True) later since we only want to sort the top | 
 |     # level keys, not all keys. | 
 |     milestones = { | 
 |         str(k): milestones[str(k)] | 
 |         for k in sorted([int(s) for s in milestones]) | 
 |     } | 
 |     return milestones | 
 |  | 
 |  | 
 | def add_milestone(milestones, milestone_num, branch): | 
 |     if str(milestone_num) in milestones: | 
 |         raise RuntimeError('Milestone %d already exists' % milestone_num) | 
 |  | 
 |     milestones[str(milestone_num)] = { | 
 |         'name': f'm{milestone_num}', | 
 |         'chromium_project': f'chromium-m{milestone_num}', | 
 |         'ref': f'refs/heads/{branch}', | 
 |         'platforms': [ | 
 |             'linux', | 
 |             'mac', | 
 |             'win', | 
 |         ], | 
 |     } | 
 |  | 
 |     return _sort_milestones(milestones) | 
 |  | 
 |  | 
 | def remove_milestone(milestones, milestone_num): | 
 |     if str(milestone_num) not in milestones: | 
 |         raise RuntimeError('Milestone %d does not exist' % milestone_num) | 
 |     del milestones[str(milestone_num)] | 
 |     # Not strictly necessary, but returning a value keeps this consistent with | 
 |     # add_milestone. | 
 |     return milestones | 
 |  | 
 |  | 
 | def cmd_activate(args): | 
 |     with open(args.milestones_json) as infile: | 
 |         milestones = json.load(infile) | 
 |     milestones = add_milestone(milestones, args.milestone, args.branch) | 
 |     with open(args.milestones_json, 'w') as outfile: | 
 |         json.dump(milestones, outfile, indent=4) | 
 |  | 
 |  | 
 | def cmd_deactivate(args): | 
 |     with open(args.milestones_json) as infile: | 
 |         milestones = json.load(infile) | 
 |     milestones = remove_milestone(milestones, args.milestone) | 
 |     with open(args.milestones_json, 'w') as outfile: | 
 |         json.dump(milestones, outfile, indent=4) | 
 |  | 
 |  | 
 | def main(): | 
 |     args = parse_args() | 
 |     args.func(args) | 
 |  | 
 |  | 
 | if __name__ == '__main__': | 
 |     main() |