Java tutorial
/* * Copyright 2015 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 static org.vafer.jdeb.utils.Utils.lookupIfEmpty; import static org.vafer.jdeb.utils.Utils.isBlank; 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.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarConstants; import org.apache.maven.artifact.Artifact; import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.AbstractMojo; 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.utils.MapVariableResolver; import org.vafer.jdeb.utils.SymlinkUtils; import org.vafer.jdeb.utils.Utils; import org.vafer.jdeb.utils.VariableResolver; /** * Creates Debian package */ @Mojo(name = "jdeb", defaultPhase = LifecyclePhase.PACKAGE) public class DebMojo extends AbstractMojo { @Component private MavenProjectHelper projectHelper; @Component(hint = "jdeb-sec") private SecDispatcher secDispatcher; /** * Defines the name of deb 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]]_all.[[extension]]") private String deb; /** * Explicitly defines the path to the control directory. At least the * control file is mandatory. */ @Parameter(defaultValue = "[[baseDir]]/src/deb/control") private String controlDir; /** * Explicitly define the file to read the changes from. */ @Parameter(defaultValue = "[[baseDir]]/CHANGES.txt") private String changesIn; /** * Explicitly define the file where to write the changes to. */ @Parameter(defaultValue = "[[buildDir]]/[[artifactId]]_[[version]]_all.changes") private String changesOut; /** * Explicitly define the file where to write the changes of the changes input to. */ @Parameter(defaultValue = "[[baseDir]]/CHANGES.txt") private String changesSave; /** * The compression method used for the data file (none, gzip, bzip2 or xz) */ @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: * 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 = "deb") private String type; /** * The project base directory */ @Parameter(defaultValue = "${basedir}", required = true, readonly = true) private File baseDir; /** * The Maven Session Object */ @Parameter(defaultValue = "${session}", readonly = true) private MavenSession session; /** * The Maven Project Object */ @Parameter(defaultValue = "${project}", readonly = true) private MavenProject project; /** * The build directory */ @Parameter(property = "project.build.directory", required = true, readonly = true) private File buildDirectory; /** * The classifier of attached artifact */ @Parameter private String classifier; /** * "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: * * * <pre> * <build> * <plugins> * <plugin> * <artifactId>jdeb</artifactId> * <groupId>org.vafer</groupId> * ... * <configuration> * ... * <dataSet> * <data> * <src>${project.basedir}/target/my_archive.tar.gz</src> * <include>...</include> * <exclude>...</exclude> * <mapper> * <type>perm</type> * <strip>1</strip> * <prefix>/somewhere/else</prefix> * <user>santbj</user> * <group>santbj</group> * <mode>600</mode> * </mapper> * </data> * <data> * <src>${project.build.directory}/data</src> * <include></include> * <exclude>**/.svn</exclude> * <mapper> * <type>ls</type> * <src>mapping.txt</src> * </mapper> * </data> * <data> * <type>link</type> * <linkName>/a/path/on/the/target/fs</linkName> * <linkTarget>/a/sym/link/to/the/scr/file</linkTarget> * <symlink>true</symlink> * </data> * <data> * <src>${project.basedir}/README.txt</src> * </data> * </dataSet> * </configuration> * </plugins> * </build> * </pre> * */ @Parameter private Data[] dataSet; /** * @deprecated @Parameter(defaultValue = "false") private boolean timestamped; */ /** * 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; /** * If verbose is true more build messages are logged. */ @Parameter(defaultValue = "false") private boolean verbose; /** * Indicates if the execution should be disabled. If <code>true</code>, nothing will occur during execution. * * @since 1.1 */ @Parameter(defaultValue = "false") private boolean skip; @Parameter(defaultValue = "true") private boolean skipPOMs; @Parameter(defaultValue = "false") private boolean skipSubmodules; /** * @deprecated */ @Parameter(defaultValue = "true") private boolean submodules; /** * If signPackage is true then a origin signature will be placed * in the generated package. */ @Parameter(defaultValue = "false") private boolean signPackage; /** * Defines which utility is used to verify the signed package */ @Parameter(defaultValue = "debsig-verify") private String signMethod; /** * Defines the role to sign with */ @Parameter(defaultValue = "origin") private String signRole; /** * The keyring to use for signing operations. */ @Parameter private String keyring; /** * The key to use for signing operations. */ @Parameter private String key; /** * The passphrase to use for signing operations. */ @Parameter private String passphrase; /** * The prefix to use when reading signing variables * from settings. */ @Parameter(defaultValue = "jdeb.") private String signCfgPrefix; /** * The settings. */ @Parameter(defaultValue = "${settings}") private Settings settings; @Parameter(defaultValue = "") private String propertyPrefix; /* end of parameters */ private static final String KEY = "key"; private static final String KEYRING = "keyring"; private static final String PASSPHRASE = "passphrase"; private String openReplaceToken = "[["; private String closeReplaceToken = "]]"; private Console console; private Collection<DataProducer> dataProducers = new ArrayList<DataProducer>(); private Collection<DataProducer> conffileProducers = new ArrayList<DataProducer>(); public void setOpenReplaceToken(String openReplaceToken) { this.openReplaceToken = openReplaceToken; } public void setCloseReplaceToken(String closeReplaceToken) { this.closeReplaceToken = closeReplaceToken; } protected void setData(Data[] dataSet) { this.dataSet = dataSet; dataProducers.clear(); conffileProducers.clear(); if (dataSet != null) { Collections.addAll(dataProducers, dataSet); for (Data item : dataSet) { if (item.getConffile()) { conffileProducers.add(item); } } } } protected VariableResolver initializeVariableResolver(Map<String, String> variables) { @SuppressWarnings("unchecked") final Map<String, String> projectProperties = Map.class.cast(getProject().getProperties()); @SuppressWarnings("unchecked") final Map<String, String> 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", "deb"); 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. * <ul> * <li>any "-" is replaced by "+"</li> * <li>"SNAPSHOT" is replaced with the current time and date, prepended by "~"</li> * </ul> * * @return the Maven project version */ private String getProjectVersion() { return Utils.convertToDebianVersion(getProject().getVersion(), this.snapshotExpand, 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 the artifact is of configured type (i.e. the package to generate is the main artifact) */ private boolean isType() { return type.equals(getProject().getArtifact().getType()); } /** * @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 * * @throws MojoExecutionException on error */ @Override public void execute() throws MojoExecutionException { final MavenProject project = getProject(); if (skip) { getLog().info("skipping as configured (skip)"); return; } if (skipPOMs && isPOM()) { getLog().info("skipping because artifact is a pom (skipPOMs)"); return; } if (skipSubmodules && isSubmodule()) { getLog().info("skipping submodule (skipSubmodules)"); return; } setData(dataSet); console = new MojoConsole(getLog(), verbose); initializeSignProperties(); final VariableResolver resolver = initializeVariableResolver(new HashMap<String, String>()); final File debFile = new File(Utils.replaceVariables(resolver, deb, openReplaceToken, closeReplaceToken)); final File controlDirFile = new File( Utils.replaceVariables(resolver, controlDir, openReplaceToken, closeReplaceToken)); final File installDirFile = new File( Utils.replaceVariables(resolver, installDir, openReplaceToken, closeReplaceToken)); final File changesInFile = new File( Utils.replaceVariables(resolver, changesIn, openReplaceToken, closeReplaceToken)); final File changesOutFile = new File( Utils.replaceVariables(resolver, changesOut, openReplaceToken, closeReplaceToken)); final File changesSaveFile = new File( Utils.replaceVariables(resolver, changesSave, openReplaceToken, closeReplaceToken)); final File keyringFile = keyring == null ? null : new File(Utils.replaceVariables(resolver, keyring, openReplaceToken, closeReplaceToken)); // if there are no producers defined we try to use the artifacts if (dataProducers.isEmpty()) { if (hasMainArtifact()) { Set<Artifact> artifacts = new HashSet<Artifact>(); artifacts.add(project.getArtifact()); @SuppressWarnings("unchecked") final Set<Artifact> projectArtifacts = project.getArtifacts(); for (Artifact artifact : projectArtifacts) { artifacts.add(artifact); } @SuppressWarnings("unchecked") final List<Artifact> 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 { final File path = new File(installDirFile.getPath(), file.getName()); final String entryName = path.getPath(); final boolean symbolicLink = SymlinkUtils.isSymbolicLink(path); final TarArchiveEntry e; if (symbolicLink) { e = new TarArchiveEntry(entryName, TarConstants.LF_SYMLINK); e.setLinkName(SymlinkUtils.readSymbolicLink(path)); } else { e = new TarArchiveEntry(entryName, true); } e.setUserId(0); e.setGroupId(0); e.setUserName("root"); e.setGroupName("root"); e.setMode(TarEntry.DEFAULT_FILE_MODE); e.setSize(file.length()); receiver.onEachFile(new FileInputStream(file), e); } catch (Exception e) { getLog().error(e); } } }); } else { getLog().error("No file for artifact " + artifact); } } } } try { DebMaker debMaker = new DebMaker(console, dataProducers, conffileProducers); debMaker.setDeb(debFile); debMaker.setControl(controlDirFile); debMaker.setPackage(getProject().getArtifactId()); debMaker.setDescription(getProject().getDescription()); debMaker.setHomepage(getProject().getUrl()); debMaker.setChangesIn(changesInFile); debMaker.setChangesOut(changesOutFile); debMaker.setChangesSave(changesSaveFile); debMaker.setCompression(compression); debMaker.setKeyring(keyringFile); debMaker.setKey(key); debMaker.setPassphrase(passphrase); debMaker.setSignPackage(signPackage); debMaker.setSignMethod(signMethod); debMaker.setSignRole(signRole); debMaker.setResolver(resolver); debMaker.setOpenReplaceToken(openReplaceToken); debMaker.setCloseReplaceToken(closeReplaceToken); debMaker.validate(); debMaker.makeDeb(); // Always attach unless explicitly set to false if ("true".equalsIgnoreCase(attach)) { console.info("Attaching created debian package " + debFile); if (!isType()) { projectHelper.attachArtifact(project, type, classifier, debFile); } else { project.getArtifact().setFile(debFile); } } } catch (PackagingException e) { getLog().error("Failed to create debian package " + debFile, e); throw new MojoExecutionException("Failed to create debian package " + debFile, e); } if (!isBlank(propertyPrefix)) { project.getProperties().put(propertyPrefix + "version", getProjectVersion()); project.getProperties().put(propertyPrefix + "deb", debFile.getAbsolutePath()); project.getProperties().put(propertyPrefix + "deb.name", debFile.getName()); project.getProperties().put(propertyPrefix + "changes", changesOutFile.getAbsolutePath()); project.getProperties().put(propertyPrefix + "changes.name", changesOutFile.getName()); project.getProperties().put(propertyPrefix + "changes.txt", changesSaveFile.getAbsolutePath()); project.getProperties().put(propertyPrefix + "changes.txt.name", changesSaveFile.getName()); } } /** * Initializes unspecified sign properties using available defaults * and global settings. */ private void initializeSignProperties() { if (!signPackage) { return; } if (key != null && keyring != null && passphrase != null) { return; } Map<String, String> properties = readPropertiesFromActiveProfiles(signCfgPrefix, KEY, KEYRING, PASSPHRASE); key = lookupIfEmpty(key, properties, KEY); keyring = lookupIfEmpty(keyring, properties, KEYRING); passphrase = decrypt(lookupIfEmpty(passphrase, properties, PASSPHRASE)); if (keyring == null) { try { keyring = Utils.guessKeyRingFile().getAbsolutePath(); console.info("Located keyring at " + keyring); } catch (FileNotFoundException e) { console.warn(e.getMessage()); } } } /** * 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; } /** * * @return the maven project used by this mojo */ private MavenProject getProject() { if (project.getExecutionProject() != null) { return project.getExecutionProject(); } return project; } /** * 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<String, String> readPropertiesFromActiveProfiles(final String prefix, final String... properties) { if (settings == null) { console.debug("No maven setting injected"); return Collections.emptyMap(); } final List<String> activeProfilesList = settings.getActiveProfiles(); if (activeProfilesList.isEmpty()) { console.debug("No active profiles found"); return Collections.emptyMap(); } final Map<String, String> map = new HashMap<String, String>(); final Set<String> activeProfiles = new HashSet<String>(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; } }