blob: 6716f4f19842cf1fa9252101aa5c61aa0c2bd736 [file] [log] [blame]
Brad Bishop35982432018-08-30 17:27:20 -04001#!/usr/bin/env python3
2
3# Contributors Listed Below - COPYRIGHT 2018
4# [+] International Business Machines Corp.
5#
6#
7# Licensed under the Apache License, Version 2.0 (the "License");
8# you may not use this file except in compliance with the License.
9# You may obtain a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS,
15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
16# implied. See the License for the specific language governing
17# permissions and limitations under the License.
18
19import argparse
20import os
21import sh
22import sys
23
24git = sh.git.bake('--no-pager')
25
26
27def log(msg, args):
28 if args.noisy:
29 sys.stderr.write('{}\n'.format(msg))
30
31
32def git_clone_or_reset(local_name, remote, args):
33 if not os.path.exists(local_name):
34 log('cloning into {}...'.format(local_name), args)
35 git.clone(remote, local_name)
36 else:
37 log('{} exists, updating...'.format(local_name), args)
38 git.fetch(_cwd=local_name)
39 git.reset('--hard', 'FETCH_HEAD', _cwd=local_name)
40
41
42def extract_sha_from_recipe(recipe):
43 with open(recipe) as fp:
44 project = None
45 sha = None
46
47 for line in fp:
48 if 'SRCREV' in line:
49 sha = line.split('=')[-1].replace('"', '').strip()
50 elif '_URI' in line and 'github.com/openbmc' in line:
51 uri = line.split(';')[0].split('=')[-1].replace('"', '').strip()
52 project = uri.split('/')[-1].replace('.git', '')
53
54 if project and sha:
55 return (project, sha)
56
57 raise RuntimeError('No SRCREV or URI found in {}'.format(recipe))
58
59
60def find_candidate_recipes(meta, args):
61 remote_fmt_args = (args.ssh_config_host, meta)
62 remote = 'ssh://{}/openbmc/{}'.format(*remote_fmt_args)
63 try:
64 git_clone_or_reset(meta, remote, args)
65 except sh.ErrorReturnCode as e:
66 log('{}'.format(e), args)
67 return []
68
69 grep_args = ['-l', '-e', '_URI', '--and', '-e', 'github.com/openbmc']
70 try:
71 return git.grep(*grep_args, _cwd=meta).stdout.decode('utf-8').split()
72 except sh.ErrorReturnCode_1:
73 pass
74 except sh.ErrorReturnCode as e:
75 log('{}'.format(e), args)
76
77 return []
78
79
80def find_and_process_bumps(meta, args):
81 candidate_recipes = find_candidate_recipes(meta, args)
82
83 for recipe in candidate_recipes:
84 full_recipe_path = os.path.join(meta, recipe)
85 recipe_basename = os.path.basename(full_recipe_path)
86 project_name, recipe_sha = extract_sha_from_recipe(full_recipe_path)
87
88 remote_fmt_args = (args.ssh_config_host, project_name)
89 remote = 'ssh://{}/openbmc/{}'.format(*remote_fmt_args)
90 ls_remote_args = [remote, 'refs/heads/{}'.format(args.branch)]
91 try:
92 project_sha = git('ls-remote', *ls_remote_args)
93 project_sha = project_sha.stdout.decode('utf-8').split()[0]
94 except sh.ErrorReturnCode as e:
95 log('{}'.format(e), args)
96 continue
97
98 if project_sha == recipe_sha:
99 message_args = (recipe_basename, recipe_sha[:10])
100 print('{} is up to date ({})'.format(*message_args))
101 continue
102
103 change_id = 'autobump {} {} {}'.format(recipe, recipe_sha, project_sha)
104 hash_object_args = ['-t', 'blob', '--stdin']
105 change_id = git(sh.echo(change_id), 'hash-object', *hash_object_args)
106 change_id = 'I{}'.format(change_id.strip())
107
108 query_args = ['query', 'change:{}'.format(change_id)]
109 gerrit_query_result = args.gerrit(*query_args)
110 gerrit_query_result = gerrit_query_result.stdout.decode('utf-8')
111
112 if (change_id in gerrit_query_result):
113 message_args = (recipe_basename, change_id)
114 print('{} {} already exists'.format(*message_args))
115 continue
116
117 message_args = (recipe_basename, recipe_sha[:10], project_sha[:10])
118 print('{} updating from {} to {}'.format(*message_args))
119
120 remote_args = (args.ssh_config_host, project_name)
121 remote = 'ssh://{}/openbmc/{}'.format(*remote_args)
122 git_clone_or_reset(project_name, remote, args)
123
124 try:
125 revlist = '{}..{}'.format(recipe_sha, project_sha)
126 shortlog = git.shortlog(revlist, _cwd=project_name)
127 shortlog = shortlog.stdout.decode('utf-8')
128 except sh.ErrorReturnCode as e:
129 log('{}'.format(e), args)
130 continue
131
132 reset_args = ['--hard', 'origin/{}'.format(args.branch)]
133 git.reset(*reset_args, _cwd=meta)
134
135 recipe_content = None
136 with open(full_recipe_path) as fd:
137 recipe_content = fd.read()
138
139 recipe_content = recipe_content.replace(recipe_sha, project_sha)
140 with open(full_recipe_path, 'w') as fd:
141 fd.write(recipe_content)
142
143 git.add(recipe, _cwd=meta)
144
145 commit_summary_args = (project_name, recipe_sha[:10], project_sha[:10])
146 commit_msg = '{}: srcrev bump {}..{}'.format(*commit_summary_args)
147 commit_msg += '\n\n{}'.format(shortlog)
148 commit_msg += '\n\nChange-Id: {}'.format(change_id)
149
150 git.commit(sh.echo(commit_msg), '-s', '-F', '-', _cwd=meta)
151
152 push_args = ['origin', 'HEAD:refs/for/{}/autobump'.format(args.branch)]
153 git.push(*push_args, _cwd=meta)
154
155
156def main():
157 app_description = '''OpenBMC bitbake recipe bumping tool.
158
159Find bitbake metadata files (recipes) that use the git fetcher
160and check the project repository for newer revisions.
161
162Generate commits that update bitbake metadata files with SRCREV.
163
164Push generated commits to the OpenBMC Gerrit instance for review.
165 '''
166 parser = argparse.ArgumentParser(
167 description=app_description,
168 formatter_class=argparse.RawDescriptionHelpFormatter)
169
170 parser.set_defaults(branch='master')
171 parser.add_argument(
172 '-m', '--meta-repository', dest='meta_repository', action='append',
173 help='meta repository to check for updates')
174 parser.add_argument(
175 '-v', '--verbose', dest='noisy', action='store_true',
176 help='enable verbose status messages')
177 parser.add_argument(
178 'ssh_config_host', metavar='SSH_CONFIG_HOST_ENTRY',
179 help='SSH config host entry for Gerrit connectivity')
180
181 args = parser.parse_args()
182 setattr(args, 'gerrit', sh.ssh.bake(args.ssh_config_host, 'gerrit'))
183
184 metas = getattr(args, 'meta_repository')
185 if metas is None:
186 metas = args.gerrit('ls-projects', '-m', 'meta-')
187 metas = metas.stdout.decode('utf-8').split()
188 metas = [os.path.split(x)[-1] for x in metas]
189
190 for meta in metas:
191 find_and_process_bumps(meta, args)
192
193
194if __name__ == '__main__':
195 sys.exit(0 if main() else 1)