/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.osgi.jps.build;
import aQute.bnd.osgi.Constants;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.builders.logging.ProjectBuilderLogger;
import org.jetbrains.jps.incremental.CompileContext;
import org.jetbrains.jps.incremental.messages.BuildMessage;
import org.jetbrains.jps.incremental.messages.CompilerMessage;
import org.jetbrains.jps.incremental.messages.DoneSomethingNotification;
import org.jetbrains.jps.incremental.messages.ProgressMessage;
import org.jetbrains.jps.model.java.JpsJavaExtensionService;
import org.jetbrains.jps.model.module.JpsDependencyElement;
import org.jetbrains.jps.model.module.JpsModule;
import org.jetbrains.jps.model.module.JpsModuleDependency;
import org.jetbrains.jps.model.module.JpsModuleSourceRoot;
import org.jetbrains.osgi.jps.model.JpsOsmorcExtensionService;
import org.jetbrains.osgi.jps.model.JpsOsmorcModuleExtension;
import org.jetbrains.osgi.jps.model.ManifestGenerationMode;
import org.jetbrains.osgi.jps.model.OsmorcJarContentEntry;
import org.jetbrains.osgi.jps.model.impl.JpsOsmorcModuleExtensionImpl;
import org.jetbrains.osgi.jps.util.OsgiBuildUtil;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import static com.intellij.util.ObjectUtils.coalesce;
public class OsgiBuildSession implements Reporter {
private static final Logger LOG = Logger.getInstance(OsgiBuildSession.class);
private OsmorcBuildTarget myTarget;
private CompileContext myContext;
private JpsOsmorcModuleExtension myExtension;
private JpsModule myModule;
private String myMessagePrefix;
private File myOutputJarFile;
private Collection<File> myOutputJarFiles;
private File myModuleOutputDir;
private File[] myClasses;
private File[] mySources;
private BndWrapper myBndWrapper;
private String mySourceToReport = null;
public void build(@NotNull OsmorcBuildTarget target, @NotNull CompileContext context) throws IOException {
myTarget = target;
myContext = context;
myExtension = target.getExtension();
myModule = target.getModule();
myMessagePrefix = "[" + myModule.getName() + "] ";
progress("Building OSGi bundle");
try {
prepare();
doBuild();
}
catch (OsgiBuildException e) {
error(e.getMessage(), e.getCause(), e.getSourcePath(), -1);
return;
}
for (File jarFile : myOutputJarFiles) {
if (!jarFile.exists()) {
error("Bundle was not built: " + jarFile, null, null, -1);
return;
}
}
ProjectBuilderLogger logger = context.getLoggingManager().getProjectBuilderLogger();
if (logger.isEnabled()) {
logger.logCompiledFiles(myOutputJarFiles, OsmorcBuilder.ID, "Built OSGi bundles:");
}
context.processMessage(DoneSomethingNotification.INSTANCE);
}
private void prepare() throws OsgiBuildException {
myModuleOutputDir = JpsJavaExtensionService.getInstance().getOutputDirectory(myModule, false);
if (myModuleOutputDir == null) {
throw new OsgiBuildException("Unable to determine the compiler output path for the module.");
}
String jarFileLocation = myExtension.getJarFileLocation();
if (jarFileLocation.isEmpty()) {
throw new OsgiBuildException("Bundle path is empty - please check OSGi facet settings.");
}
myOutputJarFile = new File(jarFileLocation);
myOutputJarFiles = myTarget.getOutputRoots(myContext);
for (File jarFile : myOutputJarFiles) {
if (!FileUtil.delete(jarFile)) {
throw new OsgiBuildException("Can't delete bundle file '" + jarFile + "'.");
}
}
if (!FileUtil.createParentDirs(myOutputJarFile)) {
throw new OsgiBuildException("Cannot create a directory for bundles '" + myOutputJarFile.getParent() + "'.");
}
List<File> classes = ContainerUtil.newSmartList();
if (myModuleOutputDir.exists()) {
classes.add(myModuleOutputDir);
}
for (JpsDependencyElement dependency : myModule.getDependenciesList().getDependencies()) {
if (dependency instanceof JpsModuleDependency) {
JpsModule module = ((JpsModuleDependency)dependency).getModule();
if (module != null && JpsOsmorcExtensionService.getExtension(module) == null) {
File outputDir = JpsJavaExtensionService.getInstance().getOutputDirectory(module, false);
if (outputDir != null && outputDir.exists()) {
classes.add(outputDir);
}
}
}
}
myClasses = classes.isEmpty() ? ArrayUtil.EMPTY_FILE_ARRAY : classes.toArray(new File[classes.size()]);
List<File> sources = ContainerUtil.newSmartList();
for (JpsModuleSourceRoot sourceRoot : myModule.getSourceRoots()) {
File sourceDir = sourceRoot.getFile();
if (sourceDir.exists()) {
sources.add(sourceDir);
}
}
mySources = sources.isEmpty() ? ArrayUtil.EMPTY_FILE_ARRAY : sources.toArray(new File[sources.size()]);
myBndWrapper = new BndWrapper(this);
}
private void doBuild() throws OsgiBuildException {
progress("Running Bnd to build the bundle");
if (myExtension.isUseBndFile()) {
String bndPath = myExtension.getBndFileLocation();
File bndFile = OsgiBuildUtil.findFileInModuleContentRoots(myModule, bndPath);
if (bndFile == null || !bndFile.isFile()) {
throw new OsgiBuildException("Bnd file missing '" + bndPath + "' - please check OSGi facet settings.");
}
mySourceToReport = bndFile.getAbsolutePath();
try {
myBndWrapper.build(bndFile, myClasses, mySources, myOutputJarFile);
}
catch (Exception e) {
throw new OsgiBuildException("Unexpected build error", e, null);
}
mySourceToReport = null;
}
else if (myExtension.isUseBundlorFile()) {
String bundlorPath = myExtension.getBundlorFileLocation();
File bundlorFile = OsgiBuildUtil.findFileInModuleContentRoots(myModule, bundlorPath);
if (bundlorFile == null) {
throw new OsgiBuildException("Bundlor file missing '" + bundlorPath + "' - please check OSGi facet settings.");
}
File tempFile = new File(myOutputJarFile.getAbsolutePath() + ".tmp.jar");
try {
Map<String, String> properties = Collections.singletonMap(Constants.CREATED_BY, "IntelliJ IDEA / OSGi Plugin");
myBndWrapper.build(properties, myClasses, mySources, tempFile);
}
catch (Exception e) {
throw new OsgiBuildException("Unexpected build error", e, null);
}
progress("Running Bundlor to calculate the manifest");
try {
Properties properties = OsgiBuildUtil.getMavenProjectProperties(myContext, myModule);
List<String> warnings = new BundlorWrapper().wrapModule(properties, tempFile, myOutputJarFile, bundlorFile);
for (String warning : warnings) {
warning(warning, null, bundlorFile.getPath(), -1);
}
}
finally {
if (!FileUtil.delete(tempFile)) {
warning("Can't delete temporary file '" + tempFile + "'", null, null, -1);
}
}
}
else if (myExtension.isManifestManuallyEdited() || myExtension.isOsmorcControlsManifest()) {
Map<String, String> buildProperties = getBuildProperties();
if (LOG.isDebugEnabled()) {
LOG.debug("build properties: " + buildProperties);
}
mySourceToReport = getSourceFileToReport();
try {
myBndWrapper.build(buildProperties, myClasses, mySources, myOutputJarFile);
}
catch (Exception e) {
throw new OsgiBuildException("Unexpected build error", e, null);
}
mySourceToReport = null;
}
else {
ManifestGenerationMode mode = ((JpsOsmorcModuleExtensionImpl)myExtension).getProperties().myManifestGenerationMode;
throw new OsgiBuildException("Internal error (unknown build method `" + mode + "`)");
}
}
@NotNull
private Map<String, String> getBuildProperties() throws OsgiBuildException {
Map<String, String> properties = ContainerUtil.newHashMap();
// defaults (similar to Maven)
properties.put(Constants.IMPORT_PACKAGE, "*");
properties.put(Constants.REMOVEHEADERS, Constants.INCLUDE_RESOURCE + ',' + Constants.PRIVATE_PACKAGE);
// user settings
if (myExtension.isOsmorcControlsManifest()) {
properties.putAll(myExtension.getAdditionalProperties());
properties.put(Constants.BUNDLE_SYMBOLICNAME, myExtension.getBundleSymbolicName());
properties.put(Constants.BUNDLE_VERSION, myExtension.getBundleVersion());
String activator = myExtension.getBundleActivator();
if (!StringUtil.isEmptyOrSpaces(activator)) {
properties.put(Constants.BUNDLE_ACTIVATOR, activator);
}
}
else {
File manifestFile = myExtension.getManifestFile();
if (manifestFile == null) {
throw new OsgiBuildException("Manifest file '" + myExtension.getManifestLocation() + "' missing - please check OSGi facet settings.");
}
properties.put(Constants.MANIFEST, manifestFile.getAbsolutePath());
}
// resources
List<String> resources = ContainerUtil.newSmartList();
if (myExtension.isOsmorcControlsManifest()) {
String custom = properties.get(Constants.INCLUDE_RESOURCE);
if (custom != null) {
resources.add(custom);
}
}
for (OsmorcJarContentEntry contentEntry : myExtension.getAdditionalJarContents()) {
resources.add(contentEntry.myDestination + '=' + contentEntry.mySource);
}
if (myExtension.isManifestManuallyEdited()) {
resources.add(myModuleOutputDir.getPath());
}
if (!resources.isEmpty()) {
properties.put(Constants.INCLUDE_RESOURCE, StringUtil.join(resources, ","));
}
String pattern = myExtension.getIgnoreFilePattern();
if (!StringUtil.isEmptyOrSpaces(pattern)) {
try {
//noinspection ResultOfMethodCallIgnored
Pattern.compile(pattern);
}
catch (PatternSyntaxException e) {
throw new OsgiBuildException("The file ignore pattern is invalid - please check OSGi facet settings.");
}
properties.put(Constants.DONOTCOPY, pattern);
}
if (myExtension.isOsmorcControlsManifest()) {
// support the {local-packages} instruction
progress("Calculating local packages");
LocalPackageCollector.addLocalPackages(myModuleOutputDir, properties);
}
return properties;
}
private String getSourceFileToReport() {
if (myExtension.isManifestManuallyEdited()) {
File manifestFile = myExtension.getManifestFile();
if (manifestFile != null) {
return manifestFile.getPath();
}
}
else {
File mavenProjectFile = OsgiBuildUtil.getMavenProjectPath(myContext, myModule);
if (mavenProjectFile != null) {
return mavenProjectFile.getPath();
}
}
return null;
}
@Override
public void progress(@NotNull String message) {
myContext.processMessage(new ProgressMessage(myMessagePrefix + message));
}
@Override
public void warning(@NotNull String message, @Nullable Throwable t, @Nullable String sourcePath, int lineNum) {
process(BuildMessage.Kind.WARNING, message, t, sourcePath, lineNum);
}
@Override
public void error(@NotNull String message, @Nullable Throwable t, @Nullable String sourcePath, int lineNum) {
process(BuildMessage.Kind.ERROR, message, t, sourcePath, lineNum);
}
private void process(BuildMessage.Kind kind, String text, Throwable t, String path, int line) {
LOG.warn(text, t);
myContext.processMessage(new CompilerMessage(OsmorcBuilder.ID, kind, myMessagePrefix + text, coalesce(path, mySourceToReport), -1, -1, -1, line, -1));
}
@Override
public boolean isDebugEnabled() {
return LOG.isDebugEnabled();
}
@Override
public void debug(@NotNull String message) {
LOG.debug(message);
}
@Override
public String setReportSource(String source) {
String prevSource = mySourceToReport;
mySourceToReport = source;
return prevSource;
}
}