All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.dev.javac.CompilationStateBuilder Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
/*
 * Copyright 2009 Google Inc.
 *
 * 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 com.google.gwt.dev.javac;

import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.dev.CompilerContext;
import com.google.gwt.dev.javac.JdtCompiler.AdditionalTypeProviderDelegate;
import com.google.gwt.dev.javac.JdtCompiler.UnitProcessor;
import com.google.gwt.dev.jjs.CorrelationFactory.DummyCorrelationFactory;
import com.google.gwt.dev.jjs.ast.JDeclaredType;
import com.google.gwt.dev.jjs.impl.GwtAstBuilder;
import com.google.gwt.dev.js.ast.JsRootScope;
import com.google.gwt.dev.resource.Resource;
import com.google.gwt.dev.util.StringInterner;
import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
import com.google.gwt.dev.util.log.speedtracer.DevModeEventType;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.EventType;
import com.google.gwt.thirdparty.guava.common.collect.Interner;

import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
import org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * Manages a centralized cache for compiled units.
 */
public class CompilationStateBuilder {

  /**
   * An opaque class that lets you compile more units later.
   */
  public class CompileMoreLater {

    private final class UnitProcessorImpl implements UnitProcessor {

      @Override
      public void process(CompilationUnitBuilder builder, CompilationUnitDeclaration cud,
          List compiledClasses) {
        Event event = SpeedTracerLogger.start(DevModeEventType.CSB_PROCESS);
        try {
          Map jsniMethods =
              JsniCollector.collectJsniMethods(cud, builder.getSourceMapPath(),
                  builder.getSource(), JsRootScope.INSTANCE, DummyCorrelationFactory.INSTANCE);

          JSORestrictionsChecker.check(jsoState, cud);

          // JSNI check + collect dependencies.
          final Set jsniDeps = new HashSet();
          Map jsniRefs = new HashMap();
          JsniChecker.check(cud, jsoState, jsniMethods, jsniRefs, new JsniChecker.TypeResolver() {
            @Override
            public ReferenceBinding resolveType(String sourceOrBinaryName) {
              ReferenceBinding resolveType = compiler.resolveType(sourceOrBinaryName);
              if (resolveType != null) {
                jsniDeps.add(String.valueOf(resolveType.qualifiedSourceName()));
              }
              return resolveType;
            }
          });

          Map artificialRescues =
              new HashMap();
          ArtificialRescueChecker.check(cud, builder.isGenerated(), artificialRescues);
          BinaryTypeReferenceRestrictionsChecker.check(cud);

          MethodArgNamesLookup methodArgs = MethodParamCollector.collect(cud,
              builder.getSourceMapPath());

          Interner interner = StringInterner.get();
          String packageName = interner.intern(Shared.getPackageName(builder.getTypeName()));
          List unresolvedQualified = new ArrayList();
          List unresolvedSimple = new ArrayList();
          for (char[] simpleRef : cud.compilationResult().simpleNameReferences) {
            unresolvedSimple.add(interner.intern(String.valueOf(simpleRef)));
          }
          for (char[][] qualifiedRef : cud.compilationResult().qualifiedReferences) {
            unresolvedQualified.add(interner.intern(CharOperation.toString(qualifiedRef)));
          }
          for (String jsniDep : jsniDeps) {
            unresolvedQualified.add(interner.intern(jsniDep));
          }
          ArrayList apiRefs = compiler.collectApiRefs(cud);
          for (int i = 0; i < apiRefs.size(); ++i) {
            apiRefs.set(i, interner.intern(apiRefs.get(i)));
          }
          Dependencies dependencies =
              new Dependencies(packageName, unresolvedQualified, unresolvedSimple, apiRefs);

          List types = Collections.emptyList();
          if (!cud.compilationResult().hasErrors()) {
            // Make a GWT AST.
            types = astBuilder.process(cud, builder.getSourceMapPath(), artificialRescues,
                jsniMethods, jsniRefs);
          }

          for (CompiledClass cc : compiledClasses) {
            allValidClasses.put(cc.getSourceName(), cc);
          }

          builder.setClasses(compiledClasses).setTypes(types).setDependencies(dependencies)
              .setJsniMethods(jsniMethods.values()).setMethodArgs(methodArgs).setProblems(
                  cud.compilationResult().getProblems());
          buildQueue.add(builder);
        } finally {
          event.end();
        }
      }
    }

    /**
     * A global cache of all currently-valid class files keyed by source name.
     * This is used to validate dependencies when reusing previously cached
     * units, to make sure they can be recompiled if necessary.
     */
    private final Map allValidClasses = new HashMap();

