/* * Copyright Red Hat Inc. and/or its affiliates and other contributors * as indicated by the authors tag. All rights reserved. * * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU General Public License version 2. * * This particular file is subject to the "Classpath" exception as provided in the * LICENSE file that accompanied this code. * * This program is distributed in the hope that it will be useful, but WITHOUT A * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * You should have received a copy of the GNU General Public License, * along with this distribution; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ package com.redhat.ceylon.compiler.java.loader.model; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import com.redhat.ceylon.cmr.api.ArtifactContext; import com.redhat.ceylon.cmr.api.ModuleDependencyInfo; import com.redhat.ceylon.cmr.api.ModuleInfo; import com.redhat.ceylon.cmr.api.Overrides; import com.redhat.ceylon.cmr.api.VersionComparator; import com.redhat.ceylon.common.Backend; import com.redhat.ceylon.common.Backends; import com.redhat.ceylon.common.ModuleUtil; import com.redhat.ceylon.common.Versions; import com.redhat.ceylon.compiler.typechecker.analyzer.ModuleSourceMapper; import com.redhat.ceylon.compiler.typechecker.analyzer.Warning; import com.redhat.ceylon.compiler.typechecker.context.Context; import com.redhat.ceylon.compiler.typechecker.context.PhasedUnits; import com.redhat.ceylon.compiler.typechecker.tree.Node; import com.redhat.ceylon.model.cmr.ArtifactResult; import com.redhat.ceylon.model.cmr.ImportType; import com.redhat.ceylon.model.cmr.JDKUtils; import com.redhat.ceylon.model.loader.AbstractModelLoader; import com.redhat.ceylon.model.loader.model.LazyModule; import com.redhat.ceylon.model.loader.model.LazyModuleManager; import com.redhat.ceylon.model.typechecker.model.Module; import com.redhat.ceylon.model.typechecker.model.ModuleImport; import com.redhat.ceylon.model.typechecker.util.ModuleManager; /** * * @author Stéphane Épardaud <stef@epardaud.fr> */ public class LazyModuleSourceMapper extends ModuleSourceMapper { public LazyModuleSourceMapper(Context context, LazyModuleManager moduleManager) { super(context, moduleManager); } protected LazyModuleManager getModuleManager(){ return (LazyModuleManager) super.getModuleManager(); } @Override public void resolveModule(ArtifactResult artifact, Module module, ModuleImport moduleImport, LinkedList<Module> dependencyTree, List<PhasedUnits> phasedUnitsOfDependencies, boolean forCompiledModule) { String moduleName = module.getNameAsString(); LazyModuleManager moduleManager = getModuleManager(); boolean moduleLoadedFromSource = moduleManager.isModuleLoadedFromSource(moduleName); boolean isLanguageModule = module == module.getLanguageModule(); // if this is for a module we're compiling, or for an indirectly imported module, we need to check because the // module in question will be in the classpath if(moduleLoadedFromSource || forCompiledModule){ String standardisedModuleName = ModuleUtil.toCeylonModuleName(moduleName); // check for an already loaded module with the same name but different version for(Module loadedModule : getContext().getModules().getListOfModules()){ String loadedModuleName = loadedModule.getNameAsString(); String standardisedLoadedModuleName = ModuleUtil.toCeylonModuleName(loadedModuleName); boolean sameModule = loadedModuleName.equals(moduleName); boolean similarModule = standardisedLoadedModuleName.equals(standardisedModuleName); if((sameModule || similarModule) && !loadedModule.getVersion().equals(module.getVersion()) && moduleManager.getModelLoader().isModuleInClassPath(loadedModule)){ // abort // we need this error thrown rather than the typechecker error because the typechecker currently // allows more than we do, such as having direct imports of the same module with different versions // as long as they are not reexported, but we don't support that since they all go in the same // classpath (direct imports of compiled modules) if(sameModule){ String[] versions = VersionComparator.orderVersions(module.getVersion(), loadedModule.getVersion()); String error = "source code imports two different versions of module '" + moduleName + "': "+ "version '" + versions[0] + "' and version '" + versions[1] + "'"; addErrorToModule(dependencyTree.getFirst(), error); }else{ String moduleA; String moduleB; if(loadedModuleName.compareTo(moduleName) < 0){ moduleA = ModuleUtil.makeModuleName(loadedModuleName, loadedModule.getVersion()); moduleB = ModuleUtil.makeModuleName(moduleName, module.getVersion()); }else{ moduleA = ModuleUtil.makeModuleName(moduleName, module.getVersion()); moduleB = ModuleUtil.makeModuleName(loadedModuleName, loadedModule.getVersion()); } String error = "source code imports two different versions of similar modules '" + moduleA + "' and '"+ moduleB + "'"; addWarningToModule(dependencyTree.getFirst(), Warning.similarModule, error); } return; } } } if(moduleLoadedFromSource){ super.resolveModule(artifact, module, moduleImport, dependencyTree, phasedUnitsOfDependencies, forCompiledModule); }else if(forCompiledModule || isLanguageModule || moduleManager.shouldLoadTransitiveDependencies()){ // we only add stuff to the classpath and load the modules if we need them to compile our modules moduleManager.getModelLoader().addModuleToClassPath(module, artifact); // To be able to load it from the corresponding archive if(!module.isDefault() && !moduleManager.getModelLoader().loadCompiledModule(module)){ // we didn't find module.class so it must be a java module if it's not the default module ((LazyModule)module).setJava(true); module.setNativeBackends(Backend.Java.asSet()); List<ArtifactResult> deps = artifact.dependencies(); for (ArtifactResult dep : deps) { Module dependency = moduleManager.getOrCreateModule(ModuleManager.splitModuleName(dep.name()), dep.version()); ModuleImport depImport = moduleManager.findImport(module, dependency); if (depImport == null) { moduleImport = new ModuleImport(dependency, dep.importType() == ImportType.OPTIONAL, dep.importType() == ImportType.EXPORT, Backend.Java); module.addImport(moduleImport); } } } LazyModule lazyModule = (LazyModule) module; if(!lazyModule.isJava() && !module.isDefault()){ // it must be a Ceylon module // default modules don't have any module descriptors so we can't check them Overrides overrides = getContext().getRepositoryManager().getOverrides(); if (overrides != null) { if (overrides.getArtifactOverrides(new ArtifactContext(artifact.name(), artifact.version())) != null) { Set<ModuleDependencyInfo> existingModuleDependencies = new HashSet<>(); for (ModuleImport i : lazyModule.getImports()) { Module m = i.getModule(); if (m != null) { existingModuleDependencies.add(new ModuleDependencyInfo(m.getNameAsString(), m.getVersion(), i.isOptional(), i.isExport())); } } ModuleInfo sourceModuleInfo = new ModuleInfo(null, existingModuleDependencies); ModuleInfo newModuleInfo = overrides.applyOverrides(artifact.name(), artifact.version(), sourceModuleInfo); List<ModuleImport> newModuleImports = new ArrayList<>(); for (ModuleDependencyInfo dep : newModuleInfo.getDependencies()) { Module dependency = moduleManager.getOrCreateModule(ModuleManager.splitModuleName(dep.getName()), dep.getVersion()); Backends backends = dependency.getNativeBackends(); moduleImport = new ModuleImport(dependency, dep.isOptional(), dep.isExport(), backends); newModuleImports.add(moduleImport); } module.overrideImports(newModuleImports); } } if(!Versions.isJvmBinaryVersionSupported(lazyModule.getMajor(), lazyModule.getMinor())){ attachErrorToDependencyDeclaration(moduleImport, dependencyTree, "version '"+ lazyModule.getVersion() + "' of module '" + module.getNameAsString() + "' was compiled by an incompatible version of the compiler (binary version " + lazyModule.getMajor() + "." + lazyModule.getMinor() + " of module is not compatible with binary version " + Versions.JVM_BINARY_MAJOR_VERSION + "." + Versions.JVM_BINARY_MINOR_VERSION + " of this compiler)"); } } // module is now available module.setAvailable(true); } } @Override public void attachErrorToDependencyDeclaration(ModuleImport moduleImport, List<Module> dependencyTree, String error) { // special case for the java modules, which we only get when using the wrong version String name = moduleImport.getModule().getNameAsString(); if(AbstractModelLoader.isJDKModule(name)){ error = "imported module '" + name + "' depends on JDK version '\"" + moduleImport.getModule().getVersion() + "\"' and you're compiling with Java " + JDKUtils.jdk.version; } super.attachErrorToDependencyDeclaration(moduleImport, dependencyTree, error); } @Override public void addModuleDependencyDefinition(ModuleImport moduleImport, Node definition) { super.addModuleDependencyDefinition(moduleImport, definition); Module module = moduleImport.getModule(); if(module == null) return; String nameAsString = module.getNameAsString(); String version = module.getVersion(); if(version != null && AbstractModelLoader.isJDKModule(nameAsString)){ // Add a warning if we're using a lower JDK than the one we're running on if(JDKUtils.jdk.isLowerVersion(version)){ definition.addUsageWarning(Warning.importsOtherJdk, "You import JDK7, which is provided by the JDK8 you are running on, but"+ " we cannot check that you are not using any JDK8-specific classes or methods. Upgrade your import to JDK8 if you depend on"+ " JDK8 classes or methods.", Backend.Java); } } } }