chiark / gitweb /
Wow, is that a proper Android build system?
[tripe-android] / peers.scala
CommitLineData
8eabb4ff
MW
1package uk.org.distorted.tripe; package object peers {
2
3import java.io.{BufferedReader, File, FileReader, Reader};
4import java.net.{InetAddress, Inet4Address, Inet6Address,
5 UnknownHostException};
6
7import scala.collection.mutable.{HashMap, HashSet};
8import scala.concurrent.Channel;
9import scala.util.control.Breaks;
10import scala.util.matching.Regex;
11
12val RX_COMMENT = """(?x) ^ \s* (?: [;\#] .* )? $""".r;
13val RX_GRPHDR = """(?x) ^ \s* \[ (.*) \] \s* $""".r;
14val RX_ASSGN = """(?x) ^
15 ([^\s:=] (?: [^:=]* [^\s:=])?)
16 \s* [:=] \s*
17 (| [^\s\#;]\S* (?: \s+ [^\s\#;]\S*)*)
18 (?: \s+ (?: [;\#].*)? )? $""".r;
19val RX_CONT = """(?x) ^ \s+
20 (| [^\s\#;]\S* (?: \s+ [^\s\#;]\S*)*)
21 (?: \s+ (?: [;\#].*)? )? $""".r;
22val RX_REF = """(?x) \$ \( ([^)]+) \)""".r;
23val RX_RESOLVE = """(?x) \$ ([46*]*) \[ ([^\]]+) \]""".r;
24val RX_PARENT = """(?x) [^\s,]+""".r
25
8eabb4ff
MW
26object BulkResolver {
27 val BREAK = new Breaks;
28}
29
30class BulkResolver(val nthreads: Int = 8) {
31 import BulkResolver.BREAK._;
32 class Host(val name: String) {
33 var a4, a6: Seq[InetAddress] = Seq.empty;
34
35 def addaddr(a: InetAddress) { a match {
36 case _: Inet4Address => a4 +:= a;
37 case _: Inet6Address => a6 +:= a;
38 case _ => ();
39 } }
40
41 def get(flags: String): Seq[InetAddress] = {
42 var wanta4, wanta6, any, all = false;
43 var b = Seq.newBuilder[InetAddress];
44 for (ch <- flags) ch match {
45 case '*' => all = true;
46 case '4' => wanta4 = true; any = true;
47 case '6' => wanta6 = true; any = true;
48 case _ => ???
49 }
50 if (!any) { wanta4 = true; wanta6 = true; }
51 if (wanta4) b ++= a4;
52 if (wanta6) b ++= a6;
53 (all, b.result) match {
54 case (true, aa) => aa
55 case (false, aa@(Nil | Seq(_))) => aa
56 case (false, Seq(a, _*)) => Seq(a)
57 }
58 }
59 }
60 val ch = new Channel[Host];
61 val map = HashMap[String, Host]();
62 var preparing = true;
63
64 val workers = Array.tabulate(nthreads) { i =>
65 thread(s"resolver worker #$i") {
c8292b34
MW
66 loopUnit { exit =>
67 val host = ch.read; if (host == null) exit;
8eabb4ff 68println(s";; ${Thread.currentThread.getName} resolving `${host.name}'");
c8292b34
MW
69 try {
70 for (a <- InetAddress.getAllByName(host.name)) host.addaddr(a);
71 } catch { case e: UnknownHostException => () }
8eabb4ff
MW
72 }
73println(s";; ${Thread.currentThread.getName} done'");
74 ch.write(null);
75 }
76 }
77
78 def prepare(name: String) {
79println(s";; prepare host `$name'");
80 assert(preparing);
81 if (!(map contains name)) {
82 val host = new Host(name);
83 map(name) = host;
84 ch.write(host);
85 }
86 }
87
88 def finish() {
89 assert(preparing);
90 preparing = false;
91 ch.write(null);
92 for (t <- workers) t.join();
93 }
94
95 def resolve(name: String, flags: String): Seq[InetAddress] =
96 map(name).get(flags);
97}
98
99def fmtpath(path: Seq[String]) =
100 path.reverse map { i => s"`$i'" } mkString " -> ";
101
102class ConfigSyntaxError(val file: File, val lno: Int, val msg: String)
103 extends Exception {
104 override def getMessage(): String = s"$file:$lno: $msg";
105}
106class MissingConfigSection(val sect: String) extends Exception {
107 override def getMessage(): String =
108 s"missing configuration section `$sect'";
109}
110class MissingConfigItem(val sect: String, val key: String,
111 val path: Seq[(String)]) extends Exception {
112 override def getMessage(): String = {
113 val msg = s"missing configuration item `$key' in section `$sect'";
114 if (path == Nil) msg
115 else msg + s" (wanted while expanding ${fmtpath(path)})"
116 }
117}
118class AmbiguousConfig(val key: String,
119 val v0: String, val p0: Seq[String],
120 val v1: String, val p1: Seq[String])
121 extends Exception {
122 override def getMessage(): String =
123 s"ambiguous answer resolving key `$key': " +
124 s"path ${fmtpath(p0)} yields `$v0', but ${fmtpath(p1)} yields `$v1'";
125}
126
127class ConfigCycle(val key: String, path: Seq[String]) extends Exception {
128 override def getMessage(): String =
129 s"found a cycle ${fmtpath(path)} looking up key `$key'";
130}
131class NoHostAddresses(val sect: String, val key: String, val host: String)
132 extends Exception {
133 override def getMessage(): String =
134 s"no addresses found for `$host' (key `$key' in section `$sect')";
135}
136
137object Config {
138 sealed abstract class ConfigCacheEntry;
139 case object StillLooking extends ConfigCacheEntry;
140 case object NotFound extends ConfigCacheEntry;
141 case class Found(value: String, path: Seq[String])
142 extends ConfigCacheEntry;
143}
144
145class Config { conf =>
146 import Config._;
147 class Section(val name: String) {
148 val itemmap = HashMap[String, String]();
149 val cache = HashMap[String, ConfigCacheEntry]();
150 override def toString: String = s"${getClass.getName}($name)";
151 def parents: Seq[Section] =
152 (itemmap.get("@inherit")
153 map { pp => (RX_PARENT.findAllIn(pp) map { conf.section _ }).toList }
154 getOrElse Nil);
155
156 def get_internal(key: String, path: Seq[String] = Nil):
157 Option[(String, Seq[String])] = {
158 val incpath = name +: path;
159
160 for (r <- cache.get(key)) r match {
161 case StillLooking => throw new ConfigCycle(key, incpath)
162 case NotFound => return None
163 case Found(v, p) => return Some((v, p ++ path));
164 }
165
166 for (v <- itemmap.get(key)) {
167 cache(key) = Found(v, Seq(name));
168 return Some((v, incpath));
169 }
170
171 cache(key) = StillLooking;
172
173 ((None: Option[(String, Seq[String])]) /: parents) { (st, parent) =>
174 parent.get_internal(key, incpath) match {
175 case None => st;
176 case newst@Some((v, p)) => st match {
177 case None => newst
178 case Some((vv, _)) if v == vv => st
179 case Some((vv, pp)) =>
180 throw new AmbiguousConfig(key, v, p, vv, pp)
181 }
182 }
183 } match {
184 case None => cache(key) = NotFound; None
185 case Some((v, p)) =>
186 cache(key) = Found(v, p dropRight path.length);
187 Some((v, p))
188 }
189 }
190
191 def get(key: String, resolve: Boolean = true,
192 path: Seq[String] = Nil): String = {
193 val v0 = key match {
194 case "name" => itemmap.getOrElse("name", name)
195 case _ => get_internal(key).
196 getOrElse(throw new MissingConfigItem(name, key, path))._1
197 }
198 expand(key, v0, resolve, path)
199 }
200
201 def expand(key: String, value: String, resolve: Boolean,
202 path: Seq[String]): String = {
203 val v1 = RX_REF.replaceAllIn(value, { m =>
204 Regex.quoteReplacement(get(m.group(1), resolve, path))
205 });
206 val v2 = if (!resolve) v1
207 else RX_RESOLVE.replaceAllIn(v1, { m =>
208 resolver.resolve(m.group(2), m.group(1)) match {
209 case Nil =>
210 throw new NoHostAddresses(name, key, m.group(2));
211 case addrs =>
212 Regex.quoteReplacement((addrs map { _.getHostAddress }).
213 mkString(" "));
214 }
215 })
216 v2
217 }
218
219 def items: Seq[String] = {
220 val b = Seq.newBuilder[String];
221 val seen = HashSet[String]();
222 val visiting = HashSet[String](name);
223 var stack = List(this);
224
225 while (stack != Nil) {
226 val sect = stack.head; stack = stack.tail;
227 for (k <- sect.itemmap.keys)
228 if (!(seen contains k)) { b += k; seen += k; }
229 for (p <- sect.parents)
230 if (!(visiting contains p.name))
231 { stack ::= p; visiting += p.name; }
232 }
233 b.result
234 }
235 }
236 val sectmap = new HashMap[String, Section];
237 def sections: Iterator[Section] = sectmap.values.iterator;
238 def section(name: String): Section =
239 sectmap.getOrElse(name, throw new MissingConfigSection(name));
240
241 val resolver = new BulkResolver;
242
243 def parseFile(path: File): this.type = {
244println(s";; parse ${path.getPath}");
c8292b34 245 withCleaner { clean =>
8eabb4ff
MW
246 val in = new FileReader(path); clean { in.close(); }
247
248 val lno = 1;
249 val b = new StringBuilder;
250 var key: String = null;
251 var sect: Section = null;
252 def flush() {
253 if (key != null) {
254 sect.itemmap(key) = b.result;
255println(s";; in `${sect.name}', set `$key' to `${b.result}'");
256 b.clear();
257 key = null;
258 }
259 }
260 for (line <- lines(in)) line match {
261 case RX_COMMENT() =>
262 ();
263 case RX_GRPHDR(grp) =>
264 flush();
265 sect = sectmap.getOrElseUpdate(grp, new Section(grp));
266 case RX_CONT(v) =>
267 if (key == null) {
268 throw new ConfigSyntaxError(
269 path, lno, "no config value to continue");
270 }
271 b += '\n'; b ++= v;
272 case RX_ASSGN(k, v) =>
273 if (sect == null) {
274 throw new ConfigSyntaxError(
275 path, lno, "no active section to update");
276 }
277 flush();
278 key = k; b ++= v;
279 case _ =>
280 throw new ConfigSyntaxError(path, lno, "incomprehensible line");
281 }
282 flush();
283 }
284 this
285 }
286 def parse(path: File): this.type = {
287 if (!path.isDirectory) parseFile(path);
288 else for {
289 f <- path.listFiles sortBy { _.getName };
290 name = f.getName;
291 if name.length > 0;
292 tail = name(name.length - 1);
293 if tail != '#' && tail != '~'
294 } parseFile(f);
295 this
296 }
297 def parse(path: String): this.type = parse(new File(path));
298
299 def analyse() {
300println(";; resolving all...");
301 for ((_, sect) <- sectmap) {
302println(s";; resolving in section `${sect.name}'...");
303 for (key <- sect.items) {
304println(s";; resolving in key `$key'...");
305 val mm = RX_RESOLVE.findAllIn(sect.get(key, false));
306 for (host <- mm) { resolver.prepare(mm.group(2)); }
307 }
308 }
309 resolver.finish();
310
311 def dumpsect(sect: Section) {
312 for (k <- sect.items.filterNot(_.startsWith("@")).sorted)
313 println(s";; `$k' -> `${sect.get(k)}'")
314 }
315 for (sect <- sectmap.values.toSeq sortBy { _.name })
316 if (sect.name.startsWith("@")) ();
317 else if (sect.name.startsWith("$")) {
318 println(s";; special section `${sect.name}'");
319 dumpsect(sect);
320 } else {
321 println(s";; peer section `${sect.name}'");
322 dumpsect(sect);
323 }
324 }
325}
326
327}