/*******************************************************************************
* Copyright (c) 2006-2015
* Software Technology Group, Dresden University of Technology
* DevBoost GmbH, Dresden, Amtsgericht Dresden, HRB 34001
*
* 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:
* Software Technology Group - TU Dresden, Germany;
* DevBoost GmbH - Dresden, Germany
* - initial API and implementation
******************************************************************************/
package de.devboost.buildboost.genext.updatesite.steps;
import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import de.devboost.buildboost.BuildException;
import de.devboost.buildboost.ant.AbstractAntTargetGenerator;
import de.devboost.buildboost.ant.AntTarget;
import de.devboost.buildboost.artifacts.EclipseFeature;
import de.devboost.buildboost.artifacts.Plugin;
import de.devboost.buildboost.genext.updatesite.artifacts.EclipseUpdateSite;
import de.devboost.buildboost.genext.updatesite.artifacts.EclipseUpdateSiteDeploymentSpec;
import de.devboost.buildboost.model.IDependable;
import de.devboost.buildboost.util.PluginPackagingHelper;
import de.devboost.buildboost.util.TimestampUtil;
import de.devboost.buildboost.util.XMLContent;
public class BuildUpdateSiteStep extends AbstractAntTargetGenerator {
private final EclipseUpdateSiteDeploymentSpec updateSiteSpec;
private final File targetDir;
private String usernameProperty = null;
private String passwordProperty = null;
public BuildUpdateSiteStep(EclipseUpdateSiteDeploymentSpec updateSiteSpec,
File targetDir) {
super();
this.updateSiteSpec = updateSiteSpec;
this.targetDir = targetDir;
}
@Override
public Collection<AntTarget> generateAntTargets() throws BuildException {
if (usernameProperty == null) {
usernameProperty = updateSiteSpec.getSiteUsernameProperty();
}
if (passwordProperty == null) {
passwordProperty = updateSiteSpec.getSitePasswordProperty();
}
AntTarget updateSiteTarget = generateUpdateSiteAntTarget();
return Collections.singletonList(updateSiteTarget);
}
protected AntTarget generateUpdateSiteAntTarget() throws BuildException {
EclipseUpdateSite updateSite = updateSiteSpec.getUpdateSite();
if (updateSite == null) {
throw new BuildException("Can't find update site for update site specification.");
}
String distDir = targetDir.getAbsolutePath();
XMLContent content = new XMLContent();
String updateSiteID = updateSite.getIdentifier();
File updateSiteFile = updateSite.getFile();
String updateSiteDir = distDir + File.separator + "updatesites" + File.separator + updateSiteID;
new TimestampUtil().addGetBuildTimestampFromEnvironment(content);
content.append("<!-- fallback if env.BUILD_ID is not set -->");
content.append("<tstamp/>");
content.append("<property name=\"buildid\" value=\"${DSTAMP}${TSTAMP}\" />");
content.appendLineBreak();
content.append("<echo message=\"Copying update site descriptor for update site '" + updateSiteID + "'\"/>");
content.append("<mkdir dir=\"" + updateSiteDir + "\" />");
content.append("<mkdir dir=\"" + updateSiteDir + "/plugins\" />");
content.append("<mkdir dir=\"" + updateSiteDir + "/features\" />");
content.append("<copy file=\"" + updateSiteFile.getAbsolutePath() + "\" tofile=\"" + updateSiteDir + "/site.xml\"/>");
File associatedSitesXML = new File(updateSiteFile.getParent(), "associateSites.xml");
if (associatedSitesXML.exists()) {
content.append("<copy file=\"" + associatedSitesXML.getAbsolutePath() + "\" tofile=\"" + updateSiteDir + "/associateSites.xml\"/>");
}
content.appendLineBreak();
File categoryXML = new File(updateSiteFile.getParent(), "category.xml");
if (categoryXML.exists()) {
content.append("<copy file=\"" + categoryXML.getAbsolutePath() + "\" tofile=\"" + updateSiteDir + "/category.xml\"/>");
}
content.appendLineBreak();
String updateSiteVendor = updateSiteSpec.getSiteVendor();
if (updateSiteVendor == null) {
updateSiteVendor = "Unknown vendor";
}
String excludeSrc = updateSiteSpec.getExcludeSources();
if (excludeSrc == null) {
excludeSrc = "false";
}
Collection<EclipseFeature> features = updateSite.getFeatures();
for (EclipseFeature feature : features) {
String featureID = feature.getIdentifier();
File featureFile = feature.getFile();
boolean isFeatureJAR = featureFile.getName().endsWith(".jar");
String tempDir = distDir + File.separator + "temp_features";
String tempFeatureDir = tempDir + "/" + featureID;
String featureVersion = feature.getVersion();
String featureVendor = updateSiteSpec.getFeatureVendor(featureID);
if (isFeatureJAR) {
// the feature is already packaged, we can just copy it
content.append("<copy file=\"" + featureFile.getAbsolutePath() + "\" todir=\"" + updateSiteDir + "/features\"/>");
content.append("<!-- set correct reference in site.xml -->");
content.append("<replaceregexp file=\"" + updateSiteDir + "/site.xml\" match='feature url=\"features/" + featureID + "_[0-9]*.[0-9]*.[0-9]*.v[0-9]*.jar\" id=\"" + featureID + "\" version=\"[0-9]*.[0-9]*.[0-9]*.v[0-9]*\"' replace='feature url=\"features/" + featureFile.getName() + "\" id=\"" + featureID + "\" version=\"" + featureVersion + "\"'/>");
if(categoryXML.exists()){
// <feature url="features/org.modelrefactoring.smells_0.0.0.v0.jar" id="org.modelrefactoring.smells" version="0.0.0.v0">
content.append("<replaceregexp file=\"" + updateSiteDir + "/category.xml\" match='feature url=\"features/" + featureID + "_[0-9]*.[0-9]*.[0-9]*.v[0-9]*.jar\" id=\"" + featureID + "\" version=\"[0-9]*.[0-9]*.[0-9]*.v[0-9]*\"' replace='feature url=\"features/" + featureFile.getName() + "\" id=\"" + featureID + "\" version=\"" + featureVersion + "\"'/>");
}
content.appendLineBreak();
} else {
// the feature is not packaged yet, we need to create a JAR file
content.append("<echo message=\"Building feature '" + featureID + "' for update site '" + updateSiteID + "'\"/>");
content.append("<!-- update version numbers in feature.xml -->");
content.append("<!-- copy to be modified -->");
content.append("<mkdir dir=\"" + tempFeatureDir + "\" />");
content.append("<copy file=\"" + featureFile.getAbsolutePath() + "\" tofile=\"" + tempFeatureDir + "/feature.xml\"/>");
content.append("<!-- set version in copy -->");
content.append("<replace file=\"" + tempFeatureDir + "/feature.xml\" token='\"0.0.0\"' value='\"" + featureVersion + ".v${buildid}\"'/>");
content.append("<replace file=\"" + tempFeatureDir + "/feature.xml\" token='\"" + featureVersion + "\"' value='\"" + featureVersion + ".v${buildid}\"'/>");
content.append("<replace file=\"" + tempFeatureDir + "/feature.xml\" token=\".qualifier\" value=\".v${buildid}\"/>");
Collection<IDependable> dependencies = feature.getDependencies();
for (IDependable dependency : dependencies) {
if (dependency instanceof EclipseFeature) {
EclipseFeature requiredFeature = (EclipseFeature) dependency;
if (requiredFeature.isTargetPlatformFeature()) {
// for target platform features we do not set a minimum
// version
continue;
}
content.append("<replaceregexp file=\"" + tempFeatureDir + "/feature.xml\" match='<import feature=\"" + requiredFeature.getIdentifier() + "\"' replace='<import feature=\"" + requiredFeature.getIdentifier() + "\" version=\"" + requiredFeature.getVersion() + "\" match=\"greaterOrEqual\"' />");
}
}
content.append("<!-- create empty file 'feature.properties' -->");
content.append("<touch file=\"feature.properties\"/>");
content.append("<!-- create feature JAR -->");
content.append("<jar basedir=\"" + tempFeatureDir + "\" includes=\"feature.xml\" destfile=\"" + updateSiteDir + "/features/" + featureID + "_" + featureVersion + ".v${buildid}.jar\">");
content.append("<fileset dir=\".\" includes=\"feature.properties\" />");
content.append("</jar>");
content.append("<!-- delete temporary directory -->");
content.append("<delete dir=\"" + tempDir + "\"/>");
content.append("<!-- set version in site.xml -->");
content.append("<replaceregexp file=\"" + updateSiteDir + "/site.xml\" match='feature url=\"features/" + featureID + "_[0-9]*.[0-9]*.[0-9]*.v[0-9]*.jar\" id=\"" + featureID + "\" version=\"[0-9]*.[0-9]*.[0-9]*.v[0-9]*\"' replace='feature url=\"features/" + featureID + "_" + featureVersion + ".v${buildid}.jar\" id=\"" + featureID + "\" version=\"" + featureVersion + ".v${buildid}\"'/>");
if(categoryXML.exists()){
// <feature url="features/org.modelrefactoring.smells_0.0.0.v0.jar" id="org.modelrefactoring.smells" version="0.0.0.v0">
content.append("<replaceregexp file=\"" + updateSiteDir + "/category.xml\" match='feature url=\"features/" + featureID + "_[0-9]*.[0-9]*.[0-9]*.v[0-9]*.jar\" id=\"" + featureID + "\" version=\"[0-9]*.[0-9]*.[0-9]*.v[0-9]*\"' replace='feature url=\"features/" + featureID + "_" + featureVersion + ".v${buildid}.jar\" id=\"" + featureID + "\" version=\"" + featureVersion + ".v${buildid}\"'/>");
}
content.appendLineBreak();
}
Collection<Plugin> plugins = feature.getRequiredPlugins();
for (Plugin plugin : plugins) {
addPackagePluginTasks(content, updateSiteID, updateSiteDir,
Boolean.parseBoolean(excludeSrc), featureVersion, featureVendor, plugin);
}
}
addCreateCompleteZipTask(content, updateSiteID, updateSiteDir);
String targetPath = updateSiteSpec.getSiteUploadPath();
if (targetPath != null) {
addUploadTasks(content, updateSiteID, updateSiteDir, targetPath, categoryXML);
}
AntTarget target = new AntTarget("build-update-site-" + updateSiteID, content);
return target;
}
private void addCreateCompleteZipTask(XMLContent content,
String updateSiteID, String updateSiteDir) {
String zipFilePath = updateSiteDir + "/" + getUpdateSiteCompleteFileName(updateSiteID);
content.append("<!-- Create zipped version of update site -->");
content.append("<zip destfile=\"" + zipFilePath + "\" basedir=\"" + updateSiteDir + "\" />");
content.append("<checksum file=\"" + zipFilePath + "\"/>");
}
private void addUploadTasks(XMLContent content, String updateSiteID,
String updateSiteDir, String targetPath, File categoryXML) {
// TODO this requires that jsch-0.1.48.jar is in ANTs classpath. we
// should figure out a way to provide this JAR together with BuildBoost.
content.append("<!-- Copy new version of update site to server -->");
content.append("<scp todir=\"${env." + usernameProperty + "}:${env." + passwordProperty + "}@" + targetPath + "\" port=\"22\" sftp=\"true\" trust=\"true\">");
content.append("<fileset dir=\"" + updateSiteDir + "\">");
content.append("<include name=\"features/**\"/>");
content.append("<include name=\"plugins/**\"/>");
content.append("<include name=\"associateSites.xml\"/>");
content.append("<include name=\"digest.zip\"/>");
content.append("<include name=\"COPYING\"/>");
content.append("</fileset>");
content.append("</scp>");
content.appendLineBreak();
content.append("<!-- We copy the site.xml, artifacts.jar and content.jar separately to make sure these");
content.append("are the last files that are replaced. Otherwise the files might point to JARs that ");
content.append("have not been uploaded yet. -->");
if(!categoryXML.exists()){
// if it doesn't exist just create a copy of site.xml and rename it
// must be done before uploading
content.append("<copy file=\"" + updateSiteDir + File.separator + "site.xml\" tofile=\"" + updateSiteDir + File.separator + "category.xml\" overwrite=\"true\"/>");
}
content.append("<scp todir=\"${env." + usernameProperty + "}:${env." + passwordProperty + "}@" + targetPath + "\" port=\"22\" sftp=\"true\" trust=\"true\">");
content.append("<fileset dir=\"" + updateSiteDir + "\">");
content.append("<include name=\"artifacts.jar\"/>");
content.append("<include name=\"content.jar\"/>");
content.append("<include name=\"site.xml\"/>");
content.append("<include name=\"category.xml\"/>");
content.append("</fileset>");
content.append("</scp>");
content.appendLineBreak();
content.append("<!-- Copy zipped version of update site to server (used by downstream builds) -->");
content.append("<scp todir=\"${env." + usernameProperty + "}:${env." + passwordProperty + "}@" + targetPath + "\" port=\"22\" sftp=\"true\" trust=\"true\">");
content.append("<fileset dir=\"" + updateSiteDir + "\">");
content.append("<include name=\"" + getUpdateSiteCompleteFileName(updateSiteID) + "\"/>");
content.append("<include name=\"" + getUpdateSiteCompleteFileName(updateSiteID) + ".MD5\"/>");
content.append("</fileset>");
content.append("</scp>");
content.appendLineBreak();
}
private String getUpdateSiteCompleteFileName(String updateSiteID) {
return updateSiteID + "-complete.zip";
}
private void addPackagePluginTasks(XMLContent content, String updateSiteID,
String updateSiteDir, boolean excludeSrc, String featureVersion,
String featureVendor, Plugin plugin) {
String pluginID = plugin.getIdentifier();
File pluginDirectory = plugin.getFile();
String pluginPath = pluginDirectory.getAbsolutePath();
String pluginVersion = updateSiteSpec.getPluginVersion(pluginID);
if (pluginVersion == null) {
pluginVersion = featureVersion;
}
String pluginVendor = updateSiteSpec.getPluginVendor(pluginID);
if (pluginVendor == null) {
pluginVendor = featureVendor;
}
String pluginName = updateSiteSpec.getPluginName(pluginID);
new PluginPackagingHelper().addUpdateManifestScript(content, plugin,
pluginVersion, pluginVendor, pluginName);
boolean isPackaged = plugin.isJarFile();
// TODO Use PluginPackagingHelper here instead?
if (isPackaged) {
content.append("<copy file=\"" + pluginPath + "\" todir=\"" + updateSiteDir + "/plugins\" />");
} else {
content.append("<jar destfile=\"" + updateSiteDir + "/plugins/" + pluginID + "_" + pluginVersion + ".v${buildid}.jar\" manifest=\"" + pluginPath + "/META-INF/MANIFEST.MF\">");
content.append("<fileset dir=\"" + pluginPath + "\">");
// TODO make this configurable or read the build.properties file for this
content.append("<exclude name=\"**/.*/**\"/>");
if (excludeSrc) {
Set<String> relativeSourceFolders = plugin.getRelativeSourceFolders();
for (String relativeSourceFolder : relativeSourceFolders) {
content.append("<exclude name=\"" + relativeSourceFolder + "/**\"/>");
}
}
content.append("</fileset>");
content.append("</jar>");
}
content.appendLineBreak();
}
}