
org.vafer.jdeb.maven.DebMojo Maven / Gradle / Ivy
/*
* Copyright 2007-2024 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.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.OutputTimestampResolver;
import org.vafer.jdeb.utils.SymlinkUtils;
import org.vafer.jdeb.utils.Utils;
import org.vafer.jdeb.utils.VariableResolver;
import static org.vafer.jdeb.utils.Utils.isBlank;
import static org.vafer.jdeb.utils.Utils.lookupIfEmpty;
/**
* Creates Debian package
*/
@Mojo(name = "jdeb", defaultPhase = LifecyclePhase.PACKAGE, threadSafe = true)
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;
/**
* The digest algorithm to use.
*
* @see org.bouncycastle.bcpg.HashAlgorithmTags
*/
@Parameter(defaultValue = "SHA256")
private String digest;
/**
* "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;
/**
* Template for replacing the SNAPSHOT value. A timestamp format can be provided in brackets.
* prefix[yyMMdd]suffix -> prefix151230suffix
*/
@Parameter
private String snapshotTemplate;
/**
* If verbose is true more build messages are logged.
*/
@Parameter(defaultValue = "false")
private boolean verbose;
/**
* Indicates if the execution should be disabled. If true
, nothing will occur during execution.
*
* @since 1.1
*/
@Parameter(property = "jdeb.skip", defaultValue = "false")
private boolean skip;
@Parameter(property = "jdeb.skipPOMs", defaultValue = "true")
private boolean skipPOMs;
@Parameter(property = "jdeb.skipSubmodules", 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;
/**
* If signChanges is true then changes file will be signed.
*/
@Parameter(defaultValue = "false")
private boolean signChanges;
/**
* 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;
/**
* Sets the long file mode for the resulting tar file. Valid values are "gnu", "posix", "error" or "truncate"
* @see org.apache.commons.compress.archivers.tar.TarArchiveOutputStream#setLongFileMode(int)
*/
@Parameter(defaultValue = "gnu")
private String tarLongFileMode;
/**
* Sets the big number mode for the resulting tar file. Valid values are "gnu", "posix" or "error"
* @see org.apache.commons.compress.archivers.tar.TarArchiveOutputStream#setBigNumberMode(int)
*/
@Parameter(defaultValue = "gnu")
private String tarBigNumberMode;
/**
* Timestamp for reproducible output archive entries, either formatted as ISO 8601
* yyyy-MM-dd'T'HH:mm:ssXXX
or as an int representing seconds since the epoch (like
* SOURCE_DATE_EPOCH).
*
* @since 1.9
*/
@Parameter(defaultValue = "${project.build.outputTimestamp}")
private String outputTimestamp;
/* 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 dataProducers = new ArrayList<>();
private Collection conffileProducers = new ArrayList<>();
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);
}
}
}
}
@SuppressWarnings("unchecked,rawtypes")
protected VariableResolver initializeVariableResolver( Map variables ) {
variables.putAll((Map) getProject().getProperties());
variables.putAll((Map) System.getProperties());
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());
if (getProject().getInceptionYear() != null) {
variables.put("project.inceptionYear", getProject().getInceptionYear());
}
if (getProject().getOrganization() != null) {
if (getProject().getOrganization().getName() != null) {
variables.put("project.organization.name", getProject().getOrganization().getName());
}
if (getProject().getOrganization().getUrl() != null) {
variables.put("project.organization.url", getProject().getOrganization().getUrl());
}
}
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, this.snapshotTemplate, 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
*/
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());
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 artifacts = new HashSet<>();
artifacts.add(project.getArtifact());
@SuppressWarnings("unchecked")
final Set projectArtifacts = project.getArtifacts();
artifacts.addAll(projectArtifacts);
@SuppressWarnings("unchecked")
final List attachedArtifacts = project.getAttachedArtifacts();
artifacts.addAll(attachedArtifacts);
for (Artifact artifact : artifacts) {
final File file = artifact.getFile();
if (file != null) {
dataProducers.add(new DataProducer() {
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.setSignChanges(signChanges);
debMaker.setSignMethod(signMethod);
debMaker.setSignRole(signRole);
debMaker.setResolver(resolver);
debMaker.setOpenReplaceToken(openReplaceToken);
debMaker.setCloseReplaceToken(closeReplaceToken);
debMaker.setDigest(digest);
debMaker.setTarBigNumberMode(tarBigNumberMode);
debMaker.setTarLongFileMode(tarLongFileMode);
Long outputTimestampMs = new OutputTimestampResolver(console).resolveOutputTimestamp(outputTimestamp);
debMaker.setOutputTimestampMs(outputTimestampMs);
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 && !signChanges) {
return;
}
if (key != null && keyring != null && passphrase != null) {
return;
}
Map 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 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;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy