Commit 87f1c9b1 authored by Adam Knapp's avatar Adam Knapp
Browse files

Automated JAR building feature



Change-Id: I6afaae0200c73f3eeff8ab52365b668acb2875a3
Signed-off-by: default avatarAdam Knapp <adam.knapp@sigmatechnology.se>
parent 839fe3b4
......@@ -28,7 +28,11 @@ Require-Bundle: org.eclipse.ui,
org.eclipse.core.expressions;bundle-version="3.4.600",
org.eclipse.pde.core;bundle-version="3.10.2",
org.eclipse.pde;bundle-version="3.10.1",
org.eclipse.core.commands
org.eclipse.core.commands,
org.eclipse.core.externaltools,
org.eclipse.ant.launching,
org.eclipse.jdt.launching;bundle-version="3.10.0",
org.eclipse.jdt.ui
Bundle-ActivationPolicy: lazy
Eclipse-LazyStart: true
Bundle-ClassPath: .
......
/******************************************************************************
* Copyright (c) 2000-2021 Ericsson Telecom AB
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
******************************************************************************/
package org.eclipse.titan.designer.core.ant;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.ant.launching.IAntLaunchConstants;
import org.eclipse.core.externaltools.internal.IExternalToolConstants;
import org.eclipse.core.externaltools.internal.model.ExternalToolBuilder;
import org.eclipse.core.resources.ICommand;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.Path;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationType;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.RefreshUtil;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
/**
* Utility class for generating Ant launch configuration.
* @author Adam Knapp
*/
@SuppressWarnings("restriction")
public final class AntLaunchConfigGenerator {
private static final String ANT_LAUNCH_CONFIGURATION_NAME = "JarBuilder";
private static final String ANT_LAUNCH_CONFIGURATION_EXTENSION = ".launch";
private static final String ANT_LAUNCH_CONFIGURATION_FOLDER = ".externalToolBuilders";
private static final String ANT_BUILDER_ARG1_KEY = "LaunchConfigHandle";
private static final String ANT_BUILDER_ARG1_VALUE =
"<project>/" + ANT_LAUNCH_CONFIGURATION_FOLDER + "/"
+ ANT_LAUNCH_CONFIGURATION_NAME + ANT_LAUNCH_CONFIGURATION_EXTENSION;
/**
* Adds the ANT builder to the project
* @param project Project where automated JAR export is required
*/
public static void addAntBuilder(final IProject project) throws CoreException {
final IProjectDescription description = project.getDescription();
final List<ICommand> commands = new ArrayList<ICommand>(Arrays.asList(description.getBuildSpec()));
for(ICommand command : commands) {
if (command.getBuilderName().equals(ExternalToolBuilder.ID)) {
return;
}
}
final ICommand antCommand = description.newCommand();
final Map<String, String> args = new HashMap<String, String>(1);
args.put(ANT_BUILDER_ARG1_KEY, ANT_BUILDER_ARG1_VALUE);
antCommand.setBuilderName(ExternalToolBuilder.ID);
antCommand.setBuilding(IncrementalProjectBuilder.CLEAN_BUILD, false);
antCommand.setArguments(args);
commands.add(antCommand);
description.setBuildSpec(commands.toArray(new ICommand[commands.size()]));
project.setDescription(description, null);
project.refreshLocal(IResource.DEPTH_INFINITE, null);
}
/**
* Creates a new or overwrites the existing ANT launch configuration for automated JAR export.
* @param project Project where automated JAR export is required
* @return The newly created or the overwritten ANT launch configuration. It returns {@code null},
* if the project is {@code null} or not properly set up.
* @throws CoreException
*/
public static ILaunchConfiguration createAntLaunchConfiguration(final IProject project) throws CoreException {
if (project == null) {
return null;
}
if (existAntLaunchConfiguration(project)) {
return getAntLaunchConfiguration(project);
}
final IFolder antLaunchConfigFolder = project.getFolder(new Path(ANT_LAUNCH_CONFIGURATION_FOLDER));
if (!antLaunchConfigFolder.exists()) {
antLaunchConfigFolder.create(false, true, null);
}
ILaunchConfigurationWorkingCopy wc = getAntLaunchConfigurationType().newInstance(antLaunchConfigFolder, ANT_LAUNCH_CONFIGURATION_NAME);
wc.setAttribute(IAntLaunchConstants.ATTR_ANT_MANUAL_TARGETS, "jar,");
wc.setAttribute(IAntLaunchConstants.ATTR_TARGETS_UPDATED, true);
wc.setAttribute(IAntLaunchConstants.ATTR_DEFAULT_VM_INSTALL, false);
wc.setAttribute(RefreshUtil.ATTR_REFRESH_SCOPE, "${project}");
wc.setAttribute(IDebugUIConstants.ATTR_LAUNCH_IN_BACKGROUND, false);
wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_CLASSPATH_PROVIDER, "org.eclipse.ant.ui.AntClasspathProvider");
wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_DEFAULT_CLASSPATH, true);
wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, project.getName());
wc.setAttribute(IExternalToolConstants.ATTR_BUILDER_ENABLED, true);
final String location = "${workspace_loc:/" + project.getName() + "/" + AntScriptGenerator.BUILD_XML_NAME + "}";
wc.setAttribute(IExternalToolConstants.ATTR_LOCATION, location);
wc.setAttribute(IExternalToolConstants.ATTR_RUN_BUILD_KINDS, "incremental");
wc.setAttribute(IExternalToolConstants.ATTR_TRIGGERS_CONFIGURED, true);
wc.setAttribute(IExternalToolConstants.ATTR_WORKING_DIRECTORY, "${workspace_loc:/" + project.getName() + "}");
return wc.doSave();
}
/**
* Checks whether the ANT launch configuration is already exist
* @param project Project where automated JAR export is required
* @return Return whether the ANT launch configuration is already exist
*/
public static boolean existAntLaunchConfiguration(final IProject project) {
if (project == null) {
return false;
}
final IFile antLaunchConfigFile = project.getFile(
new Path(ANT_LAUNCH_CONFIGURATION_FOLDER + File.separator
+ ANT_LAUNCH_CONFIGURATION_NAME + ANT_LAUNCH_CONFIGURATION_EXTENSION));
return antLaunchConfigFile.exists();
}
/**
* Looks for the ANT launch configuration in the specified project.
* @param project Project with ANT launch configuration
* @return Launch configuration. It returns {@code null} if the {@code project}
* is {@code null} or no proper launch configuration was found in the project.
* @throws CoreException
*/
public static ILaunchConfiguration getAntLaunchConfiguration(final IProject project) throws CoreException {
if (project == null) {
return null;
}
final IFile antLaunchConfigFile = project.getFile(
new Path(ANT_LAUNCH_CONFIGURATION_FOLDER + File.separator
+ ANT_LAUNCH_CONFIGURATION_NAME + ANT_LAUNCH_CONFIGURATION_EXTENSION));
if (antLaunchConfigFile.exists()) {
return getLaunchManager().getLaunchConfiguration(antLaunchConfigFile);
}
return null;
}
/**
* Returns the launch configuration type extension for ANT launch configurations.
* @return The launch configuration type extension for ANT launch configurations
* @see org.eclipse.debug.core.ILaunchManager#getLaunchConfigurationType
*/
public static ILaunchConfigurationType getAntLaunchConfigurationType() {
return getLaunchManager().getLaunchConfigurationType(IAntLaunchConstants.ID_ANT_BUILDER_LAUNCH_CONFIGURATION_TYPE);
}
/**
* Returns the singleton launch manager.
* @return launch manager
*/
public static ILaunchManager getLaunchManager() {
return DebugPlugin.getDefault().getLaunchManager();
}
/**
* Returns whether or not the ANT builder for the specified project is enabled.
* @param project Project with ANT builder
* @return The enabled state.
*/
public static boolean isAntBuilderEnabled(final IProject project) throws CoreException {
if (project == null) {
return false;
}
final ILaunchConfiguration config = getAntLaunchConfiguration(project);
if (config == null) {
return false;
}
return config.getAttribute(IExternalToolConstants.ATTR_BUILDER_ENABLED, false);
}
/**
* Set whether or not the ANT builder for the specified project is enabled.
* @param project Project with ANT builder
* @param enabled The enabled state.
*/
public static void setAntBuilderEnabled(final IProject project, final boolean enabled) throws CoreException {
if (project == null) {
return;
}
final ILaunchConfiguration config = getAntLaunchConfiguration(project);
if (config == null) {
return;
}
final ILaunchConfigurationWorkingCopy wc = config.getWorkingCopy();
wc.setAttribute(IExternalToolConstants.ATTR_BUILDER_ENABLED, enabled);
wc.doSave();
}
}
/******************************************************************************
* Copyright (c) 2000-2021 Ericsson Telecom AB
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
******************************************************************************/
package org.eclipse.titan.designer.core.ant;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.ArrayList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationType;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.jarpackagerfat.FatJarRsrcUrlBuilder;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jdt.launching.IRuntimeClasspathEntry;
import org.eclipse.jdt.launching.JavaRuntime;
import org.eclipse.titan.common.logging.ErrorReporter;
import org.eclipse.titan.common.path.PathUtil;
import org.eclipse.titan.common.utils.CommentUtils;
import org.eclipse.titan.common.utils.StringUtils;
import org.eclipse.titan.designer.GeneralConstants;
import org.eclipse.titan.designer.compiler.ProjectSourceCompiler;
import org.eclipse.titan.designer.properties.data.MakefileCreationData;
import org.eclipse.titan.designer.properties.data.ProjectBuildPropertyData;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* Utility class for generating Ant script for building JAR.
* @author Adam Knapp
*/
@SuppressWarnings("restriction")
public final class AntScriptGenerator {
public static final String BUILD_XML_NAME = "jarbuild.xml";
private static final String BUILD_TARGET = "jar";
private static final String REQUIRED_ANT_VERSION_TEXT = "ANT 1.7 is required";
private static final String INDENT_SPACES = " ";
private static final String DOUBLE_INDENT_SPACES = INDENT_SPACES + INDENT_SPACES;
/** @see org.eclipse.jdt.internal.ui.jarpackagerfat.JIJConstants */
private static final String REDIRECTED_CLASS_PATH_MANIFEST_NAME = "Rsrc-Class-Path";
/** @see org.eclipse.jdt.internal.ui.jarpackagerfat.JIJConstants */
private static final String REDIRECTED_MAIN_CLASS_MANIFEST_NAME = "Rsrc-Main-Class";
/** @see org.eclipse.jdt.internal.ui.jarpackagerfat.JIJConstants */
private static final String CURRENT_DIR = "./";
/** @see org.eclipse.jdt.internal.ui.jarpackagerfat.JIJConstants */
private static final String LOADER_MAIN_CLASS = "org.eclipse.jdt.internal.jarinjarloader.JarRsrcLoader";
/** @see org.eclipse.jdt.internal.ui.jarpackagerfat.FatJarAntExporter */
private static class SourceInfo {
public final boolean isJar;
public final String absPath;
public SourceInfo(boolean isJar, String absPath) {
this.isJar = isJar;
this.absPath = absPath;
}
}
/**
* Converts the array of class paths into array of SourceInfo objects
* @param classpath Array of class paths
* @return Array of SourceInfo objects
*/
private static SourceInfo[] convert(IPath[] classpath) {
SourceInfo[] result = new SourceInfo[classpath.length];
for (int i = 0; i < classpath.length; i++) {
IPath path = classpath[i];
if (path != null) {
if (path.toFile().isDirectory()) {
result[i] = new SourceInfo(false, path.toString());
} else if (path.toFile().isFile() && path.getFileExtension().equals("jar")) {
result[i] = new SourceInfo(true, path.toString());
}
}
}
return result;
}
/**
* Copies the {@code jar-in-jar-loader.zip} file into the project's {@code java_bin} folder
* @param project Project where {@code jar-in-jar-loader.zip} is required
* @throws IOException
*/
private static void copyJarInJarLoader(IProject project) throws IOException {
final String pathString = GeneralConstants.JAVA_BUILD_DIR + File.separator + FatJarRsrcUrlBuilder.JAR_RSRC_LOADER_ZIP;
final IFile zipFile = project.getFile(new Path(pathString));
if (zipFile.exists()) {
return;
}
final URI zipURI = URIUtil.toURI(zipFile.getLocation());
if (zipURI == null) {
throw new IOException("Path error: " + pathString);
}
InputStream is = JavaPlugin.getDefault().getBundle().getEntry(FatJarRsrcUrlBuilder.JAR_RSRC_LOADER_ZIP).openStream();
OutputStream os = new FileOutputStream(new File(zipURI));
byte[] buf = new byte[1024];
while (true) {
int cnt = is.read(buf);
if (cnt <= 0)
break;
os.write(buf, 0, cnt);
}
os.close();
}
/**
* Checks whether the jarbuild.xml is already exist
* @param project Project where jarbuild.xml is required
* @return Returns whether the jarbuild.xml is already exist
*/
public static boolean existsBuildXML(final IProject project) {
if (project == null) {
return false;
}
final IFile buildFile = project.getFile(BUILD_XML_NAME);
return buildFile.exists();
}
/**
* Searches for a launch configuration of Java application type that is related to the specified project.
* The launch configuration is used to get the class paths.
* @param project Project where the launch configuration is looked for
* @return The launch configuration of Java application type
* @throws CoreException
*/
private static ILaunchConfiguration findLaunchConfiguration(final IProject project) throws CoreException {
ILaunchConfigurationType type = DebugPlugin.getDefault().getLaunchManager().
getLaunchConfigurationType(IJavaLaunchConfigurationConstants.ID_JAVA_APPLICATION);
ILaunchConfiguration[] configs = DebugPlugin.getDefault().getLaunchManager().getLaunchConfigurations(type);
for (ILaunchConfiguration config : configs) {
if (project.getName().equals(config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, ""))) {
return config;
}
}
return null;
}
/**
* Creates and then stores the {@code jarbuild.xml} ANT script for the specified project's root
* @param project Project where the ANT script is required
* @return {@code true} if the generation was completed or {@code false} if error occurred during the process
*/
public static boolean generateAndStoreBuildXML(final IProject project) {
if (project == null) {
return false;
}
if (existsBuildXML(project)) {
return true;
}
try {
final Document content = generateBuildXML(project);
if (content == null) {
return false;
}
storeBuildXML(project, content);
copyJarInJarLoader(project);
} catch (CoreException e) {
ErrorReporter.logExceptionStackTrace(e);
return false;
} catch (IOException e) {
ErrorReporter.logExceptionStackTrace(e);
return false;
}
return true;
}
/**
* Generates the {@code jarbuild.xml} ANT script for the specified project
* @param project Project where the ANT script is required
* @return DOM document containing the ANT script
* @throws CoreException
* @see org.eclipse.jdt.internal.ui.jarpackagerfat.FatJarRsrcUrlAntExporter#buildANTScript
*/
public static Document generateBuildXML(final IProject project) throws CoreException {
if (project == null) {
return null;
}
final String jarPathString = project.getPersistentProperty(new QualifiedName(ProjectBuildPropertyData.QUALIFIER,
MakefileCreationData.TARGET_EXECUTABLE_PROPERTY));
if (StringUtils.isNullOrEmpty(jarPathString)) {
ErrorReporter.INTERNAL_ERROR("Jar file is null or empty");
return null;
}
final File jarFile = new File(jarPathString);
final String jarFolder = jarFile.getParent();
final String jarFileName = jarFile.getName();
if (StringUtils.isNullOrEmpty(jarFolder) || StringUtils.isNullOrEmpty(jarFileName)) {
ErrorReporter.INTERNAL_ERROR("Jar file is null or empty");
return null;
}
final ILaunchConfiguration config = findLaunchConfiguration(project);
if (config == null) {
ErrorReporter.parallelErrorDisplayInMessageDialog(
"Error while generating the ANT script 'jarbuild.xml' for project",
"No suitable launch configuration is found! Create launch configuration by " +
"selecting 'Run As' in the pop up menu");
return null;
}
final SourceInfo[] sourceInfos = convert(getClasspath(config));
DocumentBuilder docBuilder = null;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
try {
docBuilder = factory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
ErrorReporter.INTERNAL_ERROR("Could not get XML builder");
return null;
}
Document document = docBuilder.newDocument();
final String headerComment = CommentUtils.getHeaderComments(" ", GeneralConstants.VERSION_STRING).replace("for", DOUBLE_INDENT_SPACES + "for")
+ "\n " + DOUBLE_INDENT_SPACES + REQUIRED_ANT_VERSION_TEXT + "\n " + DOUBLE_INDENT_SPACES + CommentUtils.DO_NOT_EDIT_TEXT + " ";
Node comment = document.createComment(headerComment);
document.appendChild(comment);
Element projectElement = document.createElement("project");
projectElement.setAttribute("name", project.getName());
projectElement.setAttribute("default", BUILD_TARGET);
projectElement.setAttribute("basedir", ".");
projectElement.appendChild(comment);
document.appendChild(projectElement);
Element property = document.createElement("property");
property.setAttribute("name", "project");
property.setAttribute("value", project.getName());
projectElement.appendChild(property);
property = document.createElement("property");
property.setAttribute("name", "dir.build");
property.setAttribute("value", GeneralConstants.JAVA_BUILD_DIR);
projectElement.appendChild(property);
property = document.createElement("property");
property.setAttribute("name", "dir.jar");
property.setAttribute("value", jarFolder);
projectElement.appendChild(property);
property = document.createElement("property");
property.setAttribute("name", "version");
property.setAttribute("value", "1.0");
projectElement.appendChild(property);
property = document.createElement("property");
property.setAttribute("name", "main-class");
property.setAttribute("value", ProjectSourceCompiler.getPackageGeneratedRoot(project) + ".Parallel_main");
projectElement.appendChild(property);
Element target = document.createElement("target");
target.setAttribute("name", BUILD_TARGET);
target.setAttribute("description", "generate the JAR file");
projectElement.appendChild(target);
Element buildNumber = document.createElement("buildnumber");
target.appendChild(buildNumber);
comment = document.createComment(" Create the directory for JAR ");
target.appendChild(comment);
Element makeDir = document.createElement("mkdir");
makeDir.setAttribute("dir", "${dir.jar}");
target.appendChild(makeDir);
final String parametrizedJarString = "${dir.jar}/" + jarFileName;
Element jar = document.createElement("jar");
jar.setAttribute("destfile", parametrizedJarString);
target.appendChild(jar);
Element manifest = document.createElement("manifest");
jar.appendChild(manifest);
Element attribute = document.createElement("attribute");
attribute.setAttribute("name", "Main-Class");
attribute.setAttribute("value", LOADER_MAIN_CLASS);
manifest.appendChild(attribute);
attribute = document.createElement("attribute");
attribute.setAttribute("name", REDIRECTED_MAIN_CLASS_MANIFEST_NAME);
attribute.setAttribute("value", "${main-class}");
manifest.appendChild(attribute);
attribute = document.createElement("attribute");
attribute.setAttribute("name", "Class-Path");
attribute.setAttribute("value", ".");
manifest.appendChild(attribute);
attribute = document.createElement("attribute");
attribute.setAttribute("name", REDIRECTED_CLASS_PATH_MANIFEST_NAME);
StringBuilder rsrcClassPath= new StringBuilder();
rsrcClassPath.append(CURRENT_DIR);
for (SourceInfo sourceInfo : sourceInfos) {
if (sourceInfo.isJar) {
rsrcClassPath.append(" ").append(new File(sourceInfo.absPath).getName());
}
}
attribute.setAttribute("value", rsrcClassPath.toString());
manifest.appendChild(attribute);
Element zipfileset = document.createElement("zipfileset");
zipfileset.setAttribute("src", "${dir.build}/" + FatJarRsrcUrlBuilder.JAR_RSRC_LOADER_ZIP);
jar.appendChild(zipfileset);
for (SourceInfo sourceInfo : sourceInfos) {
if (sourceInfo.isJar) {
final File sourceJarFile = new File(sourceInfo.absPath);
Element fileset = document.createElement("zipfileset");
fileset.setAttribute("dir", PathUtil.getRelativePath(project.getLocation().toOSString(), sourceJarFile.getParent()));
fileset.setAttribute("includes", sourceJarFile.getName());
jar.appendChild(fileset);
} else {
Element fileset = document.createElement("fileset");
fileset.setAttribute("dir", PathUtil.getRelativePath(project.getLocation().toOSString(), sourceInfo.absPath));
jar.appendChild(fileset);
}
}
return document;
}
/**
* Gets the class paths based on the specified launch configuration
* @param configuration Launch configuration of Java application type