    private final GwtAstBuilder astBuilder = new GwtAstBuilder();

    private transient LinkedBlockingQueue buildQueue;

    /**
     * The JDT compiler.
     */
    private final JdtCompiler compiler;

    /**
     * Continuation state for JSNI checking.
     */
    private final JSORestrictionsChecker.CheckerState jsoState =
        new JSORestrictionsChecker.CheckerState();

    private final boolean suppressErrors;

    private CompilerContext compilerContext;

    public CompileMoreLater(
        CompilerContext compilerContext, AdditionalTypeProviderDelegate delegate) {
      this.compilerContext = compilerContext;
      this.compiler = new JdtCompiler(
          compilerContext, new UnitProcessorImpl());
      this.suppressErrors = !compilerContext.getOptions().isStrict();
      compiler.setAdditionalTypeProviderDelegate(delegate);
    }

    /**
     * Compiles generated source files (unless cached) and adds them to the
     * CompilationState. If the compiler aborts, logs an error and throws
     * UnableToCompleteException.
     */
    public Collection addGeneratedTypes(TreeLogger logger,
        Collection generatedUnits) throws UnableToCompleteException {
      Event event = SpeedTracerLogger.start(DevModeEventType.CSB_ADD_GENERATED_TYPES);
      try {
        return doBuildGeneratedTypes(logger, compilerContext, generatedUnits, this);
      } finally {
        event.end();
      }
    }

    public Map getValidClasses() {
      return Collections.unmodifiableMap(allValidClasses);
    }

    void addValidUnit(CompilationUnit unit) {
      compiler.addCompiledUnit(unit);
      for (CompiledClass cc : unit.getCompiledClasses()) {
        allValidClasses.put(cc.getSourceName(), cc);
      }
    }

