]> sigrok.org Git - sigrok-androidutils.git/blob - ant/src/org/sigrok/androidutils/ant/CopyLibsTask.java
969faf39cd4e1f9c266fc220af8260bf0aa4d3f9
[sigrok-androidutils.git] / ant / src / org / sigrok / androidutils / ant / CopyLibsTask.java
1 /*
2  * This file is part of the sigrok-androidutils project.
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
20 package org.sigrok.androidutils.ant;
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.io.FileInputStream;
25 import java.io.FileOutputStream;
26 import java.io.InputStream;
27 import java.io.OutputStream;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.LinkedList;
34 import java.util.Queue;
35 import java.util.TreeSet;
36 import java.util.Vector;
37 import org.apache.tools.ant.BuildException;
38 import org.apache.tools.ant.Task;
39 import org.apache.tools.ant.types.FileSet;
40 import org.apache.tools.ant.types.PatternSet;
41 import org.apache.tools.ant.types.Resource;
42 import org.apache.tools.ant.types.ResourceCollection;
43 import org.apache.tools.ant.types.resources.FileProvider;
44 import org.apache.tools.ant.types.selectors.SelectorUtils;
45
46 public class CopyLibsTask extends Task
47 {
48         private static BuildException buildException(Exception e)
49         {
50                 if (e instanceof BuildException)
51                         return (BuildException)e;
52                 else
53                         return new BuildException(e);
54         }
55
56         private static int indexOf(byte[] data, byte v, int start)
57         {
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;
65         }
66
67         private static String fixSoname(String s)
68         {
69                 int l = s.length();
70                 int i = s.lastIndexOf(".so");
71
72                 if (i >= 0 && i < (l - 3))
73                         return s.substring(0, i + 3);
74                 else
75                         return s;
76         }
77
78         protected class Library implements Comparable<Library>
79         {
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                         }
113                 }
114
115                 public int compareTo(Library l)
116                 {
117                         return destname.compareTo(l.destname);
118                 }
119
120                 protected void addNeeded(String so)
121                 {
122                         needed.add(so);
123                 }
124
125                 protected void addRpath(String rp)
126                 {
127                         for (String p : rp.split(":")) {
128                                 if (!rpath.contains(p))
129                                         rpath.add(p);
130                         }
131                 }
132
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
145                         if (d.d_tag == ElfFile.DT_RPATH ||
146                             d.d_tag == ElfFile.DT_RUNPATH) {
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;
157                 }
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));
173                                 else if (d.d_tag == ElfFile.DT_RPATH ||
174                                          d.d_tag == ElfFile.DT_RUNPATH)
175                                         addRpath(getDynstr(d, strs, strsh.sh_offset));
176                         }
177                 }
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]);
184                         }
185                 }
186
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                 }
193
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                         }
236
237                         os.close();
238                         is.close();
239                 }
240
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                 }
255
256                 protected Library(Resource r) throws Exception
257                 {
258                         this(r.as(FileProvider.class).getFile(),
259                                 new File(r.getName()).getParent());
260                 }
261
262                 public String toString()
263                 {
264                         return "Library(" + file + ")";
265                 }
266
267         };
268
269         protected class Worker
270         {
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                 {
304                         if (l2 == null) // Dependency on external lib.
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                         String[] includePatterns = patterns.getIncludePatterns(getProject());
332                         if (includePatterns != null) {
333                                 for (String patt : includePatterns) {
334                                         if (SelectorUtils.match(patt, s)) {
335                                                 include = true;
336                                                 break;
337                                         }
338                                 }
339                         }
340                         if (!include) {
341                                 String[] excludePatterns = patterns.getExcludePatterns(getProject());
342                                 if (excludePatterns != null) {
343                                         for (String patt : excludePatterns) {
344                                                 if (SelectorUtils.match(patt, s))
345                                                         return null;
346                                         }
347                                 }
348                         }
349                         l = findLibInRpath(s, subdir);
350                         if (l == null)
351                                 throw new Exception("Library " + s + " not found");
352                         addWork(l);
353                         return l;
354                 }
355
356                 protected void process(Library l) throws Exception
357                 {
358                         if (processedLibs.contains(l))
359                                 return; // Already processed.
360                         processedLibs.add(l);
361                         addRpath(l.rpath);
362                         for (String need : l.needed)
363                                 setDependency(l, getLibForSoname(need, l.subdir));
364                 }
365
366                 protected Vector<Library> topoSort(HashSet<Library> libs)
367                 {
368                         Vector<Library> order = new Vector<Library>();
369                         for (Library chk : new HashSet<Library>(libs)) {
370                                 if (!chk.dependedUpon)
371                                         libs.remove(chk);
372                         }
373                         while (!libs.isEmpty()) {
374                                 HashSet<Library> leafs = new HashSet<Library>();
375                                 for (Library chk : new HashSet<Library>(libs)) {
376                                         if (chk.dependencies.isEmpty())
377                                                 leafs.add(chk);
378                                 }
379                                 if (leafs.isEmpty())
380                                         throw new BuildException("Circular dependency found");
381                                 ArrayList<Library> llist = new ArrayList<Library>(leafs);
382                                 Collections.sort(llist);
383                                 order.addAll(llist);
384                                 libs.removeAll(leafs);
385                                 for (Library l : libs)
386                                         l.dependencies.removeAll(leafs);
387                         }
388                         return order;
389                 }
390
391                 protected void execute() throws BuildException
392                 {
393                         try {
394                                 while (!workQueue.isEmpty())
395                                         process(workQueue.remove());
396                         } catch (Exception e) {
397                                 throw buildException(e);
398                         }
399                         if (property != null) {
400                                 Vector<Library> order =
401                                         topoSort(new HashSet<Library>(processedLibs));
402                                 StringBuilder sb = new StringBuilder();
403                                 for (Library l : order) {
404                                         String name = l.destname;
405                                         if (name.startsWith("lib"))
406                                                 name = name.substring(3);
407                                         if (name.endsWith(".so"))
408                                                 name = name.substring(0, name.length() - 3);
409                                         sb.append("     <item>");
410                                         sb.append(name);
411                                         sb.append("</item>\n");
412                                 }
413                                 String orderedLibs = sb.toString();
414                                 getProject().setNewProperty(property, orderedLibs);
415                         }
416                         for (Library chk : new HashSet<Library>(processedLibs)) {
417                                 File dest = chk.getDestName(destDir);
418                                 if (dest.exists() &&
419                                         dest.lastModified() >= chk.file.lastModified())
420                                         processedLibs.remove(chk);
421                                 dest = dest.getParentFile();
422                                 if (!dest.exists())
423                                         dest.mkdirs();
424                         }
425                         if (processedLibs.isEmpty())
426                                 return;
427                         log("Copying " + processedLibs.size() + " libraries into " + destDir);
428                         ArrayList<Library> libs = new ArrayList<Library>(processedLibs);
429                         Collections.sort(libs);
430                         try {
431                                 for (Library l : libs)
432                                         l.writeTo(l.getDestName(destDir));
433                         } catch (Exception e) {
434                                 throw buildException(e);
435                         }
436                 }
437
438                 protected Worker(int mach)
439                 {
440                         machine = mach;
441                         workQueue = new LinkedList<Library>();
442                         knownLibs = new HashMap<String,Library>();
443                         processedLibs = new HashSet<Library>();
444                         allDests = new HashSet<String>();
445                         rpath = new Vector<String>();
446                 }
447
448         };
449
450         protected File destDir = null;  // The destination directory.
451         protected Vector<ResourceCollection> rcs = new Vector<ResourceCollection>();
452         protected PatternSet patterns = new PatternSet();
453         protected String property = null;
454
455         public void setTodir(File destDir)
456         {
457                 this.destDir = destDir;
458         }
459
460         public void addFileset(FileSet set)
461         {
462                 add(set);
463         }
464
465         public void add(ResourceCollection res)
466         {
467                 rcs.add(res);
468         }
469
470         public PatternSet.NameEntry createExclude()
471         {
472                 return patterns.createExclude();
473         }
474
475         public PatternSet.NameEntry createInclude()
476         {
477                 return patterns.createInclude();
478         }
479
480         public void setProperty(String prop)
481         {
482                 property = prop;
483         }
484
485         public void execute() throws BuildException
486         {
487                 HashMap<Integer,Worker> workers = new HashMap<Integer,Worker>();
488                 final int size = rcs.size();
489
490                 for (int i = 0; i < size; i++) {
491                         ResourceCollection rc = rcs.elementAt(i);
492                         for (Resource r : rc) {
493                                 if (!r.isExists()) {
494                                         String message = "Could not find library "
495                                                 + r.toLongString() + " to copy.";
496                                         throw new BuildException(message);
497                                 }
498                                 Library l;
499                                 try {
500                                         l = new Library(r);
501                                 } catch (Exception e) {
502                                         throw buildException(e);
503                                 }
504                                 Integer m = new Integer(l.elf.header.e_machine);
505                                 Worker w = workers.get(m);
506                                 if (w == null)
507                                         workers.put(m, (w = new Worker(m.intValue())));
508                                 w.addWork(l);
509                         }
510                 }
511                 ArrayList<Integer> machines = new ArrayList<Integer>(workers.keySet());
512                 Collections.sort(machines);
513                 for (Integer m : machines)
514                         workers.get(m).execute();
515         }
516 }