/* * Copyright 2003-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 jetbrains.mps.project.facets; import jetbrains.mps.extapi.module.ModuleFacetBase; import jetbrains.mps.project.AbstractModule; import jetbrains.mps.project.ProjectPathUtil; import jetbrains.mps.project.Solution; import jetbrains.mps.project.structure.modules.ModuleDescriptor; import jetbrains.mps.smodel.Generator; import jetbrains.mps.vfs.IFile; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; /** * todo: divide into two parts: JavaModuleFacetSrcImpl && JavaModuleFacetPackagedImpl */ public class JavaModuleFacetImpl extends ModuleFacetBase implements JavaModuleFacet { private static final Logger LOG = LogManager.getLogger(JavaModuleFacetImpl.class); public JavaModuleFacetImpl() { super(FACET_TYPE); } @Override public String getFacetPresentation() { return "Java"; } @Override public boolean isCompileInMps() { AbstractModule module = getModule(); if (module instanceof Generator) { return true; } ModuleDescriptor descriptor = module.getModuleDescriptor(); return descriptor != null && descriptor.getCompileInMPS(); } @NotNull @Override public AbstractModule getModule() { return (AbstractModule) super.getModule(); } @Override @Nullable public IFile getClassesGen() { if (getModule().isPackaged()) { return null; } ModuleDescriptor moduleDescriptor = getModule().getModuleDescriptor(); if (moduleDescriptor == null) { // this facet implementation doesn't know how to handle modules not based on ModuleDescriptor return null; } if (moduleDescriptor.getDeploymentDescriptor() != null) { // in fact, this is what isPackaged() shall check (according to its javadoc), but at the moment it cares about // module source dir not being in archive, which is not exactly the same, hence extra check here. return null; } // XXX there's same code in MM, shall refactor, likely move to ModuleDescriptor String sourceGenPath = ProjectPathUtil.getGeneratorOutputPath(moduleDescriptor); if (sourceGenPath == null) { // a kind of a module without generated sources, no classes_gen then. return null; } // XXX would adore IFile from ModuleDescriptor, not String. return getModule().getFileSystem().getFile(sourceGenPath).getParent().getDescendant("classes_gen"); } @Override public Set<String> getLibraryClassPath() { Set<String> libraryClassPath = new LinkedHashSet<String>(); // add additional java stub paths ModuleDescriptor moduleDescriptor = getModule().getModuleDescriptor(); if (moduleDescriptor != null) { // XXX for deployed modules, we could use DD.getLibraries here. But as long as MM updates getAdditionalJavaStubPaths of a source module // and source module is always present, enjoy. libraryClassPath.addAll(moduleDescriptor.getAdditionalJavaStubPaths()); } // add classes folder for modules compiled outside MPS if (getModule() instanceof Solution && !isCompileInMps() && !getModule().isPackaged()) { // for packaged modules, we can't tell if classes deployed with it shall go into libraryCP or into #getClassPath(). Now they // go into latter, as there's (a) no uses for #getLibraryClassPath; (b) there's no need to compile deployed modules, hence no // reason to have its external classes available in libraries. // todo: remove this logic? String generatorOutputPath = ProjectPathUtil.getGeneratorOutputPath(getModule().getModuleDescriptor()); IFile classes = null; if (generatorOutputPath != null) { // same 'sibling to sources_gen/' logic is in ModulesMiner. Location of a module as IFile would be much more handy. classes = getModule().getFileSystem().getFile(generatorOutputPath).getParent().getDescendant("classes"); } if (classes != null && classes.exists()) { libraryClassPath.add(getClassPath(classes)); } } return libraryClassPath; } @Override public final Set<String> getClassPath() { Set<String> result = new LinkedHashSet<String>(); result.addAll(getLibraryClassPath()); // XXX CP entry for IDEA-compiled modules (classes/) is part of library CP. Is it right? // On the one hand, we might need classes compiled outside of a module to build it, OTOH, it makes classes/ // somewhat different from classes_gen/ IFile classesGen = getClassesGen(); if (classesGen == null && getModule().isPackaged()) { // Despite isPackaged(), there might be modules like stub and test that lack MD or DD, doesn't hurt to check ModuleDescriptor moduleDescriptor = getModule().getModuleDescriptor(); if (moduleDescriptor != null && moduleDescriptor.getDeploymentDescriptor() != null) { // 'Right' scenario. Deployed module has DD and we take classpath from there result.addAll(moduleDescriptor.getDeploymentDescriptor().getClasspath()); } else { // Compatibility code: // Case 1. Deployed generator modules have no DD and are read independently from their source languages. // Include their separate jar (hard-coded knowledge about build layout) into classpath. if (getModule() instanceof Generator) { LOG.error(String.format("Deployed generator module %s without deployment descriptor. File: %s", getModule().getModuleReference(), getModule().getDescriptorFile())); // FIXME COMPATIBILITY CODE BELOW SHALL CEASE TO EXIST IN 2017.2 (case 1) IFile descriptorFile = ((Generator) getModule()).getSourceLanguage().getDescriptorFile(); IFile bundleHome = descriptorFile == null ? null : descriptorFile.getBundleHome(); if (bundleHome != null) { // bundleHome for module itself and {bundleHome without .jar}-generator.jar for generator String mainPath = bundleHome.getPath().substring(0, bundleHome.getPath().length() - ".jar".length()); String jarPath = mainPath + "-generator.jar"; classesGen = bundleHome.getFileSystem().getFile(jarPath); } } else if (getModule().getDescriptorFile() != null) { // CASE 2. Solution(s) bundled into single jar with classes (both from hand-written and generated sources) at the root. // HACK. Fallback for manually bundled modules (vcs.jar or mps-core.jar): // my.jar // compile output of module1 // modules // module sources of module1 // There's no DD there, and assumption is that there are classes at the jar root. // Not yet sure what's the right way to deal with them: // - specify DD (META-INF/module.xml) at build time looks most 'honest', however, with multiple modules inside same jar it's not an option, // unless we can make DD per module, not per jar (requires support in MM.tryReadFromModulesDir). Support in Build language needed, too (to // specify 'module descriptor of' under 'folder with sources of' // - Patch MD in MM when loaded from modules/ location (e.g. add DD with proper classpath there). (+) keep knowledge about deployment layout // inside MM. // - Hack here classesGen = getModule().getDescriptorFile().getBundleHome(); } } } if (classesGen != null) { result.add(getClassPath(classesGen)); } return result; } private String getClassPath(@NotNull IFile classes) { String path = classes.getPath(); if (path.contains("!")) { String[] split = path.split("!"); if (split.length > 0) { if (!split[1].isEmpty() && !split[1].equals("/")) { LOG.warn("Can not transform directory " + path + " to proper classpath while calculating classpath for module " + getModule()); } } return split[0]; } return path; } @Override public Set<String> getAdditionalSourcePaths() { ModuleDescriptor moduleDescriptor = getModule().getModuleDescriptor(); if (moduleDescriptor == null) { return Collections.emptySet(); } return new HashSet<String>(moduleDescriptor.getSourcePaths()); } }