blob: c042f9d7713a9d9331c0c263d1da6c5d528a062e [file] [log] [blame]
Brian Harring7fcc02e2012-08-05 04:10:57 -07001# 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 Frysinger4ca60152016-09-01 00:13:36 -04005"""Manage projects in the local manifest."""
Brian Harring7fcc02e2012-08-05 04:10:57 -07006
Mike Frysinger383367e2014-09-16 15:06:17 -04007from __future__ import print_function
8
Brian Harringb0043ab2012-08-05 04:09:56 -07009import platform
Brian Harring7fcc02e2012-08-05 04:10:57 -070010import os
11import xml.etree.ElementTree as ElementTree
Mike Frysinger750c5f52014-09-16 16:16:57 -040012
Mike Frysinger4ca60152016-09-01 00:13:36 -040013from chromite.lib import commandline
Brian Harringb0043ab2012-08-05 04:09:56 -070014from chromite.lib import cros_build_lib
David James97d95872012-11-16 15:09:56 -080015from chromite.lib import git
Brian Harring7fcc02e2012-08-05 04:10:57 -070016
17
Brian Harringb0043ab2012-08-05 04:09:56 -070018class Manifest(object):
Brian Harring7fcc02e2012-08-05 04:10:57 -070019 """Class which provides an abstraction for manipulating the local manifest."""
20
Brian Harringb0043ab2012-08-05 04:09:56 -070021 @classmethod
22 def FromPath(cls, path, empty_if_missing=False):
23 if os.path.isfile(path):
24 with open(path) as f:
25 return cls(f.read())
26 elif empty_if_missing:
27 cros_build_lib.Die('Manifest file, %r, not found' % path)
28 return cls()
29
Brian Harring7fcc02e2012-08-05 04:10:57 -070030 def __init__(self, text=None):
31 self._text = text or '<manifest>\n</manifest>'
Brian Harringb0043ab2012-08-05 04:09:56 -070032 self.nodes = ElementTree.fromstring(self._text)
Brian Harring7fcc02e2012-08-05 04:10:57 -070033
Rhyland Kleinb1d1f382012-08-23 11:53:45 -040034 def AddNonWorkonProject(self, name, path, remote=None, revision=None):
Brian Harringb0043ab2012-08-05 04:09:56 -070035 """Add a new nonworkon project element to the manifest tree."""
36 element = ElementTree.Element('project', name=name, path=path,
Rhyland Kleindd8ebbb2012-09-06 11:51:30 -040037 remote=remote)
Brian Harringb0043ab2012-08-05 04:09:56 -070038 element.attrib['workon'] = 'False'
Rhyland Kleindd8ebbb2012-09-06 11:51:30 -040039 if revision is not None:
40 element.attrib['revision'] = revision
Brian Harringb0043ab2012-08-05 04:09:56 -070041 self.nodes.append(element)
42 return element
Brian Harring7fcc02e2012-08-05 04:10:57 -070043
Brian Harringb0043ab2012-08-05 04:09:56 -070044 def GetProject(self, name, path=None):
Brian Harring7fcc02e2012-08-05 04:10:57 -070045 """Accessor method for getting a project node from the manifest tree.
46
47 Returns:
48 project element node from ElementTree, otherwise, None
49 """
Brian Harringb0043ab2012-08-05 04:09:56 -070050 if path is None:
51 # Use a unique value that can't ever match.
52 path = object()
53 for project in self.nodes.findall('project'):
54 if project.attrib['name'] == name or project.attrib['path'] == path:
Brian Harring7fcc02e2012-08-05 04:10:57 -070055 return project
56 return None
57
58 def ToString(self):
Brian Harringb0043ab2012-08-05 04:09:56 -070059 # Reset the tail for each node, then just do a hacky replace.
60 project = None
61 for project in self.nodes.findall('project'):
62 project.tail = '\n '
63 if project is not None:
64 # Tweak the last project to not have the trailing space.
65 project.tail = '\n'
66 # Fix manifest tag text and tail.
67 self.nodes.text = '\n '
68 self.nodes.tail = '\n'
69 return ElementTree.tostring(self.nodes)
70
71 def GetProjects(self):
72 return list(self.nodes.findall('project'))
73
74
Mike Frysingerc15efa52013-12-12 01:13:56 -050075def _AddProjectsToManifestGroups(options, *args):
Brian Harringb0043ab2012-08-05 04:09:56 -070076 """Enable the given manifest groups for the configured repository."""
77
Mike Frysingerc15efa52013-12-12 01:13:56 -050078 groups_to_enable = ['name:%s' % x for x in args]
Brian Harringb0043ab2012-08-05 04:09:56 -070079
80 git_config = options.git_config
81
David James67d73252013-09-19 17:33:12 -070082 cmd = ['config', '-f', git_config, '--get', 'manifest.groups']
83 enabled_groups = git.RunGit('.', cmd, error_code_ok=True).output.split(',')
Brian Harringb0043ab2012-08-05 04:09:56 -070084
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 James67d73252013-09-19 17:33:12 -0700103 cmd = ['config', '-f', git_config, 'manifest.groups',
104 ','.join(finalized_groups)]
105 git.RunGit('.', cmd)
Brian Harringb0043ab2012-08-05 04:09:56 -0700106
107
Gwendal Grignouf9d6d362016-09-30 09:29:20 -0700108def _AssertNotMiniLayout():
109 cros_build_lib.Die(
110 "Your repository checkout is using the old minilayout.xml workflow; "
111 "Autoupdate is no longer supported, reinstall your tree.")
Brian Harring7fcc02e2012-08-05 04:10:57 -0700112
113
Mike Frysinger4ca60152016-09-01 00:13:36 -0400114def GetParser():
115 """Return a command line parser."""
116 parser = commandline.ArgumentParser(description=__doc__)
Mike Frysinger1b8565b2016-09-13 16:03:49 -0400117
118 subparsers = parser.add_subparsers(dest='command')
119
120 subparser = subparsers.add_parser(
121 'add',
122 help='Add projects to the manifest.')
123 subparser.add_argument('-w', '--workon', action='store_true',
124 default=False, help='Is this a workon package?')
125 subparser.add_argument('-r', '--remote',
126 help='Remote project name (for non-workon packages).')
127 subparser.add_argument('-v', '--revision',
128 help='Use to override the manifest defined default '
129 'revision used for a given project.')
130 subparser.add_argument('project', help='Name of project in the manifest.')
131 subparser.add_argument('path', nargs='?', help='Local path to the project.')
132
Mike Frysinger4ca60152016-09-01 00:13:36 -0400133 return parser
134
135
Brian Harring7fcc02e2012-08-05 04:10:57 -0700136def main(argv):
Mike Frysinger4ca60152016-09-01 00:13:36 -0400137 parser = GetParser()
138 options = parser.parse_args(argv)
Ryan Cui0b1b94b2012-12-21 12:09:57 -0800139 repo_dir = git.FindRepoDir(os.getcwd())
Brian Harringb0043ab2012-08-05 04:09:56 -0700140 if not repo_dir:
141 parser.error("This script must be invoked from within a repository "
142 "checkout.")
Brian Harring7fcc02e2012-08-05 04:10:57 -0700143
Brian Harringb0043ab2012-08-05 04:09:56 -0700144 options.git_config = os.path.join(repo_dir, 'manifests.git', 'config')
Brian Harringb0043ab2012-08-05 04:09:56 -0700145 options.local_manifest_path = os.path.join(repo_dir, 'local_manifest.xml')
Brian Harringb0043ab2012-08-05 04:09:56 -0700146 options.manifest_sym_path = os.path.join(repo_dir, 'manifest.xml')
147
Gwendal Grignouf9d6d362016-09-30 09:29:20 -0700148 if (os.path.basename(os.readlink(options.manifest_sym_path)) ==
149 'minilayout.xml'):
150 _AssertNotMiniLayout()
Brian Harringb0043ab2012-08-05 04:09:56 -0700151
Mike Frysinger1b8565b2016-09-13 16:03:49 -0400152 # For now, we only support the add command.
153 assert options.command == 'add'
154 if options.workon:
155 if options.path is not None:
156 parser.error('Adding workon projects do not set project.')
Brian Harring7fcc02e2012-08-05 04:10:57 -0700157 else:
Brian Harringb0043ab2012-08-05 04:09:56 -0700158 if options.remote is None:
159 parser.error('Adding non-workon projects requires a remote.')
Mike Frysinger1b8565b2016-09-13 16:03:49 -0400160 if options.path is None:
161 parser.error('Adding non-workon projects requires a path.')
162 name = options.project
163 path = options.path
Brian Harring7fcc02e2012-08-05 04:10:57 -0700164
Rhyland Kleinb1d1f382012-08-23 11:53:45 -0400165 revision = options.revision
166 if revision is not None:
David James97d95872012-11-16 15:09:56 -0800167 if (not git.IsRefsTags(revision) and
168 not git.IsSHA1(revision)):
169 revision = git.StripRefsHeads(revision, False)
Rhyland Kleinb1d1f382012-08-23 11:53:45 -0400170
Brian Harringb0043ab2012-08-05 04:09:56 -0700171 main_manifest = Manifest.FromPath(options.manifest_sym_path,
172 empty_if_missing=False)
173 local_manifest = Manifest.FromPath(options.local_manifest_path)
Brian Harring7fcc02e2012-08-05 04:10:57 -0700174
Brian Harringb0043ab2012-08-05 04:09:56 -0700175 main_element = main_manifest.GetProject(name, path=path)
Brian Harring7fcc02e2012-08-05 04:10:57 -0700176
Brian Harringb0043ab2012-08-05 04:09:56 -0700177 if options.workon:
178 if main_element is None:
179 parser.error('No project named %r in the default manifest.' % name)
180 _AddProjectsToManifestGroups(options, main_element.attrib['name'])
181
Rhyland Kleinf33f6222012-10-25 12:15:42 -0400182 elif main_element is not None:
Rhyland Kleine5faad52012-10-31 11:58:19 -0400183 if options.remote is not None:
184 # Likely this project wasn't meant to be remote, so workon main element
Mike Frysinger383367e2014-09-16 15:06:17 -0400185 print("Project already exists in manifest. Using that as workon project.")
Rhyland Kleine5faad52012-10-31 11:58:19 -0400186 _AddProjectsToManifestGroups(options, main_element.attrib['name'])
187 else:
188 # Conflict will occur; complain.
189 parser.error("Requested project name=%r path=%r will conflict with "
190 "your current manifest %s" % (name, path, active_manifest))
Brian Harringb0043ab2012-08-05 04:09:56 -0700191
192 elif local_manifest.GetProject(name, path=path) is not None:
193 parser.error("Requested project name=%r path=%r conflicts with "
194 "your local_manifest.xml" % (name, path))
195
196 else:
Rhyland Kleinb1d1f382012-08-23 11:53:45 -0400197 element = local_manifest.AddNonWorkonProject(name=name, path=path,
198 remote=options.remote,
199 revision=revision)
Brian Harringb0043ab2012-08-05 04:09:56 -0700200 _AddProjectsToManifestGroups(options, element.attrib['name'])
201
202 with open(options.local_manifest_path, 'w') as f:
203 f.write(local_manifest.ToString())
204 return 0