Mike Frysinger | f1ba7ad | 2022-09-12 05:42:57 -0400 | [diff] [blame] | 1 | # Copyright 2010 The ChromiumOS Authors |
Brian Harring | 7fcc02e | 2012-08-05 04:10:57 -0700 | [diff] [blame] | 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
| 4 | |
Mike Frysinger | 4ca6015 | 2016-09-01 00:13:36 -0400 | [diff] [blame] | 5 | """Manage projects in the local manifest.""" |
Brian Harring | 7fcc02e | 2012-08-05 04:10:57 -0700 | [diff] [blame] | 6 | |
Brian Harring | 7fcc02e | 2012-08-05 04:10:57 -0700 | [diff] [blame] | 7 | import os |
Mike Frysinger | 807d828 | 2022-04-28 22:45:17 -0400 | [diff] [blame] | 8 | import platform |
Brian Harring | 7fcc02e | 2012-08-05 04:10:57 -0700 | [diff] [blame] | 9 | import xml.etree.ElementTree as ElementTree |
Mike Frysinger | 750c5f5 | 2014-09-16 16:16:57 -0400 | [diff] [blame] | 10 | |
Mike Frysinger | 4ca6015 | 2016-09-01 00:13:36 -0400 | [diff] [blame] | 11 | from chromite.lib import commandline |
Brian Harring | b0043ab | 2012-08-05 04:09:56 -0700 | [diff] [blame] | 12 | from chromite.lib import cros_build_lib |
David James | 97d9587 | 2012-11-16 15:09:56 -0800 | [diff] [blame] | 13 | from chromite.lib import git |
Mike Frysinger | 462dbd6 | 2019-10-19 20:26:46 -0400 | [diff] [blame] | 14 | from chromite.lib import osutils |
Brian Harring | 7fcc02e | 2012-08-05 04:10:57 -0700 | [diff] [blame] | 15 | |
| 16 | |
Gwendal Grignou | 89afc08 | 2016-09-29 21:03:20 -0700 | [diff] [blame] | 17 | class LocalManifest(object): |
Alex Klein | 68b270c | 2023-04-14 14:42:50 -0600 | [diff] [blame^] | 18 | """Abstraction for manipulating the local manifest.""" |
Brian Harring | 7fcc02e | 2012-08-05 04:10:57 -0700 | [diff] [blame] | 19 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 20 | @classmethod |
| 21 | def FromPath(cls, path, empty_if_missing=False): |
| 22 | if os.path.isfile(path): |
| 23 | return cls(osutils.ReadFile(path)) |
| 24 | elif empty_if_missing: |
Alex Klein | df8ee50 | 2022-10-18 09:48:15 -0600 | [diff] [blame] | 25 | cros_build_lib.Die("Manifest file, %r, not found", path) |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 26 | return cls() |
Brian Harring | b0043ab | 2012-08-05 04:09:56 -0700 | [diff] [blame] | 27 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 28 | def __init__(self, text=None): |
| 29 | self._text = text or "<manifest>\n</manifest>" |
| 30 | self.nodes = ElementTree.fromstring(self._text) |
Brian Harring | 7fcc02e | 2012-08-05 04:10:57 -0700 | [diff] [blame] | 31 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 32 | def AddNonWorkonProject(self, name, path, remote=None, revision=None): |
| 33 | """Add a new nonworkon project element to the manifest tree.""" |
| 34 | element = ElementTree.Element( |
| 35 | "project", name=name, path=path, remote=remote |
| 36 | ) |
| 37 | element.attrib["workon"] = "False" |
| 38 | if revision is not None: |
| 39 | element.attrib["revision"] = revision |
| 40 | self.nodes.append(element) |
| 41 | return element |
Brian Harring | 7fcc02e | 2012-08-05 04:10:57 -0700 | [diff] [blame] | 42 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 43 | def GetProject(self, name, path=None): |
| 44 | """Accessor method for getting a project node from the manifest tree. |
Brian Harring | 7fcc02e | 2012-08-05 04:10:57 -0700 | [diff] [blame] | 45 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 46 | Returns: |
Alex Klein | 68b270c | 2023-04-14 14:42:50 -0600 | [diff] [blame^] | 47 | project element node from ElementTree, otherwise, None |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 48 | """ |
| 49 | if path is None: |
| 50 | # Use a unique value that can't ever match. |
| 51 | path = object() |
| 52 | for project in self.nodes.findall("project"): |
| 53 | if project.attrib["name"] == name or project.attrib["path"] == path: |
| 54 | return project |
| 55 | return None |
Brian Harring | 7fcc02e | 2012-08-05 04:10:57 -0700 | [diff] [blame] | 56 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 57 | def ToString(self): |
| 58 | # Reset the tail for each node, then just do a hacky replace. |
| 59 | project = None |
| 60 | for project in self.nodes.findall("project"): |
| 61 | project.tail = "\n " |
| 62 | if project is not None: |
| 63 | # Tweak the last project to not have the trailing space. |
| 64 | project.tail = "\n" |
| 65 | # Fix manifest tag text and tail. |
| 66 | self.nodes.text = "\n " |
| 67 | self.nodes.tail = "\n" |
| 68 | return ElementTree.tostring(self.nodes, encoding="unicode") |
Brian Harring | b0043ab | 2012-08-05 04:09:56 -0700 | [diff] [blame] | 69 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 70 | def GetProjects(self): |
| 71 | return list(self.nodes.findall("project")) |
Brian Harring | b0043ab | 2012-08-05 04:09:56 -0700 | [diff] [blame] | 72 | |
| 73 | |
Gwendal Grignou | 89afc08 | 2016-09-29 21:03:20 -0700 | [diff] [blame] | 74 | def _AddProjectsToManifestGroups(options, new_group): |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 75 | """Enable the given manifest groups for the configured repository.""" |
Brian Harring | b0043ab | 2012-08-05 04:09:56 -0700 | [diff] [blame] | 76 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 77 | groups_to_enable = ["name:%s" % x for x in new_group] |
Brian Harring | b0043ab | 2012-08-05 04:09:56 -0700 | [diff] [blame] | 78 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 79 | git_config = options.git_config |
Brian Harring | b0043ab | 2012-08-05 04:09:56 -0700 | [diff] [blame] | 80 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 81 | cmd = ["config", "-f", git_config, "--get", "manifest.groups"] |
| 82 | enabled_groups = ( |
| 83 | git.RunGit(".", cmd, check=False).stdout.rstrip().split(",") |
| 84 | ) |
Brian Harring | b0043ab | 2012-08-05 04:09:56 -0700 | [diff] [blame] | 85 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 86 | # Note that ordering actually matters, thus why the following code |
| 87 | # is written this way. |
| 88 | # Per repo behaviour, enforce an appropriate platform group if |
| 89 | # we're converting from a default manifest group to a limited one. |
| 90 | # Finally, note we reprocess the existing groups; this is to allow |
Alex Klein | 68b270c | 2023-04-14 14:42:50 -0600 | [diff] [blame^] | 91 | # us to clean up any user screwups, or our own screwups. |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 92 | requested_groups = ( |
| 93 | ["minilayout", "platform-%s" % (platform.system().lower(),)] |
| 94 | + enabled_groups |
| 95 | + list(groups_to_enable) |
| 96 | ) |
Brian Harring | b0043ab | 2012-08-05 04:09:56 -0700 | [diff] [blame] | 97 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 98 | processed_groups = set() |
| 99 | finalized_groups = [] |
Brian Harring | b0043ab | 2012-08-05 04:09:56 -0700 | [diff] [blame] | 100 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 101 | for group in requested_groups: |
| 102 | if group not in processed_groups: |
| 103 | finalized_groups.append(group) |
| 104 | processed_groups.add(group) |
Brian Harring | b0043ab | 2012-08-05 04:09:56 -0700 | [diff] [blame] | 105 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 106 | cmd = [ |
| 107 | "config", |
| 108 | "-f", |
| 109 | git_config, |
| 110 | "manifest.groups", |
| 111 | ",".join(finalized_groups), |
| 112 | ] |
| 113 | git.RunGit(".", cmd) |
Brian Harring | b0043ab | 2012-08-05 04:09:56 -0700 | [diff] [blame] | 114 | |
| 115 | |
Gwendal Grignou | f9d6d36 | 2016-09-30 09:29:20 -0700 | [diff] [blame] | 116 | def _AssertNotMiniLayout(): |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 117 | cros_build_lib.Die( |
| 118 | "Your repository checkout is using the old minilayout.xml workflow; " |
| 119 | "Autoupdate is no longer supported, reinstall your tree." |
| 120 | ) |
Brian Harring | 7fcc02e | 2012-08-05 04:10:57 -0700 | [diff] [blame] | 121 | |
| 122 | |
Mike Frysinger | 4ca6015 | 2016-09-01 00:13:36 -0400 | [diff] [blame] | 123 | def GetParser(): |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 124 | """Return a command line parser.""" |
| 125 | parser = commandline.ArgumentParser(description=__doc__) |
Mike Frysinger | 1b8565b | 2016-09-13 16:03:49 -0400 | [diff] [blame] | 126 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 127 | # Subparsers are required by default under Python 2. Python 3 changed to |
| 128 | # not required, but didn't include a required option until 3.7. Setting |
| 129 | # the required member works in all versions (and setting dest name). |
| 130 | subparsers = parser.add_subparsers(dest="command") |
| 131 | subparsers.required = True |
Mike Frysinger | 1b8565b | 2016-09-13 16:03:49 -0400 | [diff] [blame] | 132 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 133 | subparser = subparsers.add_parser( |
| 134 | "add", help="Add projects to the manifest." |
| 135 | ) |
| 136 | subparser.add_argument( |
| 137 | "-w", |
| 138 | "--workon", |
| 139 | action="store_true", |
| 140 | default=False, |
| 141 | help="Is this a workon package?", |
| 142 | ) |
| 143 | subparser.add_argument( |
| 144 | "-r", "--remote", help="Remote project name (for non-workon packages)." |
| 145 | ) |
| 146 | subparser.add_argument( |
| 147 | "--revision", |
| 148 | help="Use to override the manifest defined default " |
| 149 | "revision used for a given project.", |
| 150 | ) |
| 151 | subparser.add_argument("project", help="Name of project in the manifest.") |
| 152 | subparser.add_argument("path", nargs="?", help="Local path to the project.") |
Mike Frysinger | 1b8565b | 2016-09-13 16:03:49 -0400 | [diff] [blame] | 153 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 154 | return parser |
Mike Frysinger | 4ca6015 | 2016-09-01 00:13:36 -0400 | [diff] [blame] | 155 | |
| 156 | |
Brian Harring | 7fcc02e | 2012-08-05 04:10:57 -0700 | [diff] [blame] | 157 | def main(argv): |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 158 | parser = GetParser() |
| 159 | options = parser.parse_args(argv) |
| 160 | repo_dir = git.FindRepoDir(os.getcwd()) |
| 161 | if not repo_dir: |
| 162 | parser.error( |
| 163 | "This script must be invoked from within a repository " "checkout." |
| 164 | ) |
Brian Harring | 7fcc02e | 2012-08-05 04:10:57 -0700 | [diff] [blame] | 165 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 166 | options.git_config = os.path.join(repo_dir, "manifests.git", "config") |
| 167 | options.local_manifest_path = os.path.join(repo_dir, "local_manifest.xml") |
Brian Harring | b0043ab | 2012-08-05 04:09:56 -0700 | [diff] [blame] | 168 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 169 | manifest_sym_path = os.path.join(repo_dir, "manifest.xml") |
| 170 | if ( |
| 171 | os.path.basename(os.path.realpath(manifest_sym_path)) |
| 172 | == "minilayout.xml" |
| 173 | ): |
| 174 | _AssertNotMiniLayout() |
Brian Harring | b0043ab | 2012-08-05 04:09:56 -0700 | [diff] [blame] | 175 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 176 | # For now, we only support the add command. |
| 177 | assert options.command == "add" |
| 178 | if options.workon: |
| 179 | if options.path is not None: |
| 180 | parser.error("Adding workon projects do not set project.") |
Rhyland Klein | e5faad5 | 2012-10-31 11:58:19 -0400 | [diff] [blame] | 181 | else: |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 182 | if options.remote is None: |
| 183 | parser.error("Adding non-workon projects requires a remote.") |
| 184 | if options.path is None: |
| 185 | parser.error("Adding non-workon projects requires a path.") |
| 186 | name = options.project |
| 187 | path = options.path |
| 188 | revision = options.revision |
| 189 | if revision is not None: |
| 190 | if not git.IsRefsTags(revision) and not git.IsSHA1(revision): |
| 191 | revision = git.StripRefsHeads(revision, False) |
Brian Harring | b0043ab | 2012-08-05 04:09:56 -0700 | [diff] [blame] | 192 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 193 | main_manifest = git.ManifestCheckout(os.getcwd()) |
| 194 | main_element = main_manifest.FindCheckouts(name) |
| 195 | if path is not None: |
| 196 | main_element_from_path = main_manifest.FindCheckoutFromPath( |
| 197 | path, strict=False |
| 198 | ) |
| 199 | if main_element_from_path is not None: |
| 200 | main_element.append(main_element_from_path) |
Brian Harring | b0043ab | 2012-08-05 04:09:56 -0700 | [diff] [blame] | 201 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 202 | local_manifest = LocalManifest.FromPath(options.local_manifest_path) |
Brian Harring | b0043ab | 2012-08-05 04:09:56 -0700 | [diff] [blame] | 203 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 204 | if options.workon: |
| 205 | if not main_element: |
| 206 | parser.error("No project named %r in the default manifest." % name) |
| 207 | _AddProjectsToManifestGroups( |
| 208 | options, [checkout["name"] for checkout in main_element] |
| 209 | ) |
| 210 | |
| 211 | elif main_element: |
| 212 | if options.remote is not None: |
Alex Klein | 68b270c | 2023-04-14 14:42:50 -0600 | [diff] [blame^] | 213 | # Likely this project wasn't meant to be remote, so workon main |
| 214 | # element. |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 215 | print( |
Alex Klein | 68b270c | 2023-04-14 14:42:50 -0600 | [diff] [blame^] | 216 | "Project already exists in manifest. " |
| 217 | "Using that as workon project." |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 218 | ) |
| 219 | _AddProjectsToManifestGroups( |
| 220 | options, [checkout["name"] for checkout in main_element] |
| 221 | ) |
| 222 | else: |
| 223 | # Conflict will occur; complain. |
| 224 | parser.error( |
| 225 | "Requested project name=%r path=%r will conflict with " |
| 226 | "your current manifest %s" |
| 227 | % (name, path, main_manifest.manifest_path) |
| 228 | ) |
| 229 | |
| 230 | elif local_manifest.GetProject(name, path=path) is not None: |
| 231 | parser.error( |
| 232 | "Requested project name=%r path=%r conflicts with " |
| 233 | "your local_manifest.xml" % (name, path) |
| 234 | ) |
| 235 | |
| 236 | else: |
| 237 | element = local_manifest.AddNonWorkonProject( |
| 238 | name=name, path=path, remote=options.remote, revision=revision |
| 239 | ) |
| 240 | _AddProjectsToManifestGroups(options, [element.attrib["name"]]) |
| 241 | |
Mike Frysinger | 31fdddd | 2023-02-24 15:50:55 -0500 | [diff] [blame] | 242 | with open(options.local_manifest_path, "w", encoding="utf-8") as f: |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 243 | f.write(local_manifest.ToString()) |
| 244 | return 0 |