blob: c798edf4fef4082136eb4364659e6692a6136899 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017 SSI Schaefer IT Solutions GmbH and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* SSI Schaefer IT Solutions GmbH
*******************************************************************************/
package org.eclipse.tea.library.build.model;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.resources.IResource;
import org.eclipse.tea.library.build.jar.JarManager;
import org.eclipse.tea.library.build.jar.ZipExec;
import org.eclipse.tea.library.build.jar.ZipExecFactory;
import org.eclipse.tea.library.build.jar.ZipExecPart;
import org.eclipse.tea.library.build.util.FileUtils;
/**
* Provides information about building a RCP plugin.
*/
public class PluginBuild extends BundleBuild<PluginData> implements Comparable<PluginBuild> {
protected Set<PluginBuild> sourceDependencies;
protected Set<MavenExternalJarBuild> mavenDependencies;
protected Set<String> workspaceDependencies;
protected final Set<PluginBuild> fragments = new TreeSet<>();
public static final Pattern MAVEN_COORDINATE_PATTERN = Pattern
.compile("([^: ]+):([^: ]+)(:([^: ]*)(:([^: ]+))?)?:([^: ]+)");
public PluginBuild(PluginData data) {
super(data);
}
/**
* Returns the name of this plugin.
*/
public final String getPluginName() {
return data.getBundleName();
}
/**
* Returns the bundle directory; {@code null} for a JAR distribution.
*/
public final File getPluginDirectory() {
return data.bundleDir;
}
/**
* Returns all existing and valid dependencies.
*/
public final Collection<PluginBuild> getSourceDependencies() {
return sourceDependencies;
}
public final Collection<MavenExternalJarBuild> getMavenExternalJarDependencies() {
return mavenDependencies;
}
/**
* Returns the names of all dependencies which could be found in the
* workspace.
*/
public final Collection<String> getWorkspaceDependencies() {
return workspaceDependencies;
}
public final Collection<PluginBuild> getFragments() {
return fragments;
}
/**
* Compares two plugins by its name.
*/
@Override
public int compareTo(PluginBuild o) {
return getPluginName().compareTo(o.getPluginName());
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof PluginBuild)) {
return false;
}
PluginBuild other = (PluginBuild) obj;
return getPluginName().equals(other.getPluginName());
}
@Override
public int hashCode() {
return getPluginName().hashCode();
}
@Override
public String toString() {
return getPluginName();
}
protected void updateDependencies(WorkspaceBuild ws) {
sourceDependencies = new TreeSet<>();
workspaceDependencies = new TreeSet<>();
mavenDependencies = new TreeSet<>();
for (ParameterValue pv : data.getDependencies()) {
addSourceDependency(ws, pv.getValue());
}
for (ParameterValue pv : data.getMavenDependencies()) {
addMavenDependency(ws, pv.getValue());
}
// check fragments
ParameterValue fragmentHost = data.getFragmentHost();
if (fragmentHost != null) {
PluginBuild host = addSourceDependency(ws, fragmentHost.getValue());
if (host != null) {
host.fragments.add(this);
} else {
ws.addHostLessFragment(this);
}
}
}
private void addMavenDependency(WorkspaceBuild ws, String value) {
// dependency format: "groupId:name:version"
Matcher matcher = MAVEN_COORDINATE_PATTERN.matcher(value);
if (matcher.matches()) {
mavenDependencies.add(new MavenExternalJarBuild(matcher.group(0)));
}
}
private PluginBuild addSourceDependency(WorkspaceBuild ws, String name) {
PluginBuild pb = ws.sourcePlugins.get(name);
if (pb != null) {
sourceDependencies.add(pb);
workspaceDependencies.add(name);
} else if (ws.isClosedOrIncomplete(name)) {
workspaceDependencies.add(name);
}
return pb;
}
@Override
public String getJarFileName(String buildVersion) {
return getPluginName() + '_' + buildVersion + ".jar";
}
/**
* Find the binary for this plugin. Only valid if {@link #getData()}.
* {@link PluginData#isBinary() isBinary()}.
*
* @return the path to the underlying backing jar file for this binary
* plugin. Never null.
* @throws Exception
* in case the file cannot be found.
*/
public File getBinaryJarFile() throws Exception {
if (!data.isBinary()) {
throw new IllegalStateException(this + " is not binary!");
}
for (IResource r : data.getProject().members()) {
String name = r.getName();
if (name.startsWith(data.getBundleName()) && name.endsWith(".jar")) {
return r.getLocation().toFile();
}
}
throw new IllegalStateException(this + " has no binary file even though it is binary?!");
}
/**
* Creates the JAR file for this plugin.
*
* @return the path to the generated jar file.
*/
@Override
public File execJarCommands(ZipExecFactory zip, File distDirectory, String buildVersion, JarManager jarManager)
throws Exception {
return execJarCommands(zip, distDirectory, buildVersion, true);
}
/**
* Creates the JAR file for this plugin. If withBinInc is
* <code>false</code>, no binary includes will be added to the jar.
*
* @return the path to the generated jar file.
*/
public File execJarCommands(ZipExecFactory zip, File distDirectory, String buildVersion, boolean withBinInc)
throws Exception {
final String oldBundleVersion = data.getBundleVersion();
final File manifest = data.getManifestFile();
if (oldBundleVersion == null || manifest == null) {
// simply create the JAR
return doExecJarCommands(zip, distDirectory, buildVersion, withBinInc);
}
// backup the manifest file
File rootDir = manifest.getParentFile().getParentFile();
final File backup = new File(rootDir, "__manifest__.bak");
FileUtils.copyFile(manifest, backup);
final long timeStamp = manifest.lastModified();
String[] classPath = data.getClassPath();
try {
// create the new manifest
data.setBundleVersion(buildVersion);
// TODO: what to do about this? don't want to have it always?
String[] enhancedCP = new String[classPath.length + (classPath.length == 0 ? 2 : 1)];
enhancedCP[0] = "external:$com.wamas.fastpatch.root$/" + data.getBundleName();
if (classPath.length == 0) {
enhancedCP[1] = ".";
}
System.arraycopy(classPath, 0, enhancedCP, 1, classPath.length);
data.setClassPath(enhancedCP);
data.writeManifest();
if (!manifest.setLastModified(timeStamp)) {
zip.log.debug("cannot set last modified time: " + manifest);
}
// create the JAR
return doExecJarCommands(zip, distDirectory, buildVersion, withBinInc);
} finally {
// restore the old manifest
FileUtils.copyFile(backup, manifest);
FileUtils.delete(backup);
data.setBundleVersion(oldBundleVersion);
data.setClassPath(classPath);
data.refreshProject();
}
}
public boolean isPreserveBinaryStructure() {
return Boolean.parseBoolean(data.getSimpleManifestValue("Preserve-Binary-Structure"));
}
private File doExecJarCommands(ZipExecFactory zip, File distDirectory, String buildVersion, boolean withBinInc)
throws Exception {
final File jarFile = new File(distDirectory, getJarFileName(buildVersion));
// remove the jar file
FileUtils.delete(jarFile);
// update the manifest for binary deployment
data.updateManifestForBinaryDeployment();
// create the ZIP executor
final ZipExec exec = zip.createZipExec();
exec.setZipFile(jarFile);
exec.setJarMode(true);
// run ZIP on 'bin' directories
Map<String, List<String>> binaryFolders = data.getBinaryFolders();
String[] binInc = data.getBinaryIncludes();
for (String inc : binInc) {
List<String> paths = binaryFolders.get(inc);
if (paths != null && !paths.isEmpty()) {
for (String path : paths) {
File binDir = new File(data.getBundleDir(), path);
if (binDir.isDirectory() && binDir.list().length > 0) {
ZipExecPart part = new ZipExecPart();
if (isPreserveBinaryStructure()) {
part.sourceDirectory = data.getBundleDir();
part.relativePaths.add(path);
} else {
part.sourceDirectory = binDir;
part.relativePaths.add(".");
}
part.excludeGit = true;
exec.addPart(part);
}
}
}
}
// run ZIP for binary includes
if (withBinInc) {
ZipExecPart incPart = new ZipExecPart();
for (String bin : binInc) {
// output folders are added above
if (!binaryFolders.containsKey(bin)) {
incPart.relativePaths.add(bin);
}
}
if (!incPart.relativePaths.isEmpty()) {
incPart.sourceDirectory = data.getBundleDir();
incPart.excludeGit = true;
exec.addPart(incPart);
}
}
// create the jar file
try {
exec.createZip();
} catch (Exception ex) {
throw new RuntimeException("Unable to zip plug-in '" + data.getBundleName() + "'", ex);
}
return jarFile;
}
/**
* Calculates the state of the 'unpack' flag (used by features).
*/
public boolean needUnpack() {
// check our special flag
if (data.manifest.getNeedUnpack()) {
return true;
}
// check the class path
String[] classPath = data.getClassPath();
if (classPath == null) {
return false;
}
for (String element : classPath) {
if (!element.equals(".")) {
return true;
}
}
return false;
}
/**
* Creates a self-contained ZIP file for this plugins. The result can be
* treated as a 'product' for stand-alone Java applications.
*
* @param zipFactory
* ZIP factory
* @param buildVersion
* build version of the product
* @param output
* ZIP file to build
*/
public void buildSelfContainedZip(ZipExecFactory zipFactory, String buildVersion, File output) throws Exception {
File outputDirectory = output.getParentFile();
File temp = new File(outputDirectory, "zip_" + String.valueOf(System.currentTimeMillis()));
File libs = new File(temp, getPluginName());
FileUtils.mkdirs(libs);
try {
copyLibsForSelfContainment(zipFactory, libs, buildVersion);
FileUtils.delete(output);
ZipExec exec = zipFactory.createZipExec();
exec.setZipFile(output);
ZipExecPart part = new ZipExecPart();
part.sourceDirectory = temp;
part.relativePaths.add(libs.getName());
exec.addPart(part);
exec.createZip();
} finally {
FileUtils.deleteDirectory(temp);
}
}
private void copyLibsForSelfContainment(ZipExecFactory zipFactory, File destDir, String buildVersion)
throws Exception {
if (data.isBinary()) {
disruptBinaryJar(zipFactory, destDir);
} else {
execJarCommands(zipFactory, destDir, buildVersion, false);
}
File pd = getPluginDirectory();
if (pd != null && pd.exists() && pd.isDirectory()) {
for (String inc : getData().getBinaryIncludes()) {
if (".".equals(inc)) {
continue;
}
File binFile = new File(pd, inc);
if (binFile.exists() && binFile.isFile() && binFile.getName().toLowerCase().endsWith(".jar")) {
FileUtils.copyFileToDirectory(binFile, destDir);
}
}
}
for (PluginBuild dep : getSourceDependencies()) {
dep.copyLibsForSelfContainment(zipFactory, destDir, buildVersion);
}
}
private void disruptBinaryJar(ZipExecFactory zipFactory, File destDir) throws Exception {
// unpack the binary, remove META-INF, move all jars to destDir, re-pack
// all remaining
// files into new jar
File binFile = getBinaryJarFile();
File tmp = new File(destDir, binFile.getName() + "_" + System.currentTimeMillis());
tmp.mkdirs();
final ZipExec exec = zipFactory.createZipExec();
exec.unzip(binFile, tmp);
File meta = new File(tmp, "META-INF");
if (meta.exists() && meta.isDirectory()) {
FileUtils.deleteDirectory(meta);
}
moveJarsRecursive(tmp, destDir);
File target = new File(destDir, getPluginName() + "_stripped.jar");
// now create jar of the "remains"
FileUtils.delete(target);
// create the ZIP executor
exec.setZipFile(target);
exec.setJarMode(true);
// run ZIP on the remains of the original jar
ZipExecPart part = new ZipExecPart();
part.sourceDirectory = tmp;
part.relativePaths.add(".");
part.excludeGit = true;
exec.addPart(part);
exec.createZip();
FileUtils.deleteDirectory(tmp);
}
private void moveJarsRecursive(File tmp, File destDir) throws IOException {
File[] files = tmp.listFiles();
if (files == null || files.length == 0) {
return;
}
for (File f : files) {
if (f.isFile() && f.getName().endsWith(".jar")) {
FileUtils.moveFile(f, new File(destDir, f.getName()));
}
if (f.isDirectory()) {
moveJarsRecursive(f, destDir);
}
}
}
}