net.sf.debianmaven.PackageMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of debian-maven-plugin Show documentation
Show all versions of debian-maven-plugin Show documentation
This plugin helps building DEB packages from Maven projects.
The packages can be used in DEB-based operating systems such
as Debian and Ubuntu. The plugin uses external Debian tools
to do the actual packaging.
package net.sf.debianmaven;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Calendar;
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 java.util.Vector;
import java.util.zip.GZIPOutputStream;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.collections15.MultiMap;
import org.apache.commons.collections15.multimap.MultiHashMap;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteException;
import org.apache.commons.exec.PumpStreamHandler;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
/**
* Generates a Debian package.
*
* Uses Debian utilities: dpkg-deb and fakeroot.
*
* @goal package
* @phase package
* @requiresDependencyResolution
*/
public class PackageMojo extends AbstractDebianMojo
{
/**
* @required
* @parameter expression="${deb.package.priority}" default-value="optional"
*/
protected String packagePriority;
/**
* @required
* @parameter expression="${deb.package.section}" default-value="contrib/utils"
*/
protected String packageSection;
/**
* @required
* @parameter expression="${deb.package.title}" default-value="${project.name}"
*/
protected String packageTitle;
/**
* @required
* @parameter expression="${deb.package.description}" default-value="${project.description}"
*/
protected String packageDescription;
/**
* @parameter
*/
protected String[] packageDependencies;
/**
* @parameter
*/
protected String[] packageConflicts;
/**
* @parameter expression="${deb.project.url}" default-value="${project.organization.url}"
*/
protected String projectUrl;
/**
* @parameter expression="${deb.project.organization}" default-value="${project.organization.name}"
*/
protected String projectOrganization;
/**
* @parameter expression="${deb.include.jar}"
*/
@Deprecated
protected String includeJar;
/**
* @parameter
*/
@Deprecated
protected String[] includeJars;
/**
* @parameter expression="${deb.exclude.all-jars}"
*/
@Deprecated
protected String excludeAllJars;
/**
* @parameter
* @since 1.0.3
*/
protected Set includeArtifacts;
/**
* @parameter
* @since 1.0.3
*/
protected Set excludeArtifacts;
/**
* @parameter default-value="false"
* @since 1.0.3
*/
protected boolean excludeAllArtifacts;
/**
* @parameter default-value="false"
* @since 1.0.3
*/
protected boolean excludeAllDependencies;
/**
* @parameter default-value="true"
* @since 1.0.3
*/
protected boolean includeAttachedArtifacts;
/**
* @parameter
* @since 1.0.9
*/
protected String packageFilename;
/**
* The Maven project object
*
* @parameter expression="${project}"
*/
private MavenProject project;
private File createTargetLibDir()
{
File targetLibDir = new File(stageDir, "usr/share/lib/" + packageName);
targetLibDir.mkdirs();
return targetLibDir;
}
private void createSymlink(File symlink, String target) throws ExecuteException, MojoExecutionException, IOException
{
if (symlink.exists())
symlink.delete();
runProcess(new String[]{"ln", "-s", target, symlink.toString()}, true);
}
private void writeIncludeFile(File targetLibDir, String artifactId, String version, Collection dependencies) throws IOException, MojoExecutionException
{
if (dependencies == null)
dependencies = Collections.emptySet();
File deplist = new File(targetLibDir, String.format("%s-%s.inc", artifactId, version));
FileWriter out = new FileWriter(deplist);
try
{
out.write(String.format("artifacts=%s\n", StringUtils.join(new HashSet(dependencies), ":")));
}
finally
{
out.close();
}
createSymlink(new File(targetLibDir, String.format("%s.inc", artifactId)), deplist.getName());
}
private boolean includeArtifact(Artifact a)
{
boolean doExclude = excludeArtifacts != null && (a.getDependencyTrail() == null || Collections.disjoint(a.getDependencyTrail(), excludeArtifacts));
if (doExclude)
return false;
if (includeArtifacts == null)
return true;
if (a.getDependencyTrail() == null)
return true;
return Collections.disjoint(a.getDependencyTrail(), includeArtifacts);
}
private File copyArtifact(Artifact a, File targetLibDir) throws IOException, MojoExecutionException
{
if (a.getFile() == null)
throw new MojoExecutionException(String.format("No file was built for required artifact: %s:%s:%s", a.getGroupId(), a.getArtifactId(), a.getVersion()));
getLog().info(String.format("Artifact: %s", a.getFile().getPath()));
File src = a.getFile();
File trg = new File(targetLibDir, src.getName());
FileUtils.copyFile(src, trg);
//TODO: which version should we use? trying both versions for now...
String linkname = src.getName().replaceFirst("-"+a.getBaseVersion(), "");
if (linkname.equals(src.getName()))
linkname = linkname.replaceFirst("-"+a.getVersion(), "");
if (!linkname.equals(src.getName()))
createSymlink(new File(targetLibDir, linkname), a.getFile().getName());
return trg;
}
@SuppressWarnings("unchecked")
private void copyAttachedArtifacts() throws FileNotFoundException, IOException, MojoExecutionException
{
if (!includeAttachedArtifacts)
{
getLog().info("Skipping attached project artifacts.");
return;
}
getLog().info("Copying attached project artifacts.");
File targetLibDir = createTargetLibDir();
for (Artifact a : (Collection)project.getAttachedArtifacts())
copyArtifact(a, targetLibDir);
}
@SuppressWarnings("unchecked")
private void copyArtifacts() throws FileNotFoundException, IOException, MojoExecutionException
{
if (excludeAllArtifacts)
{
getLog().info("Skipping regular project artifacts and dependencies.");
return;
}
File targetLibDir = createTargetLibDir();
Collection artifacts = new ArrayList();
// consider the current artifact only if it exists (e.g. pom, war packaging generates no artifact)
if (project.getArtifact().getFile() != null)
artifacts.add(project.getArtifact());
if (excludeAllDependencies)
getLog().info("Copying regular project artifacts but not dependencies.");
else
{
getLog().info("Copying regular project artifacts and dependencies.");
for (Artifact a : (Collection)project.getArtifacts())
{
if (a.getScope().equals("runtime") || a.getScope().equals("compile"))
artifacts.add(a);
}
}
/*
* TODO: this code doesn't work as it should due to limitations of Maven API; see also:
* http://jira.codehaus.org/browse/MNG-4831
*/
Map ids = new HashMap();
for (Artifact a : artifacts)
ids.put(a.getId(), a);
MultiMap deps = new MultiHashMap();
for (Artifact a : artifacts)
{
if (includeArtifact(a))
{
File trg = copyArtifact(a, targetLibDir);
if (a.getDependencyTrail() != null)
{
for (String id : a.getDependencyTrail())
{
Artifact depending = ids.get(id);
if (depending != null)
deps.put(depending, trg.getPath().substring(stageDir.getPath().length()));
}
}
}
}
for (Artifact a : artifacts)
{
if (includeArtifact(a))
writeIncludeFile(targetLibDir, a.getArtifactId(), a.getVersion(), deps.get(a));
}
}
private void generateCopyright() throws IOException
{
File targetDocDir = new File(stageDir, "usr/share/doc/" + packageName);
targetDocDir.mkdirs();
File copyrightFile = new File(targetDocDir, "copyright");
if (!copyrightFile.exists())
{
PrintWriter out = new PrintWriter(new FileWriter(copyrightFile));
out.println(packageName);
out.println(projectUrl);
out.println();
out.printf("Copyright %d %s\n", Calendar.getInstance().get(Calendar.YEAR), projectOrganization);
out.println();
out.println("The entire code base may be distributed under the terms of the GNU General");
out.println("Public License (GPL).");
out.println();
out.println("See /usr/share/common-licenses/GPL");
out.close();
}
}
@Override
protected File getPackageFile()
{
String filename = this.packageFilename;
String packageArchitecture = this.packageArchitecture;
if (packageArchitecture == null) {
packageArchitecture = "all";
}
if (filename == null) {
filename = String.format("%s_%s-%s_%s.deb", packageName, getPackageVersion(), packageRevision, packageArchitecture);
}
return new File(targetDir, filename);
}
private void generateControl(File target) throws IOException
{
getLog().info("Generating control file: "+target);
PrintWriter out = new PrintWriter(new FileWriter(target));
out.println("Package: "+packageName);
out.println("Version: "+getPackageVersion());
if (packageSection != null)
out.println("Section: "+packageSection);
if (packagePriority != null)
out.println("Priority: "+packagePriority);
out.println("Architecture: "+packageArchitecture);
if (packageDependencies != null && packageDependencies.length > 0)
out.println("Depends: " + StringUtils.join(processVersion(packageDependencies), ", "));
if (packageConflicts != null && packageConflicts.length > 0) {
out.println("Conflicts: " + StringUtils.join(processVersion(packageConflicts), ", "));
}
out.printf("Installed-Size: %d\n", 1 + FileUtils.sizeOfDirectory(stageDir) / 1024);
if (maintainerName != null || maintainerEmail != null)
{
out.print("Maintainer:");
if (maintainerName != null)
out.print(" "+maintainerName);
if (maintainerEmail != null)
out.printf(" <%s>", maintainerEmail);
out.println();
}
if (projectUrl != null)
out.println("Homepage: "+projectUrl);
if (packageTitle != null) {
if (packageTitle.length() > 60)
{
getLog().warn("Package title will be truncated to the upper limit of 60 characters.");
out.println("Description: "+packageTitle.substring(0, 60));
}
else
out.println("Description: "+packageTitle);
out.println(getFormattedDescription());
}
out.close();
}
private String getFormattedDescription()
{
String desc = packageDescription.trim();
desc = desc.replaceAll("\\s+", " ");
return " " + desc;
}
private void generateConffiles(File target) throws IOException
{
List conffiles = new Vector();
File configDir = new File(stageDir, "etc");
if (configDir.exists())
{
Collection files = FileUtils.listFiles(configDir, null, true);
for (File f : files)
{
if (f.isFile())
conffiles.add(f.toString().substring(stageDir.toString().length()));
}
}
if (conffiles.size() > 0)
{
PrintWriter out = new PrintWriter(new FileWriter(target));
for (String fname : conffiles)
out.println(fname);
out.close();
}
}
private void generateMd5Sums(File target) throws IOException
{
PrintWriter out = new PrintWriter(new FileWriter(target));
Collection files = FileUtils.listFiles(stageDir, null, true);
for (File f : files)
{
// check whether the file is a non-regular file
if (!f.isFile())
continue;
// check whether the file is a possible link
if (!f.getAbsolutePath().equals(f.getCanonicalPath()))
continue;
String fname = f.toString().substring(stageDir.toString().length() + 1);
if (!fname.startsWith("DEBIAN"))
{
FileInputStream fis = new FileInputStream(f);
String md5 = DigestUtils.md5Hex(fis);
fis.close();
out.printf("%s %s\n", md5, fname);
}
}
out.close();
}
private void generateManPages() throws MojoExecutionException, ExecuteException, IOException
{
File source = new File(sourceDir, "man");
if (!source.exists())
{
getLog().info("No manual page directory found: "+source);
return;
}
int npages = 0;
Collection files = FileUtils.listFiles(source, null, true);
for (File f : files)
{
if (f.isFile() && f.getName().matches(".*[.][1-9]$"))
{
char section = f.getName().charAt(f.getName().length()-1);
File target = new File(stageDir, String.format("usr/share/man/man%c/%s.gz", section, f.getName()));
target.getParentFile().mkdirs();
CommandLine cmdline = new CommandLine("groff");
cmdline.addArguments(new String[]{"-man", "-Tascii", f.getPath()});
getLog().info("Start process: "+cmdline);
GZIPOutputStream os = new GZIPOutputStream(new FileOutputStream(target));
try
{
PumpStreamHandler streamHandler = new PumpStreamHandler(os, new LogOutputStream(getLog()));
DefaultExecutor exec = new DefaultExecutor();
exec.setWorkingDirectory(f.getParentFile());
exec.setStreamHandler(streamHandler);
int exitval = exec.execute(cmdline);
if (exitval == 0)
getLog().info("Manual page generated: "+target.getPath());
else
{
getLog().warn("Exit code "+exitval);
throw new MojoExecutionException("Process returned non-zero exit code: "+cmdline);
}
}
finally
{
os.close();
}
npages++;
}
}
if (npages == 0)
getLog().info("No manual pages found in directory: "+source);
}
private void generatePackage() throws IOException, MojoExecutionException
{
runProcess(new String[]{"fakeroot", "--", "dpkg-deb", "--build", stageDir.toString(), getPackageFile().toString()}, true);
}
private void checkDeprecated(boolean haveParameter, String paramName) throws MojoExecutionException
{
if (haveParameter)
throw new MojoExecutionException("Deprecated parameter used: "+paramName);
}
protected void executeDebMojo() throws MojoExecutionException
{
checkDeprecated(includeJar != null, "includeJar");
checkDeprecated(includeJars != null && includeJars.length > 0, "includeJars");
checkDeprecated(excludeAllJars != null, "excludeAllJars");
File targetDebDir = new File(stageDir, "DEBIAN");
if (!targetDebDir.exists() && !targetDebDir.mkdirs())
throw new MojoExecutionException("Unable to create directory: "+targetDebDir);
try
{
generateManPages();
copyAttachedArtifacts();
copyArtifacts();
generateCopyright();
generateConffiles(new File(targetDebDir, "conffiles"));
generateControl(new File(targetDebDir, "control"));
generateMd5Sums(new File(targetDebDir, "md5sums"));
generatePackage();
}
catch (IOException e)
{
getLog().error(e.toString());
throw new MojoExecutionException(e.toString());
}
}
}