/******************************************************************************* * Copyright (c) 2012-2015 Codenvy, S.A. * 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: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.builder.maven; import org.eclipse.che.api.builder.BuilderException; import org.eclipse.che.api.builder.dto.BuildRequest; import org.eclipse.che.api.builder.dto.BuilderEnvironment; import org.eclipse.che.api.builder.internal.BuildLogger; import org.eclipse.che.api.builder.internal.BuildResult; import org.eclipse.che.api.builder.internal.Builder; import org.eclipse.che.api.builder.internal.BuilderConfiguration; import org.eclipse.che.api.builder.internal.BuilderTaskType; import org.eclipse.che.api.builder.internal.Constants; import org.eclipse.che.api.builder.internal.DelegateBuildLogger; import org.eclipse.che.api.builder.internal.SourceManagerEvent; import org.eclipse.che.api.builder.internal.SourceManagerListener; import org.eclipse.che.api.builder.internal.SourcesManager; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.core.util.CommandLine; import org.eclipse.che.commons.json.JsonHelper; import org.eclipse.che.commons.lang.IoUtil; import org.eclipse.che.commons.lang.ZipUtils; import org.eclipse.che.dto.server.DtoFactory; import org.eclipse.che.ide.maven.tools.MavenArtifact; import org.eclipse.che.ide.maven.tools.MavenUtils; import org.eclipse.che.ide.maven.tools.Model; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import java.io.BufferedReader; import java.io.FilenameFilter; import java.io.IOException; import java.io.Writer; import java.nio.charset.Charset; import java.nio.file.Files; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; /** * Builder based on Maven. * * @author andrew00x * @author Eugene Voevodin */ @Singleton public class MavenBuilder extends Builder { private static final Logger LOG = LoggerFactory.getLogger(MavenBuilder.class); /** Rules for builder assembly plugin. Use it for create jar with included dependencies */ public static final String ASSEMBLY_DESCRIPTOR_FOR_JAR_WITH_DEPENDENCIES = "<assembly>\n" + " <id>jar-with-dependencies</id>\n" + " <formats>\n" + " <format>zip</format>\n" + " </formats>\n" + " <includeBaseDirectory>false</includeBaseDirectory>\n" + " <dependencySets>\n" + " <dependencySet>\n" + " <outputDirectory>/lib</outputDirectory>\n" + " <unpack>false</unpack>\n" + " <useProjectArtifact>false</useProjectArtifact>\n" + " </dependencySet>\n" + " </dependencySets>\n" + " <files>\n" + " <file>\n" + " <source>${project.build.directory}/${project.build.finalName}.jar</source>\n" + " <outputDirectory>/</outputDirectory>\n" + " <destName>application.jar</destName>\n" + " </file>\n" + " </files>\n" + "</assembly>\n"; private static final String ASSEMBLY_DESCRIPTOR_FOR_JAR_WITH_DEPENDENCIES_FILE = "jar-with-dependencies-assembly-descriptor.xml"; private static final String DEPENDENCIES_JSON_FILE = "dependencies.json"; private static final FilenameFilter SOURCES_AND_DOCS_FILTER = new FilenameFilter() { @Override public boolean accept(java.io.File dir, String name) { return !(name.endsWith("-sources.jar") || name.endsWith("-javadoc.jar")); } }; private final Map<String, String> mavenProperties; @Inject public MavenBuilder(@Named(Constants.BASE_DIRECTORY) java.io.File rootDirectory, @Named(Constants.NUMBER_OF_WORKERS) int numberOfWorkers, @Named(Constants.QUEUE_SIZE) int queueSize, @Named(Constants.KEEP_RESULT_TIME) int cleanupTime, EventService eventService) { super(rootDirectory, numberOfWorkers, queueSize, cleanupTime, eventService); Map<String, String> myMavenProperties = null; try { myMavenProperties = MavenUtils.getMavenVersionInformation(); } catch (IOException e) { LOG.error(e.getMessage(), e); } if (myMavenProperties == null) { mavenProperties = Collections.emptyMap(); } else { mavenProperties = Collections.unmodifiableMap(myMavenProperties); } } @Override public String getName() { return "maven"; } @Override public String getDescription() { return "Apache Maven based builder implementation"; } @Override public Map<String, BuilderEnvironment> getEnvironments() { final Map<String, BuilderEnvironment> env = new HashMap<>(4); final Map<String, String> properties = new HashMap<>(mavenProperties); properties.remove("Maven home"); properties.remove("Java home"); final BuilderEnvironment def = DtoFactory.getInstance().createDto(BuilderEnvironment.class) .withId("default") .withIsDefault(true) .withDisplayName(properties.get("Maven version")) .withProperties(properties); env.put(def.getId(), def); return env; } @Override protected CommandLine createCommandLine(BuilderConfiguration config) throws BuilderException { final CommandLine commandLine = new CommandLine(MavenUtils.getMavenExecCommand()); commandLine.add("--batch-mode"); final List<String> targets = config.getTargets(); final java.io.File workDir = config.getWorkDir(); switch (config.getTaskType()) { case DEFAULT: if (!targets.isEmpty()) { commandLine.add(targets); } else { commandLine.add("clean", "install"); } if (((BuildRequest)config.getRequest()).isSkipTest()) { commandLine.add("-Dmaven.test.skip"); } if (config.getRequest().isIncludeDependencies()) { // Project sources isn't available yet. Postpone parsing of pom.xml file until sources becomes available. final SourcesManager sourcesManager = getSourcesManager(); final SourceManagerListener sourceListener = new SourceManagerListener() { @Override public void afterDownload(SourceManagerEvent event) { if (workDir.equals(event.getWorkDir())) { try { final Model model = Model.readFrom(workDir); final String packaging = model.getPackaging(); if ((packaging == null || "jar".equals(packaging)) && !MavenUtils.isCodenvyExtensionProject(model)) { addJarWithDependenciesAssemblyDescriptor(workDir, commandLine); } } catch (Exception e) { throw new IllegalStateException(e); } finally { sourcesManager.removeListener(this); } } } }; sourcesManager.addListener(sourceListener); } break; case LIST_DEPS: if (!targets.isEmpty()) { LOG.warn("Targets {} ignored when list dependencies", targets); } commandLine.add("clean", "dependency:list"); break; case COPY_DEPS: if (!targets.isEmpty()) { LOG.warn("Targets {} ignored when copy dependencies", targets); } commandLine.add("clean", "dependency:copy-dependencies").addPair("-Dmdep.failOnMissingClassifierArtifact", "false"); break; } commandLine.add(config.getOptions()); return commandLine; } private void addJarWithDependenciesAssemblyDescriptor(java.io.File workDir, CommandLine commandLine) throws IOException { Files.write(new java.io.File(workDir, ASSEMBLY_DESCRIPTOR_FOR_JAR_WITH_DEPENDENCIES_FILE).toPath(), ASSEMBLY_DESCRIPTOR_FOR_JAR_WITH_DEPENDENCIES.getBytes()); commandLine.add("assembly:single"); commandLine.addPair("-Ddescriptor", ASSEMBLY_DESCRIPTOR_FOR_JAR_WITH_DEPENDENCIES_FILE); } @Override protected BuildResult getTaskResult(FutureBuildTask task, boolean successful) throws BuilderException { if (!successful) { return new BuildResult(false, getBuildReport(task)); } if (!isMavenTaskSuccess(task)) { return new BuildResult(false, getBuildReport(task)); } final BuilderConfiguration config = task.getConfiguration(); final java.io.File workDir = config.getWorkDir(); final BuildResult result = new BuildResult(true, getBuildReport(task)); java.io.File[] files = null; switch (config.getTaskType()) { case DEFAULT: final Model model; try { model = Model.readFrom(workDir); } catch (IOException e) { throw new BuilderException(e); } String packaging = model.getPackaging(); if (packaging == null) { packaging = "jar"; } if (packaging.equals("pom")) { final List<Model> modules; final List<java.io.File> results = new LinkedList<>(); try { modules = MavenUtils.getModules(workDir); } catch (IOException e) { throw new BuilderException(e); } for (Model child : modules) { String childPackaging = child.getPackaging(); if (childPackaging == null) { childPackaging = "jar"; } final String fileExt; String ext = MavenUtils.getFileExtensionByPackaging(childPackaging); if (ext == null) { ext = '.' + childPackaging; } fileExt = ext; final java.io.File[] a = new java.io.File(child.getProjectDirectory(), "target").listFiles(new FilenameFilter() { @Override public boolean accept(java.io.File dir, String name) { return SOURCES_AND_DOCS_FILTER.accept(dir, name) && name.endsWith(fileExt); } }); if (a != null && a.length > 0) { Collections.addAll(results, a); } } files = results.toArray(new java.io.File[results.size()]); } else { final String fileExt; String ext = MavenUtils.getFileExtensionByPackaging(packaging); if (ext == null) { ext = packaging.equals("jar") && config.getRequest().isIncludeDependencies() && !MavenUtils.isCodenvyExtensionProject(model) ? "jar-with-dependencies.zip" : ('.' + packaging); } fileExt = ext; files = new java.io.File(workDir, "target").listFiles(new FilenameFilter() { @Override public boolean accept(java.io.File dir, String name) { return SOURCES_AND_DOCS_FILTER.accept(dir, name) && name.endsWith(fileExt); } }); } if (files.length == 0) { files = new java.io.File(workDir, "target").listFiles(SOURCES_AND_DOCS_FILTER); } break; case LIST_DEPS: files = workDir.listFiles(new FilenameFilter() { @Override public boolean accept(java.io.File dir, String name) { return name.equals(DEPENDENCIES_JSON_FILE); } }); break; case COPY_DEPS: final java.io.File target = new java.io.File(workDir, "target"); final java.io.File dependencies = new java.io.File(target, "dependency"); if (dependencies.isDirectory() && dependencies.list().length > 0) { final java.io.File zip = new java.io.File(target, "dependencies.zip"); try { ZipUtils.zipDir(dependencies.getAbsolutePath(), dependencies, zip, IoUtil.ANY_FILTER); } catch (IOException e) { throw new BuilderException(e); } files = new java.io.File[]{zip}; } break; } if (files != null && files.length > 0) { Collections.addAll(result.getResults(), files); } return result; } private boolean isMavenTaskSuccess(FutureBuildTask task) throws BuilderException { boolean mavenSuccess = false; BufferedReader logReader = null; try { logReader = new BufferedReader(task.getBuildLogger().getReader()); String line; while ((line = logReader.readLine()) != null) { line = MavenUtils.removeLoggerPrefix(line); if ("BUILD SUCCESS".equals(line)) { mavenSuccess = true; break; } } } catch (IOException e) { throw new BuilderException(e); } finally { if (logReader != null) { try { logReader.close(); } catch (IOException ignored) { } } } return mavenSuccess; } /** * Get build report. By default show link to the surefire reports. * * @param task * task * @return report or {@code null} if surefire reports is not available */ protected java.io.File getBuildReport(FutureBuildTask task) { final java.io.File dir = task.getConfiguration().getWorkDir(); final String reports = "target" + java.io.File.separatorChar + "surefire-reports"; final java.io.File reportsDir = new java.io.File(dir, reports); return reportsDir.exists() ? reportsDir : null; } @Override protected BuildLogger createBuildLogger(BuilderConfiguration configuration, java.io.File logFile) throws BuilderException { final BuildLogger buildLogger = super.createBuildLogger(configuration, logFile); if (configuration.getTaskType() == BuilderTaskType.LIST_DEPS) { // collect dependencies in json file return new DependencyBuildLogger(buildLogger, new java.io.File(configuration.getWorkDir(), DEPENDENCIES_JSON_FILE)); } return buildLogger; } private static class DependencyBuildLogger extends DelegateBuildLogger { final java.io.File jsonFile; boolean dependencyStarted; List<MavenArtifact> dependencies; DependencyBuildLogger(BuildLogger buildLogger, java.io.File jsonFile) { super(buildLogger); this.jsonFile = jsonFile; this.dependencies = new LinkedList<>(); } @Override public void writeLine(String line) throws IOException { if (line != null) { final String trimmed = MavenUtils.removeLoggerPrefix(line); if (dependencyStarted) { if (trimmed.isEmpty()) { dependencyStarted = false; try (Writer writer = Files.newBufferedWriter(jsonFile.toPath(), Charset.forName("UTF-8"))) { writer.write(JsonHelper.toJson(dependencies)); } } else { final MavenArtifact artifact = MavenUtils.parseMavenArtifact(line); if (artifact != null) { dependencies.add(artifact); } } } else if ("The following files have been resolved:".equals(trimmed)) { dependencyStarted = true; } } super.writeLine(line); } } }