    /**
     * Compiles the source code in each supplied CompilationUnitBuilder into a CompilationUnit and
     * reports errors.
     *
     * 

A compilation unit is considered invalid if any of its dependencies (recursively) isn't * being compiled and isn't in allValidClasses, or if it has a signature that doesn't match * a dependency. Valid compilation units will be added to cachedUnits and the unit cache, and * their types will be added to allValidClasses. Invalid compilation units will be removed.

* *

I/O: serializes the AST of each Java type to DiskCache. (This happens even if the * compilation unit is later dropped.) If we're using the persistent unit cache, each valid * unit will also be serialized to the gwt-unitcache file. (As a result, each AST will be * copied there from the DiskCache.) A new persistent unit cache file will be created * each time compile() is called (if there's at least one valid unit) and the entire cache * will be rewritten to disk every {@link PersistentUnitCache#CACHE_FILE_THRESHOLD} files.

* *

This function won't report errors in invalid source files unless suppressErrors is false. * Instead, a summary giving the number of invalid files will be logged.

* *

If the JDT compiler aborts, logs an error and throws UnableToCompleteException. (This * doesn't happen for normal compile errors.)

*/ Collection compile(TreeLogger logger, CompilerContext compilerContext, Collection builders, Map cachedUnits, EventType eventType) throws UnableToCompleteException { // Initialize the set of valid classes to the initially cached units. for (CompilationUnit unit : cachedUnits.values()) { for (CompiledClass cc : unit.getCompiledClasses()) { // Map by source name. String sourceName = cc.getSourceName(); allValidClasses.put(sourceName, cc); } } ArrayList resultUnits = new ArrayList(); do { final TreeLogger branch = logger.branch(TreeLogger.TRACE, "Compiling..."); // Compile anything that needs to be compiled. buildQueue = new LinkedBlockingQueue(); final ArrayList newlyBuiltUnits = new ArrayList(); final CompilationUnitBuilder sentinel = CompilationUnitBuilder.create((GeneratedUnit) null); final Throwable[] workerException = new Throwable[1]; final ProgressLogger progressLogger = new ProgressLogger(branch, TreeLogger.TRACE, builders.size(), 10); Thread buildThread = new Thread() { @Override public void run() { int processedCompilationUnitBuilders = 0; try { do { CompilationUnitBuilder builder = buildQueue.take(); if (!progressLogger.isTimerStarted()) { // Set start time here, after first job has arrived, since it can take a little // while for the first job to arrive, and this helps with the accuracy of the // estimated times. progressLogger.startTimer(); } if (builder == sentinel) { return; } // Expensive, must serialize GWT AST types to bytes. CompilationUnit unit = builder.build(); newlyBuiltUnits.add(unit); processedCompilationUnitBuilders++; progressLogger.updateProgress(processedCompilationUnitBuilders); } while (true); } catch (Throwable e) { workerException[0] = e; } } }; buildThread.setName("CompilationUnitBuilder"); buildThread.start(); Event jdtCompilerEvent = SpeedTracerLogger.start(eventType); long compilationStartNanos = System.nanoTime(); try { compiler.doCompile(branch, builders); } finally { jdtCompilerEvent.end(); } buildQueue.add(sentinel); try { buildThread.join(); long compilationNanos = System.nanoTime() - compilationStartNanos; // Convert nanos to seconds. double compilationSeconds = compilationNanos / (double) TimeUnit.SECONDS.toNanos(1); branch.log(TreeLogger.TRACE, String.format("Compilation completed in %.02f seconds", compilationSeconds)); if (workerException[0] != null) { throw workerException[0]; } } catch (RuntimeException e) { throw e; } catch (Throwable e) { throw new RuntimeException("Exception processing units", e); } finally { buildQueue = null; } resultUnits.addAll(newlyBuiltUnits); builders.clear(); // Resolve all newly built unit deps against the global classes. for (CompilationUnit unit : newlyBuiltUnits) { unit.getDependencies().resolve(allValidClasses); } /* * Invalidate any cached units with invalid refs. */ Collection invalidatedUnits = new ArrayList(); for (Iterator> it = cachedUnits.entrySet().iterator(); it.hasNext();) { Entry entry = it.next(); CompilationUnit unit = entry.getValue(); boolean isValid = unit.getDependencies().validate(logger, allValidClasses); if (isValid && unit.isError()) { // See if the unit has classes that can't provide a // NameEnvironmentAnswer for (CompiledClass cc : unit.getCompiledClasses()) { try { cc.getNameEnvironmentAnswer(); } catch (ClassFormatException ex) { isValid = false; break; } } } if (!isValid) { if (logger.isLoggable(TreeLogger.TRACE)) { logger.log(TreeLogger.TRACE, "Invalid Unit: " + unit.getTypeName()); } invalidatedUnits.add(unit); builders.add(entry.getKey()); it.remove(); } } if (invalidatedUnits.size() > 0) { if (logger.isLoggable(TreeLogger.TRACE)) { logger.log(TreeLogger.TRACE, "Invalid units found: " + invalidatedUnits.size()); } } // Any units we invalidated must now be removed from the valid classes. for (CompilationUnit unit : invalidatedUnits) { for (CompiledClass cc : unit.getCompiledClasses()) { allValidClasses.remove(cc.getSourceName()); } } } while (builders.size() > 0); for (CompilationUnit unit : resultUnits) { unitCache.add(unit); } // Any remaining cached units are valid now. resultUnits.addAll(cachedUnits.values()); // Done with a pass of the build - tell the cache its OK to cleanup // stale cache files. unitCache.cleanup(logger); // Sort, then report all errors (re-report for cached units). Collections.sort(resultUnits, CompilationUnit.COMPARATOR); logger = logger.branch(TreeLogger.DEBUG, "Validating units:"); int errorCount = 0; for (CompilationUnit unit : resultUnits) { if (CompilationProblemReporter.reportErrors(logger, unit, suppressErrors)) { errorCount++; } } if (suppressErrors && errorCount > 0 && !logger.isLoggable(TreeLogger.TRACE) && logger.isLoggable(TreeLogger.INFO)) { logger.log(TreeLogger.INFO, "Ignored " + errorCount + " unit" + (errorCount > 1 ? "s" : "") + " with compilation errors in first pass.\n" + "Compile with -strict or with -logLevel set to TRACE or DEBUG to see all errors."); } return resultUnits; } } private static final CompilationStateBuilder instance = new CompilationStateBuilder(); /** * Use previously compiled {@link CompilationUnit}s to pre-populate the unit * cache. */ public static void addArchive(CompilationUnitArchive module) { UnitCache unitCache = instance.unitCache; for (CachedCompilationUnit archivedUnit : module.getUnits().values()) { if (archivedUnit.getTypesSerializedVersion() != GwtAstBuilder.getSerializationVersion()) { continue; } CompilationUnit cachedCompilationUnit = unitCache.find(archivedUnit.getResourcePath()); // A previously cached unit might be from the persistent cache or another // archive. if (cachedCompilationUnit == null || cachedCompilationUnit.getLastModified() < archivedUnit.getLastModified()) { unitCache.addArchivedUnit(archivedUnit); } } } /** * Compiles the given source files and adds them to the CompilationState. See * {@link CompileMoreLater#compile} for details. * * @throws UnableToCompleteException if the compiler aborts (not a normal compile error). */ public static CompilationState buildFrom( TreeLogger logger, CompilerContext compilerContext, Set resources) throws UnableToCompleteException { return buildFrom(logger, compilerContext, resources, null); } /** * Compiles the given source files and adds them to the CompilationState. See * {@link CompileMoreLater#compile} for details. * * @throws UnableToCompleteException if the compiler aborts (not a normal compile error). */ public static CompilationState buildFrom(TreeLogger logger, CompilerContext compilerContext, Set resources, AdditionalTypeProviderDelegate delegate) throws UnableToCompleteException { Event event = SpeedTracerLogger.start(DevModeEventType.CSB_BUILD_FROM_ORACLE); try { return instance.doBuildFrom(logger, compilerContext, resources, delegate); } finally { event.end(); } } public static CompilationStateBuilder get() { return instance; } /** * Called to setup the directory where the persistent {@link CompilationUnit} * cache should be stored. Only the first call to init() will have an effect. */ public static synchronized void init(TreeLogger logger, File cacheDirectory) { instance.unitCache = UnitCacheFactory.get(logger, cacheDirectory); } /** * A cache to store compilation units. This value may be overridden with an * explicit call to {@link #init(TreeLogger, File)}. */ private UnitCache unitCache = new MemoryUnitCache(); /** * Build a new compilation state from a source oracle. Allow the caller to * specify a compiler delegate that will handle undefined names. * * TODO: maybe use a finer brush than to synchronize the whole thing. */ public synchronized CompilationState doBuildFrom(TreeLogger logger, CompilerContext compilerContext, Set resources, AdditionalTypeProviderDelegate compilerDelegate) throws UnableToCompleteException { // Units we definitely want to build. List builders = new ArrayList(); // Units we don't want to rebuild unless we have to. Map cachedUnits = new IdentityHashMap(); CompileMoreLater compileMoreLater = new CompileMoreLater(compilerContext, compilerDelegate); // For each incoming Java source file... for (Resource resource : resources) { // Create a builder for all incoming units. CompilationUnitBuilder builder = CompilationUnitBuilder.create(resource); CompilationUnit cachedUnit = unitCache.find(resource.getPathPrefix() + resource.getPath()); // Try to rescue cached units from previous sessions where a jar has been // recompiled. if (cachedUnit != null && cachedUnit.getLastModified() != resource.getLastModified()) { unitCache.remove(cachedUnit); if (cachedUnit instanceof CachedCompilationUnit && cachedUnit.getContentId().equals(builder.getContentId())) { CachedCompilationUnit updatedUnit = new CachedCompilationUnit((CachedCompilationUnit) cachedUnit, resource .getLastModified(), resource.getLocation()); unitCache.add(updatedUnit); } else { cachedUnit = null; } } if (cachedUnit != null) { cachedUnits.put(builder, cachedUnit); compileMoreLater.addValidUnit(cachedUnit); continue; } builders.add(builder); } if (logger.isLoggable(TreeLogger.TRACE)) { logger.log(TreeLogger.TRACE, "Found " + cachedUnits.size() + " cached/archived units. Used " + cachedUnits.size() + " / " + resources.size() + " units from cache."); } Collection resultUnits = compileMoreLater.compile( logger, compilerContext, builders, cachedUnits, CompilerEventType.JDT_COMPILER_CSB_FROM_ORACLE); return new CompilationState(logger, resultUnits, compileMoreLater); } public CompilationState doBuildFrom( TreeLogger logger, CompilerContext compilerContext, Set resources) throws UnableToCompleteException { return doBuildFrom(logger, compilerContext, resources, null); } /** * Compile new generated units into an existing state. * * TODO: maybe use a finer brush than to synchronize the whole thing. */ synchronized Collection doBuildGeneratedTypes(TreeLogger logger, CompilerContext compilerContext, Collection generatedUnits, CompileMoreLater compileMoreLater) throws UnableToCompleteException { // Units we definitely want to build. List builders = new ArrayList(); // Units we don't want to rebuild unless we have to. Map cachedUnits = new IdentityHashMap(); // For each incoming generated Java source file... for (GeneratedUnit generatedUnit : generatedUnits) { // Create a builder for all incoming units. CompilationUnitBuilder builder = CompilationUnitBuilder.create(generatedUnit); // Look for units previously compiled CompilationUnit cachedUnit = unitCache.find(builder.getContentId()); if (cachedUnit != null) { // Recompile generated units with errors so source can be dumped. if (!cachedUnit.isError()) { cachedUnits.put(builder, cachedUnit); compileMoreLater.addValidUnit(cachedUnit); continue; } } builders.add(builder); } return compileMoreLater.compile(logger, compilerContext, builders, cachedUnits, CompilerEventType.JDT_COMPILER_CSB_GENERATED); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy