| Left: | ||
| Right: |
| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/env python | |
| 2 # coding: utf-8 | |
| 3 | |
| 4 # This file is part of the Adblock Plus build tools, | |
| 5 # Copyright (C) 2006-2014 Eyeo GmbH | |
| 6 # | |
| 7 # Adblock Plus is free software: you can redistribute it and/or modify | |
| 8 # it under the terms of the GNU General Public License version 3 as | |
| 9 # published by the Free Software Foundation. | |
| 10 # | |
| 11 # Adblock Plus is distributed in the hope that it will be useful, | |
| 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 14 # GNU General Public License for more details. | |
| 15 # | |
| 16 # You should have received a copy of the GNU General Public License | |
| 17 # along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. | |
| 18 | |
| 19 import sys | |
| 20 import os | |
| 21 import re | |
| 22 import codecs | |
| 23 import errno | |
| 24 import subprocess | |
| 25 import urlparse | |
| 26 from collections import OrderedDict | |
| 27 | |
| 28 class Mercurial(): | |
|
Sebastian Noack
2014/09/02 08:12:30
Since Mercurial and Git only have static functions
Wladimir Palant
2014/09/02 14:48:03
I'd rather keep it all in one file. Keep in mind t
Sebastian Noack
2014/09/03 19:23:07
Agreed. But you could just initiate those classes
Felix Dahlke
2014/09/05 08:55:53
I actually preferred the static methods :P As it w
Sebastian Noack
2014/09/05 09:29:30
Think of those classes as singletons. There is sup
Felix Dahlke
2014/09/05 09:55:01
A singleton is arguably also the wrong choice when
Sebastian Noack
2014/09/05 10:47:06
We just need two objects, with different implement
Felix Dahlke
2014/09/05 14:01:54
Sure, you normally don't pass namespaces around, i
Sebastian Noack
2014/09/05 14:24:46
Not in Python. staticmethod is just a regular func
| |
| 29 @staticmethod | |
| 30 def istype(repodir): | |
| 31 return os.path.exists(os.path.join(repodir, ".hg")) | |
| 32 | |
| 33 @staticmethod | |
| 34 def clone(source, target): | |
| 35 if not source.endswith("/"): | |
| 36 source += "/" | |
| 37 subprocess.check_call(["hg", "clone", "--quiet", "--noupdate", source, targe t]) | |
| 38 | |
| 39 @staticmethod | |
| 40 def get_revision_id(repo, rev=None): | |
| 41 command = ["hg", "id", "--repository", repo, "--id"] | |
| 42 if rev: | |
| 43 command.extend(["--rev", rev]) | |
| 44 | |
| 45 result, dummy = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=sub process.PIPE).communicate() | |
|
Sebastian Noack
2014/09/02 08:12:30
Any reason, not using check_output() here?
Wladimir Palant
2014/09/02 14:48:03
Yes, I need to drop stderr output. "hg id" won't b
Sebastian Noack
2014/09/03 19:23:07
subprocess.check_output(stderr=open(os.devnull, "w
Wladimir Palant
2014/09/03 21:57:47
What about exceptions? There is no subprocess.outp
Sebastian Noack
2014/09/04 10:07:30
Sure, if you also have to ignore non-zero status c
| |
| 46 return result.strip() | |
| 47 | |
| 48 @staticmethod | |
| 49 def pull(repo): | |
| 50 subprocess.check_call(["hg", "pull", "--repository", repo, "--quiet"]) | |
| 51 | |
| 52 @staticmethod | |
| 53 def update(repo, rev): | |
| 54 subprocess.check_call(["hg", "update", "--repository", repo, "--quiet", "--c heck", "--rev", rev]) | |
| 55 | |
| 56 class Git(): | |
| 57 @staticmethod | |
| 58 def istype(repodir): | |
| 59 return os.path.exists(os.path.join(repodir, ".git")) | |
| 60 | |
| 61 @staticmethod | |
| 62 def clone(source, target): | |
| 63 source = source.rstrip("/") | |
| 64 if not source.endswith(".git"): | |
| 65 source += ".git" | |
| 66 subprocess.check_call(["git", "clone", "--quiet", "--no-checkout", source, t arget]) | |
| 67 | |
| 68 @staticmethod | |
| 69 def get_revision_id(repo, rev="HEAD"): | |
| 70 command = ["git", "-C", repo, "rev-parse", "--revs-only", rev] | |
| 71 result, dummy = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=sub process.PIPE).communicate() | |
|
Sebastian Noack
2014/09/02 08:12:30
Again, any reason you don't use check_output()?
Wladimir Palant
2014/09/02 14:48:03
With the --revs-only flag - no real reason, I can
| |
| 72 return result.strip() | |
| 73 | |
| 74 @staticmethod | |
| 75 def pull(repo): | |
| 76 subprocess.check_call(["git", "-C", repo, "pull", "--quiet", "--all"]) | |
| 77 | |
| 78 @staticmethod | |
| 79 def update(repo, rev): | |
| 80 subprocess.check_call(["git", "-C", repo, "checkout", "--quiet", rev]) | |
| 81 | |
| 82 repo_types = { | |
| 83 "hg": Mercurial, | |
| 84 "git": Git, | |
| 85 } | |
| 86 | |
| 87 def parse_spec(path, line): | |
| 88 if "=" not in line: | |
| 89 print >>sys.stderr, "Invalid line in file %s: %s" % (path, line) | |
|
Sebastian Noack
2014/09/02 08:12:30
I prefer to use the warnings module here, instead
Wladimir Palant
2014/09/02 14:48:03
I ended up overriding warning.showwarning() to avo
Sebastian Noack
2014/09/03 19:23:07
You should have a look at warnings.filterwarnings(
Wladimir Palant
2014/09/03 21:57:47
I already did, so what? I don't need to switch off
Sebastian Noack
2014/09/04 10:07:30
I thought you were talking about filtering out irr
Wladimir Palant
2014/09/04 13:32:12
Thank you, the logging module is indeed a lot bett
| |
| 90 return None, None | |
| 91 | |
| 92 key, value = line.split("=", 1) | |
| 93 key = key.strip() | |
| 94 items = value.split() | |
| 95 if not len(items): | |
| 96 print >>sys.stderr, "No value specified for key %s in file %s" % (key, path) | |
| 97 return key, None | |
| 98 | |
| 99 result = OrderedDict() | |
| 100 if not key.startswith("_"): | |
| 101 result["_source"] = items.pop(0) | |
| 102 | |
| 103 for item in items: | |
| 104 if ":" in item: | |
| 105 type, value = item.split(":", 1) | |
| 106 else: | |
| 107 type, value = ("*", item) | |
| 108 if type in result: | |
| 109 print >>sys.stderr, "Ignoring duplicate value for type %s (key %s in file %s)" % (type, key, path) | |
| 110 else: | |
| 111 result[type] = value | |
| 112 return key, result | |
| 113 | |
| 114 def read_deps(repodir): | |
| 115 result = {} | |
| 116 deps_path = os.path.join(repodir, ".sub") | |
| 117 try: | |
| 118 with codecs.open(deps_path, "r", encoding="utf-8") as handle: | |
| 119 for line in handle: | |
| 120 # Remove comments and whitespace | |
| 121 line = re.sub(r"#.*", "", line).strip() | |
| 122 if not line: | |
| 123 continue | |
| 124 | |
| 125 key, spec = parse_spec(deps_path, line) | |
| 126 if spec: | |
| 127 result[key] = spec | |
| 128 return result | |
| 129 except IOError, e: | |
| 130 if e.errno != errno.ENOENT: | |
| 131 raise | |
| 132 return None | |
| 133 | |
| 134 def safe_join(path, subpath): | |
| 135 return os.path.join(path, *filter(lambda f: f.count(".") != len(f), subpath.sp lit("/"))) | |
|
Sebastian Noack
2014/09/02 08:12:30
I prefer list comprehensions over filter(lambda: .
| |
| 136 | |
| 137 def get_repo_type(repo): | |
| 138 for name, repotype in repo_types.iteritems(): | |
| 139 if repotype.istype(repo): | |
| 140 return name | |
| 141 return None | |
| 142 | |
| 143 def ensure_repo(config, parentrepo, dir): | |
| 144 target = safe_join(parentrepo, dir) | |
| 145 if os.path.exists(target): | |
| 146 return | |
| 147 | |
| 148 parenttype = get_repo_type(parentrepo) | |
| 149 type = None | |
| 150 for key in config.get("_root", {}): | |
| 151 if key == parenttype or (key in repo_types and type == None): | |
|
Sebastian Noack
2014/09/02 08:12:30
Please use "is" operator when comparing to None.
| |
| 152 type = key | |
| 153 if type == None: | |
| 154 raise Exception("No valid source found to create %s" % dir) | |
| 155 | |
| 156 url = urlparse.urljoin(config["_root"][type], config[dir]["_source"]) | |
| 157 print "Cloning repository %s into %s" % (url, target) | |
| 158 repo_types[type].clone(url, target) | |
| 159 | |
| 160 def update_repo(config, parentrepo, dir): | |
| 161 target = safe_join(parentrepo, dir) | |
| 162 type = get_repo_type(target) | |
| 163 if type == None: | |
| 164 print >>sys.stderr, "Type of repository %s unknown, skipping update" % targe t | |
| 165 return | |
| 166 | |
| 167 if type in config[dir]: | |
| 168 revision = config[dir][type] | |
| 169 elif "*" in config[dir]: | |
| 170 revision = config[dir]["*"] | |
| 171 else: | |
| 172 print >>sys.stderr, "No revision specified for repository %s (type %s), skip ping update" % (target, type) | |
| 173 return | |
| 174 | |
| 175 resolved_revision = repo_types[type].get_revision_id(target, revision) | |
| 176 if not resolved_revision: | |
| 177 print "Revision %s is unknown, downloading remote changes" % revision | |
| 178 repo_types[type].pull(target) | |
| 179 resolved_revision = repo_types[type].get_revision_id(target, revision) | |
| 180 if not resolved_revision: | |
| 181 raise Exception("Failed to resolve revision %s" % revision) | |
| 182 | |
| 183 current_revision = repo_types[type].get_revision_id(target) | |
| 184 if resolved_revision != current_revision: | |
| 185 print "Updating repository %s to revision %s" % (target, resolved_revision) | |
| 186 repo_types[type].update(target, resolved_revision) | |
| 187 | |
| 188 def resolve_deps(repodir, level=0): | |
| 189 config = read_deps(repodir) | |
| 190 if config == None: | |
| 191 if level == 0: | |
| 192 print >>sys.stderr, "No .sub file in directory %s, nothing to do..." % rep odir | |
| 193 return | |
| 194 if level >= 10: | |
| 195 print >>sys.stderr, "Too much subrepository nesting, ignoring %s" % repo | |
| 196 | |
| 197 for dir in config: | |
| 198 if dir.startswith("_"): | |
| 199 continue | |
| 200 target = safe_join(repodir, dir) | |
| 201 ensure_repo(config, repodir, dir) | |
| 202 update_repo(config, repodir, dir) | |
| 203 resolve_deps(target, level + 1) | |
| 204 | |
| 205 if __name__ == "__main__": | |
| 206 repos = sys.argv[1:] | |
| 207 if not len(repos): | |
| 208 repos = [os.getcwd()] | |
| 209 for repo in repos: | |
| 210 resolve_deps(repo) | |
| OLD | NEW |