| OLD | NEW |
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # Copyright (c) 2010 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """A wrapper script to manage a set of client modules in different SCM. | 6 """A wrapper script to manage a set of client modules in different SCM. |
| 7 | 7 |
| 8 This script is intended to be used to help basic management of client | 8 This script is intended to be used to help basic management of client |
| 9 program sources residing in one or more Subversion modules and Git | 9 program sources residing in one or more Subversion modules and Git |
| 10 repositories, along with other modules it depends on, also in Subversion or Git, | 10 repositories, along with other modules it depends on, also in Subversion or Git, |
| (...skipping 261 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 272 # To use the trunk of a component instead of what's in DEPS: | 272 # To use the trunk of a component instead of what's in DEPS: |
| 273 #"component": "https://svnserver/component/trunk/", | 273 #"component": "https://svnserver/component/trunk/", |
| 274 # To exclude a component from your working copy: | 274 # To exclude a component from your working copy: |
| 275 #"data/really_large_component": None, | 275 #"data/really_large_component": None, |
| 276 }, | 276 }, |
| 277 "safesync_url": "%(safesync_url)s" | 277 "safesync_url": "%(safesync_url)s" |
| 278 }, | 278 }, |
| 279 ] | 279 ] |
| 280 """) | 280 """) |
| 281 | 281 |
| 282 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\ |
| 283 { "name" : "%(solution_name)s", |
| 284 "url" : "%(solution_url)s", |
| 285 "custom_deps" : { |
| 286 %(solution_deps)s, |
| 287 }, |
| 288 "safesync_url": "%(safesync_url)s" |
| 289 }, |
| 290 """) |
| 291 |
| 292 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\ |
| 293 # An element of this array (a "solution") describes a repository directory |
| 294 # that will be checked out into your working copy. Each solution may |
| 295 # optionally define additional dependencies (via its DEPS file) to be |
| 296 # checked out alongside the solution's directory. A solution may also |
| 297 # specify custom dependencies (via the "custom_deps" property) that |
| 298 # override or augment the dependencies specified by the DEPS file. |
| 299 # If a "safesync_url" is specified, it is assumed to reference the location of |
| 300 # a text file which contains nothing but the last known good SCM revision to |
| 301 # sync against. It is fetched if specified and used unless --head is passed |
| 302 |
| 303 solutions = [ |
| 304 %(solution_list)s |
| 305 ] |
| 306 """) |
| 307 |
| 282 | 308 |
| 283 ## GClient implementation. | 309 ## GClient implementation. |
| 284 | 310 |
| 285 | 311 |
| 286 class GClient(object): | 312 class GClient(object): |
| 287 """Object that represent a gclient checkout.""" | 313 """Object that represent a gclient checkout.""" |
| 288 | 314 |
| 289 supported_commands = [ | 315 supported_commands = [ |
| 290 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update', | 316 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update', |
| 291 'runhooks' | 317 'runhooks' |
| (...skipping 502 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 794 if revision_overrides.has_key(revision_elem[0]) and \ | 820 if revision_overrides.has_key(revision_elem[0]) and \ |
| 795 revision_overrides[revision_elem[0]] != revision_elem[1]: | 821 revision_overrides[revision_elem[0]] != revision_elem[1]: |
| 796 raise gclient_utils.Error( | 822 raise gclient_utils.Error( |
| 797 "Conflicting revision numbers specified.") | 823 "Conflicting revision numbers specified.") |
| 798 revision_overrides[revision_elem[0]] = revision_elem[1] | 824 revision_overrides[revision_elem[0]] = revision_elem[1] |
| 799 | 825 |
| 800 solutions = self.GetVar("solutions") | 826 solutions = self.GetVar("solutions") |
| 801 if not solutions: | 827 if not solutions: |
| 802 raise gclient_utils.Error("No solution specified") | 828 raise gclient_utils.Error("No solution specified") |
| 803 | 829 |
| 804 entries = {} | |
| 805 entries_deps_content = {} | |
| 806 | |
| 807 # Inner helper to generate base url and rev tuple (including honoring | 830 # Inner helper to generate base url and rev tuple (including honoring |
| 808 # |revision_overrides|) | 831 # |revision_overrides|) |
| 809 def GetURLAndRev(name, original_url): | 832 def GetURLAndRev(name, original_url): |
| 810 url, revision = gclient_utils.SplitUrlRevision(original_url) | 833 url, revision = gclient_utils.SplitUrlRevision(original_url) |
| 811 if not revision: | 834 if not revision: |
| 812 if revision_overrides.has_key(name): | 835 if revision_overrides.has_key(name): |
| 813 return (url, revision_overrides[name]) | 836 return (url, revision_overrides[name]) |
| 814 else: | 837 else: |
| 815 scm = gclient_scm.CreateSCM(solution["url"], self._root_dir, name) | 838 scm = gclient_scm.CreateSCM(solution["url"], self._root_dir, name) |
| 816 return (url, scm.revinfo(self._options, [], None)) | 839 return (url, scm.revinfo(self._options, [], None)) |
| 817 else: | 840 else: |
| 818 if revision_overrides.has_key(name): | 841 if revision_overrides.has_key(name): |
| 819 return (url, revision_overrides[name]) | 842 return (url, revision_overrides[name]) |
| 820 else: | 843 else: |
| 821 return (url, revision) | 844 return (url, revision) |
| 822 | 845 |
| 846 # text of the snapshot gclient file |
| 847 new_gclient = "" |
| 848 # Dictionary of { path : SCM url } to ensure no duplicate solutions |
| 849 solution_names = {} |
| 823 # Run on the base solutions first. | 850 # Run on the base solutions first. |
| 824 for solution in solutions: | 851 for solution in solutions: |
| 852 # Dictionary of { path : SCM url } to describe the gclient checkout |
| 853 entries = {} |
| 854 entries_deps_content = {} |
| 825 name = solution["name"] | 855 name = solution["name"] |
| 826 if name in entries: | 856 if name in solution_names: |
| 827 raise gclient_utils.Error("solution %s specified more than once" % name) | 857 raise gclient_utils.Error("solution %s specified more than once" % name) |
| 828 (url, rev) = GetURLAndRev(name, solution["url"]) | 858 (url, rev) = GetURLAndRev(name, solution["url"]) |
| 829 entries[name] = "%s@%s" % (url, rev) | 859 entries[name] = "%s@%s" % (url, rev) |
| 860 solution_names[name] = "%s@%s" % (url, rev) |
| 830 deps_file = solution.get("deps_file", self._options.deps_file) | 861 deps_file = solution.get("deps_file", self._options.deps_file) |
| 831 if '/' in deps_file or '\\' in deps_file: | 862 if '/' in deps_file or '\\' in deps_file: |
| 832 raise gclient_utils.Error('deps_file name must not be a path, just a ' | 863 raise gclient_utils.Error('deps_file name must not be a path, just a ' |
| 833 'filename.') | 864 'filename.') |
| 834 try: | 865 try: |
| 835 deps_content = gclient_utils.FileRead( | 866 deps_content = gclient_utils.FileRead( |
| 836 os.path.join(self._root_dir, name, deps_file)) | 867 os.path.join(self._root_dir, name, deps_file)) |
| 837 except IOError, e: | 868 except IOError, e: |
| 838 if e.errno != errno.ENOENT: | 869 if e.errno != errno.ENOENT: |
| 839 raise | 870 raise |
| 840 deps_content = "" | 871 deps_content = "" |
| 841 entries_deps_content[name] = deps_content | 872 entries_deps_content[name] = deps_content |
| 842 | 873 |
| 843 # Process the dependencies next (sort alphanumerically to ensure that | 874 # Process the dependencies next (sort alphanumerically to ensure that |
| 844 # containing directories get populated first and for readability) | 875 # containing directories get populated first and for readability) |
| 845 deps = self._ParseAllDeps(entries, entries_deps_content) | 876 deps = self._ParseAllDeps(entries, entries_deps_content) |
| 846 deps_to_process = deps.keys() | 877 deps_to_process = deps.keys() |
| 847 deps_to_process.sort() | 878 deps_to_process.sort() |
| 848 | 879 |
| 849 # First pass for direct dependencies. | 880 # First pass for direct dependencies. |
| 850 for d in deps_to_process: | 881 for d in deps_to_process: |
| 851 if type(deps[d]) == str: | 882 if type(deps[d]) == str: |
| 852 (url, rev) = GetURLAndRev(d, deps[d]) | 883 (url, rev) = GetURLAndRev(d, deps[d]) |
| 853 entries[d] = "%s@%s" % (url, rev) | 884 entries[d] = "%s@%s" % (url, rev) |
| 854 | 885 |
| 855 # Second pass for inherited deps (via the From keyword) | 886 # Second pass for inherited deps (via the From keyword) |
| 856 for d in deps_to_process: | 887 for d in deps_to_process: |
| 857 if type(deps[d]) != str: | 888 if type(deps[d]) != str: |
| 858 deps_parent_url = entries[deps[d].module_name] | 889 deps_parent_url = entries[deps[d].module_name] |
| 859 if deps_parent_url.find("@") < 0: | 890 if deps_parent_url.find("@") < 0: |
| 860 raise gclient_utils.Error("From %s missing revisioned url" % | 891 raise gclient_utils.Error("From %s missing revisioned url" % |
| 861 deps[d].module_name) | 892 deps[d].module_name) |
| 862 content = gclient_utils.FileRead(os.path.join(self._root_dir, | 893 content = gclient_utils.FileRead(os.path.join( |
| 863 deps[d].module_name, | 894 self._root_dir, |
| 864 self._options.deps_file)) | 895 deps[d].module_name, |
| 865 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {}) | 896 self._options.deps_file)) |
| 866 (url, rev) = GetURLAndRev(d, sub_deps[d]) | 897 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {}) |
| 867 entries[d] = "%s@%s" % (url, rev) | 898 (url, rev) = GetURLAndRev(d, sub_deps[d]) |
| 868 print(";\n".join(["%s: %s" % (x, entries[x]) | 899 entries[d] = "%s@%s" % (url, rev) |
| 869 for x in sorted(entries.keys())])) | 900 |
| 870 | 901 # Build the snapshot configuration string |
| 871 | 902 if self._options.snapshot: |
| 903 url = entries.pop(name) |
| 904 custom_deps = ",\n ".join(["\"%s\": \"%s\"" % (x, entries[x]) |
| 905 for x in sorted(entries.keys())]) |
| 906 |
| 907 new_gclient += DEFAULT_SNAPSHOT_SOLUTION_TEXT % { |
| 908 'solution_name': name, |
| 909 'solution_url': url, |
| 910 'safesync_url' : "", |
| 911 'solution_deps': custom_deps, |
| 912 } |
| 913 else: |
| 914 print(";\n".join(["%s: %s" % (x, entries[x]) |
| 915 for x in sorted(entries.keys())])) |
| 916 |
| 917 # Print the snapshot configuration file |
| 918 if self._options.snapshot: |
| 919 config = DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient} |
| 920 snapclient = GClient(self._root_dir, self._options) |
| 921 snapclient.SetConfig(config) |
| 922 print(snapclient._config_content) |
| 923 |
| 924 |
| 872 ## gclient commands. | 925 ## gclient commands. |
| 873 | 926 |
| 874 | 927 |
| 875 def DoCleanup(options, args): | 928 def DoCleanup(options, args): |
| 876 """Handle the cleanup subcommand. | 929 """Handle the cleanup subcommand. |
| 877 | 930 |
| 878 Raises: | 931 Raises: |
| 879 Error: if client isn't configured properly. | 932 Error: if client isn't configured properly. |
| 880 """ | 933 """ |
| 881 client = GClient.LoadCurrentConfig(options) | 934 client = GClient.LoadCurrentConfig(options) |
| (...skipping 262 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1144 default=False, | 1197 default=False, |
| 1145 help="Skip svn up whenever possible by requesting " | 1198 help="Skip svn up whenever possible by requesting " |
| 1146 "actual HEAD revision from the repository") | 1199 "actual HEAD revision from the repository") |
| 1147 option_parser.add_option("", "--head", action="store_true", default=False, | 1200 option_parser.add_option("", "--head", action="store_true", default=False, |
| 1148 help=("skips any safesync_urls specified in " | 1201 help=("skips any safesync_urls specified in " |
| 1149 "configured solutions")) | 1202 "configured solutions")) |
| 1150 option_parser.add_option("", "--delete_unversioned_trees", | 1203 option_parser.add_option("", "--delete_unversioned_trees", |
| 1151 action="store_true", default=False, | 1204 action="store_true", default=False, |
| 1152 help=("on update, delete any unexpected " | 1205 help=("on update, delete any unexpected " |
| 1153 "unversioned trees that are in the checkout")) | 1206 "unversioned trees that are in the checkout")) |
| 1207 option_parser.add_option("", "--snapshot", action="store_true", default=False, |
| 1208 help=("(revinfo only), create a snapshot file " |
| 1209 "of the current version of all repositories")) |
| 1210 option_parser.add_option("", "--gclientfile", default=None, |
| 1211 metavar="FILENAME", |
| 1212 help=("specify an alternate .gclient file")) |
| 1154 | 1213 |
| 1155 if len(argv) < 2: | 1214 if len(argv) < 2: |
| 1156 # Users don't need to be told to use the 'help' command. | 1215 # Users don't need to be told to use the 'help' command. |
| 1157 option_parser.print_help() | 1216 option_parser.print_help() |
| 1158 return 1 | 1217 return 1 |
| 1159 # Add manual support for --version as first argument. | 1218 # Add manual support for --version as first argument. |
| 1160 if argv[1] == '--version': | 1219 if argv[1] == '--version': |
| 1161 option_parser.print_version() | 1220 option_parser.print_version() |
| 1162 return 0 | 1221 return 0 |
| 1163 | 1222 |
| 1164 # Add manual support for --help as first argument. | 1223 # Add manual support for --help as first argument. |
| 1165 if argv[1] == '--help': | 1224 if argv[1] == '--help': |
| 1166 argv[1] = 'help' | 1225 argv[1] = 'help' |
| 1167 | 1226 |
| 1168 command = argv[1] | 1227 command = argv[1] |
| 1169 options, args = option_parser.parse_args(argv[2:]) | 1228 options, args = option_parser.parse_args(argv[2:]) |
| 1170 | 1229 |
| 1171 if len(argv) < 3 and command == "help": | 1230 if len(argv) < 3 and command == "help": |
| 1172 option_parser.print_help() | 1231 option_parser.print_help() |
| 1173 return 0 | 1232 return 0 |
| 1174 | 1233 |
| 1175 if options.verbose > 1: | 1234 if options.verbose > 1: |
| 1176 logging.basicConfig(level=logging.DEBUG) | 1235 logging.basicConfig(level=logging.DEBUG) |
| 1177 | 1236 |
| 1178 # Files used for configuration and state saving. | 1237 # Files used for configuration and state saving. |
| 1179 options.config_filename = os.environ.get("GCLIENT_FILE", ".gclient") | 1238 options.config_filename = os.environ.get("GCLIENT_FILE", ".gclient") |
| 1180 options.entries_filename = ".gclient_entries" | 1239 if options.gclientfile: |
| 1240 options.config_filename = options.gclientfile |
| 1241 options.entries_filename = options.config_filename + "_entries" |
| 1181 options.deps_file = "DEPS" | 1242 options.deps_file = "DEPS" |
| 1182 | 1243 |
| 1183 options.platform = sys.platform | 1244 options.platform = sys.platform |
| 1184 return DispatchCommand(command, options, args) | 1245 return DispatchCommand(command, options, args) |
| 1185 | 1246 |
| 1186 | 1247 |
| 1187 if "__main__" == __name__: | 1248 if "__main__" == __name__: |
| 1188 try: | 1249 try: |
| 1189 result = Main(sys.argv) | 1250 result = Main(sys.argv) |
| 1190 except gclient_utils.Error, e: | 1251 except gclient_utils.Error, e: |
| 1191 print >> sys.stderr, "Error: %s" % str(e) | 1252 print >> sys.stderr, "Error: %s" % str(e) |
| 1192 result = 1 | 1253 result = 1 |
| 1193 sys.exit(result) | 1254 sys.exit(result) |
| 1194 | 1255 |
| 1195 # vim: ts=2:sw=2:tw=80:et: | 1256 # vim: ts=2:sw=2:tw=80:et: |
| OLD | NEW |