blob: 1ada173b7228b9a5b708efcaed6eb4f5d60959a3 [file] [log] [blame]
Raman Tenneti6a872c92021-01-14 19:17:50 -08001# Copyright (C) 2021 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Gavin Makea2e3302023-03-11 06:46:20 +000015"""Provide functionality to get projects and their commit ids from Superproject.
Raman Tenneti6a872c92021-01-14 19:17:50 -080016
17For more information on superproject, check out:
18https://en.wikibooks.org/wiki/Git/Submodules_and_Superprojects
19
20Examples:
LaMont Jonesff6b1da2022-06-01 21:03:34 +000021 superproject = Superproject(manifest, name, remote, revision)
Raman Tenneti784e16f2021-06-11 17:29:45 -070022 UpdateProjectsResult = superproject.UpdateProjectsRevisionId(projects)
Raman Tenneti6a872c92021-01-14 19:17:50 -080023"""
24
Xin Li0cb6e922021-06-16 10:19:00 -070025import functools
Mike Frysinger64477332023-08-21 21:20:32 -040026import hashlib
Raman Tenneti6a872c92021-01-14 19:17:50 -080027import os
28import sys
Xin Li0cb6e922021-06-16 10:19:00 -070029import time
Raman Tenneti784e16f2021-06-11 17:29:45 -070030from typing import NamedTuple
Gavin Mak8d37f612025-05-05 14:13:48 -070031import urllib.parse
Raman Tenneti6a872c92021-01-14 19:17:50 -080032
Mike Frysinger64477332023-08-21 21:20:32 -040033from git_command import git_require
34from git_command import GitCommand
Xin Li0cb6e922021-06-16 10:19:00 -070035from git_config import RepoConfig
Daniel Kutik035f22a2022-12-13 12:34:23 +010036from git_refs import GitRefs
Raman Tenneti6a872c92021-01-14 19:17:50 -080037
Mike Frysinger64477332023-08-21 21:20:32 -040038
Gavin Makea2e3302023-03-11 06:46:20 +000039_SUPERPROJECT_GIT_NAME = "superproject.git"
40_SUPERPROJECT_MANIFEST_NAME = "superproject_override.xml"
Raman Tenneti8d43dea2021-02-07 16:30:27 -080041
Raman Tenneti6a872c92021-01-14 19:17:50 -080042
Raman Tenneti784e16f2021-06-11 17:29:45 -070043class SyncResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +000044 """Return the status of sync and whether caller should exit."""
Raman Tenneti784e16f2021-06-11 17:29:45 -070045
Gavin Makea2e3302023-03-11 06:46:20 +000046 # Whether the superproject sync was successful.
47 success: bool
48 # Whether the caller should exit.
49 fatal: bool
Raman Tenneti784e16f2021-06-11 17:29:45 -070050
51
52class CommitIdsResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +000053 """Return the commit ids and whether caller should exit."""
Raman Tenneti784e16f2021-06-11 17:29:45 -070054
Gavin Makea2e3302023-03-11 06:46:20 +000055 # A dictionary with the projects/commit ids on success, otherwise None.
56 commit_ids: dict
57 # Whether the caller should exit.
58 fatal: bool
Raman Tenneti784e16f2021-06-11 17:29:45 -070059
60
61class UpdateProjectsResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +000062 """Return the overriding manifest file and whether caller should exit."""
Raman Tenneti784e16f2021-06-11 17:29:45 -070063
Gavin Makea2e3302023-03-11 06:46:20 +000064 # Path name of the overriding manifest file if successful, otherwise None.
65 manifest_path: str
66 # Whether the caller should exit.
67 fatal: bool
Raman Tenneti784e16f2021-06-11 17:29:45 -070068
69
Mike Frysingerd4aee652023-10-19 05:13:32 -040070class Superproject:
Gavin Makea2e3302023-03-11 06:46:20 +000071 """Get commit ids from superproject.
Raman Tenneti6a872c92021-01-14 19:17:50 -080072
Emily Shaffer8a6d1722023-09-15 13:26:38 -070073 Initializes a bare local copy of a superproject for the manifest. This
74 allows lookup of commit ids for all projects. It contains
75 _project_commit_ids which is a dictionary with project/commit id entries.
Raman Tenneti6a872c92021-01-14 19:17:50 -080076 """
Raman Tenneti6a872c92021-01-14 19:17:50 -080077
Gavin Makea2e3302023-03-11 06:46:20 +000078 def __init__(
79 self,
80 manifest,
81 name,
82 remote,
83 revision,
84 superproject_dir="exp-superproject",
85 ):
86 """Initializes superproject.
LaMont Jonesd56e2eb2022-04-07 18:14:46 +000087
Gavin Makea2e3302023-03-11 06:46:20 +000088 Args:
89 manifest: A Manifest object that is to be written to a file.
90 name: The unique name of the superproject
91 remote: The RemoteSpec for the remote.
92 revision: The name of the git branch to track.
93 superproject_dir: Relative path under |manifest.subdir| to checkout
94 superproject.
95 """
96 self._project_commit_ids = None
97 self._manifest = manifest
98 self.name = name
99 self.remote = remote
100 self.revision = self._branch = revision
101 self._repodir = manifest.repodir
102 self._superproject_dir = superproject_dir
103 self._superproject_path = manifest.SubmanifestInfoDir(
104 manifest.path_prefix, superproject_dir
105 )
106 self._manifest_path = os.path.join(
107 self._superproject_path, _SUPERPROJECT_MANIFEST_NAME
108 )
109 git_name = hashlib.md5(remote.name.encode("utf8")).hexdigest() + "-"
110 self._remote_url = remote.url
111 self._work_git_name = git_name + _SUPERPROJECT_GIT_NAME
112 self._work_git = os.path.join(
113 self._superproject_path, self._work_git_name
114 )
LaMont Jonesd56e2eb2022-04-07 18:14:46 +0000115
Gavin Makea2e3302023-03-11 06:46:20 +0000116 # The following are command arguemnts, rather than superproject
117 # attributes, and were included here originally. They should eventually
118 # become arguments that are passed down from the public methods, instead
119 # of being treated as attributes.
120 self._git_event_log = None
121 self._quiet = False
122 self._print_messages = False
LaMont Jonesd56e2eb2022-04-07 18:14:46 +0000123
Gavin Makea2e3302023-03-11 06:46:20 +0000124 def SetQuiet(self, value):
125 """Set the _quiet attribute."""
126 self._quiet = value
Raman Tenneti6a872c92021-01-14 19:17:50 -0800127
Gavin Makea2e3302023-03-11 06:46:20 +0000128 def SetPrintMessages(self, value):
129 """Set the _print_messages attribute."""
130 self._print_messages = value
Raman Tennetiae86a462021-07-27 08:54:59 -0700131
Gavin Makea2e3302023-03-11 06:46:20 +0000132 @property
Scott Lee3c8bae22025-05-27 18:36:42 +0000133 def commit_id(self):
134 """Returns the commit ID of the superproject checkout."""
135 cmd = ["rev-parse", self.revision]
136 p = GitCommand(
137 None, # project
138 cmd,
139 gitdir=self._work_git,
140 bare=True,
141 capture_stdout=True,
142 capture_stderr=True,
143 )
144 retval = p.Wait()
145 if retval != 0:
146 self._LogWarning(
147 "git rev-parse call failed, command: git {}, "
148 "return code: {}, stderr: {}",
149 cmd,
Scott Leeb262d0e2025-06-10 19:34:18 +0000150 retval,
151 p.stderr,
Scott Lee3c8bae22025-05-27 18:36:42 +0000152 )
153 return None
154 return p.stdout
155
156 @property
Gavin Makea2e3302023-03-11 06:46:20 +0000157 def project_commit_ids(self):
158 """Returns a dictionary of projects and their commit ids."""
159 return self._project_commit_ids
Raman Tenneti8db30d62021-07-06 21:30:06 -0700160
Gavin Makea2e3302023-03-11 06:46:20 +0000161 @property
162 def manifest_path(self):
163 """Returns the manifest path if the path exists or None."""
164 return (
165 self._manifest_path if os.path.exists(self._manifest_path) else None
166 )
Raman Tennetid8e8ae82021-09-15 16:32:33 -0700167
Gavin Mak8d37f612025-05-05 14:13:48 -0700168 @property
169 def repo_id(self):
170 """Returns the repo ID for the superproject.
171
172 For example, if the superproject points to:
173 https://android-review.googlesource.com/platform/superproject/
174 Then the repo_id would be:
175 android/platform/superproject
176 """
Gavin Mak0cb88a82025-06-03 21:06:42 -0700177 review_url = self.remote.review
178 if review_url:
Gavin Mak8d37f612025-05-05 14:13:48 -0700179 parsed_url = urllib.parse.urlparse(review_url)
Gavin Mak0cb88a82025-06-03 21:06:42 -0700180 netloc = parsed_url.netloc
181 if netloc:
Gavin Mak8d37f612025-05-05 14:13:48 -0700182 parts = netloc.split("-review", 1)
183 host = parts[0]
Gavin Mak08815ad2025-05-23 10:35:34 -0700184 rev = GitRefs(self._work_git).get("HEAD")
185 return f"{host}/{self.name}@{rev}"
Gavin Mak8d37f612025-05-05 14:13:48 -0700186 return None
187
Gavin Makea2e3302023-03-11 06:46:20 +0000188 def _LogMessage(self, fmt, *inputs):
189 """Logs message to stderr and _git_event_log."""
190 message = f"{self._LogMessagePrefix()} {fmt.format(*inputs)}"
191 if self._print_messages:
192 print(message, file=sys.stderr)
Gavin Mak854fe442025-08-14 19:30:06 +0000193 if self._git_event_log:
194 self._git_event_log.ErrorEvent(message, fmt)
Raman Tenneti5637afc2021-08-11 09:26:30 -0700195
Gavin Makea2e3302023-03-11 06:46:20 +0000196 def _LogMessagePrefix(self):
197 """Returns the prefix string to be logged in each log message"""
198 return (
199 f"repo superproject branch: {self._branch} url: {self._remote_url}"
200 )
Raman Tenneti5637afc2021-08-11 09:26:30 -0700201
Gavin Makea2e3302023-03-11 06:46:20 +0000202 def _LogError(self, fmt, *inputs):
203 """Logs error message to stderr and _git_event_log."""
204 self._LogMessage(f"error: {fmt}", *inputs)
Raman Tenneti6a872c92021-01-14 19:17:50 -0800205
Gavin Makea2e3302023-03-11 06:46:20 +0000206 def _LogWarning(self, fmt, *inputs):
207 """Logs warning message to stderr and _git_event_log."""
208 self._LogMessage(f"warning: {fmt}", *inputs)
Raman Tenneti6a872c92021-01-14 19:17:50 -0800209
Gavin Makea2e3302023-03-11 06:46:20 +0000210 def _Init(self):
211 """Sets up a local Git repository to get a copy of a superproject.
Raman Tenneti9e787532021-02-01 11:47:06 -0800212
Gavin Makea2e3302023-03-11 06:46:20 +0000213 Returns:
214 True if initialization is successful, or False.
215 """
216 if not os.path.exists(self._superproject_path):
217 os.mkdir(self._superproject_path)
218 if not self._quiet and not os.path.exists(self._work_git):
219 print(
220 "%s: Performing initial setup for superproject; this might "
221 "take several minutes." % self._work_git
222 )
223 cmd = ["init", "--bare", self._work_git_name]
224 p = GitCommand(
225 None,
226 cmd,
227 cwd=self._superproject_path,
228 capture_stdout=True,
229 capture_stderr=True,
230 )
231 retval = p.Wait()
232 if retval:
233 self._LogWarning(
234 "git init call failed, command: git {}, "
235 "return code: {}, stderr: {}",
236 cmd,
237 retval,
238 p.stderr,
239 )
240 return False
241 return True
Joanna Wang0e4f1e72022-12-08 17:46:28 -0500242
Gavin Makea2e3302023-03-11 06:46:20 +0000243 def _Fetch(self):
244 """Fetches a superproject for the manifest based on |_remote_url|.
Joanna Wang0e4f1e72022-12-08 17:46:28 -0500245
Gavin Makea2e3302023-03-11 06:46:20 +0000246 This runs git fetch which stores a local copy the superproject.
Raman Tenneti9e787532021-02-01 11:47:06 -0800247
Gavin Makea2e3302023-03-11 06:46:20 +0000248 Returns:
249 True if fetch is successful, or False.
250 """
251 if not os.path.exists(self._work_git):
252 self._LogWarning("git fetch missing directory: {}", self._work_git)
253 return False
254 if not git_require((2, 28, 0)):
255 self._LogWarning(
256 "superproject requires a git version 2.28 or later"
257 )
258 return False
259 cmd = [
260 "fetch",
261 self._remote_url,
262 "--depth",
263 "1",
264 "--force",
265 "--no-tags",
266 "--filter",
267 "blob:none",
268 ]
Raman Tenneti6a872c92021-01-14 19:17:50 -0800269
Gavin Makea2e3302023-03-11 06:46:20 +0000270 # Check if there is a local ref that we can pass to --negotiation-tip.
271 # If this is the first fetch, it does not exist yet.
272 # We use --negotiation-tip to speed up the fetch. Superproject branches
273 # do not share commits. So this lets git know it only needs to send
274 # commits reachable from the specified local refs.
275 rev_commit = GitRefs(self._work_git).get(f"refs/heads/{self.revision}")
276 if rev_commit:
277 cmd.extend(["--negotiation-tip", rev_commit])
Raman Tenneti6a872c92021-01-14 19:17:50 -0800278
Gavin Makea2e3302023-03-11 06:46:20 +0000279 if self._branch:
280 cmd += [self._branch + ":" + self._branch]
281 p = GitCommand(
282 None,
283 cmd,
Emily Shaffer8a6d1722023-09-15 13:26:38 -0700284 gitdir=self._work_git,
285 bare=True,
Gavin Makea2e3302023-03-11 06:46:20 +0000286 capture_stdout=True,
287 capture_stderr=True,
288 )
289 retval = p.Wait()
290 if retval:
291 self._LogWarning(
292 "git fetch call failed, command: git {}, "
293 "return code: {}, stderr: {}",
294 cmd,
295 retval,
296 p.stderr,
297 )
298 return False
299 return True
Raman Tennetice64e3d2021-02-08 13:27:41 -0800300
Gavin Makea2e3302023-03-11 06:46:20 +0000301 def _LsTree(self):
302 """Gets the commit ids for all projects.
Raman Tenneti6a872c92021-01-14 19:17:50 -0800303
Gavin Makea2e3302023-03-11 06:46:20 +0000304 Works only in git repositories.
Raman Tenneti6a872c92021-01-14 19:17:50 -0800305
Gavin Makea2e3302023-03-11 06:46:20 +0000306 Returns:
Scott Lee3c8bae22025-05-27 18:36:42 +0000307 data: data returned from 'git ls-tree ...'. None on error.
Gavin Makea2e3302023-03-11 06:46:20 +0000308 """
309 if not os.path.exists(self._work_git):
310 self._LogWarning(
311 "git ls-tree missing directory: {}", self._work_git
312 )
313 return None
314 data = None
315 branch = "HEAD" if not self._branch else self._branch
316 cmd = ["ls-tree", "-z", "-r", branch]
LaMont Jonesd56e2eb2022-04-07 18:14:46 +0000317
Gavin Makea2e3302023-03-11 06:46:20 +0000318 p = GitCommand(
319 None,
320 cmd,
Emily Shaffer8a6d1722023-09-15 13:26:38 -0700321 gitdir=self._work_git,
322 bare=True,
Gavin Makea2e3302023-03-11 06:46:20 +0000323 capture_stdout=True,
324 capture_stderr=True,
325 )
326 retval = p.Wait()
327 if retval == 0:
328 data = p.stdout
329 else:
330 self._LogWarning(
331 "git ls-tree call failed, command: git {}, "
332 "return code: {}, stderr: {}",
333 cmd,
334 retval,
335 p.stderr,
336 )
Scott Lee3c8bae22025-05-27 18:36:42 +0000337 return None
Gavin Makea2e3302023-03-11 06:46:20 +0000338 return data
Raman Tenneti21dce3d2021-02-09 00:26:31 -0800339
Gavin Makea2e3302023-03-11 06:46:20 +0000340 def Sync(self, git_event_log):
341 """Gets a local copy of a superproject for the manifest.
LaMont Jones2cc3ab72022-04-13 15:58:58 +0000342
Gavin Makea2e3302023-03-11 06:46:20 +0000343 Args:
344 git_event_log: an EventLog, for git tracing.
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800345
Gavin Makea2e3302023-03-11 06:46:20 +0000346 Returns:
347 SyncResult
348 """
349 self._git_event_log = git_event_log
350 if not self._manifest.superproject:
351 self._LogWarning(
352 "superproject tag is not defined in manifest: {}",
353 self._manifest.manifestFile,
354 )
355 return SyncResult(False, False)
Raman Tenneti6a872c92021-01-14 19:17:50 -0800356
Gavin Makea2e3302023-03-11 06:46:20 +0000357 should_exit = True
358 if not self._remote_url:
359 self._LogWarning(
360 "superproject URL is not defined in manifest: {}",
361 self._manifest.manifestFile,
362 )
363 return SyncResult(False, should_exit)
Raman Tenneti21dce3d2021-02-09 00:26:31 -0800364
Gavin Makea2e3302023-03-11 06:46:20 +0000365 if not self._Init():
366 return SyncResult(False, should_exit)
367 if not self._Fetch():
368 return SyncResult(False, should_exit)
369 if not self._quiet:
370 print(
371 "%s: Initial setup for superproject completed." % self._work_git
372 )
373 return SyncResult(True, False)
Raman Tenneti6a872c92021-01-14 19:17:50 -0800374
Gavin Makea2e3302023-03-11 06:46:20 +0000375 def _GetAllProjectsCommitIds(self):
376 """Get commit ids for all projects from superproject and save them.
Raman Tenneti6a872c92021-01-14 19:17:50 -0800377
Gavin Makea2e3302023-03-11 06:46:20 +0000378 Commit ids are saved in _project_commit_ids.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800379
Gavin Makea2e3302023-03-11 06:46:20 +0000380 Returns:
381 CommitIdsResult
382 """
383 sync_result = self.Sync(self._git_event_log)
384 if not sync_result.success:
385 return CommitIdsResult(None, sync_result.fatal)
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800386
Gavin Makea2e3302023-03-11 06:46:20 +0000387 data = self._LsTree()
388 if not data:
389 self._LogWarning(
390 "git ls-tree failed to return data for manifest: {}",
391 self._manifest.manifestFile,
392 )
393 return CommitIdsResult(None, True)
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800394
Gavin Makea2e3302023-03-11 06:46:20 +0000395 # Parse lines like the following to select lines starting with '160000'
396 # and build a dictionary with project path (last element) and its commit
397 # id (3rd element).
398 #
399 # 160000 commit 2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea\tart\x00
400 # 120000 blob acc2cbdf438f9d2141f0ae424cec1d8fc4b5d97f\tbootstrap.bash\x00 # noqa: E501
401 commit_ids = {}
402 for line in data.split("\x00"):
403 ls_data = line.split(None, 3)
404 if not ls_data:
405 break
406 if ls_data[0] == "160000":
407 commit_ids[ls_data[3]] = ls_data[2]
Raman Tenneti784e16f2021-06-11 17:29:45 -0700408
Gavin Makea2e3302023-03-11 06:46:20 +0000409 self._project_commit_ids = commit_ids
410 return CommitIdsResult(commit_ids, False)
Raman Tenneti784e16f2021-06-11 17:29:45 -0700411
Gavin Makea2e3302023-03-11 06:46:20 +0000412 def _WriteManifestFile(self):
413 """Writes manifest to a file.
Raman Tenneti784e16f2021-06-11 17:29:45 -0700414
Gavin Makea2e3302023-03-11 06:46:20 +0000415 Returns:
416 manifest_path: Path name of the file into which manifest is written
417 instead of None.
418 """
419 if not os.path.exists(self._superproject_path):
420 self._LogWarning(
421 "missing superproject directory: {}", self._superproject_path
422 )
423 return None
424 manifest_str = self._manifest.ToXml(
Peter Kjellerstedt2b6de522025-11-18 20:13:03 +0100425 filter_groups=self._manifest.GetManifestGroupsStr(),
426 omit_local=True,
Gavin Makea2e3302023-03-11 06:46:20 +0000427 ).toxml()
428 manifest_path = self._manifest_path
429 try:
430 with open(manifest_path, "w", encoding="utf-8") as fp:
431 fp.write(manifest_str)
Jason R. Coombsae824fb2023-10-20 23:32:40 +0545432 except OSError as e:
Gavin Makea2e3302023-03-11 06:46:20 +0000433 self._LogError("cannot write manifest to : {} {}", manifest_path, e)
434 return None
435 return manifest_path
Raman Tenneti784e16f2021-06-11 17:29:45 -0700436
Gavin Makea2e3302023-03-11 06:46:20 +0000437 def _SkipUpdatingProjectRevisionId(self, project):
438 """Checks if a project's revision id needs to be updated or not.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800439
Gavin Makea2e3302023-03-11 06:46:20 +0000440 Revision id for projects from local manifest will not be updated.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800441
Gavin Makea2e3302023-03-11 06:46:20 +0000442 Args:
443 project: project whose revision id is being updated.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800444
Gavin Makea2e3302023-03-11 06:46:20 +0000445 Returns:
446 True if a project's revision id should not be updated, or False,
447 """
448 path = project.relpath
449 if not path:
450 return True
451 # Skip the project with revisionId.
452 if project.revisionId:
453 return True
454 # Skip the project if it comes from the local manifest.
455 return project.manifest.IsFromLocalManifest(project)
Raman Tenneti784e16f2021-06-11 17:29:45 -0700456
Gavin Makea2e3302023-03-11 06:46:20 +0000457 def UpdateProjectsRevisionId(self, projects, git_event_log):
458 """Update revisionId of every project in projects with the commit id.
Raman Tenneti784e16f2021-06-11 17:29:45 -0700459
Gavin Makea2e3302023-03-11 06:46:20 +0000460 Args:
461 projects: a list of projects whose revisionId needs to be updated.
462 git_event_log: an EventLog, for git tracing.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800463
Gavin Makea2e3302023-03-11 06:46:20 +0000464 Returns:
465 UpdateProjectsResult
466 """
467 self._git_event_log = git_event_log
468 commit_ids_result = self._GetAllProjectsCommitIds()
469 commit_ids = commit_ids_result.commit_ids
470 if not commit_ids:
471 return UpdateProjectsResult(None, commit_ids_result.fatal)
472
473 projects_missing_commit_ids = []
474 for project in projects:
475 if self._SkipUpdatingProjectRevisionId(project):
476 continue
477 path = project.relpath
478 commit_id = commit_ids.get(path)
479 if not commit_id:
480 projects_missing_commit_ids.append(path)
481
482 # If superproject doesn't have a commit id for a project, then report an
483 # error event and continue as if do not use superproject is specified.
484 if projects_missing_commit_ids:
485 self._LogWarning(
486 "please file a bug using {} to report missing "
487 "commit_ids for: {}",
488 self._manifest.contactinfo.bugurl,
489 projects_missing_commit_ids,
490 )
491 return UpdateProjectsResult(None, False)
492
493 for project in projects:
494 if not self._SkipUpdatingProjectRevisionId(project):
495 project.SetRevisionId(commit_ids.get(project.relpath))
496
497 manifest_path = self._WriteManifestFile()
498 return UpdateProjectsResult(manifest_path, False)
Xin Li0cb6e922021-06-16 10:19:00 -0700499
500
501@functools.lru_cache(maxsize=None)
502def _UseSuperprojectFromConfiguration():
Gavin Makea2e3302023-03-11 06:46:20 +0000503 """Returns the user choice of whether to use superproject."""
504 user_cfg = RepoConfig.ForUser()
505 time_now = int(time.time())
Xin Li0cb6e922021-06-16 10:19:00 -0700506
Gavin Makea2e3302023-03-11 06:46:20 +0000507 user_value = user_cfg.GetBoolean("repo.superprojectChoice")
508 if user_value is not None:
509 user_expiration = user_cfg.GetInt("repo.superprojectChoiceExpire")
510 if (
511 user_expiration is None
512 or user_expiration <= 0
513 or user_expiration >= time_now
514 ):
515 # TODO(b/190688390) - Remove prompt when we are comfortable with the
516 # new default value.
517 if user_value:
518 print(
519 (
520 "You are currently enrolled in Git submodules "
521 "experiment (go/android-submodules-quickstart). Use "
522 "--no-use-superproject to override.\n"
523 ),
524 file=sys.stderr,
525 )
526 else:
527 print(
528 (
529 "You are not currently enrolled in Git submodules "
530 "experiment (go/android-submodules-quickstart). Use "
531 "--use-superproject to override.\n"
532 ),
533 file=sys.stderr,
534 )
535 return user_value
Xin Li0cb6e922021-06-16 10:19:00 -0700536
Gavin Makea2e3302023-03-11 06:46:20 +0000537 # We don't have an unexpired choice, ask for one.
538 system_cfg = RepoConfig.ForSystem()
539 system_value = system_cfg.GetBoolean("repo.superprojectChoice")
540 if system_value:
541 # The system configuration is proposing that we should enable the
542 # use of superproject. Treat the user as enrolled for two weeks.
543 #
544 # TODO(b/190688390) - Remove prompt when we are comfortable with the new
545 # default value.
546 userchoice = True
547 time_choiceexpire = time_now + (86400 * 14)
548 user_cfg.SetString(
549 "repo.superprojectChoiceExpire", str(time_choiceexpire)
550 )
551 user_cfg.SetBoolean("repo.superprojectChoice", userchoice)
552 print(
553 "You are automatically enrolled in Git submodules experiment "
554 "(go/android-submodules-quickstart) for another two weeks.\n",
555 file=sys.stderr,
556 )
557 return True
Xin Li0cb6e922021-06-16 10:19:00 -0700558
Gavin Makea2e3302023-03-11 06:46:20 +0000559 # For all other cases, we would not use superproject by default.
560 return False
Xin Li0cb6e922021-06-16 10:19:00 -0700561
562
LaMont Jones5fa912b2022-04-14 14:41:13 +0000563def PrintMessages(use_superproject, manifest):
Gavin Makea2e3302023-03-11 06:46:20 +0000564 """Returns a boolean if error/warning messages are to be printed.
LaMont Jones5fa912b2022-04-14 14:41:13 +0000565
Gavin Makea2e3302023-03-11 06:46:20 +0000566 Args:
567 use_superproject: option value from optparse.
568 manifest: manifest to use.
569 """
570 return use_superproject is not None or bool(manifest.superproject)
Raman Tennetib55769a2021-08-13 11:47:24 -0700571
572
LaMont Jones5fa912b2022-04-14 14:41:13 +0000573def UseSuperproject(use_superproject, manifest):
Gavin Makea2e3302023-03-11 06:46:20 +0000574 """Returns a boolean if use-superproject option is enabled.
Xin Li0cb6e922021-06-16 10:19:00 -0700575
Gavin Makea2e3302023-03-11 06:46:20 +0000576 Args:
577 use_superproject: option value from optparse.
578 manifest: manifest to use.
LaMont Jonesff6b1da2022-06-01 21:03:34 +0000579
Gavin Makea2e3302023-03-11 06:46:20 +0000580 Returns:
581 Whether the superproject should be used.
582 """
LaMont Jones5fa912b2022-04-14 14:41:13 +0000583
Gavin Makea2e3302023-03-11 06:46:20 +0000584 if not manifest.superproject:
585 # This (sub) manifest does not have a superproject definition.
586 return False
587 elif use_superproject is not None:
588 return use_superproject
LaMont Jonesd56e2eb2022-04-07 18:14:46 +0000589 else:
Gavin Makea2e3302023-03-11 06:46:20 +0000590 client_value = manifest.manifestProject.use_superproject
591 if client_value is not None:
592 return client_value
593 elif manifest.superproject:
594 return _UseSuperprojectFromConfiguration()
595 else:
596 return False