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