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