From bad5bb9fe88c763840d39c5c9b1a56782ef379c5 Mon Sep 17 00:00:00 2001 From: crea-doo Date: Tue, 20 May 2014 21:27:11 +0200 Subject: [PATCH 1/2] Added first version of spk support --- src/main/java/org/vafer/jdeb/DataBuilder.java | 231 ++++++------ ...rolBuilder.java => DebControlBuilder.java} | 4 +- .../java/org/vafer/jdeb/DebDataBuilder.java | 59 +++ src/main/java/org/vafer/jdeb/DebMaker.java | 80 +--- .../java/org/vafer/jdeb/PackageMaker.java | 177 +++++++++ .../java/org/vafer/jdeb/SpkDataBuilder.java | 68 ++++ .../java/org/vafer/jdeb/SpkInfoBuilder.java | 84 +++++ src/main/java/org/vafer/jdeb/SpkMaker.java | 192 ++++++++++ .../org/vafer/jdeb/SpkScriptsBuilder.java | 141 +++++++ .../java/org/vafer/jdeb/ant/SpkAntTask.java | 133 +++++++ .../jdeb/debian/BinaryPackageControlFile.java | 2 + .../org/vafer/jdeb/debian/ChangesFile.java | 1 + .../org/vafer/jdeb/debian/ControlFile.java | 2 + .../vafer/jdeb/maven/AbstractPackageMojo.java | 217 +++++++++++ .../java/org/vafer/jdeb/maven/DebMojo.java | 122 +----- .../java/org/vafer/jdeb/maven/SpkMojo.java | 350 ++++++++++++++++++ .../jdeb/producers/DataProducerDirectory.java | 3 - .../jdeb/producers/DataProducerFile.java | 1 - .../producers/DataProducerPathTemplate.java | 1 - .../jdeb/synology/BinaryPackageInfoFile.java | 94 +++++ .../org/vafer/jdeb/synology/InfoFile.java | 234 ++++++++++++ .../jdeb/{debian => utils}/ControlField.java | 2 +- .../java/org/vafer/jdeb/SpkMakerTestCase.java | 167 +++++++++ .../vafer/jdeb/ant/SpkAntTaskTestCase.java | 244 ++++++++++++ .../synology/PackageInfoFileTestCase.java | 100 +++++ .../{testbuild.xml => debtestbuild.xml} | 2 +- .../resources/org/vafer/jdeb/spk/data.tar.bz2 | Bin 0 -> 164 bytes .../resources/org/vafer/jdeb/spk/data.tgz | Bin 0 -> 148 bytes .../resources/org/vafer/jdeb/spk/data.zip | Bin 0 -> 221 bytes .../org/vafer/jdeb/spk/data/test/testfile | 1 + src/test/resources/org/vafer/jdeb/spk/info | 9 + .../org/vafer/jdeb/spk/scripts/postinst | 4 + .../org/vafer/jdeb/spk/scripts/preuninst | 4 + .../org/vafer/jdeb/spk/test/testfile4 | 1 + src/test/resources/spktestbuild.xml | 104 ++++++ 35 files changed, 2517 insertions(+), 317 deletions(-) rename src/main/java/org/vafer/jdeb/{ControlBuilder.java => DebControlBuilder.java} (98%) create mode 100644 src/main/java/org/vafer/jdeb/DebDataBuilder.java create mode 100644 src/main/java/org/vafer/jdeb/PackageMaker.java create mode 100644 src/main/java/org/vafer/jdeb/SpkDataBuilder.java create mode 100644 src/main/java/org/vafer/jdeb/SpkInfoBuilder.java create mode 100644 src/main/java/org/vafer/jdeb/SpkMaker.java create mode 100644 src/main/java/org/vafer/jdeb/SpkScriptsBuilder.java create mode 100644 src/main/java/org/vafer/jdeb/ant/SpkAntTask.java create mode 100644 src/main/java/org/vafer/jdeb/maven/AbstractPackageMojo.java create mode 100644 src/main/java/org/vafer/jdeb/maven/SpkMojo.java create mode 100644 src/main/java/org/vafer/jdeb/synology/BinaryPackageInfoFile.java create mode 100644 src/main/java/org/vafer/jdeb/synology/InfoFile.java rename src/main/java/org/vafer/jdeb/{debian => utils}/ControlField.java (99%) create mode 100644 src/test/java/org/vafer/jdeb/SpkMakerTestCase.java create mode 100644 src/test/java/org/vafer/jdeb/ant/SpkAntTaskTestCase.java create mode 100644 src/test/java/org/vafer/jdeb/synology/PackageInfoFileTestCase.java rename src/test/resources/{testbuild.xml => debtestbuild.xml} (98%) create mode 100644 src/test/resources/org/vafer/jdeb/spk/data.tar.bz2 create mode 100644 src/test/resources/org/vafer/jdeb/spk/data.tgz create mode 100644 src/test/resources/org/vafer/jdeb/spk/data.zip create mode 100644 src/test/resources/org/vafer/jdeb/spk/data/test/testfile create mode 100644 src/test/resources/org/vafer/jdeb/spk/info create mode 100644 src/test/resources/org/vafer/jdeb/spk/scripts/postinst create mode 100644 src/test/resources/org/vafer/jdeb/spk/scripts/preuninst create mode 100644 src/test/resources/org/vafer/jdeb/spk/test/testfile4 create mode 100644 src/test/resources/spktestbuild.xml diff --git a/src/main/java/org/vafer/jdeb/DataBuilder.java b/src/main/java/org/vafer/jdeb/DataBuilder.java index 03d467cd4..b2e55f194 100644 --- a/src/main/java/org/vafer/jdeb/DataBuilder.java +++ b/src/main/java/org/vafer/jdeb/DataBuilder.java @@ -33,21 +33,20 @@ import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; import org.apache.commons.compress.archivers.tar.TarConstants; import org.apache.commons.compress.archivers.zip.ZipEncoding; -import org.apache.commons.compress.archivers.zip.ZipEncodingHelper; import org.apache.commons.compress.compressors.CompressorException; import org.vafer.jdeb.utils.Utils; /** * Builds the data archive of the Debian package. */ -class DataBuilder { +abstract class DataBuilder { - private Console console; + protected Console console; - private ZipEncoding encoding; + protected ZipEncoding encoding; - private static final class Total { - private BigInteger count = BigInteger.valueOf(0); + protected static final class Total { + protected BigInteger count = BigInteger.valueOf(0); public void add( long size ) { count = count.add(BigInteger.valueOf(size)); @@ -58,12 +57,7 @@ public String toString() { } } - DataBuilder(Console console) { - this.console = console; - this.encoding = ZipEncodingHelper.getZipEncoding(null); - } - - private void checkField(String name, int length) throws IOException { + protected void checkField(String name, int length) throws IOException { if (name != null) { ByteBuffer b = encoding.encode(name); if (b.limit() > length) { @@ -98,59 +92,61 @@ BigInteger buildData(Collection producers, File output, final Stri final Total dataSize = new Total(); - final List addedDirectories = new ArrayList(); - final DataConsumer receiver = new DataConsumer() { - public void onEachDir( String dirname, String linkname, String user, int uid, String group, int gid, int mode, long size ) throws IOException { - // Check link name - checkField(linkname, TarConstants.NAMELEN); - // Check user name - checkField(user, TarConstants.UNAMELEN); - // Check group name - checkField(group, TarConstants.GNAMELEN); + final List addedDirectories = new ArrayList(); + final DataConsumer receiver = new DataConsumer() { + public void onEachDir(String dirname, String linkname, String user, int uid, String group, int gid, int mode, long size) throws IOException { + // Check link name + checkField(linkname, TarConstants.NAMELEN); + // Check user name + checkField(user, TarConstants.UNAMELEN); + // Check group name + checkField(group, TarConstants.GNAMELEN); - dirname = fixPath(dirname); + dirname = fixPath(dirname); - createParentDirectories(dirname, user, uid, group, gid); + createParentDirectories(dirname, user, uid, group, gid); - // The directory passed in explicitly by the caller also gets the passed-in mode. (Unlike - // the parent directories for now. See related comments at "int mode =" in - // createParentDirectories, including about a possible bug.) - createDirectory(dirname, user, uid, group, gid, mode, 0); + // The directory passed in explicitly by the caller also gets + // the passed-in mode. (Unlike + // the parent directories for now. See related comments at + // "int mode =" in + // createParentDirectories, including about a possible bug.) + createDirectory(dirname, user, uid, group, gid, mode, 0); - console.debug("dir: " + dirname); - } - - public void onEachFile( InputStream inputStream, String filename, String linkname, String user, int uid, String group, int gid, int mode, long size ) throws IOException { - // Check link name - checkField(linkname, TarConstants.NAMELEN); - // Check user name - checkField(user, TarConstants.UNAMELEN); - // Check group name - checkField(group, TarConstants.GNAMELEN); + console.debug("dir: " + dirname); + } - filename = fixPath(filename); + public void onEachFile(InputStream inputStream, String filename, String linkname, String user, int uid, String group, int gid, int mode, long size) throws IOException { + // Check link name + checkField(linkname, TarConstants.NAMELEN); + // Check user name + checkField(user, TarConstants.UNAMELEN); + // Check group name + checkField(group, TarConstants.GNAMELEN); - createParentDirectories(filename, user, uid, group, gid); + filename = fixPath(filename); - final TarArchiveEntry entry = new TarArchiveEntry(filename, true); + createParentDirectories(filename, user, uid, group, gid); - entry.setUserName(user); - entry.setUserId(uid); - entry.setGroupName(group); - entry.setGroupId(gid); - entry.setMode(mode); - entry.setSize(size); + final TarArchiveEntry entry = new TarArchiveEntry(filename, true); - tarOutputStream.putArchiveEntry(entry); + entry.setUserName(user); + entry.setUserId(uid); + entry.setGroupName(group); + entry.setGroupId(gid); + entry.setMode(mode); + entry.setSize(size); - dataSize.add(size); - digest.reset(); + tarOutputStream.putArchiveEntry(entry); - Utils.copy(inputStream, new DigestOutputStream(tarOutputStream, digest)); + dataSize.add(size); + digest.reset(); - final String md5 = Utils.toHex(digest.digest()); + Utils.copy(inputStream, new DigestOutputStream(tarOutputStream, digest)); - tarOutputStream.closeArchiveEntry(); + final String md5 = Utils.toHex(digest.digest()); + + tarOutputStream.closeArchiveEntry(); console.debug( "file:" + entry.getName() + @@ -165,33 +161,33 @@ public void onEachFile( InputStream inputStream, String filename, String linknam " md5: " + md5 ); - // append to file md5 list - checksums.append(md5).append(" ").append(entry.getName()).append('\n'); - } + // append to file md5 list + checksums.append(md5).append(" ").append(entry.getName()).append('\n'); + } - public void onEachLink(String path, String linkname, boolean symlink, String user, int uid, String group, int gid, int mode) throws IOException { - // Check link name - checkField(linkname, TarConstants.NAMELEN); - // Check user name - checkField(user, TarConstants.UNAMELEN); - // Check group name - checkField(group, TarConstants.GNAMELEN); + public void onEachLink(String path, String linkname, boolean symlink, String user, int uid, String group, int gid, int mode) throws IOException { + // Check link name + checkField(linkname, TarConstants.NAMELEN); + // Check user name + checkField(user, TarConstants.UNAMELEN); + // Check group name + checkField(group, TarConstants.GNAMELEN); - path = fixPath(path); + path = fixPath(path); - createParentDirectories(path, user, uid, group, gid); + createParentDirectories(path, user, uid, group, gid); - final TarArchiveEntry entry = new TarArchiveEntry(path, symlink ? TarArchiveEntry.LF_SYMLINK : TarArchiveEntry.LF_LINK); - entry.setLinkName(linkname); + final TarArchiveEntry entry = new TarArchiveEntry(path, symlink ? TarArchiveEntry.LF_SYMLINK : TarArchiveEntry.LF_LINK, true); + entry.setLinkName(linkname); - entry.setUserName(user); - entry.setUserId(uid); - entry.setGroupName(group); - entry.setGroupId(gid); - entry.setMode(mode); + entry.setUserName(user); + entry.setUserId(uid); + entry.setGroupName(group); + entry.setGroupId(gid); + entry.setMode(mode); - tarOutputStream.putArchiveEntry(entry); - tarOutputStream.closeArchiveEntry(); + tarOutputStream.putArchiveEntry(entry); + tarOutputStream.closeArchiveEntry(); console.debug( "link:" + entry.getName() + @@ -201,36 +197,41 @@ public void onEachLink(String path, String linkname, boolean symlink, String use " userid:" + entry.getUserId() + " groupname:" + entry.getGroupName() + " groupid:" + entry.getGroupId() - ); - } - - - private void createDirectory( String directory, String user, int uid, String group, int gid, int mode, long size ) throws IOException { - // All dirs should end with "/" when created, or the test DebAndTaskTestCase.testTarFileSet() thinks its a file - // and so thinks it has the wrong permission. - // This consistency also helps when checking if a directory already exists in addedDirectories. - - if (!directory.endsWith("/")) { - directory += "/"; - } - - if (!addedDirectories.contains(directory)) { - TarArchiveEntry entry = new TarArchiveEntry(directory, true); - entry.setUserName(user); - entry.setUserId(uid); - entry.setGroupName(group); - entry.setGroupId(gid); - entry.setMode(mode); - entry.setSize(size); - - tarOutputStream.putArchiveEntry(entry); - tarOutputStream.closeArchiveEntry(); - addedDirectories.add(directory); // so addedDirectories consistently have "/" for finding duplicates. - } - } + ); + } + + private void createDirectory(String directory, String user, int uid, String group, int gid, int mode, long size) throws IOException { + // All dirs should end with "/" when created, or the test + // DebAndTaskTestCase.testTarFileSet() thinks its a file + // and so thinks it has the wrong permission. + // This consistency also helps when checking if a directory + // already exists in addedDirectories. + + if (directory == null) { + return; + } + + if (!directory.endsWith("/")) { + directory += "/"; + } + + if (!addedDirectories.contains(directory)) { + TarArchiveEntry entry = new TarArchiveEntry(directory, true); + entry.setUserName(user); + entry.setUserId(uid); + entry.setGroupName(group); + entry.setGroupId(gid); + entry.setMode(mode); + entry.setSize(size); + + tarOutputStream.putArchiveEntry(entry); + tarOutputStream.closeArchiveEntry(); + addedDirectories.add(directory); // so addedDirectories consistently have "/" for finding duplicates. + } + } private void createParentDirectories( String filename, String user, int uid, String group, int gid ) throws IOException { - String dirname = fixPath(new File(filename).getParent()); + String dirname = fixPath(new File(filename).getParent()); // Debian packages must have parent directories created // before sub-directories or files can be installed. @@ -238,7 +239,7 @@ private void createParentDirectories( String filename, String user, int uid, Str // in a .deb package, but the ./usr/lib/foo directory didn't // exist, the package installation would fail. The .deb must // then have an entry for ./usr/lib/foo and then ./usr/lib/foo/bar - + if (dirname == null) { return; } @@ -246,9 +247,11 @@ private void createParentDirectories( String filename, String user, int uid, Str // The loop below will create entries for all parent directories // to ensure that .deb packages will install correctly. String[] pathParts = dirname.split("/"); - String parentDir = "./"; + String parentDir = fixPath(pathParts[0] + "/"); + for (int i = 1; i < pathParts.length; i++) { parentDir += pathParts[i] + "/"; + // Make it so the dirs can be traversed by users. // We could instead try something more granular, like setting the directory // permission to 'rx' for each of the 3 user/group/other read permissions @@ -284,24 +287,8 @@ private void createParentDirectories( String filename, String user, int uid, Str return dataSize.count; } - private String fixPath( String path ) { - if (path == null || path.equals(".")) { - return path; - } - - // If we're receiving directory names from Windows, then we'll convert to use slash - // This does eliminate the ability to use of a backslash in a directory name on *NIX, - // but in practice, this is a non-issue - if (path.contains("\\")) { - path = path.replace('\\', '/'); - } - // ensure the path is like : ./foo/bar - if (path.startsWith("/")) { - path = "." + path; - } else if (!path.startsWith("./")) { - path = "./" + path; - } - return path; - } + protected abstract String fixPath( String path ); + + protected abstract String getPathPrefix(); } diff --git a/src/main/java/org/vafer/jdeb/ControlBuilder.java b/src/main/java/org/vafer/jdeb/DebControlBuilder.java similarity index 98% rename from src/main/java/org/vafer/jdeb/ControlBuilder.java rename to src/main/java/org/vafer/jdeb/DebControlBuilder.java index 8fa58ec32..146dc3e80 100644 --- a/src/main/java/org/vafer/jdeb/ControlBuilder.java +++ b/src/main/java/org/vafer/jdeb/DebControlBuilder.java @@ -46,7 +46,7 @@ /** * Builds the control archive of the Debian package. */ -class ControlBuilder { +class DebControlBuilder { /** The name of the package maintainer scripts */ private static final Set MAINTAINER_SCRIPTS = new HashSet(Arrays.asList("preinst", "postinst", "prerm", "postrm", "config")); @@ -59,7 +59,7 @@ class ControlBuilder { private final String openReplaceToken; private final String closeReplaceToken; - ControlBuilder(Console console, VariableResolver resolver, String openReplaceToken, String closeReplaceToken) { + DebControlBuilder(Console console, VariableResolver resolver, String openReplaceToken, String closeReplaceToken) { this.console = console; this.resolver = resolver; this.openReplaceToken = openReplaceToken; diff --git a/src/main/java/org/vafer/jdeb/DebDataBuilder.java b/src/main/java/org/vafer/jdeb/DebDataBuilder.java new file mode 100644 index 000000000..7baecb661 --- /dev/null +++ b/src/main/java/org/vafer/jdeb/DebDataBuilder.java @@ -0,0 +1,59 @@ +/* + * Copyright 2014 The jdeb developers. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.vafer.jdeb; + +import org.apache.commons.compress.archivers.zip.ZipEncodingHelper; + +/** + * Builds the data archive of the Debian package. + */ +class DebDataBuilder extends DataBuilder { + + private static final String PATH_PREFIX = "./"; + + DebDataBuilder(final Console console) { + this.console = console; + this.encoding = ZipEncodingHelper.getZipEncoding(null); + } + + @Override + protected String fixPath(String path) { + if (path == null || path.equals(".")) { + return path; + } + + // If we're receiving directory names from Windows, then we'll convert to use slash + // This does eliminate the ability to use of a backslash in a directory name on *NIX, + // but in practice, this is a non-issue + if (path.contains("\\")) { + path = path.replace('\\', '/'); + } + // ensure the path is like : ./foo/bar + if (path.startsWith("/")) { + path = "." + path; + } else if (!path.startsWith(PATH_PREFIX)) { + path = PATH_PREFIX + path; + } + return path; + } + + @Override + protected String getPathPrefix() { + return PATH_PREFIX; + } + +} diff --git a/src/main/java/org/vafer/jdeb/DebMaker.java b/src/main/java/org/vafer/jdeb/DebMaker.java index 7b0be6ae6..8bf20b8d3 100644 --- a/src/main/java/org/vafer/jdeb/DebMaker.java +++ b/src/main/java/org/vafer/jdeb/DebMaker.java @@ -16,6 +16,7 @@ package org.vafer.jdeb; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -27,10 +28,8 @@ import java.util.Date; import java.util.List; -import org.apache.commons.compress.archivers.ar.ArArchiveEntry; import org.apache.commons.compress.archivers.ar.ArArchiveOutputStream; import org.apache.commons.io.IOUtils; -import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureGenerator; import org.bouncycastle.openpgp.PGPUtil; @@ -43,7 +42,6 @@ import org.vafer.jdeb.signing.PGPSigner; import org.vafer.jdeb.utils.PGPSignatureOutputStream; import org.vafer.jdeb.utils.Utils; -import org.vafer.jdeb.utils.VariableResolver; /** * A generic class for creating Debian archives. Even supports signed changes @@ -52,10 +50,7 @@ * @author Torsten Curdt * @author Bryan Sant */ -public class DebMaker { - - /** A console to output log message with */ - private Console console; +public class DebMaker extends PackageMaker { /** The Debian package produced */ private File deb; @@ -96,26 +91,14 @@ public class DebMaker { /** The file where to write the changes of the changes input to */ private File changesSave; - /** The compression method used for the data file (none, gzip, bzip2 or xz) */ - private String compression = "gzip"; - /** Whether to sign the package that is created */ private boolean signPackage; - private VariableResolver variableResolver; - private String openReplaceToken; - private String closeReplaceToken; - - private final Collection dataProducers = new ArrayList(); - private final Collection conffilesProducers = new ArrayList(); public DebMaker(Console console, Collection dataProducers, Collection conffileProducers) { - this.console = console; - if (dataProducers != null) { - this.dataProducers.addAll(dataProducers); - } + super(console, dataProducers); if (conffileProducers != null) { this.conffilesProducers.addAll(conffileProducers); } @@ -177,18 +160,6 @@ public void setPassphrase(String passphrase) { this.passphrase = passphrase; } - public void setCompression(String compression) { - this.compression = compression; - } - - public void setResolver(VariableResolver variableResolver) { - this.variableResolver = variableResolver; - } - - private boolean isWritableFile(File file) { - return !file.exists() || file.isFile() && file.canWrite(); - } - /** * Validates the input parameters. */ @@ -267,7 +238,7 @@ public void makeDeb() throws PackagingException { PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(new BcPGPContentSignerBuilder(signer.getSecretKey().getPublicKey().getAlgorithm(), digest)); signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, signer.getPrivateKey()); - packageControlFile = createSignedDeb(Compression.toEnum(compression), signatureGenerator); + packageControlFile = createSignedDeb(Compression.toEnum(compression), signatureGenerator, signer); } else { packageControlFile = createDeb(Compression.toEnum(compression)); } @@ -362,6 +333,7 @@ public void onEachFile( InputStream inputStream, String filename, String linknam if (tempConffileItem.startsWith(".")) { tempConffileItem = tempConffileItem.substring(1); } + console.info("Adding conffile: " + tempConffileItem); result.add(tempConffileItem); } @@ -392,7 +364,7 @@ public void onEachLink(String path, String linkname, boolean symlink, String use * @throws PackagingException */ public BinaryPackageControlFile createDeb(Compression compression) throws PackagingException { - return createSignedDeb(compression, null); + return createSignedDeb(compression, null, null); } /** * Create the debian archive with from the provided control files and data producers. @@ -403,7 +375,7 @@ public BinaryPackageControlFile createDeb(Compression compression) throws Packag * @return PackageDescriptor * @throws PackagingException */ - public BinaryPackageControlFile createSignedDeb(Compression compression, final PGPSignatureGenerator signatureGenerator ) throws PackagingException { + public BinaryPackageControlFile createSignedDeb(Compression compression, final PGPSignatureGenerator signatureGenerator, final PGPSigner signer ) throws PackagingException { File tempData = null; File tempControl = null; @@ -412,7 +384,7 @@ public BinaryPackageControlFile createSignedDeb(Compression compression, final P tempControl = File.createTempFile("deb", "control"); console.debug("Building data"); - DataBuilder dataBuilder = new DataBuilder(console); + DebDataBuilder dataBuilder = new DebDataBuilder(console); StringBuilder md5s = new StringBuilder(); BigInteger size = dataBuilder.buildData(dataProducers, tempData, md5s, compression); @@ -420,7 +392,7 @@ public BinaryPackageControlFile createSignedDeb(Compression compression, final P List tempConffiles = populateConffiles(conffilesProducers); console.debug("Building control"); - ControlBuilder controlBuilder = new ControlBuilder(console, variableResolver, openReplaceToken, closeReplaceToken); + DebControlBuilder controlBuilder = new DebControlBuilder(console, variableResolver, openReplaceToken, closeReplaceToken); BinaryPackageControlFile packageControlFile = controlBuilder.createPackageControlFile(new File(control, "control"), size); if (packageControlFile.get("Package") == null) { packageControlFile.set("Package", packageName); @@ -462,6 +434,12 @@ public BinaryPackageControlFile createSignedDeb(Compression compression, final P addTo(sigStream, tempControl); addTo(sigStream, tempData); addTo(ar, "_gpgorigin", sigStream.generateASCIISignature()); + + final ByteArrayOutputStream os = new ByteArrayOutputStream(); + signer.clearSign("HUHUHU", os); + + addTo(ar, "_gpgbuilder", new String(os.toByteArray())); + os.close(); } ar.close(); @@ -484,26 +462,6 @@ public BinaryPackageControlFile createSignedDeb(Compression compression, final P } } - private void addTo(ArArchiveOutputStream pOutput, String pName, String pContent) throws IOException { - final byte[] content = pContent.getBytes(); - pOutput.putArchiveEntry(new ArArchiveEntry(pName, content.length)); - pOutput.write(content); - pOutput.closeArchiveEntry(); - } - - private void addTo(ArArchiveOutputStream pOutput, String pName, File pContent) throws IOException { - pOutput.putArchiveEntry(new ArArchiveEntry(pName, pContent.length())); - - final InputStream input = new FileInputStream(pContent); - try { - Utils.copy(input, pOutput); - } finally { - input.close(); - } - - pOutput.closeArchiveEntry(); - } - private void addTo(final PGPSignatureOutputStream pOutput, final String pContent) throws IOException { final byte[] content = pContent.getBytes(); pOutput.write(content); @@ -517,12 +475,4 @@ private void addTo(final PGPSignatureOutputStream pOutput, final File pContent) input.close(); } } - - public void setOpenReplaceToken(String openReplaceToken) { - this.openReplaceToken = openReplaceToken; - } - - public void setCloseReplaceToken(String closeReplaceToken) { - this.closeReplaceToken = closeReplaceToken; - } } diff --git a/src/main/java/org/vafer/jdeb/PackageMaker.java b/src/main/java/org/vafer/jdeb/PackageMaker.java new file mode 100644 index 000000000..45ca4d9f5 --- /dev/null +++ b/src/main/java/org/vafer/jdeb/PackageMaker.java @@ -0,0 +1,177 @@ +/* + * Copyright 2014 The jdeb developers. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.vafer.jdeb; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; + +import org.apache.commons.compress.archivers.ar.ArArchiveEntry; +import org.apache.commons.compress.archivers.ar.ArArchiveOutputStream; +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; +import org.vafer.jdeb.producers.DataProducerDirectory; +import org.vafer.jdeb.utils.Utils; +import org.vafer.jdeb.utils.VariableResolver; + +/** + * A abstract class for creating archives. + * + * @author Torsten Curdt + * @author Bryan Sant + */ +public abstract class PackageMaker { + + /** A console to output log message with */ + protected Console console; + + /** The compression method used for the data file (none, gzip, bzip2 or xz) */ + protected String compression = "gzip"; + + protected VariableResolver variableResolver; + protected String openReplaceToken; + protected String closeReplaceToken; + + protected final Collection dataProducers = new ArrayList(); + + public PackageMaker(final Console console, final Collection dataProducers) { + this.console = console; + if (dataProducers != null) { + this.dataProducers.addAll(dataProducers); + } + } + + public final void setCompression(final String compression) { + this.compression = compression; + } + + public final void setResolver(final VariableResolver variableResolver) { + this.variableResolver = variableResolver; + } + + protected final boolean isWritableFile(final File file) { + return !file.exists() || file.isFile() && file.canWrite(); + } + + protected final void addTo(final ArArchiveOutputStream pOutput, final String pName, final String pContent) throws IOException { + final byte[] content = pContent.getBytes(); + pOutput.putArchiveEntry(new ArArchiveEntry(pName, content.length)); + pOutput.write(content); + pOutput.closeArchiveEntry(); + } + + protected final void addTo(final ArArchiveOutputStream pOutput, final String pName, final File pContent) throws IOException { + pOutput.putArchiveEntry(new ArArchiveEntry(pName, pContent.length())); + + final InputStream input = new FileInputStream(pContent); + try { + Utils.copy(input, pOutput); + } finally { + input.close(); + } + + pOutput.closeArchiveEntry(); + } + + protected final void addTo(final TarArchiveOutputStream pOutput, final String pName, final String pContent, final String user, final int uid, final String group, final int gid, final int mode) throws IOException { + final byte[] content = pContent.getBytes(); + addTo(pOutput, pName, content, user, uid, group, gid, mode); + } + + protected final void addTo(final TarArchiveOutputStream pOutput, final String pName, final byte[] pContent, final String user, final int uid, final String group, final int gid, final int mode) throws IOException { + TarArchiveEntry entry = new TarArchiveEntry(pName); + entry.setSize(pContent.length); + entry.setUserName(user); + entry.setUserId(uid); + entry.setGroupName(group); + entry.setGroupId(gid); + entry.setMode(mode); + + pOutput.putArchiveEntry(entry); + pOutput.write(pContent); + pOutput.closeArchiveEntry(); + } + + protected final void addTo(final TarArchiveOutputStream pOutput, final String pName, final File pContent, final String user, final int uid, final String group, final int gid, final int mode) throws IOException { + TarArchiveEntry entry = new TarArchiveEntry(pName); + entry.setSize(pContent.length()); + entry.setUserName(user); + entry.setUserId(uid); + entry.setGroupName(group); + entry.setGroupId(gid); + entry.setMode(mode); + + pOutput.putArchiveEntry(entry); + + final InputStream input = new FileInputStream(pContent); + try { + Utils.copy(input, pOutput); + } finally { + input.close(); + } + pOutput.closeArchiveEntry(); + } + + protected final void addDirectoryTo(final ArArchiveOutputStream pOutput, final String pDirName, final File pDir) throws IOException { + if (pOutput == null || pDir == null || pDirName == null) { + return; + } + + final String pDirPrefix; + if (pDirName.endsWith("/")) { + pDirPrefix = pDirName; + } else { + pDirPrefix = pDirName + "/"; + } + + DataProducerDirectory dirProducer = new DataProducerDirectory(pDir, null, null, null); + DataConsumer dirConsumer = new DataConsumer() { + @Override + public void onEachLink(final String path, final String linkName, final boolean symlink, final String user, final int uid, final String group, final int gid, final int mode) throws IOException { + + } + + @Override + public void onEachFile(final InputStream input, final String filename, final String linkname, final String user, final int uid, final String group, final int gid, final int mode, final long size) throws IOException { + pOutput.putArchiveEntry(new ArArchiveEntry(pDirPrefix + filename, size)); + try { + Utils.copy(input, pOutput); + } finally { + input.close(); + } + pOutput.closeArchiveEntry(); + } + + @Override + public void onEachDir(final String dirname, final String linkname, final String user, final int uid, final String group, final int gid, final int mode, final long size) throws IOException { + // + } + }; + dirProducer.produce(dirConsumer); + } + + public final void setOpenReplaceToken(final String openReplaceToken) { + this.openReplaceToken = openReplaceToken; + } + + public final void setCloseReplaceToken(final String closeReplaceToken) { + this.closeReplaceToken = closeReplaceToken; + } +} diff --git a/src/main/java/org/vafer/jdeb/SpkDataBuilder.java b/src/main/java/org/vafer/jdeb/SpkDataBuilder.java new file mode 100644 index 000000000..a86d0a791 --- /dev/null +++ b/src/main/java/org/vafer/jdeb/SpkDataBuilder.java @@ -0,0 +1,68 @@ +/* + * Copyright 2014 The jdeb developers. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.vafer.jdeb; + +import org.apache.commons.compress.archivers.zip.ZipEncodingHelper; + +/** + * Builds the data archive of the synology package. + */ +class SpkDataBuilder extends DataBuilder { + + private static final String PATH_PREFIX = ""; + + SpkDataBuilder(final Console console) { + this.console = console; + this.encoding = ZipEncodingHelper.getZipEncoding(null); + } + + @Override + protected String fixPath(String path) { + if (path == null || path.equals(".")) { + return path; + } + + // If we're receiving directory names from Windows, then we'll convert to use slash + // This does eliminate the ability to use of a backslash in a directory name on *NIX, + // but in practice, this is a non-issue + if (path.contains("\\")) { + path = path.replace('\\', '/'); + } + + // ensure the path is like : foo/bar + if (path.startsWith("/")) { + path = path.substring(1); + } + /* + if (!path.startsWith(PATH_PREFIX)) { + path = PATH_PREFIX + path; + } + */ + + if (path.contains("//")) { + path = path.replace("//", "/"); + } + + return path; + } + + @Override + protected String getPathPrefix() { + return PATH_PREFIX; + } + +} diff --git a/src/main/java/org/vafer/jdeb/SpkInfoBuilder.java b/src/main/java/org/vafer/jdeb/SpkInfoBuilder.java new file mode 100644 index 000000000..a2f32f338 --- /dev/null +++ b/src/main/java/org/vafer/jdeb/SpkInfoBuilder.java @@ -0,0 +1,84 @@ +/* + * Copyright 2014 The jdeb developers. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.vafer.jdeb; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.text.ParseException; + +import org.vafer.jdeb.synology.BinaryPackageInfoFile; +import org.vafer.jdeb.utils.FilteredFile; +import org.vafer.jdeb.utils.VariableResolver; + +/** + * Builds the info file of the Synology package. + */ +class SpkInfoBuilder { + + private Console console; + private VariableResolver resolver; + + SpkInfoBuilder(Console console, VariableResolver resolver) { + this.console = console; + this.resolver = resolver; + } + + /** + * Creates a package info file from the specified file. The + * Maintainer field is overridden by the SPKEMAIL and + * SPKFULLNAME environment variables if defined. + * + * @param file + * the control file + * @param pDataSize + * the size of the installed package + */ + public BinaryPackageInfoFile createPackageInfoFile(final File file, final BigInteger pDataSize, final String pChecksum) throws IOException, ParseException { + final BinaryPackageInfoFile packageInfoFile; + if (file != null) { + FilteredFile infoFile = new FilteredFile(new FileInputStream(file), resolver); + packageInfoFile = new BinaryPackageInfoFile(infoFile.toString()); + } else { + packageInfoFile = new BinaryPackageInfoFile(""); + } + + packageInfoFile.set("extractsize", pDataSize.toString()); + + packageInfoFile.set("checksum", pChecksum); + + // override the Version if the SPKVERSION environment variable is defined + final String spkVersion = System.getenv("SPKVERSION"); + if (spkVersion != null) { + packageInfoFile.set("version", spkVersion); + console.debug("Using version'" + spkVersion + "' from the environment variables."); + } + + // override the Maintainer field if the SPKFULLNAME and SPKEMAIL environment variables are defined + final String spkFullName = System.getenv("SPKFULLNAME"); + final String spkEmail = System.getenv("SPKEMAIL"); + + if (spkFullName != null && spkEmail != null) { + final String maintainer = spkFullName + " <" + spkEmail + ">"; + packageInfoFile.set("maintainer", maintainer); + console.debug("Using maintainer '" + maintainer + "' from the environment variables."); + } + + return packageInfoFile; + } +} diff --git a/src/main/java/org/vafer/jdeb/SpkMaker.java b/src/main/java/org/vafer/jdeb/SpkMaker.java new file mode 100644 index 000000000..c537a2ced --- /dev/null +++ b/src/main/java/org/vafer/jdeb/SpkMaker.java @@ -0,0 +1,192 @@ +/* + * Copyright 2014 The jdeb developers. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.vafer.jdeb; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Collection; + +import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; +import org.vafer.jdeb.mapping.PermMapper; +import org.vafer.jdeb.synology.BinaryPackageInfoFile; + +/** + * A generic class for creating Synology archives. + * + * @author Torsten Curdt + * @author Bryan Sant + */ +public class SpkMaker extends PackageMaker { + + /** The Synology package produced */ + private File spk; + + /** The info file */ + private File info; + + /** The directory containing the script files to build the package */ + private File scripts; + + /** The name of the package. Default value if not specified in the info file */ + private String packageName; + + /** The description of the package. Default value if not specified in the control file */ + private String description; + + + public SpkMaker(Console console, Collection dataProducers) { + super(console, dataProducers); + } + + public void setSpk(File spk) { + this.spk = spk; + } + + public void setInfo(File info) { + this.info = info; + } + + public void setScripts(File scripts) { + this.scripts = scripts; + } + + public void setPackage(String packageName) { + this.packageName = packageName; + } + + public void setDescription(String description) { + this.description = description; + } + + /** + * Validates the input parameters. + */ + public void validate() throws PackagingException { + if (scripts == null || !scripts.isDirectory()) { + throw new PackagingException("The 'scripts' attribute doesn't point to a directory."); + } + + if (Compression.toEnum(compression) == null || !Compression.toEnum(compression).equals(Compression.GZIP)) { + throw new PackagingException("The compression method '" + compression + "' is not supported (expected 'gzip')"); + } + + if (spk == null) { + throw new PackagingException("You need to specify where the spk file is supposed to be created."); + } + + if (dataProducers.size() == 0) { + throw new PackagingException("You need to provide at least one reference to a tgz or directory with data."); + } + } + + public void makeSpk() throws PackagingException { + try { + console.info("Creating synology package: " + spk); + + createSpk(Compression.toEnum(compression)); + } catch (Exception e) { + throw new PackagingException("Failed to create synology package " + spk, e); + } + } + + /** + * Create the synology archive with from the provided script files and data producers. + * + * @param compression the compression method used for the data file + * @return BinaryPackageInfoFile + * @throws PackagingException + */ + public BinaryPackageInfoFile createSpk(Compression compression) throws PackagingException { + File tempData = null; + File tempInfo = null; + + try { + tempData = File.createTempFile("spk", "data"); + + console.debug("Building data"); + SpkDataBuilder dataBuilder = new SpkDataBuilder(console); + StringBuilder md5s = new StringBuilder(); + BigInteger size = dataBuilder.buildData(dataProducers, tempData, md5s, compression); + + MessageDigest md = MessageDigest.getInstance("MD5"); + String digestMD5 = getDigest(new FileInputStream(tempData), md, 2048); + + console.debug("Building info"); + SpkInfoBuilder infoBuilder = new SpkInfoBuilder(console, variableResolver); + BinaryPackageInfoFile packageInfoFile = infoBuilder.createPackageInfoFile(info, size, digestMD5); + if (packageInfoFile.get("package") == null) { + packageInfoFile.set("package", packageName); + } + if (packageInfoFile.get("description") == null) { + packageInfoFile.set("description", description); + } + + if (!packageInfoFile.isValid()) { + throw new PackagingException("Info file fields are invalid " + packageInfoFile.invalidFields() + + ". The following fields are mandatory: " + packageInfoFile.getMandatoryFields() + + ". Please check your pom.xml/build.xml and your info file."); + } + + spk.getParentFile().mkdirs(); + + + TarArchiveOutputStream tar = new TarArchiveOutputStream(new FileOutputStream(spk)); + + addTo(tar, "INFO", packageInfoFile.toString(), "root", 0, "root", 0, PermMapper.toMode("644")); + addTo(tar, "package.tgz", tempData, "root", 0, "root", 0, PermMapper.toMode("644")); + SpkScriptsBuilder scriptsBuilder = new SpkScriptsBuilder(console, variableResolver, openReplaceToken, closeReplaceToken); + scriptsBuilder.addDirectoryTo(tar, "scripts", scripts); + + tar.close(); + + return packageInfoFile; + + } catch (Exception e) { + throw new PackagingException("Could not create synology package", e); + } finally { + if (tempData != null) { + if (!tempData.delete()) { + console.warn("Could not delete the temporary file " + tempData); + } + } + if (tempInfo != null) { + if (!tempInfo.delete()) { + console.warn("Could not delete the temporary file " + tempInfo); + } + } + } + } + + private String getDigest(InputStream is, MessageDigest md, int byteArraySize) throws NoSuchAlgorithmException, IOException { + String checksum = null; + byte[] buffer = new byte[8192]; + int numOfBytesRead; + while ((numOfBytesRead = is.read(buffer)) > 0) { + md.update(buffer, 0, numOfBytesRead); + } + byte[] hash = md.digest(); + checksum = new BigInteger(1, hash).toString(16); // don't use this, truncates leading zero + + return checksum; + } +} diff --git a/src/main/java/org/vafer/jdeb/SpkScriptsBuilder.java b/src/main/java/org/vafer/jdeb/SpkScriptsBuilder.java new file mode 100644 index 000000000..87306ea38 --- /dev/null +++ b/src/main/java/org/vafer/jdeb/SpkScriptsBuilder.java @@ -0,0 +1,141 @@ +/* + * Copyright 2014 The jdeb developers. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.vafer.jdeb; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; +import org.apache.tools.ant.DirectoryScanner; +import org.vafer.jdeb.mapping.PermMapper; +import org.vafer.jdeb.producers.DataProducerDirectory; +import org.vafer.jdeb.utils.FilteredFile; +import org.vafer.jdeb.utils.VariableResolver; + +/** + * Builds the scripts directory of the Synology package. + */ +class SpkScriptsBuilder { + + /** The name of the package maintainer scripts */ + private static final Set MAINTAINER_SCRIPTS = new HashSet(Arrays.asList("postinst", "postuninst", "postupgrade", "preinst", "preuninst", "preupgrade", "start-stop-status")); + + private Console console; + private VariableResolver resolver; + private final String openReplaceToken; + private final String closeReplaceToken; + + SpkScriptsBuilder(Console console, VariableResolver resolver, String openReplaceToken, String closeReplaceToken) { + this.console = console; + this.resolver = resolver; + this.openReplaceToken = openReplaceToken; + this.closeReplaceToken = closeReplaceToken; + } + + protected final void addDirectoryTo(final TarArchiveOutputStream pOutput, final String pName, final File pDir) throws IOException { + if (pOutput == null || pDir == null || pName == null) { + return; + } + + final String pDirName; + if (pName.endsWith("/")) { + pDirName = pName; + } else { + pDirName = pName + "/"; + } + + pOutput.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU); + + final TarArchiveEntry entry = new TarArchiveEntry(pDirName, true); + entry.setNames("root", "root"); + entry.setMode(PermMapper.toMode("644")); + + pOutput.putArchiveEntry(entry); + pOutput.closeArchiveEntry(); + + DataProducerDirectory dirProducer = new DataProducerDirectory(pDir, null, null, null); + DataConsumer dirConsumer = new DataConsumer() { + @Override + public void onEachLink(final String path, final String linkName, final boolean symlink, final String user, final int uid, final String group, final int gid, final int mode) throws IOException { + // + } + + @Override + public void onEachFile(final InputStream input, final String filename, final String linkname, final String user, final int uid, final String group, final int gid, final int mode, final long size) throws IOException { + FilteredFile configurationFile = new FilteredFile(input, resolver); + configurationFile.setOpenToken(openReplaceToken); + configurationFile.setCloseToken(closeReplaceToken); + + addScriptsEntry(pOutput, pName, filename, configurationFile.toString().getBytes("UTF-8"), user, uid, group, gid, mode); + } + + @Override + public void onEachDir(final String dirname, final String linkname, final String user, final int uid, final String group, final int gid, final int mode, final long size) throws IOException { + // + } + }; + dirProducer.produce(dirConsumer); + } + + private static void addScriptsEntry(final TarArchiveOutputStream pOutput, final String pDir, final String pName, final String pContent, final String user, final int uid, final String group, final int gid, final int mode) throws IOException { + final byte[] content = pContent.getBytes("UTF-8"); + addScriptsEntry(pOutput, pDir, pName, content, user, uid, group, gid, mode); + } + + private static void addScriptsEntry(final TarArchiveOutputStream pOutput, final String pDir, final String pName, final byte[] pContent, final String user, final int uid, final String group, final int gid, final int mode) throws IOException { + final String pDirName; + if (pDir.endsWith("/")) { + pDirName = pDir; + } else { + pDirName = pDir + "/"; + } + + final TarArchiveEntry entry = new TarArchiveEntry(pDirName + pName, true); + entry.setSize(pContent.length); + entry.setNames("root", "root"); + + if (MAINTAINER_SCRIPTS.contains(pName)) { + entry.setMode(PermMapper.toMode("755")); + } else { + entry.setMode(PermMapper.toMode("644")); + } + + pOutput.putArchiveEntry(entry); + pOutput.write(pContent); + pOutput.closeArchiveEntry(); + } + + /** + * Tells if the specified directory is ignored by default (.svn, cvs, etc) + * + * @param directory + */ + private boolean isDefaultExcludes(File directory) { + for (String pattern : DirectoryScanner.getDefaultExcludes()) { + if (DirectoryScanner.match(pattern, directory.getAbsolutePath().replace("\\", "/"))) { + return true; + } + } + + return false; + } +} diff --git a/src/main/java/org/vafer/jdeb/ant/SpkAntTask.java b/src/main/java/org/vafer/jdeb/ant/SpkAntTask.java new file mode 100644 index 000000000..999f55117 --- /dev/null +++ b/src/main/java/org/vafer/jdeb/ant/SpkAntTask.java @@ -0,0 +1,133 @@ +/* + * Copyright 2014 The jdeb developers. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.vafer.jdeb.ant; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.taskdefs.Tar; +import org.apache.tools.ant.types.FileSet; +import org.vafer.jdeb.Console; +import org.vafer.jdeb.DataProducer; +import org.vafer.jdeb.PackagingException; +import org.vafer.jdeb.SpkMaker; +import org.vafer.jdeb.producers.DataProducerFileSet; + +/** + * AntTask for creating synology archives. + * + * @author Torsten Curdt + */ +public class SpkAntTask extends MatchingTask { + + /** The Synology package produced */ + private File spk; + + /** The directory containing the scripts to build the package */ + private File scriptsDir; + + /** The path to the info file */ + private File infoFile; + + /** The compression method used for the data file (gzip) */ + private String compression = "gzip"; + + /** Trigger the verbose mode detailing all operations */ + private boolean verbose; + + private Collection links = new ArrayList(); + + private Collection dataProducers = new ArrayList(); + + + public void setDestfile( File spk ) { + this.spk = spk; + } + + public void setScripts( File scripts ) { + this.scriptsDir = scripts; + } + + public void setInfo( File infoFile ) { + this.infoFile = infoFile; + } + + public void setCompression( String compression ) { + this.compression = compression; + } + + public void setVerbose( boolean verbose ) { + this.verbose = verbose; + } + + public void addFileSet( FileSet fileset ) { + dataProducers.add(new DataProducerFileSet(fileset)); + } + + public void addTarFileSet( Tar.TarFileSet fileset ) { + dataProducers.add(new DataProducerFileSet(fileset)); + } + + public void addData( Data data ) { + dataProducers.add(data); + } + + public void addLink( Link link ) { + links.add(link); + } + + public void execute() { + // add the data producers for the links + for (Link link : links) { + dataProducers.add(link.toDataProducer()); + } + + // validate the type of the elements + for (DataProducer dataProducer : dataProducers) { + if (dataProducer instanceof Data) { + Data data = (Data) dataProducer; + if (data.getType() == null) { + throw new BuildException("The type of the data element wasn't specified (expected 'file', 'directory' or 'archive')"); + } else if (!Arrays.asList("file", "directory", "archive").contains(data.getType().toLowerCase())) { + throw new BuildException("The type '" + data.getType() + "' of the data element is unknown (expected 'file', 'directory' or 'archive')"); + } + } + } + + Console console = new TaskConsole(this, verbose); + + SpkMaker spkMaker = new SpkMaker(console, dataProducers); + spkMaker.setSpk(spk); + spkMaker.setScripts(scriptsDir); + spkMaker.setInfo(infoFile); + spkMaker.setCompression(compression); + + try { + spkMaker.validate(); + spkMaker.makeSpk(); + + } catch (PackagingException e) { + log("Failed to create the Synology package " + spk, e, Project.MSG_ERR); + throw new BuildException("Failed to create the Synology package " + spk, e); + } + } +} diff --git a/src/main/java/org/vafer/jdeb/debian/BinaryPackageControlFile.java b/src/main/java/org/vafer/jdeb/debian/BinaryPackageControlFile.java index 993196918..e3538c567 100644 --- a/src/main/java/org/vafer/jdeb/debian/BinaryPackageControlFile.java +++ b/src/main/java/org/vafer/jdeb/debian/BinaryPackageControlFile.java @@ -20,6 +20,8 @@ import java.io.InputStream; import java.text.ParseException; +import org.vafer.jdeb.utils.ControlField; + /** * Binary package control file. * diff --git a/src/main/java/org/vafer/jdeb/debian/ChangesFile.java b/src/main/java/org/vafer/jdeb/debian/ChangesFile.java index e3c46c7c6..1db120408 100644 --- a/src/main/java/org/vafer/jdeb/debian/ChangesFile.java +++ b/src/main/java/org/vafer/jdeb/debian/ChangesFile.java @@ -17,6 +17,7 @@ package org.vafer.jdeb.debian; import org.vafer.jdeb.changes.ChangeSet; +import org.vafer.jdeb.utils.ControlField; import java.text.DateFormat; import java.text.SimpleDateFormat; diff --git a/src/main/java/org/vafer/jdeb/debian/ControlFile.java b/src/main/java/org/vafer/jdeb/debian/ControlFile.java index e014683b2..ad0705fa1 100644 --- a/src/main/java/org/vafer/jdeb/debian/ControlFile.java +++ b/src/main/java/org/vafer/jdeb/debian/ControlFile.java @@ -30,6 +30,8 @@ import java.util.Map; import java.util.Set; +import org.vafer.jdeb.utils.ControlField; + /** * A control file as specified by the Debian policy. * diff --git a/src/main/java/org/vafer/jdeb/maven/AbstractPackageMojo.java b/src/main/java/org/vafer/jdeb/maven/AbstractPackageMojo.java new file mode 100644 index 000000000..222511589 --- /dev/null +++ b/src/main/java/org/vafer/jdeb/maven/AbstractPackageMojo.java @@ -0,0 +1,217 @@ +/* + * Copyright 2014 The jdeb developers. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.vafer.jdeb.maven; + +import java.io.File; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.MavenProjectHelper; +import org.apache.maven.settings.Profile; +import org.apache.maven.settings.Settings; +import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher; +import org.sonatype.plexus.components.sec.dispatcher.SecDispatcherException; +import org.vafer.jdeb.Console; + +public abstract class AbstractPackageMojo extends AbstractPluginMojo { + + @Component + protected MavenProjectHelper projectHelper; + + @Component(hint = "jdeb-sec") + private SecDispatcher secDispatcher; + + /** + * Boolean option whether to attach the artifact to the project + */ + @Parameter(defaultValue = "true") + protected String attach; + + /** + * The project base directory + */ + @Parameter(defaultValue = "${basedir}", required = true, readonly = true) + protected File baseDir; + + /** + * Run the plugin on all sub-modules. + * If set to false, the plugin will be run in the same folder where the + * mvn command was invoked + */ + @Parameter(defaultValue = "true") + protected boolean submodules; + + /** + * The Maven Session Object + */ + @Component + protected MavenSession session; + + /** + * The classifier of attached artifact + */ + @Parameter + protected String classifier; + + /** + * If verbose is true more build messages are logged. + */ + @Parameter(defaultValue = "false") + protected boolean verbose; + + /** + * Indicates if the execution should be disabled. If true, nothing will occur during execution. + * + * @since 1.1 + */ + @Parameter(defaultValue = "false") + protected boolean skip; + + /** + * The settings. + */ + @Parameter(defaultValue = "${settings}") + protected Settings settings; + + /* end of parameters */ + + + protected String openReplaceToken = "[["; + protected String closeReplaceToken = "]]"; + protected Console console; + + public void setOpenReplaceToken( String openReplaceToken ) { + this.openReplaceToken = openReplaceToken; + } + + public void setCloseReplaceToken( String closeReplaceToken ) { + this.closeReplaceToken = closeReplaceToken; + } + + /** + * @return whether the artifact is a POM or not + */ + protected final boolean isPOM() { + String type = getProject().getArtifact().getType(); + return "pom".equalsIgnoreCase(type); + } + + /** + * @return whether or not Maven is currently operating in the execution root + */ + protected final boolean isSubmodule() { + // FIXME there must be a better way + return !session.getExecutionRootDirectory().equalsIgnoreCase(baseDir.toString()); + } + + /** + * @return whether or not the main artifact was created + */ + protected final boolean hasMainArtifact() { + final MavenProject project = getProject(); + final Artifact artifact = project.getArtifact(); + return artifact.getFile() != null && artifact.getFile().isFile(); + } + + /** + * Decrypts given passphrase if needed using maven security dispatcher. + * See http://maven.apache.org/guides/mini/guide-encryption.html for details. + * + * @param maybeEncryptedPassphrase possibly encrypted passphrase + * @return decrypted passphrase + */ + protected final String decrypt( final String maybeEncryptedPassphrase ) { + if (maybeEncryptedPassphrase == null) { + return null; + } + + try { + final String decrypted = secDispatcher.decrypt(maybeEncryptedPassphrase); + if (maybeEncryptedPassphrase.equals(decrypted)) { + console.info("Passphrase was not encrypted"); + } else { + console.info("Passphrase was successfully decrypted"); + } + return decrypted; + } catch (SecDispatcherException e) { + console.warn("Unable to decrypt passphrase: " + e.getMessage()); + } + + return maybeEncryptedPassphrase; + } + + + /** + * Read properties from the active profiles. + * + * Goes through all active profiles (in the order the + * profiles are defined in settings.xml) and extracts + * the desired properties (if present). The prefix is + * used when looking up properties in the profile but + * not in the returned map. + * + * @param prefix The prefix to use or null if no prefix should be used + * @param properties The properties to read + * + * @return A map containing the values for the properties that were found + */ + public final Map readPropertiesFromActiveProfiles( final String prefix, + final String... properties ) { + if (settings == null) { + console.debug("No maven setting injected"); + return Collections.emptyMap(); + } + + final List activeProfilesList = settings.getActiveProfiles(); + if (activeProfilesList.isEmpty()) { + console.debug("No active profiles found"); + return Collections.emptyMap(); + } + + final Map map = new HashMap(); + final Set activeProfiles = new HashSet(activeProfilesList); + + // Iterate over all active profiles in order + for (final Profile profile : settings.getProfiles()) { + // Check if the profile is active + final String profileId = profile.getId(); + if (activeProfiles.contains(profileId)) { + console.debug("Trying active profile " + profileId); + for (final String property : properties) { + final String propKey = prefix != null ? prefix + property : property; + final String value = profile.getProperties().getProperty(propKey); + if (value != null) { + console.debug("Found property " + property + " in profile " + profileId); + map.put(property, value); + } + } + } + } + + return map; + } + +} diff --git a/src/main/java/org/vafer/jdeb/maven/DebMojo.java b/src/main/java/org/vafer/jdeb/maven/DebMojo.java index d2135aa5a..35891f629 100644 --- a/src/main/java/org/vafer/jdeb/maven/DebMojo.java +++ b/src/main/java/org/vafer/jdeb/maven/DebMojo.java @@ -36,12 +36,8 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; -import org.apache.maven.project.MavenProjectHelper; -import org.apache.maven.settings.Profile; import org.apache.maven.settings.Settings; import org.apache.tools.tar.TarEntry; -import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher; -import org.sonatype.plexus.components.sec.dispatcher.SecDispatcherException; import org.vafer.jdeb.Console; import org.vafer.jdeb.DataConsumer; import org.vafer.jdeb.DataProducer; @@ -56,15 +52,8 @@ /** * Creates Debian package */ -@SuppressWarnings("unused") @Mojo(name = "jdeb", defaultPhase = LifecyclePhase.PACKAGE) -public class DebMojo extends AbstractPluginMojo { - - @Component - private MavenProjectHelper projectHelper; - - @Component(hint = "jdeb-sec") - private SecDispatcher secDispatcher; +public class DebMojo extends AbstractPackageMojo { /** * Defines the name of deb package. @@ -111,12 +100,6 @@ public class DebMojo extends AbstractPluginMojo { @Parameter(defaultValue = "gzip") private String compression; - /** - * Boolean option whether to attach the artifact to the project - */ - @Parameter(defaultValue = "true") - private String attach; - /** * The location where all package files will be installed. By default, all * packages are installed in /opt (see the FHS here: @@ -361,31 +344,6 @@ private String getProjectVersion() { return Utils.convertToDebianVersion(getProject().getVersion(), this.snapshotExpand || this.timestamped, this.snapshotEnv, session.getStartTime()); } - /** - * @return whether the artifact is a POM or not - */ - private boolean isPOM() { - String type = getProject().getArtifact().getType(); - return "pom".equalsIgnoreCase(type); - } - - /** - * @return whether or not Maven is currently operating in the execution root - */ - private boolean isSubmodule() { - // FIXME there must be a better way - return !session.getExecutionRootDirectory().equalsIgnoreCase(baseDir.toString()); - } - - /** - * @return whether or not the main artifact was created - */ - private boolean hasMainArtifact() { - final MavenProject project = getProject(); - final Artifact artifact = project.getArtifact(); - return artifact.getFile() != null && artifact.getFile().isFile(); - } - /** * Main entry point * @@ -550,82 +508,4 @@ private void initializeSignProperties() { } } - /** - * Decrypts given passphrase if needed using maven security dispatcher. - * See http://maven.apache.org/guides/mini/guide-encryption.html for details. - * - * @param maybeEncryptedPassphrase possibly encrypted passphrase - * @return decrypted passphrase - */ - private String decrypt( final String maybeEncryptedPassphrase ) { - if (maybeEncryptedPassphrase == null) { - return null; - } - - try { - final String decrypted = secDispatcher.decrypt(maybeEncryptedPassphrase); - if (maybeEncryptedPassphrase.equals(decrypted)) { - console.info("Passphrase was not encrypted"); - } else { - console.info("Passphrase was successfully decrypted"); - } - return decrypted; - } catch (SecDispatcherException e) { - console.warn("Unable to decrypt passphrase: " + e.getMessage()); - } - - return maybeEncryptedPassphrase; - } - - - /** - * Read properties from the active profiles. - * - * Goes through all active profiles (in the order the - * profiles are defined in settings.xml) and extracts - * the desired properties (if present). The prefix is - * used when looking up properties in the profile but - * not in the returned map. - * - * @param prefix The prefix to use or null if no prefix should be used - * @param properties The properties to read - * - * @return A map containing the values for the properties that were found - */ - public Map readPropertiesFromActiveProfiles( final String prefix, - final String... properties ) { - if (settings == null) { - console.debug("No maven setting injected"); - return Collections.emptyMap(); - } - - final List activeProfilesList = settings.getActiveProfiles(); - if (activeProfilesList.isEmpty()) { - console.debug("No active profiles found"); - return Collections.emptyMap(); - } - - final Map map = new HashMap(); - final Set activeProfiles = new HashSet(activeProfilesList); - - // Iterate over all active profiles in order - for (final Profile profile : settings.getProfiles()) { - // Check if the profile is active - final String profileId = profile.getId(); - if (activeProfiles.contains(profileId)) { - console.debug("Trying active profile " + profileId); - for (final String property : properties) { - final String propKey = prefix != null ? prefix + property : property; - final String value = profile.getProperties().getProperty(propKey); - if (value != null) { - console.debug("Found property " + property + " in profile " + profileId); - map.put(property, value); - } - } - } - } - - return map; - } - } diff --git a/src/main/java/org/vafer/jdeb/maven/SpkMojo.java b/src/main/java/org/vafer/jdeb/maven/SpkMojo.java new file mode 100644 index 000000000..d0896173b --- /dev/null +++ b/src/main/java/org/vafer/jdeb/maven/SpkMojo.java @@ -0,0 +1,350 @@ +/* + * Copyright 2014 The jdeb developers. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.vafer.jdeb.maven; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.MavenProjectHelper; +import org.apache.maven.settings.Profile; +import org.apache.maven.settings.Settings; +import org.apache.tools.tar.TarEntry; +import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher; +import org.sonatype.plexus.components.sec.dispatcher.SecDispatcherException; +import org.vafer.jdeb.Console; +import org.vafer.jdeb.DataConsumer; +import org.vafer.jdeb.DataProducer; +import org.vafer.jdeb.DebMaker; +import org.vafer.jdeb.PackagingException; +import org.vafer.jdeb.SpkMaker; +import org.vafer.jdeb.utils.MapVariableResolver; +import org.vafer.jdeb.utils.Utils; +import org.vafer.jdeb.utils.VariableResolver; + +import static org.vafer.jdeb.utils.Utils.lookupIfEmpty; + +/** + * Creates Synology package + */ +@SuppressWarnings("unused") +@Mojo(name = "spk", defaultPhase = LifecyclePhase.PACKAGE) +public class SpkMojo extends AbstractPackageMojo { + + /** + * Defines the name of spk package. + */ + @Parameter + private String name; + + /** + * Defines the pattern of the name of final artifacts. Possible + * substitutions are [[baseDir]] [[buildDir]] [[artifactId]] [[version]] + * [[extension]] and [[groupId]]. + */ + @Parameter(defaultValue = "[[buildDir]]/[[artifactId]]_[[version]].[[extension]]") + private String spk; + + /** + * Explicitly defines the path to the info file. + */ + @Parameter(defaultValue = "[[baseDir]]/src/spk/info") + private String infoFile; + + /** + * Explicitly defines the path to the scripts directory. + */ + @Parameter(defaultValue = "[[baseDir]]/src/spk/scripts") + private String scriptsDir; + + /** + * The compression method used for the data file (gzip) + */ + @Parameter(defaultValue = "gzip") + private String compression; + + /** + * The location where all package files will be installed. By default, all + * packages are installed in /opt (see the FHS here: + * http://www.pathname.com/ + * fhs/pub/fhs-2.3.html#OPTADDONAPPLICATIONSOFTWAREPACKAGES) + */ + @Parameter(defaultValue = "/opt/[[artifactId]]") + private String installDir; + + /** + * The type of attached artifact + */ + @Parameter(defaultValue = "spk") + protected String type; + + /** + * "data" entries used to determine which files should be added to this deb. + * The "data" entries may specify a tarball (tar.gz, tar.bz2, tgz), a + * directory, or a normal file. An entry would look something like this in + * your pom.xml: + * + *
+     *   
+     *     
+     *       
+     *       jdeb
+     *       org.vafer
+     *       ...
+     *       
+     *         ...
+     *         
+     *           
+     *             ${project.basedir}/target/my_archive.tar.gz
+     *             ...
+     *             ...
+     *             
+     *               perm
+     *               1
+     *               /somewhere/else
+     *               santbj
+     *               santbj
+     *               600
+     *             
+     *           
+     *           
+     *             ${project.build.directory}/data
+     *             
+     *             **/.svn
+     *             
+     *               ls
+     *               mapping.txt
+     *             
+     *           
+     *           
+     *             link
+     *             /a/path/on/the/target/fs
+     *             /a/sym/link/to/the/scr/file
+     *             true
+     *           
+     *           
+     *             ${project.basedir}/README.txt
+     *           
+     *         
+     *       
+     *     
+     *   
+     * 
+ */ + @Parameter + private Data[] dataSet; + + /** + * When enabled SNAPSHOT inside the version gets replaced with current timestamp or + * if set a value of a environment variable. + */ + @Parameter(defaultValue = "false") + private boolean snapshotExpand; + + /** + * Which environment variable to check for the SNAPSHOT value. + * If the variable is not set/empty it will default to use the timestamp. + */ + @Parameter(defaultValue = "SNAPSHOT") + private String snapshotEnv; + + /* end of parameters */ + + private Collection dataProducers = new ArrayList(); + + protected void setData( Data[] dataSet ) { + this.dataSet = dataSet; + dataProducers.clear(); + if (dataSet != null) { + Collections.addAll(dataProducers, dataSet); + } + } + + protected VariableResolver initializeVariableResolver( Map variables ) { + @SuppressWarnings("unchecked") + final Map projectProperties = Map.class.cast(getProject().getProperties()); + @SuppressWarnings("unchecked") + final Map systemProperties = Map.class.cast(System.getProperties()); + + variables.putAll(projectProperties); + variables.putAll(systemProperties); + variables.put("name", name != null ? name : getProject().getName()); + variables.put("artifactId", getProject().getArtifactId()); + variables.put("groupId", getProject().getGroupId()); + variables.put("version", getProjectVersion()); + variables.put("description", getProject().getDescription()); + variables.put("extension", "spk"); + variables.put("baseDir", getProject().getBasedir().getAbsolutePath()); + variables.put("buildDir", buildDirectory.getAbsolutePath()); + variables.put("project.version", getProject().getVersion()); + variables.put("url", getProject().getUrl()); + + return new MapVariableResolver(variables); + } + + /** + * Doc some cleanup and conversion on the Maven project version. + *
    + *
  • any "-" is replaced by "+"
  • + *
  • "SNAPSHOT" is replaced with the current time and date, prepended by "~"
  • + *
+ * + * @return the Maven project version + */ + private String getProjectVersion() { + return Utils.convertToDebianVersion(getProject().getVersion(), this.snapshotExpand, this.snapshotEnv, session.getStartTime()); + } + + /** + * Main entry point + * + * @throws MojoExecutionException on error + */ + public void execute() throws MojoExecutionException { + + final MavenProject project = getProject(); + + if (skip) { + getLog().info("skipping execution as configured"); + return; + } + + if (isPOM()) { + getLog().info("skipping execution because artifact is a pom"); + return; + } + + if (isSubmodule() && !submodules) { + getLog().info("skipping sub module: jdeb executing at top-level only"); + return; + } + + + setData(dataSet); + + console = new MojoConsole(getLog(), verbose); + + final VariableResolver resolver = initializeVariableResolver(new HashMap()); + + final File spkFile = new File(Utils.replaceVariables(resolver, spk, openReplaceToken, closeReplaceToken)); + final File spkInfoFile = new File(Utils.replaceVariables(resolver, infoFile, openReplaceToken, closeReplaceToken)); + final File scriptsDirFile = new File(Utils.replaceVariables(resolver, scriptsDir, openReplaceToken, closeReplaceToken)); + final File installDirFile = new File(Utils.replaceVariables(resolver, installDir, openReplaceToken, closeReplaceToken)); + + // if there are no producers defined we try to use the artifacts + if (dataProducers.isEmpty()) { + + if (!hasMainArtifact()) { + + final String packaging = project.getPackaging(); + if ("pom".equalsIgnoreCase(packaging)) { + getLog().warn("Creating empty synology package."); + } else { + throw new MojoExecutionException( + "Nothing to include into the synology package. " + + "Did you maybe forget to add a tag or call the plugin directly?"); + } + + } else { + + Set artifacts = new HashSet(); + + artifacts.add(project.getArtifact()); + + @SuppressWarnings("unchecked") + final Set projectArtifacts = project.getArtifacts(); + + for (Artifact artifact : projectArtifacts) { + artifacts.add(artifact); + } + + @SuppressWarnings("unchecked") + final List attachedArtifacts = project.getAttachedArtifacts(); + + for (Artifact artifact : attachedArtifacts) { + artifacts.add(artifact); + } + + for (Artifact artifact : artifacts) { + final File file = artifact.getFile(); + if (file != null) { + dataProducers.add(new DataProducer() { + @Override + public void produce( final DataConsumer receiver ) { + try { + receiver.onEachFile( + new FileInputStream(file), + new File(installDirFile, file.getName()).getAbsolutePath(), + "", + "root", 0, "root", 0, + TarEntry.DEFAULT_FILE_MODE, + file.length()); + } catch (Exception e) { + getLog().error(e); + } + } + }); + } else { + getLog().error("No file for artifact " + artifact); + } + } + } + } + + try { + SpkMaker spkMaker = new SpkMaker(console, dataProducers); + spkMaker.setSpk(spkFile); + spkMaker.setInfo(spkInfoFile); + spkMaker.setScripts(scriptsDirFile); + spkMaker.setPackage(getProject().getArtifactId()); + spkMaker.setDescription(getProject().getDescription()); + spkMaker.setCompression(compression); + spkMaker.setResolver(resolver); + spkMaker.setOpenReplaceToken(openReplaceToken); + spkMaker.setCloseReplaceToken(closeReplaceToken); + spkMaker.validate(); + spkMaker.makeSpk(); + + // Always attach unless explicitly set to false + if ("true".equalsIgnoreCase(attach)) { + console.info("Attaching created synology package " + spkFile); + projectHelper.attachArtifact(project, type, classifier, spkFile); + } + + } catch (PackagingException e) { + getLog().error("Failed to create synology package " + spkFile, e); + throw new MojoExecutionException("Failed to create synology package " + spkFile, e); + } + } + +} diff --git a/src/main/java/org/vafer/jdeb/producers/DataProducerDirectory.java b/src/main/java/org/vafer/jdeb/producers/DataProducerDirectory.java index 8bece1282..8216691ce 100644 --- a/src/main/java/org/vafer/jdeb/producers/DataProducerDirectory.java +++ b/src/main/java/org/vafer/jdeb/producers/DataProducerDirectory.java @@ -16,11 +16,8 @@ package org.vafer.jdeb.producers; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; -import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.tools.ant.DirectoryScanner; import org.vafer.jdeb.DataConsumer; import org.vafer.jdeb.DataProducer; diff --git a/src/main/java/org/vafer/jdeb/producers/DataProducerFile.java b/src/main/java/org/vafer/jdeb/producers/DataProducerFile.java index 7bea326d3..985deeba6 100644 --- a/src/main/java/org/vafer/jdeb/producers/DataProducerFile.java +++ b/src/main/java/org/vafer/jdeb/producers/DataProducerFile.java @@ -18,7 +18,6 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.vafer.jdeb.DataConsumer; diff --git a/src/main/java/org/vafer/jdeb/producers/DataProducerPathTemplate.java b/src/main/java/org/vafer/jdeb/producers/DataProducerPathTemplate.java index b38343bd0..4f06a377d 100644 --- a/src/main/java/org/vafer/jdeb/producers/DataProducerPathTemplate.java +++ b/src/main/java/org/vafer/jdeb/producers/DataProducerPathTemplate.java @@ -17,7 +17,6 @@ import java.io.IOException; -import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.vafer.jdeb.DataConsumer; import org.vafer.jdeb.DataProducer; import org.vafer.jdeb.mapping.Mapper; diff --git a/src/main/java/org/vafer/jdeb/synology/BinaryPackageInfoFile.java b/src/main/java/org/vafer/jdeb/synology/BinaryPackageInfoFile.java new file mode 100644 index 000000000..a6f4b8f10 --- /dev/null +++ b/src/main/java/org/vafer/jdeb/synology/BinaryPackageInfoFile.java @@ -0,0 +1,94 @@ +/* + * Copyright 2014 The jdeb developers. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.vafer.jdeb.synology; + +import java.io.IOException; +import java.io.InputStream; +import java.text.ParseException; + +import org.vafer.jdeb.utils.ControlField; + +/** + * Binary package info file. + * + * @author Torsten Curdt + */ +public final class BinaryPackageInfoFile extends InfoFile { + + private static final ControlField[] FIELDS = { + new ControlField("package", true), + new ControlField("version", true), + new ControlField("maintainer", true), + new ControlField("description", true, ControlField.Type.MULTILINE), + new ControlField("arch", true), + new ControlField("adminport"), + new ControlField("adminurl"), + new ControlField("firmware"), + new ControlField("reloadui"), + new ControlField("package_icon"), + new ControlField("extractsize"), + new ControlField("checksum") + }; + + public BinaryPackageInfoFile() { + set("arch", "noarch"); + } + + public BinaryPackageInfoFile(final String input) throws IOException, ParseException { + parse(input); + } + + public BinaryPackageInfoFile(final InputStream input) throws IOException, ParseException { + parse(input); + } + + @Override + public void set(final String field, final String value) { + super.set(field, value); + } + + @Override + protected ControlField[] getFields() { + return FIELDS; + } + + /** + * Returns the short description of the package. The short description + * consists in the first line of the Description field. + * + * @return + */ + public String getShortDescription() { + if (get("description") == null) { + return null; + } + + return get("description").split("\n")[0]; + } + + protected String getDelimiter() { + return "="; + } + + protected String getValuePrefix() { + return ""; + } + + protected String getValueWrapper() { + return "\""; + } +} diff --git a/src/main/java/org/vafer/jdeb/synology/InfoFile.java b/src/main/java/org/vafer/jdeb/synology/InfoFile.java new file mode 100644 index 000000000..c85ff4d68 --- /dev/null +++ b/src/main/java/org/vafer/jdeb/synology/InfoFile.java @@ -0,0 +1,234 @@ +/* + * Copyright 2014 The jdeb developers. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.vafer.jdeb.synology; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.vafer.jdeb.utils.ControlField; + +/** + * A info file for synology packages. + * + * @author Torsten Curdt + */ +public abstract class InfoFile { + + protected final Map values = new LinkedHashMap(); + + public void parse(final String input) throws IOException, ParseException { + parse(new ByteArrayInputStream(input.getBytes("UTF-8"))); + } + + public void parse(final InputStream input) throws IOException, ParseException { + BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8")); + StringBuilder buffer = new StringBuilder(); + String field = null; + int linenr = 0; + while (true) { + final String line = reader.readLine(); + + if (line == null) { + // flush value of the previous field + set(field, buffer.toString()); + break; + } + + linenr++; + + if (line.length() == 0) { + throw new ParseException("Empty line", linenr); + } + + final char first = line.charAt(0); + if (Character.isLetter(first)) { + + // new field + + // flush value of the previous field + set(field, buffer.toString()); + buffer = new StringBuilder(); + + + final int i = line.indexOf(getDelimiter()); + + if (i < 0) { + throw new ParseException("Line misses '" + getDelimiter() + "' delimiter", linenr); + } + + field = line.substring(0, i).trim(); + + buffer.append(stripPrefixAndWrapper(line.substring(i + 1)).trim()); + + continue; + } + + // continuing old value, lines with only a dot are ignored + buffer.append('\n'); + if (!".".equals(line.substring(1).trim())) { + buffer.append(stripPrefixAndWrapper(line.substring(1)).trim()); + } + } + reader.close(); + + } + + private String stripPrefixAndWrapper(final String value) { + String tempValue = value; + if (tempValue.startsWith(getValuePrefix())) { + tempValue = tempValue.substring(getValuePrefix().length()); + } + if (tempValue.startsWith(getValueWrapper())) { + tempValue = tempValue.substring(getValueWrapper().length()); + } + if (tempValue.endsWith(getValueWrapper())) { + tempValue = tempValue.substring(0, tempValue.length() - getValueWrapper().length()); + } + return tempValue; + } + + public void set(final String field, final String value) { + if (field != null && !field.isEmpty()) { + values.put(field, value); + } + } + + public String get(final String field) { + return values.get(field); + } + + protected abstract ControlField[] getFields(); + + public List getMandatoryFields() { + List fields = new ArrayList(); + + for (ControlField field : getFields()) { + if (field.isMandatory()) { + fields.add(field.getName()); + } + } + + return fields; + } + + public boolean isValid() { + return invalidFields().size() == 0; + } + + public Set invalidFields() { + Set invalid = new HashSet(); + + for (ControlField field : getFields()) { + if (field.isMandatory() && get(field.getName()) == null) { + invalid.add(field.getName()); + } + } + + return invalid; + } + + /** + * Returns the field with the specified value properly formatted. Multiline + * values are automatically indented, and dots are added on the empty lines. + * + *
+     * Field-Name: value
+     * 
+ */ + private String formatField(final String value, final ControlField field) { + StringBuilder s = new StringBuilder(); + + if (value != null && value.trim().length() > 0) { + boolean continuationLine = false; + + s.append(field.getName()).append(getDelimiter()); + if (field.isFirstLineEmpty()) { + s.append("\n"); + continuationLine = true; + } + + try { + BufferedReader reader = new BufferedReader(new StringReader(getValueWrapper() + value + getValueWrapper())); + String line; + while ((line = reader.readLine()) != null) { + if (continuationLine && line.trim().length() == 0) { + // put a dot on the empty continuation lines + s.append(" .\n"); + } else if (continuationLine) { + s.append(" ").append(line).append("\n"); + } else { + s.append(line).append("\n"); + } + + continuationLine = true; + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + return s.toString(); + } + + public String toString(final ControlField... fields) { + final StringBuilder s = new StringBuilder(); + for (ControlField field : fields) { + String value = values.get(field.getName()); + s.append(formatField(value, field)); + } + return s.toString(); + } + + public String toString() { + final List fields = new ArrayList(); + for (String field : values.keySet()) { + ControlField controlField = getField(field); + if (controlField != null) { + fields.add(controlField); + } else { + fields.add(new ControlField(field)); + } + } + return toString(fields.toArray(new ControlField[fields.size()])); + } + + private ControlField getField(final String name) { + for (ControlField field : getFields()) { + if (field != null && field.getName() != null && field.getName().equals(name)) { + return field; + } + } + return null; + } + + protected abstract String getDelimiter(); + + protected abstract String getValuePrefix(); + + protected abstract String getValueWrapper(); +} diff --git a/src/main/java/org/vafer/jdeb/debian/ControlField.java b/src/main/java/org/vafer/jdeb/utils/ControlField.java similarity index 99% rename from src/main/java/org/vafer/jdeb/debian/ControlField.java rename to src/main/java/org/vafer/jdeb/utils/ControlField.java index c08e73b2f..5c905dd67 100644 --- a/src/main/java/org/vafer/jdeb/debian/ControlField.java +++ b/src/main/java/org/vafer/jdeb/utils/ControlField.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.vafer.jdeb.debian; +package org.vafer.jdeb; import java.io.BufferedReader; import java.io.IOException; diff --git a/src/test/java/org/vafer/jdeb/SpkMakerTestCase.java b/src/test/java/org/vafer/jdeb/SpkMakerTestCase.java new file mode 100644 index 000000000..fca4ac211 --- /dev/null +++ b/src/test/java/org/vafer/jdeb/SpkMakerTestCase.java @@ -0,0 +1,167 @@ +/* + * Copyright 2014 The jdeb developers. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.vafer.jdeb; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import junit.framework.TestCase; + +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.output.NullOutputStream; +import org.vafer.jdeb.mapping.PermMapper; +import org.vafer.jdeb.producers.DataProducerArchive; +import org.vafer.jdeb.producers.DataProducerDirectory; +import org.vafer.jdeb.producers.DataProducerLink; +import org.vafer.jdeb.synology.BinaryPackageInfoFile; +import org.vafer.jdeb.utils.InformationInputStream; +import org.vafer.jdeb.utils.MapVariableResolver; + +public class SpkMakerTestCase extends TestCase { + + public void testCreation() throws Exception { + File spk = new File("target/test-classes/test-creation.spk"); + if (spk.exists() && !spk.delete()) { + fail("Couldn't delete " + spk); + } + + File info = new File(getClass().getResource("spk/info").toURI()); + File archive1 = new File(getClass().getResource("spk/data.tgz").toURI()); + File archive2 = new File(getClass().getResource("spk/data.tar.bz2").toURI()); + File archive3 = new File(getClass().getResource("spk/data.zip").toURI()); + File directory = new File(getClass().getResource("spk/data").toURI()); + + DataProducer[] data = new DataProducer[] { + new DataProducerArchive(archive1, null, null, null), + new DataProducerArchive(archive2, null, null, null), + new DataProducerArchive(archive3, null, null, null), + new DataProducerDirectory(directory, null, new String[] { "**/.svn/**" }, null), + new DataProducerLink("/link/path-element.ext", "/link/target-element.ext", true, null, null, null) + }; + + SpkMaker maker = new SpkMaker(new NullConsole(), Arrays.asList(data)); + maker.setInfo(info); + maker.setScripts(new File(getClass().getResource("spk/scripts").toURI())); + maker.setSpk(spk); + + BinaryPackageInfoFile packageInfoFile = maker.createSpk(Compression.GZIP); + + assertTrue(packageInfoFile.isValid()); + + final Map filesInSpk = new HashMap(); + + ArchiveWalker.walkPackage(spk, new ArchiveVisitor() { + public void visit(TarArchiveEntry entry, byte[] content) throws IOException { + filesInSpk.put(entry.getName(), entry); + } + }); + + assertTrue("testfile wasn't found in the package", filesInSpk.containsKey("test/testfile")); + assertTrue("testfile2 wasn't found in the package", filesInSpk.containsKey("test/testfile2")); + assertTrue("testfile3 wasn't found in the package", filesInSpk.containsKey("test/testfile3")); + assertTrue("testfile4 wasn't found in the package", filesInSpk.containsKey("test/testfile4")); + assertTrue("/link/path-element.ext wasn't found in the package", filesInSpk.containsKey("link/path-element.ext")); + //assertEquals("/link/path-element.ext has wrong link target", "/link/target-element.ext", filesInSpk.get("link/path-element.ext").getLinkName()); + + assertTrue("Cannot delete the file " + spk, spk.delete()); + } + + public void testScriptFilesPermissions() throws Exception { + File spk = new File("target/test-classes/test-permissions.spk"); + if (spk.exists() && !spk.delete()) { + fail("Couldn't delete " + spk); + } + + Collection producers = Arrays.asList(new DataProducer[] {new EmptyDataProducer()}); + SpkMaker maker = new SpkMaker(new NullConsole(), producers); + maker.setSpk(spk); + maker.setInfo(new File("target/test-classes/org/vafer/jdeb/spk/info")); + maker.setScripts(new File("target/test-classes/org/vafer/jdeb/spk/scripts")); + + maker.createSpk(Compression.GZIP); + + // now reopen the package and check the control files + assertTrue("package not build", spk.exists()); + + boolean found = ArchiveWalker.walkFileTar(spk, new ArchiveVisitor() { + public void visit(final TarArchiveEntry entry, final byte[] content) throws IOException { + assertTrue("prefix", !entry.getName().startsWith("./")); + + InformationInputStream infoStream = new InformationInputStream(new ByteArrayInputStream(content)); + IOUtils.copy(infoStream, NullOutputStream.NULL_OUTPUT_STREAM); + + if (entry.getName().startsWith("scripts/")) { + if (infoStream.isShell()) { + assertTrue("Permissions on " + entry.getName() + " should be 755", entry.getMode() == PermMapper.toMode("755")); + } else { + assertTrue("Permissions on " + entry.getName() + " should be 644", entry.getMode() == PermMapper.toMode("644")); + } + + assertTrue(entry.getName() + " doesn't have Unix line endings", infoStream.hasUnixLineEndings()); + + assertEquals("user", 0, entry.getUserId()); + assertEquals("group", 0, entry.getGroupId()); + } else { + assertFalse("directory found in the archive", entry.isDirectory()); + } + } + }); + + assertTrue("Script files not found in the package", found); + } + + public void testScriptFilesVariables() throws Exception { + File spk = new File("target/test-classes/test-scripts.spk"); + if (spk.exists() && !spk.delete()) { + fail("Couldn't delete " + spk); + } + + Map variables = new HashMap(); + variables.put("name", "jdeb"); + variables.put("version", "1.0"); + + Collection producers = Arrays.asList(new DataProducer[] {new EmptyDataProducer()}); + SpkMaker maker = new SpkMaker(new NullConsole(), producers); + maker.setSpk(spk); + maker.setInfo(new File("target/test-classes/org/vafer/jdeb/spk/info")); + maker.setScripts(new File("target/test-classes/org/vafer/jdeb/spk/scripts")); + maker.setResolver(new MapVariableResolver(variables)); + + maker.createSpk(Compression.GZIP); + + // now reopen the package and check the control files + assertTrue("package not build", spk.exists()); + + boolean found = ArchiveWalker.walkFileTar(spk, new ArchiveVisitor() { + public void visit(TarArchiveEntry entry, byte[] content) throws IOException { + if (entry.getName().contains("postinst") || entry.getName().contains("prerm")) { + String body = new String(content, "ISO-8859-1"); + assertFalse("Variables not replaced in the script file " + entry.getName(), body.contains("[[name]] [[version]]")); + assertTrue("Expected variables not found in the script file " + entry.getName(), body.contains("jdeb 1.0")); + } + } + }); + + assertTrue("Script files not found in the package", found); + } +} diff --git a/src/test/java/org/vafer/jdeb/ant/SpkAntTaskTestCase.java b/src/test/java/org/vafer/jdeb/ant/SpkAntTaskTestCase.java new file mode 100644 index 000000000..9c8c2ac8c --- /dev/null +++ b/src/test/java/org/vafer/jdeb/ant/SpkAntTaskTestCase.java @@ -0,0 +1,244 @@ +/* + * Copyright 2014 The jdeb developers. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.vafer.jdeb.ant; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.concurrent.atomic.AtomicBoolean; + +import junit.framework.TestCase; + +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DefaultLogger; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectHelper; +import org.vafer.jdeb.ArchiveVisitor; +import org.vafer.jdeb.ArchiveWalker; + +/** + * @author Emmanuel Bourg + */ +public final class SpkAntTaskTestCase extends TestCase { + + private Project project; + + protected void setUp() throws Exception { + project = new Project(); + project.setCoreLoader(getClass().getClassLoader()); + project.init(); + + File buildFile = new File("target/test-classes/spktestbuild.xml"); + project.setBaseDir(buildFile.getParentFile()); + + final ProjectHelper helper = ProjectHelper.getProjectHelper(); + helper.parse(project, buildFile); + + // remove the package previously build + File deb = new File("target/test.spk"); + if (deb.exists()) { + assertTrue("Unable to remove the test archive", deb.delete()); + } + } + + public void testMissingInfo() { + try { + project.executeTarget("missing-info"); + fail("No exception thrown"); + } catch (BuildException e) { + // expected + } + } + + public void testInvalidInfo() { + try { + project.executeTarget("invalid-info"); + fail("No exception thrown"); + } catch (BuildException e) { + // expected + } + } + + public void testMissingDestFile() { + try { + project.executeTarget("missing-destfile"); + fail("No exception thrown"); + } catch (BuildException e) { + // expected + } + } + + public void testEmptyPackage() { + try { + project.executeTarget("empty-package"); + fail("No exception thrown"); + } catch (BuildException e) { + // expected + } + } + + public void testPackageWithArchive() { + project.executeTarget("with-archive"); + + assertTrue("package not build", new File("target/test-classes/test.spk").exists()); + } + + public void testPackageWithMissingArchive() { + try { + project.executeTarget("with-missing-archive"); + fail("No exception thrown"); + } catch (BuildException e) { + // expected + } + } + + public void testPackageWithDirectory() { + project.executeTarget("with-directory"); + + assertTrue("package not build", new File("target/test-classes/test.spk").exists()); + } + + public void testPackageWithMissingDirectory() { + try { + project.executeTarget("with-missing-directory"); + fail("No exception thrown"); + } catch (BuildException e) { + // expected + } + } + + /** + * Redirects the Ant output to the specified stream. + */ + private void redirectOutput( OutputStream out ) { + DefaultLogger logger = new DefaultLogger(); + logger.setOutputPrintStream(new PrintStream(out)); + logger.setMessageOutputLevel(Project.MSG_INFO); + project.addBuildListener(logger); + } + + public void testVerboseEnabled() { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + redirectOutput(out); + + project.executeTarget("verbose-enabled"); + + assertTrue(out.toString().contains("Total size")); + } + + public void testVerboseDisabled() { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + redirectOutput(out); + + project.executeTarget("verbose-disabled"); + + assertTrue(!out.toString().contains("Total size")); + } + + public void testMissingDataType() { + try { + project.executeTarget("missing-data-type"); + fail("No exception thrown"); + } catch (BuildException e) { + // expected + } + } + + public void testUnknownDataType() { + try { + project.executeTarget("unknown-data-type"); + fail("No exception thrown"); + } catch (BuildException e) { + // expected + } + } + + public void testFileSet() { + project.executeTarget("fileset"); + + assertTrue("package not build", new File("target/test-classes/test.spk").exists()); + } + + public void testTarFileSet() throws Exception { + project.executeTarget("tarfileset"); + + File spk = new File("target/test-classes/test.spk"); + assertTrue("package not build", spk.exists()); + + ArchiveWalker.walkPackage(spk, new ArchiveVisitor() { + public void visit(TarArchiveEntry entry, byte[] content) throws IOException { + assertTrue("prefix: " + entry.getName(), entry.getName().startsWith("foo/")); + if (entry.isDirectory()) { + assertEquals("directory mode (" + entry.getName() + ")", 040700, entry.getMode()); + } else { + assertEquals("file mode (" + entry.getName() + ")", 0100600, entry.getMode()); + } + assertEquals("user", "ebourg", entry.getUserName()); + assertEquals("group", "ebourg", entry.getGroupName()); + } + }); + } + + public void testLink() throws Exception { + project.executeTarget("link"); + + File spk = new File("target/test-classes/test.spk"); + assertTrue("package not build", spk.exists()); + + final AtomicBoolean linkFound = new AtomicBoolean(false); + + ArchiveWalker.walkPackage(spk, new ArchiveVisitor() { + public void visit(TarArchiveEntry entry, byte[] content) throws IOException { + if (entry.isSymbolicLink()) { + linkFound.set(true); + assertEquals("link mode (" + entry.getName() + ")", 0120755, entry.getMode()); + } + assertEquals("user", "ebourg", entry.getUserName()); + assertEquals("group", "ebourg", entry.getGroupName()); + } + }); + + assertTrue("Link not found", linkFound.get()); + } + + public void testMapper() throws Exception { + project.executeTarget("perm-mapper"); + + File spk = new File("target/test-classes/test.spk"); + assertTrue("package not build", spk.exists()); + + ArchiveWalker.walkPackage(spk, new ArchiveVisitor() { + public void visit(TarArchiveEntry entry, byte[] content) throws IOException { + if (entry.isFile()) { + assertEquals("file mode (" + entry.getName() + ")", 0700, entry.getMode()); + } + } + }); + } + + public void testUnkownCompression() throws Exception { + try { + project.executeTarget("unknown-compression"); + fail("No exception thrown"); + } catch (BuildException e) { + // expected + } + } +} diff --git a/src/test/java/org/vafer/jdeb/synology/PackageInfoFileTestCase.java b/src/test/java/org/vafer/jdeb/synology/PackageInfoFileTestCase.java new file mode 100644 index 000000000..0b0a4a488 --- /dev/null +++ b/src/test/java/org/vafer/jdeb/synology/PackageInfoFileTestCase.java @@ -0,0 +1,100 @@ +/* + * Copyright 2014 The jdeb developers. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.vafer.jdeb.synology; + +import java.io.FileInputStream; +import java.text.ParseException; + +import org.vafer.jdeb.debian.BinaryPackageControlFile; +import org.vafer.jdeb.synology.BinaryPackageInfoFile; + +import junit.framework.TestCase; + +public final class PackageInfoFileTestCase extends TestCase { + + public void testParse() throws Exception { + String input = + "Key1=\"Value1\"\n" + + "Key2=\"Value2\"\n" + + " Value2.1\"\n" + + " Value2.2\"\n" + + "Key3=\"Value3\"\n"; + + BinaryPackageInfoFile d = new BinaryPackageInfoFile(input); + assertFalse(d.isValid()); + + assertEquals("key 1", "Value1", d.get("Key1")); + assertEquals("key 2", "Value2\nValue2.1\nValue2.2", d.get("Key2")); + assertEquals("key 3", "Value3", d.get("Key3")); + } + + public void testToString() throws Exception { + BinaryPackageInfoFile packageInfoFile = new BinaryPackageInfoFile(); + packageInfoFile.set("package", "test-package"); + packageInfoFile.set("description", "This is\na description\non several lines"); + packageInfoFile.set("version", "1.0"); + + String s = packageInfoFile.toString(); + + BinaryPackageInfoFile packageInfoFile2 = new BinaryPackageInfoFile(s); + assertEquals("package", packageInfoFile.get("package"), packageInfoFile2.get("package")); + assertEquals("description", packageInfoFile.get("description"), packageInfoFile2.get("description")); + assertEquals("version", packageInfoFile.get("version"), packageInfoFile2.get("version")); + } + + public void testEmptyLines() throws Exception { + String input = + "Key1: Value1\n" + + "Key2: Value2\n" + + "\n"; + try { + new BinaryPackageControlFile(input); + fail("Should throw a ParseException"); + } catch (ParseException e) { + } + } + + public void testGetShortDescription() { + BinaryPackageInfoFile packageInfoFile = new BinaryPackageInfoFile(); + + assertNull(packageInfoFile.getShortDescription()); + + packageInfoFile.set("description", "This is the short description\nThis is the loooooong description"); + + assertEquals("short description", "This is the short description", packageInfoFile.getShortDescription()); + + packageInfoFile.set("description", "\nThere is no short description"); + + assertEquals("short description", "", packageInfoFile.getShortDescription()); + } + + public void testGetDescription() throws Exception { + BinaryPackageInfoFile packageInfoFile = new BinaryPackageInfoFile(); + packageInfoFile.parse(new FileInputStream("target/test-classes/org/vafer/jdeb/spk/info")); + + assertEquals("description", "revision @REVISION@, test package\n" + + "This is a sample package control file.\n\n" + + "Use for testing purposes only.", + packageInfoFile.get("description")); + } + + public void testGetUserDefinedFields() throws Exception { + BinaryPackageInfoFile packageInfoFile = new BinaryPackageInfoFile(); + packageInfoFile.parse(new FileInputStream("target/test-classes/org/vafer/jdeb/spk/info")); + assertEquals("userdefinedfield", "This is a user defined field.", packageInfoFile.get("userdefinedfield")); + } +} diff --git a/src/test/resources/testbuild.xml b/src/test/resources/debtestbuild.xml similarity index 98% rename from src/test/resources/testbuild.xml rename to src/test/resources/debtestbuild.xml index 225281493..b0f2ad562 100644 --- a/src/test/resources/testbuild.xml +++ b/src/test/resources/debtestbuild.xml @@ -7,7 +7,7 @@ - + diff --git a/src/test/resources/org/vafer/jdeb/spk/data.tar.bz2 b/src/test/resources/org/vafer/jdeb/spk/data.tar.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..f2e1e533ec052e7c374d2ad10071928027112bd0 GIT binary patch literal 164 zcmV;V09*e;T4*^jL0KkKS%1yuxBvjKdxXM}0RTV%|9}t(bETdD00ICAAOMtvqe`37 zLr17$GHoCVq*3ZUO*GR$&@ydImcND^a#A2&SVTh_RgG~?%5-lzq=+V>Xe=Y)9Aq0T z_`8xR!i0nVZ#BSFA4SCg literal 0 HcmV?d00001 diff --git a/src/test/resources/org/vafer/jdeb/spk/data.tgz b/src/test/resources/org/vafer/jdeb/spk/data.tgz new file mode 100644 index 0000000000000000000000000000000000000000..d208640884c18632462ab2c20d385d776bf214a5 GIT binary patch literal 148 zcmb2|=3wZ}^l)cjetXH0>yUwf!$nK4yazr$mp*rFQ`nKU{QLGmbJ4EVksSYZjk;Nc zetazaIQht>vjMNGxBdMia9J@ucj?N`SyPiHWqsHkckQTtvDc&PrTcz<+!_D7{#5n< xeLmlh&q#jvqA^ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 98812f4bc5889506e2a15f2c91291fc20573e51d Mon Sep 17 00:00:00 2001 From: crea-doo Date: Tue, 20 May 2014 21:50:02 +0200 Subject: [PATCH 2/2] Added first version of spk support --- .../org/vafer/jdeb/utils/ControlField.java | 2 +- .../java/org/vafer/jdeb/ArchiveWalker.java | 95 +++++++++++++++++-- .../org/vafer/jdeb/DataBuilderTestCase.java | 4 +- .../vafer/jdeb/ant/DebAntTaskTestCase.java | 2 +- .../ControlFieldTestCase.java | 2 +- 5 files changed, 93 insertions(+), 12 deletions(-) rename src/test/java/org/vafer/jdeb/{debian => utils}/ControlFieldTestCase.java (97%) diff --git a/src/main/java/org/vafer/jdeb/utils/ControlField.java b/src/main/java/org/vafer/jdeb/utils/ControlField.java index 5c905dd67..efe9c119b 100644 --- a/src/main/java/org/vafer/jdeb/utils/ControlField.java +++ b/src/main/java/org/vafer/jdeb/utils/ControlField.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.vafer.jdeb; +package org.vafer.jdeb.utils; import java.io.BufferedReader; import java.io.IOException; diff --git a/src/test/java/org/vafer/jdeb/ArchiveWalker.java b/src/test/java/org/vafer/jdeb/ArchiveWalker.java index f3bdfae72..1e0c0fb1a 100644 --- a/src/test/java/org/vafer/jdeb/ArchiveWalker.java +++ b/src/test/java/org/vafer/jdeb/ArchiveWalker.java @@ -59,20 +59,100 @@ public static void walk(ArchiveInputStream in, ArchiveVisitor visitor) throws IO } } - public static boolean walkControl(File deb, final ArchiveVisitor visitor) throws IOException { - return walkEmbedded(deb, "control.tar", visitor, Compression.GZIP); + public static boolean walkFileAr(File archive, final ArchiveVisitor visitor) throws IOException { + final AtomicBoolean found = new AtomicBoolean(false); + ArArchiveInputStream in = new ArArchiveInputStream(new FileInputStream(archive)); + ArchiveWalker.walk(in, new ArchiveVisitor() { + public void visit(ArArchiveEntry entry, byte[] content) throws IOException { + found.set(true); + visitor.visit(entry, content); + } + }); + return found.get(); + } + + public static boolean walkFileTar(File archive, final ArchiveVisitor visitor) throws IOException { + final AtomicBoolean found = new AtomicBoolean(false); + TarArchiveInputStream in = new TarArchiveInputStream(new FileInputStream(archive)); + ArchiveWalker.walk(in, new ArchiveVisitor() { + public void visit(TarArchiveEntry entry, byte[] content) throws IOException { + found.set(true); + visitor.visit(entry, content); + } + }); + return found.get(); + } + + public static boolean walkControl(final File archive, final ArchiveVisitor visitor) throws IOException { + return walkEmbeddedInAr(archive, "control.tar", visitor, Compression.GZIP); } - public static boolean walkData(File deb, final ArchiveVisitor visitor, final Compression compression) throws IOException { - return walkEmbedded(deb, "data.tar", visitor, compression); + public static boolean walkPackage(final File archive, final ArchiveVisitor visitor) throws IOException { + return walkEmbeddedInTar(archive, "package", visitor, Compression.GZIP, ".tgz"); } - public static boolean walkEmbedded(File deb, final String name, final ArchiveVisitor visitor, final Compression compression) throws IOException { + public static boolean walkData(final File archive, final ArchiveVisitor visitor, final Compression compression) throws IOException { + return walkEmbeddedInAr(archive, "data.tar", visitor, compression); + } + + public static boolean walkEmbeddedInAr(final File archive, final String name, final ArchiveVisitor visitor, final Compression compression) throws IOException { + return walkEmbeddedInAr(archive, name, visitor, compression, null); + } + + public static boolean walkEmbeddedInAr(final File archive, final String name, final ArchiveVisitor visitor, final Compression compression, final String extension) throws IOException { final AtomicBoolean found = new AtomicBoolean(false); - ArArchiveInputStream in = new ArArchiveInputStream(new FileInputStream(deb)); + ArArchiveInputStream in = new ArArchiveInputStream(new FileInputStream(archive)); + + final String packageName; + if (extension != null) { + packageName = name + extension; + } else { + packageName = name + compression.getExtension(); + } + ArchiveWalker.walk(in, new ArchiveVisitor() { public void visit(ArArchiveEntry entry, byte[] content) throws IOException { - if (entry.getName().equals(name + compression.getExtension())) { + if (entry.getName().equals(packageName)) { + InputStream in = new ByteArrayInputStream(content); + if (compression == Compression.GZIP) { + in = new GZIPInputStream(in); + } else if (compression == Compression.XZ) { + in = new XZCompressorInputStream(in); + } else if (compression == Compression.BZIP2) { + in = new BZip2CompressorInputStream(in); + } + + ArchiveWalker.walk(new TarArchiveInputStream(in), new ArchiveVisitor() { + public void visit(TarArchiveEntry entry, byte[] content) throws IOException { + found.set(true); + visitor.visit(entry, content); + } + }); + } + } + }); + + return found.get(); + } + + public static boolean walkEmbeddedInTar(final File archive, final String name, final ArchiveVisitor visitor, final Compression compression) throws IOException { + return walkEmbeddedInTar(archive, name, visitor, compression, null); + } + + public static boolean walkEmbeddedInTar(File archive, final String name, final ArchiveVisitor visitor, final Compression compression, final String extension) throws IOException { + final AtomicBoolean found = new AtomicBoolean(false); + TarArchiveInputStream in = new TarArchiveInputStream(new FileInputStream(archive)); + + final String packageName; + if (extension != null) { + packageName = name + extension; + } else { + packageName = name + compression.getExtension(); + } + + ArchiveWalker.walk(in, new ArchiveVisitor() { + public void visit(TarArchiveEntry entry, byte[] content) throws IOException { + if (entry.getName().equals(packageName)) { InputStream in = new ByteArrayInputStream(content); if (compression == Compression.GZIP) { in = new GZIPInputStream(in); @@ -91,6 +171,7 @@ public void visit(TarArchiveEntry entry, byte[] content) throws IOException { } } }); + return found.get(); } } diff --git a/src/test/java/org/vafer/jdeb/DataBuilderTestCase.java b/src/test/java/org/vafer/jdeb/DataBuilderTestCase.java index e4169a76e..a24fc06e9 100644 --- a/src/test/java/org/vafer/jdeb/DataBuilderTestCase.java +++ b/src/test/java/org/vafer/jdeb/DataBuilderTestCase.java @@ -34,7 +34,7 @@ public class DataBuilderTestCase extends TestCase { * (this test can only fail on Windows) */ public void testBuildDataWithFileSet() throws Exception { - DataBuilder builder = new DataBuilder(new NullConsole()); + DebDataBuilder builder = new DebDataBuilder(new NullConsole()); Project project = new Project(); project.setCoreLoader(getClass().getClassLoader()); @@ -58,7 +58,7 @@ public void testCreateParentDirectories() throws Exception { archive.delete(); } - DataBuilder builder = new DataBuilder(new NullConsole()); + DebDataBuilder builder = new DebDataBuilder(new NullConsole()); DataProducer producer = new DataProducerFile(new File("pom.xml"), "/usr/share/myapp/pom.xml", null, null, null); diff --git a/src/test/java/org/vafer/jdeb/ant/DebAntTaskTestCase.java b/src/test/java/org/vafer/jdeb/ant/DebAntTaskTestCase.java index e14dcd075..6ade5de43 100644 --- a/src/test/java/org/vafer/jdeb/ant/DebAntTaskTestCase.java +++ b/src/test/java/org/vafer/jdeb/ant/DebAntTaskTestCase.java @@ -52,7 +52,7 @@ protected void setUp() throws Exception { project.setCoreLoader(getClass().getClassLoader()); project.init(); - File buildFile = new File("target/test-classes/testbuild.xml"); + File buildFile = new File("target/test-classes/debtestbuild.xml"); project.setBaseDir(buildFile.getParentFile()); final ProjectHelper helper = ProjectHelper.getProjectHelper(); diff --git a/src/test/java/org/vafer/jdeb/debian/ControlFieldTestCase.java b/src/test/java/org/vafer/jdeb/utils/ControlFieldTestCase.java similarity index 97% rename from src/test/java/org/vafer/jdeb/debian/ControlFieldTestCase.java rename to src/test/java/org/vafer/jdeb/utils/ControlFieldTestCase.java index 45291b594..ad38cc6b9 100644 --- a/src/test/java/org/vafer/jdeb/debian/ControlFieldTestCase.java +++ b/src/test/java/org/vafer/jdeb/utils/ControlFieldTestCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.vafer.jdeb.debian; +package org.vafer.jdeb.utils; import junit.framework.TestCase;