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