1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package net.sourceforge.javadpkg.impl;
20
21 import java.io.BufferedWriter;
22 import java.io.ByteArrayOutputStream;
23 import java.io.File;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.OutputStream;
28 import java.io.OutputStreamWriter;
29 import java.util.HashMap;
30 import java.util.Map;
31
32 import org.apache.commons.compress.archivers.ar.ArArchiveEntry;
33 import org.apache.commons.compress.archivers.ar.ArArchiveOutputStream;
34 import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
35 import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
36 import org.apache.commons.compress.compressors.CompressorOutputStream;
37 import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
38 import org.apache.commons.compress.compressors.gzip.GzipParameters;
39
40 import net.sourceforge.javadpkg.BuildException;
41 import net.sourceforge.javadpkg.ChangeLog;
42 import net.sourceforge.javadpkg.ChangeLogBuilder;
43 import net.sourceforge.javadpkg.Context;
44 import net.sourceforge.javadpkg.Copyright;
45 import net.sourceforge.javadpkg.CopyrightBuilder;
46 import net.sourceforge.javadpkg.DebianPackageBuilder;
47 import net.sourceforge.javadpkg.DebianPackageConstants;
48 import net.sourceforge.javadpkg.DocumentPaths;
49 import net.sourceforge.javadpkg.MD5SumsBuilder;
50 import net.sourceforge.javadpkg.Script;
51 import net.sourceforge.javadpkg.ScriptBuilder;
52 import net.sourceforge.javadpkg.ScriptVariableReplacer;
53 import net.sourceforge.javadpkg.control.BinaryControl;
54 import net.sourceforge.javadpkg.control.Control;
55 import net.sourceforge.javadpkg.control.ControlBuilder;
56 import net.sourceforge.javadpkg.control.PackageVersion;
57 import net.sourceforge.javadpkg.control.Size;
58 import net.sourceforge.javadpkg.control.impl.ControlBuilderImpl;
59 import net.sourceforge.javadpkg.io.DataSource;
60 import net.sourceforge.javadpkg.io.DataSwap;
61 import net.sourceforge.javadpkg.io.DataTarget;
62 import net.sourceforge.javadpkg.io.FileMode;
63 import net.sourceforge.javadpkg.io.FileOwner;
64 import net.sourceforge.javadpkg.io.Streams;
65 import net.sourceforge.javadpkg.io.impl.DataStreamTarget;
66 import net.sourceforge.javadpkg.io.impl.DataTempFileSwap;
67 import net.sourceforge.javadpkg.replace.ReplacementException;
68 import net.sourceforge.javadpkg.replace.Replacements;
69 import net.sourceforge.javadpkg.replace.ReplacementsMap;
70 import net.sourceforge.javadpkg.store.DataStore;
71 import net.sourceforge.javadpkg.store.DataStoreImpl;
72
73
74
75
76
77
78
79
80
81
82 public class DebianPackageBuilderImpl implements DebianPackageBuilder, DebianPackageConstants {
83
84
85
86
87
88
89
90
91 private static final String FILE_FORMAT_VERSION = "2.0";
92
93
94
95
96
97
98
99
100 private Script defaultPreInstall;
101
102
103
104
105
106 private Script defaultPostInstall;
107
108
109
110
111
112 private Script defaultPreRemove;
113
114
115
116
117
118 private Script defaultPostRemove;
119
120
121 private ControlBuilder controlBuilder;
122
123 private MD5SumsBuilder md5SumsBuilder;
124
125 private ScriptBuilder scriptBuilder;
126
127 private ScriptVariableReplacer scriptVariableReplacer;
128
129 private CopyrightBuilder copyrightBuilder;
130
131 private ChangeLogBuilder changeLogBuilder;
132
133
134 private Control control;
135
136 private Size installedSizeOverhead;
137
138 private Script preInstall;
139
140 private Script postInstall;
141
142 private Script preRemove;
143
144 private Script postRemove;
145
146 private DataStore dataStore;
147
148 private Copyright copyright;
149
150 private ChangeLog changeLog;
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173 protected DebianPackageBuilderImpl(Script defaultPreInstall, Script defaultPostInstall, Script defaultPreRemove,
174 Script defaultPostRemove) {
175
176 super();
177
178 if (defaultPreInstall == null)
179 throw new IllegalArgumentException("Argument defaultPreInstall is null.");
180 if (defaultPostInstall == null)
181 throw new IllegalArgumentException("Argument defaultPostInstall is null.");
182 if (defaultPreRemove == null)
183 throw new IllegalArgumentException("Argument defaultPreRemove is null.");
184 if (defaultPostRemove == null)
185 throw new IllegalArgumentException("Argument defaultPostRemove is null.");
186
187 this.defaultPreInstall = defaultPreInstall;
188 this.defaultPostInstall = defaultPostInstall;
189 this.defaultPreRemove = defaultPreRemove;
190 this.defaultPostRemove = defaultPostRemove;
191
192 this.controlBuilder = new ControlBuilderImpl();
193 this.md5SumsBuilder = new MD5SumsBuilderImpl();
194 this.scriptBuilder = new ScriptBuilderImpl();
195 this.scriptVariableReplacer = new ScriptVariableReplacerImpl();
196 this.copyrightBuilder = new CopyrightBuilderImpl();
197 this.changeLogBuilder = new ChangeLogBuilderImpl();
198
199 this.control = null;
200 this.installedSizeOverhead = null;
201 this.preInstall = null;
202 this.postInstall = null;
203 this.preRemove = null;
204 this.postRemove = null;
205 this.dataStore = new DataStoreImpl();
206 this.copyright = null;
207 this.changeLog = null;
208 }
209
210
211 @Override
212 public void setControl(Control control) {
213 this.control = control;
214 }
215
216
217 @Override
218 public void setInstalledSizeOverhead(Size installedSizeOverhead) {
219 this.installedSizeOverhead = installedSizeOverhead;
220 }
221
222
223 @Override
224 public void setPreInstall(Script preInstall) {
225 this.preInstall = preInstall;
226 }
227
228
229 @Override
230 public void setPostInstall(Script postInstall) {
231 this.postInstall = postInstall;
232 }
233
234
235 @Override
236 public void setPreRemove(Script preRemove) {
237 this.preRemove = preRemove;
238 }
239
240
241 @Override
242 public void setPostRemove(Script postRemove) {
243 this.postRemove = postRemove;
244 }
245
246
247 @Override
248 public void addDataDirectory(String path) {
249 if (path == null)
250 throw new IllegalArgumentException("Argument path is null.");
251
252 this.dataStore.addDirectory(path);
253 }
254
255
256 @Override
257 public void addDataDirectory(String path, FileOwner owner, FileMode mode) {
258 if (path == null)
259 throw new IllegalArgumentException("Argument path is null.");
260 if (owner == null)
261 throw new IllegalArgumentException("Argument owner is null.");
262 if (mode == null)
263 throw new IllegalArgumentException("Argument mode is null.");
264
265 this.dataStore.addDirectory(path, owner, mode);
266 }
267
268
269 @Override
270 public void addDataFile(DataSource source, String path) {
271 if (source == null)
272 throw new IllegalArgumentException("Argument source is null.");
273 if (path == null)
274 throw new IllegalArgumentException("Argument path is null.");
275
276 this.dataStore.addFile(source, path);
277 }
278
279
280 @Override
281 public void addDataFile(DataSource source, String path, FileOwner owner, FileMode mode) {
282
283 if (source == null)
284 throw new IllegalArgumentException("Argument source is null.");
285 if (path == null)
286 throw new IllegalArgumentException("Argument path is null.");
287 if (owner == null)
288 throw new IllegalArgumentException("Argument owner is null.");
289 if (mode == null)
290 throw new IllegalArgumentException("Argument mode is null.");
291
292 this.dataStore.addFile(source, path, owner, mode);
293 }
294
295
296 @Override
297 public void addDataSymLink(String path, String target, FileOwner owner, FileMode mode) {
298
299 if (path == null)
300 throw new IllegalArgumentException("Argument path is null.");
301 if (target == null)
302 throw new IllegalArgumentException("Argument target is null.");
303 if (owner == null)
304 throw new IllegalArgumentException("Argument owner is null.");
305 if (mode == null)
306 throw new IllegalArgumentException("Argument mode is null.");
307
308 this.dataStore.addSymLink(path, target, owner, mode);
309 }
310
311
312 @Override
313 public void setCopyright(Copyright copyright) {
314 this.copyright = copyright;
315 }
316
317
318 @Override
319 public void setChangeLog(ChangeLog changeLog) {
320 this.changeLog = changeLog;
321 }
322
323
324 @Override
325 public void buildDebianPackage(DataTarget target, Context context) throws IOException, BuildException {
326
327
328 if (target == null)
329 throw new IllegalArgumentException("Argument target is null.");
330 if (context == null)
331 throw new IllegalArgumentException("Argument context is null.");
332
333 if (this.control == null)
334 throw new IllegalStateException("Can't build Debian package because no control information is set.");
335 if (this.copyright == null)
336 throw new IllegalStateException("Can't build Debian package because no copyright is set.");
337 if (this.changeLog == null)
338 throw new IllegalStateException("Can't build Debian package because no change log is set.");
339
340
341 try (OutputStream out = target.getOutputStream()) {
342 try (ArArchiveOutputStream arOut = new ArArchiveOutputStream(out)) {
343
344 this.writeVersion(target, arOut);
345
346
347 this.writeControl(target, context, arOut);
348
349
350 this.writeData(target, context, arOut);
351 }
352 }
353 }
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368 private void writeVersion(DataTarget target, ArArchiveOutputStream out) throws IOException {
369 ByteArrayOutputStream arrayOut;
370 byte[] data;
371 ArArchiveEntry entry;
372
373
374 try {
375
376 arrayOut = new ByteArrayOutputStream();
377 try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(arrayOut, UTF_8_CHARSET))) {
378 writer.write(FILE_FORMAT_VERSION);
379 writer.write('\n');
380 }
381 data = arrayOut.toByteArray();
382
383
384 entry = new ArArchiveEntry("debian-binary", data.length);
385 out.putArchiveEntry(entry);
386 out.write(data);
387 out.closeArchiveEntry();
388 } catch (IOException e) {
389 throw new IOException("Couldn't write |debian-binary| in AR archive |" + target.getName() + "|: " + e.getMessage(),
390 e);
391 }
392 }
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411 private void writeControl(DataTarget target, Context context, ArArchiveOutputStream out)
412 throws IOException, BuildException {
413
414 File controlFile;
415 Size installedSize = null;
416 PackageVersion version;
417 boolean deleted = false;
418
419
420
421 if (this.control instanceof BinaryControl) {
422 installedSize = ((BinaryControl) this.control).getInstalledSize();
423 }
424 if (installedSize == null) {
425 try {
426 installedSize = this.dataStore.getSize();
427 } catch (IOException e) {
428 throw new IOException(
429 "Couldn't determine size of all files which should be added to the Debian package: " + e.getMessage(),
430 e);
431 }
432 }
433
434 if (this.installedSizeOverhead != null) {
435 installedSize = new Size(installedSize.getBytes() + this.installedSizeOverhead.getBytes());
436 }
437
438
439
440
441
442
443 controlFile = File.createTempFile(CONTROL_NAME, TAR_GZIP_SUFFIX);
444 try {
445 try (FileOutputStream fileOut = new FileOutputStream(controlFile)) {
446 try (TarArchiveOutputStream tarOut = this.createTarArchive(fileOut)) {
447
448 this.writeTarEntry(tarOut, "", ROOT_GROUP_ID, ROOT_GROUP_NAME, ROOT_USER_ID, ROOT_USER_NAME, DIRECTORY_MODE,
449 null);
450
451
452 try (DataSwap swap = new DataTempFileSwap(CONTROL_ENTRY)) {
453 this.controlBuilder.buildControl(this.control, installedSize, swap.getTarget(), context);
454 this.writeTarEntry(tarOut, CONTROL_ENTRY, ROOT_GROUP_ID, ROOT_GROUP_NAME, ROOT_USER_ID, ROOT_USER_NAME,
455 FILE_MODE, swap.getSource());
456 }
457
458
459 try (DataSwap swap = new DataTempFileSwap(MD5SUMS_ENTRY)) {
460 this.md5SumsBuilder.buildMD5Sums(this.dataStore, swap.getTarget());
461 this.writeTarEntry(tarOut, MD5SUMS_ENTRY, ROOT_GROUP_ID, ROOT_GROUP_NAME, ROOT_USER_ID, ROOT_USER_NAME,
462 FILE_MODE, swap.getSource());
463 }
464
465
466
467 if (this.control instanceof BinaryControl) {
468 version = ((BinaryControl) this.control).getVersion();
469
470
471 this.writeScript(tarOut, this.preInstall, this.defaultPreInstall, PREINST_ENTRY, version, context);
472
473
474 this.writeScript(tarOut, this.postInstall, this.defaultPostInstall, POSTINST_ENTRY, version, context);
475
476
477 this.writeScript(tarOut, this.preRemove, this.defaultPreRemove, PRERM_ENTRY, version, context);
478
479
480 this.writeScript(tarOut, this.postRemove, this.defaultPostRemove, POSTRM_ENTRY, version, context);
481 }
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518 }
519 }
520
521
522 this.writeArEntry(out, CONTROL_NAME + TAR_GZIP_SUFFIX, controlFile);
523 } finally {
524
525 if (controlFile.exists() && !controlFile.delete()) {
526 deleted = false;
527 } else {
528 deleted = true;
529 }
530 }
531 if (!deleted)
532 throw new IOException("Couldn't delete temporary control file |" + controlFile.getAbsolutePath() + "|.");
533 }
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558 private void writeScript(TarArchiveOutputStream out, Script script, Script defaultScript, String name,
559 PackageVersion version, Context context) throws IOException, BuildException {
560
561 Script effectiveScript;
562 Map<String, String> variables;
563 Replacements replacements;
564
565
566 if (script == null) {
567 variables = new HashMap<>();
568
569 variables.put("deb.version", version.getText());
570 replacements = new ReplacementsMap(variables);
571 try {
572 effectiveScript = this.scriptVariableReplacer.replaceScriptVariables(defaultScript, replacements, context);
573 } catch (ReplacementException e) {
574 throw new BuildException("Couldn't write default script |" + name + "|: " + e.getMessage(), e);
575 }
576 } else {
577 effectiveScript = script;
578 }
579
580 try (DataSwap swap = new DataTempFileSwap(name)) {
581 this.scriptBuilder.buildScript(swap.getTarget(), effectiveScript);
582 this.writeTarEntry(out, name, ROOT_GROUP_ID, ROOT_GROUP_NAME, ROOT_USER_ID, ROOT_USER_NAME, SCRIPT_MODE,
583 swap.getSource());
584 }
585 }
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604 @SuppressWarnings("resource")
605 private void writeData(DataTarget target, Context context, ArArchiveOutputStream out) throws IOException, BuildException {
606 BinaryControl control;
607 DocumentPaths paths;
608 String[] ensurePaths;
609 File dataFile;
610 boolean deleted = false;
611 DataSwap swapCopyright = null, swapChangeLog = null;
612
613
614
615 if (this.control instanceof BinaryControl) {
616 control = (BinaryControl) this.control;
617 } else
618 throw new BuildException("Found control |" + this.control + "| of type |"
619 + (this.control == null ? "null" : this.control.getClass().getCanonicalName())
620 + ", but only control of type |" + BinaryControl.class.getCanonicalName() + "| is supported.");
621 paths = new DocumentPathsImpl(control.getPackage());
622
623
624
625
626 if (this.dataStore.exists(paths.getCopyrightPath()))
627 throw new BuildException("Found |" + paths.getCopyrightPath()
628 + " added to the builder, but the copyright should be added through the corresponding method of the builder.");
629 else if (this.dataStore.exists(paths.getChangeLogPath()))
630 throw new BuildException("Found |" + paths.getChangeLogPath()
631 + " added to the builder, but the copyright should be added through the corresponding method of the builder.");
632 else if (this.dataStore.exists(paths.getChangeLogGzipPath()))
633 throw new BuildException("Found |" + paths.getChangeLogGzipPath()
634 + " added to the builder, but the copyright should be added through the corresponding method of the builder.");
635 else if (this.dataStore.exists(paths.getChangeLogHtmlPath()))
636 throw new BuildException("Found |" + paths.getChangeLogHtmlPath()
637 + " added to the builder, but the copyright should be added through the corresponding method of the builder.");
638 else if (this.dataStore.exists(paths.getChangeLogHtmlGzipPath()))
639 throw new BuildException("Found |" + paths.getChangeLogHtmlGzipPath()
640 + " added to the builder, but the copyright should be added through the corresponding method of the builder.");
641
642
643 ensurePaths = new String[] { USR_PATH, USR_SHARE_PATH, paths.getDocumentBasePath(), paths.getDocumentPath() };
644 for (String ensurePath : ensurePaths) {
645 if (!this.dataStore.exists(ensurePath)) {
646 this.dataStore.addDirectory(ensurePath);
647 }
648 }
649
650
651
652
653
654
655 dataFile = File.createTempFile(DATA_NAME, TAR_GZIP_SUFFIX);
656 try {
657
658 swapCopyright = new DataTempFileSwap("copyright");
659 this.copyrightBuilder.buildCopyright(this.copyright, swapCopyright.getTarget(), context);
660 this.dataStore.addFile(swapCopyright.getSource(), paths.getCopyrightPath());
661
662 try (DataSwap swap = new DataTempFileSwap("changelog")) {
663 this.changeLogBuilder.buildChangeLog(this.changeLog, swap.getTarget(), context);
664 swapChangeLog = new DataTempFileSwap("changelog.gz");
665 Streams.compressGzip(swap.getSource(), swapChangeLog.getTarget(), 9);
666 }
667 this.dataStore.addFile(swapChangeLog.getSource(), paths.getChangeLogGzipPath());
668
669
670 try (FileOutputStream fileOut = new FileOutputStream(dataFile)) {
671 try (TarArchiveOutputStream tarOut = this.createTarArchive(fileOut)) {
672 this.dataStore.write(tarOut);
673 }
674 }
675
676
677 this.writeArEntry(out, DATA_NAME + TAR_GZIP_SUFFIX, dataFile);
678 } finally {
679
680 swapChangeLog.close();
681
682
683 swapCopyright.close();
684
685
686 if (dataFile.exists() && !dataFile.delete()) {
687 deleted = false;
688 } else {
689 deleted = true;
690 }
691 }
692 if (!deleted)
693 throw new IOException("Couldn't delete temporary data file |" + dataFile.getAbsolutePath() + "|.");
694 }
695
696
697
698
699
700
701
702
703
704
705
706
707
708 @SuppressWarnings("resource")
709 private TarArchiveOutputStream createTarArchive(OutputStream out) throws IOException {
710 TarArchiveOutputStream tarOut;
711 GzipParameters gzipParameters;
712 CompressorOutputStream compressorOut;
713
714
715
716 gzipParameters = new GzipParameters();
717 gzipParameters.setCompressionLevel(9);
718 compressorOut = new GzipCompressorOutputStream(out, gzipParameters);
719
720 tarOut = new TarArchiveOutputStream(compressorOut);
721 tarOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
722 return tarOut;
723 }
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750 private void writeTarEntry(TarArchiveOutputStream out, String name, long groupId, String groupName, long userId,
751 String userName, int mode, DataSource source) throws IOException {
752
753 StringBuilder entryName;
754 TarArchiveEntry tarEntry;
755
756
757
758 entryName = new StringBuilder();
759 if (!name.startsWith("./")) {
760 entryName.append('.');
761 if (!name.startsWith("/")) {
762 entryName.append('/');
763 }
764 }
765 entryName.append(name);
766
767
768 tarEntry = new TarArchiveEntry(entryName.toString());
769 tarEntry.setGroupId(groupId);
770 tarEntry.setGroupName(groupName);
771 tarEntry.setUserId(userId);
772 tarEntry.setUserName(userName);
773 tarEntry.setMode(mode);
774 if (source != null) {
775 if (source.getLength() < 0)
776 throw new IOException("Couldn't create entry |" + name
777 + "| in TAR archive stream: Couldn't determine size of source |" + source.getName() + "|.");
778 tarEntry.setSize(source.getLength());
779 }
780 try {
781 out.putArchiveEntry(tarEntry);
782 } catch (IOException e) {
783 throw new IOException("Couldn't create entry |" + name + "| in TAR archive stream: " + e.getMessage(), e);
784 }
785
786
787 if (source != null) {
788 try {
789 if (source.getLength() > 0) {
790 try (DataTarget target = new DataStreamTarget(out, name, false)) {
791 Streams.copy(source, target);
792 }
793 }
794 } catch (IOException e) {
795 throw new IOException("Couldn't create entry |" + name + "| in TAR archive stream: " + e.getMessage(), e);
796 }
797 }
798
799
800 try {
801 out.closeArchiveEntry();
802 } catch (IOException e) {
803 throw new IOException("Couldn't create entry |" + name + "| in TAR archive stream: " + e.getMessage(), e);
804 }
805 }
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822 private void writeArEntry(ArArchiveOutputStream out, String name, File file) throws IOException {
823 ArArchiveEntry entry;
824
825
826 try {
827 entry = new ArArchiveEntry(name, file.length());
828 out.putArchiveEntry(entry);
829 try (InputStream in = Streams.createBufferedFileInputStream(file)) {
830 Streams.copy(in, out);
831 }
832 out.closeArchiveEntry();
833 } catch (IOException e) {
834 throw new IOException("Couldn't write file |" + file.getAbsolutePath() + "| as entry |" + name
835 + "| into the archive stream: " + e.getMessage(), e);
836 }
837 }
838
839
840 }