Mercurial > cgi-bin > hgweb.cgi > JpegWasher
comparison lib/mod/src/de/masters_of_disaster/ant/tasks/ar/Ar.java @ 33:3d86f0391168
Work on improving the build system.
author | David Barts <davidb@stashtea.com> |
---|---|
date | Fri, 24 Apr 2020 19:45:57 -0700 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
32:c06edc56669b | 33:3d86f0391168 |
---|---|
1 package de.masters_of_disaster.ant.tasks.ar; | |
2 | |
3 import java.io.BufferedOutputStream; | |
4 import java.io.File; | |
5 import java.io.FileInputStream; | |
6 import java.io.FileOutputStream; | |
7 import java.io.IOException; | |
8 import java.util.Enumeration; | |
9 import java.util.Vector; | |
10 import org.apache.tools.ant.BuildException; | |
11 import org.apache.tools.ant.DirectoryScanner; | |
12 import org.apache.tools.ant.Project; | |
13 import org.apache.tools.ant.taskdefs.MatchingTask; | |
14 import org.apache.tools.ant.types.EnumeratedAttribute; | |
15 import org.apache.tools.ant.types.FileSet; | |
16 import org.apache.tools.ant.util.FileUtils; | |
17 import org.apache.tools.ant.util.MergingMapper; | |
18 import org.apache.tools.ant.util.SourceFileScanner; | |
19 import org.apache.tools.zip.UnixStat; | |
20 | |
21 /** | |
22 * Creates an ar archive. | |
23 * | |
24 * @ant.task category="packaging" | |
25 */ | |
26 public class Ar extends MatchingTask { | |
27 File destFile; | |
28 File baseDir; | |
29 | |
30 private ArLongFileMode longFileMode = new ArLongFileMode(); | |
31 | |
32 Vector filesets = new Vector(); | |
33 | |
34 /** | |
35 * Indicates whether the user has been warned about long files already. | |
36 */ | |
37 private boolean longWarningGiven = false; | |
38 | |
39 /** | |
40 * Add a new fileset with the option to specify permissions | |
41 * @return the ar fileset to be used as the nested element. | |
42 */ | |
43 public ArFileSet createArFileSet() { | |
44 ArFileSet fileset = new ArFileSet(); | |
45 filesets.addElement(fileset); | |
46 return fileset; | |
47 } | |
48 | |
49 | |
50 /** | |
51 * Set the name/location of where to create the ar file. | |
52 * @param destFile The output of the tar | |
53 */ | |
54 public void setDestFile(File destFile) { | |
55 this.destFile = destFile; | |
56 } | |
57 | |
58 /** | |
59 * This is the base directory to look in for things to ar. | |
60 * @param baseDir the base directory. | |
61 */ | |
62 public void setBasedir(File baseDir) { | |
63 this.baseDir = baseDir; | |
64 } | |
65 | |
66 /** | |
67 * Set how to handle long files, those with a name>16 chars or containing spaces. | |
68 * Optional, default=warn. | |
69 * <p> | |
70 * Allowable values are | |
71 * <ul> | |
72 * <li> truncate - names are truncated to the maximum length, spaces are replaced by '_' | |
73 * <li> fail - names greater than the maximum cause a build exception | |
74 * <li> warn - names greater than the maximum cause a warning and TRUNCATE is used | |
75 * <li> bsd - BSD variant is used if any names are greater than the maximum. | |
76 * <li> gnu - GNU variant is used if any names are greater than the maximum. | |
77 * <li> omit - files with a name greater than the maximum are omitted from the archive | |
78 * </ul> | |
79 * @param mode the mode to handle long file names. | |
80 */ | |
81 public void setLongfile(ArLongFileMode mode) { | |
82 this.longFileMode = mode; | |
83 } | |
84 | |
85 /** | |
86 * do the business | |
87 * @throws BuildException on error | |
88 */ | |
89 public void execute() throws BuildException { | |
90 if (destFile == null) { | |
91 throw new BuildException("destFile attribute must be set!", | |
92 getLocation()); | |
93 } | |
94 | |
95 if (destFile.exists() && destFile.isDirectory()) { | |
96 throw new BuildException("destFile is a directory!", | |
97 getLocation()); | |
98 } | |
99 | |
100 if (destFile.exists() && !destFile.canWrite()) { | |
101 throw new BuildException("Can not write to the specified destFile!", | |
102 getLocation()); | |
103 } | |
104 | |
105 Vector savedFileSets = (Vector) filesets.clone(); | |
106 try { | |
107 if (baseDir != null) { | |
108 if (!baseDir.exists()) { | |
109 throw new BuildException("basedir does not exist!", | |
110 getLocation()); | |
111 } | |
112 | |
113 // add the main fileset to the list of filesets to process. | |
114 ArFileSet mainFileSet = new ArFileSet(fileset); | |
115 mainFileSet.setDir(baseDir); | |
116 filesets.addElement(mainFileSet); | |
117 } | |
118 | |
119 if (filesets.size() == 0) { | |
120 throw new BuildException("You must supply either a basedir " | |
121 + "attribute or some nested filesets.", | |
122 getLocation()); | |
123 } | |
124 | |
125 // check if ar is out of date with respect to each | |
126 // fileset | |
127 boolean upToDate = true; | |
128 for (Enumeration e = filesets.elements(); e.hasMoreElements();) { | |
129 ArFileSet fs = (ArFileSet) e.nextElement(); | |
130 String[] files = fs.getFiles(getProject()); | |
131 | |
132 if (!archiveIsUpToDate(files, fs.getDir(getProject()))) { | |
133 upToDate = false; | |
134 } | |
135 | |
136 for (int i = 0; i < files.length; ++i) { | |
137 if (destFile.equals(new File(fs.getDir(getProject()), | |
138 files[i]))) { | |
139 throw new BuildException("An ar file cannot include " | |
140 + "itself", getLocation()); | |
141 } | |
142 } | |
143 } | |
144 | |
145 if (upToDate) { | |
146 log("Nothing to do: " + destFile.getAbsolutePath() | |
147 + " is up to date.", Project.MSG_INFO); | |
148 return; | |
149 } | |
150 | |
151 log("Building ar: " + destFile.getAbsolutePath(), Project.MSG_INFO); | |
152 | |
153 ArOutputStream aOut = null; | |
154 try { | |
155 aOut = new ArOutputStream( | |
156 new BufferedOutputStream( | |
157 new FileOutputStream(destFile))); | |
158 if (longFileMode.isTruncateMode() | |
159 || longFileMode.isWarnMode()) { | |
160 aOut.setLongFileMode(ArOutputStream.LONGFILE_TRUNCATE); | |
161 } else if (longFileMode.isFailMode() | |
162 || longFileMode.isOmitMode()) { | |
163 aOut.setLongFileMode(ArOutputStream.LONGFILE_ERROR); | |
164 } else if (longFileMode.isBsdMode()) { | |
165 aOut.setLongFileMode(ArOutputStream.LONGFILE_BSD); | |
166 } else { | |
167 // GNU | |
168 aOut.setLongFileMode(ArOutputStream.LONGFILE_GNU); | |
169 } | |
170 | |
171 longWarningGiven = false; | |
172 for (Enumeration e = filesets.elements(); | |
173 e.hasMoreElements();) { | |
174 ArFileSet fs = (ArFileSet) e.nextElement(); | |
175 String[] files = fs.getFiles(getProject()); | |
176 if (files.length > 1 && fs.getFullpath().length() > 0) { | |
177 throw new BuildException("fullpath attribute may only " | |
178 + "be specified for " | |
179 + "filesets that specify a " | |
180 + "single file."); | |
181 } | |
182 for (int i = 0; i < files.length; i++) { | |
183 File f = new File(fs.getDir(getProject()), files[i]); | |
184 arFile(f, aOut, fs); | |
185 } | |
186 } | |
187 } catch (IOException ioe) { | |
188 String msg = "Problem creating AR: " + ioe.getMessage(); | |
189 throw new BuildException(msg, ioe, getLocation()); | |
190 } finally { | |
191 FileUtils.close(aOut); | |
192 } | |
193 } finally { | |
194 filesets = savedFileSets; | |
195 } | |
196 } | |
197 | |
198 /** | |
199 * ar a file | |
200 * @param file the file to ar | |
201 * @param aOut the output stream | |
202 * @param arFileSet the fileset that the file came from. | |
203 * @throws IOException on error | |
204 */ | |
205 protected void arFile(File file, ArOutputStream aOut, ArFileSet arFileSet) | |
206 throws IOException { | |
207 FileInputStream fIn = null; | |
208 | |
209 if (file.isDirectory()) { | |
210 return; | |
211 } | |
212 | |
213 String fileName = file.getName(); | |
214 | |
215 String fullpath = arFileSet.getFullpath(); | |
216 if (fullpath.length() > 0) { | |
217 fileName = fullpath.substring(fullpath.lastIndexOf('/')); | |
218 } | |
219 | |
220 // don't add "" to the archive | |
221 if (fileName.length() <= 0) { | |
222 return; | |
223 } | |
224 | |
225 try { | |
226 if ((fileName.length() >= ArConstants.NAMELEN) | |
227 || (-1 != fileName.indexOf(' '))) { | |
228 if (longFileMode.isOmitMode()) { | |
229 log("Omitting: " + fileName, Project.MSG_INFO); | |
230 return; | |
231 } else if (longFileMode.isWarnMode()) { | |
232 if (!longWarningGiven) { | |
233 log("Resulting ar file contains truncated or space converted filenames", | |
234 Project.MSG_WARN); | |
235 longWarningGiven = true; | |
236 } | |
237 log("Entry: \"" + fileName + "\" longer than " | |
238 + ArConstants.NAMELEN + " characters or containing spaces.", | |
239 Project.MSG_WARN); | |
240 } else if (longFileMode.isFailMode()) { | |
241 throw new BuildException("Entry: \"" + fileName | |
242 + "\" longer than " + ArConstants.NAMELEN | |
243 + "characters or containting spaces.", getLocation()); | |
244 } | |
245 } | |
246 | |
247 ArEntry ae = new ArEntry(fileName); | |
248 ae.setFileDate(file.lastModified()); | |
249 ae.setUserId(arFileSet.getUid()); | |
250 ae.setGroupId(arFileSet.getGid()); | |
251 ae.setMode(arFileSet.getMode()); | |
252 ae.setSize(file.length()); | |
253 | |
254 aOut.putNextEntry(ae); | |
255 | |
256 fIn = new FileInputStream(file); | |
257 | |
258 byte[] buffer = new byte[8 * 1024]; | |
259 int count = 0; | |
260 do { | |
261 aOut.write(buffer, 0, count); | |
262 count = fIn.read(buffer, 0, buffer.length); | |
263 } while (count != -1); | |
264 | |
265 aOut.closeEntry(); | |
266 } finally { | |
267 if (fIn != null) { | |
268 fIn.close(); | |
269 } | |
270 } | |
271 } | |
272 | |
273 /** | |
274 * Is the archive up to date in relationship to a list of files. | |
275 * @param files the files to check | |
276 * @param dir the base directory for the files. | |
277 * @return true if the archive is up to date. | |
278 */ | |
279 protected boolean archiveIsUpToDate(String[] files, File dir) { | |
280 SourceFileScanner sfs = new SourceFileScanner(this); | |
281 MergingMapper mm = new MergingMapper(); | |
282 mm.setTo(destFile.getAbsolutePath()); | |
283 return sfs.restrict(files, dir, null, mm).length == 0; | |
284 } | |
285 | |
286 /** | |
287 * This is a FileSet with the option to specify permissions | |
288 * and other attributes. | |
289 */ | |
290 public static class ArFileSet extends FileSet { | |
291 private String[] files = null; | |
292 | |
293 private int fileMode = UnixStat.FILE_FLAG | UnixStat.DEFAULT_FILE_PERM; | |
294 private int uid; | |
295 private int gid; | |
296 private String fullpath = ""; | |
297 | |
298 /** | |
299 * Creates a new <code>ArFileSet</code> instance. | |
300 * Using a fileset as a constructor argument. | |
301 * | |
302 * @param fileset a <code>FileSet</code> value | |
303 */ | |
304 public ArFileSet(FileSet fileset) { | |
305 super(fileset); | |
306 } | |
307 | |
308 /** | |
309 * Creates a new <code>ArFileSet</code> instance. | |
310 * | |
311 */ | |
312 public ArFileSet() { | |
313 super(); | |
314 } | |
315 | |
316 /** | |
317 * Get a list of files and directories specified in the fileset. | |
318 * @param p the current project. | |
319 * @return a list of file and directory names, relative to | |
320 * the baseDir for the project. | |
321 */ | |
322 public String[] getFiles(Project p) { | |
323 if (files == null) { | |
324 DirectoryScanner ds = getDirectoryScanner(p); | |
325 files = ds.getIncludedFiles(); | |
326 } | |
327 | |
328 return files; | |
329 } | |
330 | |
331 /** | |
332 * A 3 digit octal string, specify the user, group and | |
333 * other modes in the standard Unix fashion; | |
334 * optional, default=0644 | |
335 * @param octalString a 3 digit octal string. | |
336 */ | |
337 public void setMode(String octalString) { | |
338 this.fileMode = | |
339 UnixStat.FILE_FLAG | Integer.parseInt(octalString, 8); | |
340 } | |
341 | |
342 /** | |
343 * @return the current mode. | |
344 */ | |
345 public int getMode() { | |
346 return fileMode; | |
347 } | |
348 | |
349 /** | |
350 * The UID for the ar entry; optional, default="0" | |
351 * @param uid the id of the user for the ar entry. | |
352 */ | |
353 public void setUid(int uid) { | |
354 this.uid = uid; | |
355 } | |
356 | |
357 /** | |
358 * @return the UID for the ar entry | |
359 */ | |
360 public int getUid() { | |
361 return uid; | |
362 } | |
363 | |
364 /** | |
365 * The GID for the ar entry; optional, default="0" | |
366 * @param gid the group id. | |
367 */ | |
368 public void setGid(int gid) { | |
369 this.gid = gid; | |
370 } | |
371 | |
372 /** | |
373 * @return the group identifier. | |
374 */ | |
375 public int getGid() { | |
376 return gid; | |
377 } | |
378 | |
379 /** | |
380 * If the fullpath attribute is set, the file in the fileset | |
381 * is written with the last part of the path in the archive. | |
382 * If the fullpath ends in '/' the file is omitted from the archive. | |
383 * It is an error to have more than one file specified in such a fileset. | |
384 * @param fullpath the path to use for the file in a fileset. | |
385 */ | |
386 public void setFullpath(String fullpath) { | |
387 this.fullpath = fullpath; | |
388 } | |
389 | |
390 /** | |
391 * @return the path to use for a single file fileset. | |
392 */ | |
393 public String getFullpath() { | |
394 return fullpath; | |
395 } | |
396 } | |
397 | |
398 /** | |
399 * Set of options for long file handling in the task. | |
400 */ | |
401 public static class ArLongFileMode extends EnumeratedAttribute { | |
402 /** permissible values for longfile attribute */ | |
403 public static final String | |
404 WARN = "warn", | |
405 FAIL = "fail", | |
406 TRUNCATE = "truncate", | |
407 GNU = "gnu", | |
408 BSD = "bsd", | |
409 OMIT = "omit"; | |
410 | |
411 private final String[] validModes = {WARN, FAIL, TRUNCATE, GNU, BSD, OMIT}; | |
412 | |
413 /** Constructor, defaults to "warn" */ | |
414 public ArLongFileMode() { | |
415 super(); | |
416 setValue(WARN); | |
417 } | |
418 | |
419 /** | |
420 * @return the possible values for this enumerated type. | |
421 */ | |
422 public String[] getValues() { | |
423 return validModes; | |
424 } | |
425 | |
426 /** | |
427 * @return true if value is "truncate". | |
428 */ | |
429 public boolean isTruncateMode() { | |
430 return TRUNCATE.equalsIgnoreCase(getValue()); | |
431 } | |
432 | |
433 /** | |
434 * @return true if value is "warn". | |
435 */ | |
436 public boolean isWarnMode() { | |
437 return WARN.equalsIgnoreCase(getValue()); | |
438 } | |
439 | |
440 /** | |
441 * @return true if value is "gnu". | |
442 */ | |
443 public boolean isGnuMode() { | |
444 return GNU.equalsIgnoreCase(getValue()); | |
445 } | |
446 | |
447 /** | |
448 * @return true if value is "bsd". | |
449 */ | |
450 public boolean isBsdMode() { | |
451 return BSD.equalsIgnoreCase(getValue()); | |
452 } | |
453 | |
454 /** | |
455 * @return true if value is "fail". | |
456 */ | |
457 public boolean isFailMode() { | |
458 return FAIL.equalsIgnoreCase(getValue()); | |
459 } | |
460 | |
461 /** | |
462 * @return true if value is "omit". | |
463 */ | |
464 public boolean isOmitMode() { | |
465 return OMIT.equalsIgnoreCase(getValue()); | |
466 } | |
467 } | |
468 } |