]> sigrok.org Git - sigrok-androidutils.git/blame - ant/src/org/sigrok/androidutils/ant/CopyLibsTask.java
Bundle Java bindings into an AAR for ease of use
[sigrok-androidutils.git] / ant / src / org / sigrok / androidutils / ant / CopyLibsTask.java
CommitLineData
ea3ce762 1/*
5de7ce63 2 * This file is part of the sigrok-androidutils project.
ea3ce762
MC
3 *
4 * Copyright (C) 2014 Marcus Comstedt <marcus@mc.pp.se>
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20package org.sigrok.androidutils.ant;
21
22import java.io.File;
23import java.io.IOException;
24import java.io.FileInputStream;
25import java.io.FileOutputStream;
26import java.io.InputStream;
27import java.io.OutputStream;
28import java.util.ArrayList;
29import java.util.Arrays;
30import java.util.Collections;
31import java.util.HashMap;
32import java.util.HashSet;
33import java.util.LinkedList;
34import java.util.Queue;
35import java.util.TreeSet;
36import java.util.Vector;
37import org.apache.tools.ant.BuildException;
711d21ce 38import org.apache.tools.ant.DynamicAttribute;
ea3ce762
MC
39import org.apache.tools.ant.Task;
40import org.apache.tools.ant.types.FileSet;
41import org.apache.tools.ant.types.PatternSet;
42import org.apache.tools.ant.types.Resource;
43import org.apache.tools.ant.types.ResourceCollection;
44import org.apache.tools.ant.types.resources.FileProvider;
45import org.apache.tools.ant.types.selectors.SelectorUtils;
46
711d21ce 47public class CopyLibsTask extends Task implements DynamicAttribute
6f1c3a93 48{
995e06f3
MC
49 private static final HashMap<String,String> blacklist;
50
51 static {
7a07206c 52 HashMap<String,String> bl = new HashMap<String,String>();
fcfb9679
UH
53 bl.put("libpcre.so", "libercp.so");
54 blacklist = bl;
995e06f3
MC
55 }
56
6f1c3a93
UH
57 private static BuildException buildException(Exception e)
58 {
59 if (e instanceof BuildException)
60 return (BuildException)e;
ea3ce762 61 else
6f1c3a93 62 return new BuildException(e);
ea3ce762
MC
63 }
64
6f1c3a93 65 private static int indexOf(byte[] data, byte v, int start)
ea3ce762 66 {
6f1c3a93
UH
67 if (data != null) {
68 for (int i = start; i < data.length; i++) {
69 if (data[i] == v)
70 return i;
71 }
72 }
73 return -1;
ea3ce762
MC
74 }
75
6f1c3a93 76 private static String fixSoname(String s)
ea3ce762 77 {
6f1c3a93
UH
78 int l = s.length();
79 int i = s.lastIndexOf(".so");
ea3ce762 80
6f1c3a93 81 if (i >= 0 && i < (l - 3))
995e06f3
MC
82 s = s.substring(0, i + 3);
83
84 String bl = blacklist.get(s);
85 if (bl != null)
fcfb9679 86 s = bl;
995e06f3
MC
87
88 return s;
ea3ce762
MC
89 }
90
6f1c3a93 91 protected class Library implements Comparable<Library>
ea3ce762 92 {
6f1c3a93
UH
93 protected final File file;
94 protected final ElfFile elf;
95 protected final HashSet<String> needed;
96 protected final Vector<String> rpath;
97 protected final TreeSet<Range> fixups;
98 protected final String subdir;
99 protected String soname, destname;
100 protected final HashSet<Library> dependencies;
101 protected boolean dependedUpon;
102
103 protected class Range implements Comparable<Range>
104 {
105 public final long start, end;
995e06f3 106 public final byte[] replacement;
6f1c3a93
UH
107
108 public int compareTo(Range r)
109 {
110 if (start < r.start)
111 return -1;
112 else if (start > r.start)
113 return 1;
114 else if (end < r.end)
115 return -1;
116 else if (end > r.end)
117 return 1;
118 else
119 return 0;
120 }
121
995e06f3 122 public Range(long start, long end, byte[] replacement)
6f1c3a93
UH
123 {
124 this.start = start;
125 this.end = end;
995e06f3
MC
126 this.replacement = replacement;
127 }
128
129 public Range(long start, long end)
130 {
131 this(start, end, null);
6f1c3a93 132 }
ea3ce762 133 }
ea3ce762 134
6f1c3a93
UH
135 public int compareTo(Library l)
136 {
137 return destname.compareTo(l.destname);
138 }
ea3ce762 139
6f1c3a93
UH
140 protected void addNeeded(String so)
141 {
142 needed.add(so);
143 }
ea3ce762 144
6f1c3a93
UH
145 protected void addRpath(String rp)
146 {
147 for (String p : rp.split(":")) {
148 if (!rpath.contains(p))
149 rpath.add(p);
150 }
151 }
ea3ce762 152
6f1c3a93
UH
153 private String getDynstr(ElfFile.Dynamic d, byte[] s,
154 long base) throws Exception
155 {
156 int offs = (int)d.d_val;
157 int nul = indexOf(s, (byte)0, offs);
158
159 if (nul < 0)
160 throw new Exception("Invalid dynamic string");
161
162 String name = new String(s, offs, nul-offs, "US-ASCII");
163 offs += base;
164
0c38754c
MC
165 if (d.d_tag == ElfFile.DT_RPATH ||
166 d.d_tag == ElfFile.DT_RUNPATH) {
6f1c3a93
UH
167 // Zap rpath,
168 fixups.add(new Range(offs, offs + name.length()));
169 } else {
170 String fix = fixSoname(name);
171 if (fix.length() < name.length()) {
172 fixups.add(new Range(offs + fix.length(),
173 offs + name.length()));
174 }
995e06f3 175 if (!fix.equals(name.substring(0, fix.length()))) {
fcfb9679 176 fixups.add(new Range(offs, offs + fix.length(), fix.getBytes("US-ASCII")));
995e06f3 177 }
6f1c3a93
UH
178 }
179 return name;
ea3ce762 180 }
6f1c3a93
UH
181
182 private void checkDynamic(ElfFile.SectionHeader dynsh,
183 ElfFile.SectionHeader strsh) throws Exception
184 {
185 byte strs[] = new byte[(int)strsh.sh_size];
186
187 elf.read(strsh, strs);
188
189 for (ElfFile.Dynamic d : elf.readDynamic(dynsh)) {
190 if (d.d_tag == ElfFile.DT_NULL)
191 break;
192 else if (d.d_tag == ElfFile.DT_SONAME)
193 soname = getDynstr(d, strs, strsh.sh_offset);
194 else if (d.d_tag == ElfFile.DT_NEEDED)
195 addNeeded(getDynstr(d, strs, strsh.sh_offset));
0c38754c
MC
196 else if (d.d_tag == ElfFile.DT_RPATH ||
197 d.d_tag == ElfFile.DT_RUNPATH)
6f1c3a93
UH
198 addRpath(getDynstr(d, strs, strsh.sh_offset));
199 }
ea3ce762 200 }
6f1c3a93
UH
201
202 private void checkElf() throws Exception
203 {
204 for (ElfFile.SectionHeader sh : elf.secHeaders) {
205 if (sh.sh_type == ElfFile.SHT_DYNAMIC)
206 checkDynamic(sh, elf.secHeaders[sh.sh_link]);
ea3ce762 207 }
ea3ce762 208 }
ea3ce762 209
6f1c3a93
UH
210 protected File getDestName(File dest)
211 {
711d21ce 212 File d = (subdir == null? dest : new File(dest, subdir));
6f1c3a93
UH
213 File f = new File(d, destname);
214 return f;
215 }
ea3ce762 216
6f1c3a93
UH
217 protected void writeTo(File dest) throws IOException
218 {
219 FileInputStream is = new FileInputStream(file);
220 FileOutputStream os = new FileOutputStream(dest);
221 byte[] buf = new byte[65536];
222 TreeSet<Range> ranges = new TreeSet<Range>(fixups);
223 long offs = 0;
224
225 outer: for(;;) {
226 long next = offs + buf.length;
227 if (!ranges.isEmpty())
228 next = ranges.first().start;
229 if (next > offs) {
230 long chunk = next - offs;
231 if (chunk > buf.length)
232 chunk = buf.length;
233 int r = is.read(buf, 0, (int)chunk);
234 if (r < 0)
235 break;
236 os.write(buf, 0, r);
237 offs += r;
238 continue;
239 }
240 while (!ranges.isEmpty() && ranges.first().start <= offs) {
241 Range rg = ranges.pollFirst();
242 if (rg.end > offs) {
243 long chunk = rg.end - offs;
244 while (chunk > 0) {
245 int slice = (chunk > buf.length ? buf.length : (int)chunk);
246 int r = is.read(buf, 0, slice);
247 if (r < 0)
248 break outer;
249 if (r > 0) {
995e06f3 250 if (rg.replacement == null)
fcfb9679 251 Arrays.fill(buf, 0, r, (byte)0);
995e06f3 252 else
fcfb9679 253 System.arraycopy(rg.replacement, (int)(offs-rg.start), buf, 0, r);
6f1c3a93
UH
254 os.write(buf, 0, r);
255 chunk -= r;
256 }
257 }
258 offs = rg.end;
259 }
260 }
261 }
ea3ce762 262
6f1c3a93
UH
263 os.close();
264 is.close();
265 }
ea3ce762 266
6f1c3a93
UH
267 protected Library(File f, String s) throws Exception
268 {
269 file = f;
270 subdir = s;
271 elf = new ElfFile(file);
272 needed = new HashSet<String>();
273 rpath = new Vector<String>();
274 fixups = new TreeSet<Range>();
275 soname = f.getName();
276 dependencies = new HashSet<Library>();
277 dependedUpon = false;
278 checkElf();
279 destname = fixSoname(soname);
280 }
ea3ce762 281
6f1c3a93
UH
282 protected Library(Resource r) throws Exception
283 {
284 this(r.as(FileProvider.class).getFile(),
285 new File(r.getName()).getParent());
286 }
ea3ce762 287
6f1c3a93
UH
288 public String toString()
289 {
290 return "Library(" + file + ")";
291 }
ea3ce762 292
6f1c3a93 293 };
ea3ce762 294
6f1c3a93 295 protected class Worker
ea3ce762 296 {
6f1c3a93
UH
297 protected final int machine;
298 protected final Queue<Library> workQueue;
299 protected final HashMap<String,Library> knownLibs;
300 protected final HashSet<Library> processedLibs;
301 protected final HashSet<String> allDests;
302 protected final Vector<String> rpath;
303
304 protected void addWork(Library l)
305 {
306 if (l == null)
307 return;
308 Library kl = knownLibs.get(l.soname);
309 if (kl == l)
310 return; // Already processed.
311 if (kl != null)
312 throw new BuildException("Multiple libs with the same soname " + l.soname);
313 knownLibs.put(l.soname, l);
314 if (allDests.contains(l.destname))
315 throw new BuildException("Multiple libs with simplified soname " + l.destname);
316 allDests.add(l.destname);
317 workQueue.add(l);
318 }
319
320 protected void addRpath(Vector<String> rp)
321 {
322 for (String p : rp) {
323 if (!rpath.contains(p))
324 rpath.add(p);
325 }
326 }
327
328 protected void setDependency(Library l1, Library l2)
329 {
ed3a4c3c 330 if (l2 == null) // Dependency on external lib.
6f1c3a93
UH
331 return;
332 l1.dependencies.add(l2);
333 l2.dependedUpon = true;
334 }
335
336 protected Library findLibInRpath(String s, String subdir)
337 throws Exception
338 {
339 for (String p : rpath) {
340 File f = new File(p, s);
341 if (f.exists()) {
342 Library l = new Library(f, subdir);
343 if (l.elf.header.e_machine == machine)
344 return l;
345 }
346 }
347 return null;
348 }
349
350 protected Library getLibForSoname(String s, String subdir)
351 throws Exception
352 {
353 Library l = knownLibs.get(s);
354 if (l != null)
355 return l;
356 boolean include = false;
4785171b
MC
357 String[] includePatterns = patterns.getIncludePatterns(getProject());
358 if (includePatterns != null) {
359 for (String patt : includePatterns) {
360 if (SelectorUtils.match(patt, s)) {
361 include = true;
362 break;
363 }
6f1c3a93
UH
364 }
365 }
366 if (!include) {
4785171b
MC
367 String[] excludePatterns = patterns.getExcludePatterns(getProject());
368 if (excludePatterns != null) {
369 for (String patt : excludePatterns) {
370 if (SelectorUtils.match(patt, s))
371 return null;
372 }
6f1c3a93
UH
373 }
374 }
375 l = findLibInRpath(s, subdir);
376 if (l == null)
377 throw new Exception("Library " + s + " not found");
378 addWork(l);
ea3ce762
MC
379 return l;
380 }
6f1c3a93
UH
381
382 protected void process(Library l) throws Exception
383 {
384 if (processedLibs.contains(l))
ed3a4c3c 385 return; // Already processed.
6f1c3a93
UH
386 processedLibs.add(l);
387 addRpath(l.rpath);
388 for (String need : l.needed)
389 setDependency(l, getLibForSoname(need, l.subdir));
390 }
391
392 protected Vector<Library> topoSort(HashSet<Library> libs)
393 {
394 Vector<Library> order = new Vector<Library>();
395 for (Library chk : new HashSet<Library>(libs)) {
396 if (!chk.dependedUpon)
397 libs.remove(chk);
398 }
399 while (!libs.isEmpty()) {
400 HashSet<Library> leafs = new HashSet<Library>();
401 for (Library chk : new HashSet<Library>(libs)) {
402 if (chk.dependencies.isEmpty())
403 leafs.add(chk);
404 }
405 if (leafs.isEmpty())
406 throw new BuildException("Circular dependency found");
407 ArrayList<Library> llist = new ArrayList<Library>(leafs);
408 Collections.sort(llist);
409 order.addAll(llist);
410 libs.removeAll(leafs);
411 for (Library l : libs)
412 l.dependencies.removeAll(leafs);
413 }
414 return order;
415 }
416
417 protected void execute() throws BuildException
418 {
419 try {
420 while (!workQueue.isEmpty())
421 process(workQueue.remove());
422 } catch (Exception e) {
423 throw buildException(e);
424 }
425 if (property != null) {
426 Vector<Library> order =
ed3a4c3c 427 topoSort(new HashSet<Library>(processedLibs));
6f1c3a93
UH
428 StringBuilder sb = new StringBuilder();
429 for (Library l : order) {
430 String name = l.destname;
431 if (name.startsWith("lib"))
432 name = name.substring(3);
433 if (name.endsWith(".so"))
434 name = name.substring(0, name.length() - 3);
435 sb.append(" <item>");
436 sb.append(name);
437 sb.append("</item>\n");
438 }
439 String orderedLibs = sb.toString();
440 getProject().setNewProperty(property, orderedLibs);
441 }
442 for (Library chk : new HashSet<Library>(processedLibs)) {
443 File dest = chk.getDestName(destDir);
444 if (dest.exists() &&
445 dest.lastModified() >= chk.file.lastModified())
446 processedLibs.remove(chk);
447 dest = dest.getParentFile();
448 if (!dest.exists())
449 dest.mkdirs();
450 }
451 if (processedLibs.isEmpty())
452 return;
453 log("Copying " + processedLibs.size() + " libraries into " + destDir);
454 ArrayList<Library> libs = new ArrayList<Library>(processedLibs);
455 Collections.sort(libs);
456 try {
457 for (Library l : libs)
458 l.writeTo(l.getDestName(destDir));
459 } catch (Exception e) {
460 throw buildException(e);
461 }
462 }
463
464 protected Worker(int mach)
465 {
466 machine = mach;
467 workQueue = new LinkedList<Library>();
468 knownLibs = new HashMap<String,Library>();
469 processedLibs = new HashSet<Library>();
470 allDests = new HashSet<String>();
471 rpath = new Vector<String>();
472 }
473
474 };
475
fcfb9679 476 protected File destDir = null; // The destination directory.
6f1c3a93
UH
477 protected Vector<ResourceCollection> rcs = new Vector<ResourceCollection>();
478 protected PatternSet patterns = new PatternSet();
479 protected String property = null;
711d21ce 480 protected Vector<String> rpath = new Vector<String>();
6f1c3a93
UH
481
482 public void setTodir(File destDir)
483 {
484 this.destDir = destDir;
ea3ce762
MC
485 }
486
711d21ce
MC
487 public void setDynamicAttribute(String name, String value)
488 {
489 if ("rpath-link".equals(name))
490 this.rpath.add(value);
491 else
492 throw new BuildException("copylibs doesn't support the \"" + name + "\" attribute");
493 }
494
6f1c3a93 495 public void addFileset(FileSet set)
ea3ce762 496 {
6f1c3a93 497 add(set);
ea3ce762
MC
498 }
499
6f1c3a93 500 public void add(ResourceCollection res)
ea3ce762 501 {
6f1c3a93 502 rcs.add(res);
ea3ce762
MC
503 }
504
6f1c3a93 505 public PatternSet.NameEntry createExclude()
ea3ce762 506 {
6f1c3a93 507 return patterns.createExclude();
ea3ce762
MC
508 }
509
6f1c3a93 510 public PatternSet.NameEntry createInclude()
ea3ce762 511 {
6f1c3a93 512 return patterns.createInclude();
ea3ce762
MC
513 }
514
6f1c3a93 515 public void setProperty(String prop)
ea3ce762 516 {
6f1c3a93 517 property = prop;
ea3ce762 518 }
6f1c3a93
UH
519
520 public void execute() throws BuildException
521 {
522 HashMap<Integer,Worker> workers = new HashMap<Integer,Worker>();
523 final int size = rcs.size();
524
525 for (int i = 0; i < size; i++) {
526 ResourceCollection rc = rcs.elementAt(i);
527 for (Resource r : rc) {
528 if (!r.isExists()) {
529 String message = "Could not find library "
530 + r.toLongString() + " to copy.";
531 throw new BuildException(message);
532 }
533 Library l;
534 try {
535 l = new Library(r);
536 } catch (Exception e) {
537 throw buildException(e);
538 }
539 Integer m = new Integer(l.elf.header.e_machine);
540 Worker w = workers.get(m);
711d21ce 541 if (w == null) {
6f1c3a93 542 workers.put(m, (w = new Worker(m.intValue())));
711d21ce
MC
543 w.addRpath(rpath);
544 }
6f1c3a93
UH
545 w.addWork(l);
546 }
ea3ce762 547 }
6f1c3a93
UH
548 ArrayList<Integer> machines = new ArrayList<Integer>(workers.keySet());
549 Collections.sort(machines);
550 for (Integer m : machines)
551 workers.get(m).execute();
ea3ce762 552 }
ea3ce762 553}