public class JavaDockerClient implements DockerClient {

  private static Logger log = Logging.getLogger(JavaDockerClient.class);

  private com.github.dockerjava.api.DockerClient dockerClient;

  JavaDockerClient(com.github.dockerjava.api.DockerClient dockerClient) {
    this.dockerClient = dockerClient;
  }

  public static JavaDockerClient create(String url, String user, String password, String email) {
    final DockerClientConfig.DockerClientConfigBuilder configBuilder =
        DockerClientConfig.createDefaultConfigBuilder();
    if (StringUtils.isEmpty(url)) {
      log.info("Connecting to localhost");
    } else {
      log.info("Connecting to {}", url);
      configBuilder.withUri(url);
    }
    if (StringUtils.isNotEmpty(user)) {
      configBuilder.withUsername(user).withPassword(password).withEmail(email);
    }
    return new JavaDockerClient(DockerClientBuilder.getInstance(configBuilder).build());
  }

  @Override
  public String buildImage(File buildDir, String tag) {
    Preconditions.checkNotNull(tag, "Image tag can not be null.");
    Preconditions.checkArgument(!tag.isEmpty(), "Image tag can not be empty.");
    final BuildImageResultCallback resultCallback = new BuildImageResultCallback();
    dockerClient.buildImageCmd(buildDir).withTag(tag).exec(resultCallback);
    return resultCallback.awaitImageId();
  }

  @Override
  public String pushImage(String tag) {
    Preconditions.checkNotNull(tag, "Image tag can not be null.");
    Preconditions.checkArgument(!tag.isEmpty(), "Image tag can not be empty.");
    final PushImageResultCallback pushImageResultCallback =
        dockerClient.pushImageCmd(tag).exec(new PushImageResultCallback());
    pushImageResultCallback.awaitSuccess();
    return "";
  }

  @Override
  public void saveImage(String tag, File toFile) {
    Preconditions.checkNotNull(tag, "Image tag can not be null.");
    Preconditions.checkArgument(!tag.isEmpty(), "Image tag can not be empty.");
    try (InputStream inputStream = dockerClient.saveImageCmd(tag).exec();
        OutputStream outputStream = new FileOutputStream(toFile)) {
      byte[] buffer = new byte[10 * 1024];
      for (int length; (length = inputStream.read(buffer)) != -1; ) {
        outputStream.write(buffer, 0, length);
        outputStream.flush();
      }
    } catch (IOException e) {
      throw new GradleException("Unable to save image to " + toFile, e);
    }
  }
}
/**
 * Provides the general connection mechanics of connecting to a daemon, leaving implementations to
 * define how new daemons should be created if needed.
 *
 * <p>Subclassing instead of delegation with regard to creating new daemons seems more appropriate
 * as the way that new daemons are launched is likely to be coupled to the DaemonRegistry
 * implementation.
 */
public abstract class AbstractDaemonConnector<T extends DaemonRegistry> implements DaemonConnector {

  private static final Logger LOGGER = Logging.getLogger(AbstractDaemonConnector.class);

  private final T daemonRegistry;

  protected AbstractDaemonConnector(T daemonRegistry) {
    this.daemonRegistry = daemonRegistry;
  }

  public Connection<Object> maybeConnect() {
    return findConnection(daemonRegistry.getAll());
  }

  private Connection<Object> findConnection(List<DaemonStatus> statuses) {
    for (DaemonStatus status : statuses) {
      Address address = status.getAddress();
      try {
        return new TcpOutgoingConnector<Object>(
                new DefaultMessageSerializer<Object>(getClass().getClassLoader()))
            .connect(address);
      } catch (ConnectException e) {
        // this means the daemon died without removing its address from the registry
        // we can safely remove this address now
        daemonRegistry.remove(address);
      }
    }
    return null;
  }

  public Connection<Object> connect() {
    Connection<Object> connection = findConnection(daemonRegistry.getIdle());
    if (connection != null) {
      return connection;
    }

    LOGGER.info("Starting Gradle daemon");
    startDaemon();
    Date expiry = new Date(System.currentTimeMillis() + 30000L);
    do {
      connection = findConnection(daemonRegistry.getIdle());
      if (connection != null) {
        return connection;
      }
      try {
        Thread.sleep(200L);
      } catch (InterruptedException e) {
        throw UncheckedException.asUncheckedException(e);
      }
    } while (System.currentTimeMillis() < expiry.getTime());

    throw new GradleException("Timeout waiting to connect to Gradle daemon.");
  }

  public T getDaemonRegistry() {
    return daemonRegistry;
  }

  protected abstract void startDaemon();
}
/**
 * Caches the dependency metadata (descriptors, artifact files) in memory. Uses soft maps to reduce
 * heap pressure.
 */
public class InMemoryDependencyMetadataCache implements Stoppable {

  public static final String TOGGLE_PROPERTY = "org.gradle.resolution.memorycache";

  private static final Logger LOG = Logging.getLogger(InMemoryDependencyMetadataCache.class);

  Map<String, DependencyMetadataCache> cachePerRepo = new MapMaker().softValues().makeMap();

  final DependencyMetadataCacheStats stats = new DependencyMetadataCacheStats();

  public LocalAwareModuleVersionRepository cached(LocalAwareModuleVersionRepository input) {
    if ("false".equalsIgnoreCase(System.getProperty(TOGGLE_PROPERTY))) {
      return input;
    }

    DependencyMetadataCache dataCache = cachePerRepo.get(input.getId());
    stats.reposWrapped++;
    if (dataCache == null) {
      LOG.debug("Creating new in-memory cache for repo '{}' [{}].", input.getName(), input.getId());
      dataCache = new DependencyMetadataCache(stats);
      stats.cacheInstances++;
      cachePerRepo.put(input.getId(), dataCache);
    } else {
      LOG.debug("Reusing in-memory cache for repo '{}' [{}].", input.getName(), input.getId());
    }
    return new CachedRepository(dataCache, input, stats);
  }

  public void stop() {
    cachePerRepo.clear();
    LOG.info("In-memory dependency metadata cache closed. {}", stats);
  }
}
public class IncrementalCompilationSupport {

  private static final Logger LOG = Logging.getLogger(IncrementalCompilationSupport.class);

  public void compilationComplete(
      CompileOptions options,
      ClassDependencyInfoExtractor extractor,
      ClassDependencyInfoSerializer serializer) {
    if (options.isIncremental()) {
      Clock clock = new Clock();
      ClassDependencyInfo info = extractor.extractInfo("");
      serializer.writeInfo(info);
      LOG.lifecycle(
          "Performed class dependency analysis in {}, wrote results into {}",
          clock.getTime(),
          serializer);
    }
  }
}
Esempio n. 5
0
public class StopDaemonAction extends DaemonClientAction implements Action<ExecutionListener> {
  private static final Logger LOGGER = Logging.getLogger(StopDaemonAction.class);
  private final DaemonConnector connector;

  public StopDaemonAction(DaemonConnector connector, OutputEventListener outputEventListener) {
    super(outputEventListener);
    this.connector = connector;
  }

  public void execute(ExecutionListener executionListener) {
    Connection<Object> connection = connector.maybeConnect();
    if (connection == null) {
      LOGGER.lifecycle("Gradle daemon is not running.");
      return;
    }
    run(new Stop(), connection, executionListener);
    LOGGER.lifecycle("Gradle daemon stopped.");
  }
}
public class PlayApplicationDeploymentHandle implements DeploymentHandle {
  private final String id;
  private final PlayApplicationRunner runner;
  private final AtomicBoolean stopped = new AtomicBoolean(true);
  private PlayApplicationRunnerToken runnerToken;
  private static Logger logger = Logging.getLogger(PlayApplicationDeploymentHandle.class);

  public PlayApplicationDeploymentHandle(String id, PlayApplicationRunner runner) {
    this.id = id;
    this.runner = runner;
  }

  @Override
  public String getId() {
    return id;
  }

  @Override
  public boolean isRunning() {
    return !stopped.get();
  }

  @Override
  public void stop() {
    if (isRunning()) {
      logger.info("Stopping Play deployment handle for " + id);
      runnerToken.stop();
      stopped.set(true);
    }
  }

  public void start(PlayRunSpec spec) {
    if (stopped.get()) {
      logger.info("Starting Play deployment handle for " + id);
      runnerToken = runner.start(spec);
      stopped.set(false);
    } else {
      runnerToken.rebuildSuccess();
    }
  }
}
/**
 * Updates the daemon idle/busy status, sending a DaemonUnavailable result back to the client if the
 * daemon is busy.
 */
public class StartBuildOrRespondWithBusy extends BuildCommandOnly {

  private static final Logger LOGGER = Logging.getLogger(StartBuildOrRespondWithBusy.class);
  private final DaemonDiagnostics diagnostics;

  public StartBuildOrRespondWithBusy(DaemonDiagnostics diagnostics) {
    this.diagnostics = diagnostics;
  }

  protected void doBuild(final DaemonCommandExecution execution, final Build build) {
    DaemonStateControl stateCoordinator = execution.getDaemonStateControl();

    try {
      Runnable command =
          new Runnable() {
            public void run() {
              LOGGER.info(
                  "Daemon is about to start building: "
                      + build
                      + ". Dispatching build started information...");
              execution.getConnection().buildStarted(new BuildStarted(diagnostics));
              execution.proceed();
            }
          };

      stateCoordinator.runCommand(
          command, execution.toString(), execution.getCommandAbandonedHandler());
    } catch (DaemonUnavailableException e) {
      LOGGER.info(
          "Daemon will not handle the request: {} because is unavailable: {}",
          build,
          e.getMessage());
      execution.getConnection().daemonUnavailable(new DaemonUnavailable(e.getMessage()));
    }
  }
}
public class DefaultIsolatedAntBuilder implements IsolatedAntBuilder {

  private static final Logger LOG = Logging.getLogger(DefaultIsolatedAntBuilder.class);

  private static final Set<Finalizer> FINALIZERS = Sets.newConcurrentHashSet();
  private static final ReferenceQueue<DefaultIsolatedAntBuilder> FINALIZER_REFQUEUE =
      new ReferenceQueue<DefaultIsolatedAntBuilder>();

  static {
    // this thread is responsible for polling phantom references from the finalizer queue
    // where we will be able to trigger cleanup of resources when the root ant builder is
    // about to be garbage collected
    Thread finalizerThread =
        new Thread() {
          @Override
          public void run() {
            while (!isInterrupted()) {
              try {
                Finalizer builder = (Finalizer) FINALIZER_REFQUEUE.remove();
                FINALIZERS.remove(builder);
                builder.cleanup();
                builder.clear();
              } catch (InterruptedException e) {
                break;
              }
            }
          }
        };
    finalizerThread.setDaemon(true);
    finalizerThread.start();
  }

  private final ClassLoader antLoader;
  private final ClassLoader baseAntLoader;
  private final ClassPath libClasspath;
  private final ClassLoader antAdapterLoader;
  private final ClassPathRegistry classPathRegistry;
  private final ClassLoaderFactory classLoaderFactory;
  private final ClassPathToClassLoaderCache classLoaderCache;
  private final GroovySystemLoader gradleApiGroovyLoader;
  private final GroovySystemLoader antAdapterGroovyLoader;

  public DefaultIsolatedAntBuilder(
      ClassPathRegistry classPathRegistry, ClassLoaderFactory classLoaderFactory) {
    this.classPathRegistry = classPathRegistry;
    this.classLoaderFactory = classLoaderFactory;
    this.libClasspath = new DefaultClassPath();
    GroovySystemLoaderFactory groovySystemLoaderFactory = new GroovySystemLoaderFactory();
    this.classLoaderCache = new ClassPathToClassLoaderCache(groovySystemLoaderFactory);

    List<File> antClasspath =
        Lists.newArrayList(classPathRegistry.getClassPath("ANT").getAsFiles());
    // Need tools.jar for compile tasks
    File toolsJar = Jvm.current().getToolsJar();
    if (toolsJar != null) {
      antClasspath.add(toolsJar);
    }

    antLoader = classLoaderFactory.createIsolatedClassLoader(new DefaultClassPath(antClasspath));
    FilteringClassLoader loggingLoader = new FilteringClassLoader(getClass().getClassLoader());
    loggingLoader.allowPackage("org.slf4j");
    loggingLoader.allowPackage("org.apache.commons.logging");
    loggingLoader.allowPackage("org.apache.log4j");
    loggingLoader.allowClass(Logger.class);
    loggingLoader.allowClass(LogLevel.class);

    this.baseAntLoader =
        new CachingClassLoader(new MultiParentClassLoader(antLoader, loggingLoader));

    // Need gradle core to pick up ant logging adapter, AntBuilder and such
    ClassPath gradleCoreUrls = classPathRegistry.getClassPath("GRADLE_CORE");
    gradleCoreUrls = gradleCoreUrls.plus(classPathRegistry.getClassPath("GROOVY"));

    // Need Transformer (part of AntBuilder API) from base services
    gradleCoreUrls = gradleCoreUrls.plus(classPathRegistry.getClassPath("GRADLE_BASE_SERVICES"));
    this.antAdapterLoader = new URLClassLoader(gradleCoreUrls.getAsURLArray(), baseAntLoader);

    gradleApiGroovyLoader =
        groovySystemLoaderFactory.forClassLoader(this.getClass().getClassLoader());
    antAdapterGroovyLoader = groovySystemLoaderFactory.forClassLoader(antAdapterLoader);

    // register finalizer for the root builder only!
    FINALIZERS.add(new Finalizer(this, FINALIZER_REFQUEUE));
  }

  protected DefaultIsolatedAntBuilder(DefaultIsolatedAntBuilder copy, Iterable<File> libClasspath) {
    this.classPathRegistry = copy.classPathRegistry;
    this.classLoaderFactory = copy.classLoaderFactory;
    this.antLoader = copy.antLoader;
    this.baseAntLoader = copy.baseAntLoader;
    this.antAdapterLoader = copy.antAdapterLoader;
    this.libClasspath = new DefaultClassPath(libClasspath);
    this.gradleApiGroovyLoader = copy.gradleApiGroovyLoader;
    this.antAdapterGroovyLoader = copy.antAdapterGroovyLoader;
    this.classLoaderCache = copy.classLoaderCache;
  }

  public ClassPathToClassLoaderCache getClassLoaderCache() {
    return classLoaderCache;
  }

  public IsolatedAntBuilder withClasspath(Iterable<File> classpath) {
    if (LOG.isDebugEnabled()) {
      LOG.debug(String.format("Forking a new isolated ant builder for classpath : %s", classpath));
    }
    return new DefaultIsolatedAntBuilder(this, classpath);
  }

  public void execute(final Closure antClosure) {
    classLoaderCache.withCachedClassLoader(
        libClasspath,
        gradleApiGroovyLoader,
        antAdapterGroovyLoader,
        new Factory<ClassLoader>() {
          @Override
          public ClassLoader create() {
            return new URLClassLoader(libClasspath.getAsURLArray(), baseAntLoader);
          }
        },
        new Action<CachedClassLoader>() {
          @Override
          public void execute(CachedClassLoader cachedClassLoader) {
            ClassLoader classLoader = cachedClassLoader.getClassLoader();
            Object antBuilder =
                newInstanceOf("org.gradle.api.internal.project.ant.BasicAntBuilder");
            Object antLogger =
                newInstanceOf("org.gradle.api.internal.project.ant.AntLoggingAdapter");

            // This looks ugly, very ugly, but that is apparently what Ant does itself
            ClassLoader originalLoader = Thread.currentThread().getContextClassLoader();
            Thread.currentThread().setContextClassLoader(classLoader);

            try {
              configureAntBuilder(antBuilder, antLogger);

              // Ideally, we'd delegate directly to the AntBuilder, but it's Closure class is
              // different to our caller's
              // Closure class, so the AntBuilder's methodMissing() doesn't work. It just converts
              // our Closures to String
              // because they are not an instanceof it's Closure class.
              Object delegate = new AntBuilderDelegate(antBuilder, classLoader);
              ConfigureUtil.configure(antClosure, delegate);

            } finally {
              Thread.currentThread().setContextClassLoader(originalLoader);
              disposeBuilder(antBuilder, antLogger);
            }
          }
        });
  }

  private Object newInstanceOf(String className) {
    // we must use a String literal here, otherwise using things like Foo.class.name will trigger
    // unnecessary
    // loading of classes in the classloader of the DefaultIsolatedAntBuilder, which is not what we
    // want.
    try {
      return antAdapterLoader.loadClass(className).newInstance();
    } catch (Exception e) {
      // should never happen
      throw UncheckedException.throwAsUncheckedException(e);
    }
  }

  // We *absolutely* need to avoid polluting the project with ClassInfo from *our* classloader
  // So this class must NOT call any dynamic Groovy code. This means we must do what follows using
  // good old java reflection!

  private Object getProject(Object antBuilder) throws Exception {
    return antBuilder.getClass().getMethod("getProject").invoke(antBuilder);
  }

  protected void configureAntBuilder(Object antBuilder, Object antLogger) {
    try {
      Object project = getProject(antBuilder);
      Class<?> projectClass = project.getClass();
      ClassLoader cl = projectClass.getClassLoader();
      Class<?> buildListenerClass = cl.loadClass("org.apache.tools.ant.BuildListener");
      Method addBuildListener =
          projectClass.getDeclaredMethod("addBuildListener", buildListenerClass);
      Method removeBuildListener =
          projectClass.getDeclaredMethod("removeBuildListener", buildListenerClass);
      Method getBuildListeners = projectClass.getDeclaredMethod("getBuildListeners");
      Vector listeners = (Vector) getBuildListeners.invoke(project);
      removeBuildListener.invoke(project, listeners.get(0));
      addBuildListener.invoke(project, antLogger);
    } catch (Exception ex) {
      throw UncheckedException.throwAsUncheckedException(ex);
    }
  }

  protected void disposeBuilder(Object antBuilder, Object antLogger) {
    try {
      Object project = getProject(antBuilder);
      Class<?> projectClass = project.getClass();
      ClassLoader cl = projectClass.getClassLoader();
      // remove build listener
      Class<?> buildListenerClass = cl.loadClass("org.apache.tools.ant.BuildListener");
      Method removeBuildListener =
          projectClass.getDeclaredMethod("removeBuildListener", buildListenerClass);
      removeBuildListener.invoke(project, antLogger);
      antBuilder.getClass().getDeclaredMethod("close").invoke(antBuilder);
    } catch (Exception ex) {
      throw UncheckedException.throwAsUncheckedException(ex);
    }
  }

  private static class Finalizer extends PhantomReference<DefaultIsolatedAntBuilder> {
    private final ClassPathToClassLoaderCache classLoaderCache;
    private final GroovySystemLoader gradleApiGroovyLoader;
    private final GroovySystemLoader antAdapterGroovyLoader;
    private final ClassLoader antLoader;
    private final ClassLoader antAdapterLoader;

    public Finalizer(
        DefaultIsolatedAntBuilder referent, ReferenceQueue<? super DefaultIsolatedAntBuilder> q) {
      super(referent, q);
      classLoaderCache = referent.classLoaderCache;
      gradleApiGroovyLoader = referent.gradleApiGroovyLoader;
      antAdapterGroovyLoader = referent.antAdapterGroovyLoader;
      antLoader = referent.antLoader;
      antAdapterLoader = referent.antAdapterLoader;
    }

    public void cleanup() {
      classLoaderCache.stop();

      // Remove classes from core Gradle API
      gradleApiGroovyLoader.discardTypesFrom(antAdapterLoader);
      gradleApiGroovyLoader.discardTypesFrom(antLoader);

      // Shutdown the adapter Groovy system
      antAdapterGroovyLoader.shutdown();
    }
  }
}
Esempio n. 9
0
/**
 * This just wraps up an OutputPanel so it has a tab header that can be dynamic. The current (rather
 * awkward) JTabbedPane implementation is to separate the tab contents from its component. This only
 * works with java 1.6 or later.
 *
 * @author mhunsicker
 */
public class OutputTab extends OutputPanel {

  private static final Logger LOGGER = Logging.getLogger(OutputTab.class);

  private JPanel mainPanel;
  private JLabel mainTextLabel;
  private JLabel pinnedLabel;
  private JLabel closeLabel;

  private static ImageIcon closeIcon;
  private static ImageIcon closeHighlightIcon;
  private static ImageIcon pinnedIcon;

  public OutputTab(
      GradlePluginLord gradlePluginLord,
      OutputPanelParent parent,
      String header,
      AlternateUIInteraction alternateUIInteraction) {
    super(gradlePluginLord, parent, alternateUIInteraction);
    mainPanel = new JPanel();
    mainPanel.setOpaque(false);
    mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.X_AXIS));

    mainTextLabel = new JLabel(header);

    if (pinnedIcon == null) {
      pinnedIcon =
          getImageIconResource("/org/gradle/gradleplugin/userinterface/swing/generic/pin.png");
    }

    pinnedLabel = new JLabel(pinnedIcon);
    pinnedLabel.setVisible(isPinned());

    setupCloseLabel();

    mainPanel.add(mainTextLabel);
    mainPanel.add(Box.createHorizontalStrut(5));
    mainPanel.add(pinnedLabel);
    mainPanel.add(closeLabel);
  }

  private void setupCloseLabel() {
    if (closeIcon == null) {
      closeIcon = getImageIconResource("close.png");
      closeHighlightIcon = getImageIconResource("close-highlight.png");
    }

    closeLabel = new JLabel(closeIcon);
    closeLabel.addMouseListener(
        new MouseAdapter() {
          @Override
          public void mouseEntered(MouseEvent e) {
            closeLabel.setIcon(closeHighlightIcon);
          }

          @Override
          public void mouseExited(MouseEvent e) {
            closeLabel.setIcon(closeIcon);
          }

          public void mouseClicked(MouseEvent e) {
            close();
          }
        });
  }

  private BufferedImage getImageResource(String imageResourceName) {
    InputStream inputStream = getClass().getResourceAsStream(imageResourceName);
    if (inputStream != null) {
      try {
        BufferedImage image = ImageIO.read(inputStream);
        return image;
      } catch (IOException e) {
        LOGGER.error("Reading image " + imageResourceName, e);
      }
    }

    return null;
  }

  private ImageIcon getImageIconResource(String imageIconResourceName) {
    BufferedImage image = getImageResource(imageIconResourceName);
    if (image != null) {
      return new ImageIcon(image);
    }
    return null;
  }

  /**
   * Call this before you use this tab. It resets its output as well as enabling buttons
   * appropriately.
   *
   * @author mhunsicker
   */
  @Override
  public void reset() {
    super.reset();
    closeLabel.setEnabled(true);
  }

  public Component getTabHeader() {
    return mainPanel;
  }

  public void setTabHeaderText(String newText) {
    mainTextLabel.setText(newText);
  }

  public boolean close() {
    closeLabel.setEnabled(false); // provide feedback to the user that we received their click

    boolean result = super.close();
    if (result) {
      closeLabel.setEnabled(true);
    }

    return result;
  }

  /**
   * Overridden so we can indicate the pinned state.
   *
   * @param pinned whether or not we're pinned
   * @author mhunsicker
   */
  @Override
  public void setPinned(boolean pinned) {
    pinnedLabel.setVisible(pinned);

    super.setPinned(pinned);
  }
}
Esempio n. 10
0
/**
 * Provides the mechanics of connecting to a daemon, starting one via a given runnable if no
 * suitable daemons are already available.
 */
public class DefaultDaemonConnector implements DaemonConnector {
  private static final Logger LOGGER = Logging.getLogger(DefaultDaemonConnector.class);
  public static final int DEFAULT_CONNECT_TIMEOUT = 30000;
  private final DaemonRegistry daemonRegistry;
  protected final OutgoingConnector connector;
  private final DaemonStarter daemonStarter;
  private long connectTimeout = DefaultDaemonConnector.DEFAULT_CONNECT_TIMEOUT;

  public DefaultDaemonConnector(
      DaemonRegistry daemonRegistry, OutgoingConnector connector, DaemonStarter daemonStarter) {
    this.daemonRegistry = daemonRegistry;
    this.connector = connector;
    this.daemonStarter = daemonStarter;
  }

  public void setConnectTimeout(long connectTimeout) {
    this.connectTimeout = connectTimeout;
  }

  public long getConnectTimeout() {
    return connectTimeout;
  }

  public DaemonRegistry getDaemonRegistry() {
    return daemonRegistry;
  }

  public DaemonClientConnection maybeConnect(ExplainingSpec<DaemonContext> constraint) {
    return findConnection(daemonRegistry.getAll(), constraint);
  }

  public DaemonClientConnection connect(ExplainingSpec<DaemonContext> constraint) {
    DaemonClientConnection connection = findConnection(daemonRegistry.getIdle(), constraint);
    if (connection != null) {
      return connection;
    }

    return startDaemon(constraint);
  }

  private DaemonClientConnection findConnection(
      List<DaemonInfo> daemonInfos, ExplainingSpec<DaemonContext> constraint) {
    for (final DaemonInfo daemonInfo : daemonInfos) {
      if (!constraint.isSatisfiedBy(daemonInfo.getContext())) {
        LOGGER.debug(
            "Found daemon (address: {}, idle: {}) however its context does not match the desired criteria.\n"
                + constraint.whyUnsatisfied(daemonInfo.getContext())
                + "\n"
                + "  Looking for a different daemon...",
            daemonInfo.getAddress(),
            daemonInfo.isIdle());
        continue;
      }

      try {
        return connectToDaemon(daemonInfo, new CleanupOnStaleAddress(daemonInfo, true));
      } catch (ConnectException e) {
        LOGGER.debug(
            "Cannot connect to the daemon at "
                + daemonInfo.getAddress()
                + " due to "
                + e
                + ". Trying a different daemon...");
      }
    }
    return null;
  }

  public DaemonClientConnection startDaemon(ExplainingSpec<DaemonContext> constraint) {
    LOGGER.info("Starting Gradle daemon");
    final DaemonStartupInfo startupInfo = daemonStarter.startDaemon();
    LOGGER.debug("Started Gradle Daemon: {}", startupInfo);
    long expiry = System.currentTimeMillis() + connectTimeout;
    do {
      DaemonClientConnection daemonConnection = connectToDaemonWithId(startupInfo, constraint);
      if (daemonConnection != null) {
        return daemonConnection;
      }
      try {
        Thread.sleep(200L);
      } catch (InterruptedException e) {
        throw UncheckedException.throwAsUncheckedException(e);
      }
    } while (System.currentTimeMillis() < expiry);

    throw new DaemonConnectionException(
        "Timeout waiting to connect to the Gradle daemon.\n" + startupInfo.describe());
  }

  private DaemonClientConnection connectToDaemonWithId(
      DaemonStartupInfo startupInfo, ExplainingSpec<DaemonContext> constraint)
      throws ConnectException {
    // Look for 'our' daemon among the busy daemons - a daemon will start in busy state so that
    // nobody else will grab it.
    for (DaemonInfo daemonInfo : daemonRegistry.getBusy()) {
      if (daemonInfo.getContext().getUid().equals(startupInfo.getUid())) {
        try {
          if (!constraint.isSatisfiedBy(daemonInfo.getContext())) {
            throw new DaemonConnectionException(
                "The newly created daemon process has a different context than expected."
                    + "\nIt won't be possible to reconnect to this daemon. Context mismatch: "
                    + "\n"
                    + constraint.whyUnsatisfied(daemonInfo.getContext()));
          }
          return connectToDaemon(daemonInfo, new CleanupOnStaleAddress(daemonInfo, false));
        } catch (ConnectException e) {
          throw new DaemonConnectionException(
              "Could not connect to the Gradle daemon.\n" + startupInfo.describe(), e);
        }
      }
    }
    return null;
  }

  private DaemonClientConnection connectToDaemon(
      DaemonInfo daemonInfo, DaemonClientConnection.StaleAddressDetector staleAddressDetector)
      throws ConnectException {
    RemoteConnection<Object> connection;
    try {
      connection = connector.connect(daemonInfo.getAddress()).create(getClass().getClassLoader());
    } catch (ConnectException e) {
      staleAddressDetector.maybeStaleAddress(e);
      throw e;
    }
    return new DaemonClientConnection(
        connection, daemonInfo.getContext().getUid(), staleAddressDetector);
  }

  private class CleanupOnStaleAddress implements DaemonClientConnection.StaleAddressDetector {
    private final DaemonInfo daemonInfo;
    private final boolean exposeAsStale;

    public CleanupOnStaleAddress(DaemonInfo daemonInfo, boolean exposeAsStale) {
      this.daemonInfo = daemonInfo;
      this.exposeAsStale = exposeAsStale;
    }

    public boolean maybeStaleAddress(Exception failure) {
      LOGGER.info(DaemonMessages.REMOVING_DAEMON_ADDRESS_ON_FAILURE + daemonInfo);
      daemonRegistry.remove(daemonInfo.getAddress());
      return exposeAsStale;
    }
  }
}
/**
 * A tool for synchronising the state amongst different threads.
 *
 * <p>This class has no knowledge of the Daemon's internals and is designed to be used internally by
 * the daemon to coordinate itself and allow worker threads to control the daemon's busy/idle
 * status.
 *
 * <p>This is not exposed to clients of the daemon.
 */
public class DaemonStateCoordinator implements Stoppable, DaemonStateControl {
  private static final Logger LOGGER = Logging.getLogger(DaemonStateCoordinator.class);

  private enum State {
    Running,
    StopRequested,
    Stopped,
    Broken
  }

  private final Lock lock = new ReentrantLock();
  private final Condition condition = lock.newCondition();

  private State state = State.Running;
  private long lastActivityAt = -1;
  private String currentCommandExecution;

  private final Runnable onStartCommand;
  private final Runnable onFinishCommand;
  private Runnable onDisconnect;

  public DaemonStateCoordinator(Runnable onStartCommand, Runnable onFinishCommand) {
    this.onStartCommand = onStartCommand;
    this.onFinishCommand = onFinishCommand;
    updateActivityTimestamp();
  }

  private void setState(State state) {
    this.state = state;
    condition.signalAll();
  }

  private boolean awaitStop(long timeoutMs) {
    lock.lock();
    try {
      LOGGER.debug("waiting for daemon to stop or be idle for {}ms", timeoutMs);
      while (true) {
        try {
          switch (state) {
            case Running:
              if (isBusy()) {
                LOGGER.debug(DaemonMessages.DAEMON_BUSY);
                condition.await();
              } else if (hasBeenIdleFor(timeoutMs)) {
                LOGGER.debug("Daemon has been idle for requested period.");
                stop();
                return false;
              } else {
                Date waitUntil = new Date(lastActivityAt + timeoutMs);
                LOGGER.debug(DaemonMessages.DAEMON_IDLE + waitUntil);
                condition.awaitUntil(waitUntil);
              }
              break;
            case Broken:
              throw new IllegalStateException("This daemon is in a broken state.");
            case StopRequested:
              LOGGER.debug("Daemon is stopping, sleeping until state changes.");
              condition.await();
              break;
            case Stopped:
              LOGGER.debug("Daemon has stopped.");
              return true;
          }
        } catch (InterruptedException e) {
          throw UncheckedException.throwAsUncheckedException(e);
        }
      }
    } finally {
      lock.unlock();
    }
  }

  public void stopOnIdleTimeout(int timeout, TimeUnit timeoutUnits) throws DaemonStoppedException {
    if (awaitStop(timeoutUnits.toMillis(timeout))) {
      throw new DaemonStoppedException(currentCommandExecution);
    }
  }

  public void requestStop() {
    lock.lock();
    try {
      LOGGER.debug("Stop as soon as idle requested. The daemon is busy: " + isBusy());
      if (isBusy()) {
        beginStopping();
      } else {
        stop();
      }
    } finally {
      lock.unlock();
    }
  }

  /**
   * Forcibly stops the daemon, even if it is busy.
   *
   * <p>If the daemon is busy and the client is waiting for a response, it may receive “null” from
   * the daemon as the connection may be closed by this method before the result is sent back.
   *
   * @see #requestStop()
   */
  public void stop() {
    lock.lock();
    try {
      LOGGER.debug("Stop requested. The daemon is running a build: " + isBusy());
      switch (state) {
        case Running:
        case Broken:
        case StopRequested:
          setState(State.Stopped);
          break;
        case Stopped:
          break;
        default:
          throw new IllegalStateException("Daemon is in unexpected state: " + state);
      }
    } finally {
      lock.unlock();
    }
  }

  private void beginStopping() {
    switch (state) {
      case Running:
      case Broken:
        setState(State.StopRequested);
        break;
      case StopRequested:
      case Stopped:
        break;
      default:
        throw new IllegalStateException("Daemon is in unexpected state: " + state);
    }
  }

  public void requestForcefulStop() {
    lock.lock();
    try {
      try {
        if (onDisconnect != null) {
          onDisconnect.run();
        }
      } finally {
        stop();
      }
    } finally {
      lock.unlock();
    }
  }

  public void runCommand(Runnable command, String commandDisplayName, Runnable onCommandAbandoned)
      throws DaemonUnavailableException {
    onStartCommand(commandDisplayName, onCommandAbandoned);
    try {
      command.run();
    } finally {
      onFinishCommand();
    }
  }

  private void onStartCommand(String commandDisplayName, Runnable onDisconnect) {
    lock.lock();
    try {
      switch (state) {
        case Broken:
          throw new DaemonUnavailableException("This daemon is in a broken state and will stop.");
        case StopRequested:
          throw new DaemonUnavailableException("This daemon is currently stopping.");
        case Stopped:
          throw new DaemonUnavailableException("This daemon has stopped.");
      }
      if (currentCommandExecution != null) {
        throw new DaemonUnavailableException(
            String.format("This daemon is currently executing: %s", currentCommandExecution));
      }

      LOGGER.debug(
          "onStartCommand({}) called after {} minutes of idle",
          commandDisplayName,
          getIdleMinutes());
      try {
        onStartCommand.run();
        currentCommandExecution = commandDisplayName;
        this.onDisconnect = onDisconnect;
        updateActivityTimestamp();
        condition.signalAll();
      } catch (Throwable throwable) {
        setState(State.Broken);
        throw UncheckedException.throwAsUncheckedException(throwable);
      }
    } finally {
      lock.unlock();
    }
  }

  private void onFinishCommand() {
    lock.lock();
    try {
      String execution = currentCommandExecution;
      LOGGER.debug("onFinishCommand() called while execution = {}", execution);
      currentCommandExecution = null;
      onDisconnect = null;
      updateActivityTimestamp();
      switch (state) {
        case Running:
          try {
            onFinishCommand.run();
            condition.signalAll();
          } catch (Throwable throwable) {
            setState(State.Broken);
            throw UncheckedException.throwAsUncheckedException(throwable);
          }
          break;
        case StopRequested:
          stop();
          break;
        case Stopped:
          break;
        default:
          throw new IllegalStateException("Daemon is in unexpected state: " + state);
      }
    } finally {
      lock.unlock();
    }
  }

  private void updateActivityTimestamp() {
    long now = System.currentTimeMillis();
    LOGGER.debug("updating lastActivityAt to {}", now);
    lastActivityAt = now;
  }

  private double getIdleMinutes() {
    lock.lock();
    try {
      return (System.currentTimeMillis() - lastActivityAt) / 1000 / 60;
    } finally {
      lock.unlock();
    }
  }

  private boolean hasBeenIdleFor(long milliseconds) {
    return lastActivityAt < (System.currentTimeMillis() - milliseconds);
  }

  boolean isStopped() {
    return state == State.Stopped;
  }

  boolean isStoppingOrStopped() {
    return state == State.Stopped || state == State.StopRequested;
  }

  boolean isIdle() {
    return currentCommandExecution == null;
  }

  boolean isBusy() {
    return !isIdle();
  }
}
/**
 * This defines the basic behavior of all gradle protocols for interprocess communication. It
 * manages handshaking, detecting if the client executed prematurely, as well as executing alternate
 * external processes. All you need to do is extend this, implement the abstract functions, and make
 * sure you call setHasReceivedBuildCompleteNotification() when whatever you were doing is complete
 * (so we know any exiting is not premature).
 *
 * @author mhunsicker
 */
public abstract class AbstractGradleServerProtocol implements ProcessLauncherServer.Protocol {
  private static final String INIT_SCRIPT_EXTENSION = ".gradle";

  private final Logger logger = Logging.getLogger(AbstractGradleServerProtocol.class);

  protected ProcessLauncherServer server;
  private boolean continueConnection;
  private boolean waitingOnHandshakeCompletion;
  private boolean hasCompletedConnection;

  private boolean hasReceivedBuildCompleteNotification;

  private File currentDirectory;
  private File gradleHomeDirectory;

  private File customGradleExecutor;
  private String commandLine;
  private LogLevel logLevel;

  // all of this is just so we can get gradle to kill itself when we cancel
  private int killGradleServerPort;
  private KillGradleClientProtocol killGradleClientProcotol;
  private ClientProcess killGradleClient;

  protected MessageObject lastMessageReceived; // just for debugging purposes

  /** @return true if we should keep the connection alive. False if we should stop communicaiton. */
  public boolean continueConnection() {
    return continueConnection;
  }

  private StartParameter.ShowStacktrace stackTraceLevel;

  public AbstractGradleServerProtocol(
      File currentDirectory,
      File gradleHomeDirectory,
      File customGradleExecutor,
      String fullCommandLine,
      LogLevel logLevel,
      StartParameter.ShowStacktrace stackTraceLevel) {
    this.currentDirectory = currentDirectory;
    this.gradleHomeDirectory = gradleHomeDirectory;
    this.customGradleExecutor = customGradleExecutor;
    this.commandLine = fullCommandLine;
    this.logLevel = logLevel;
    this.stackTraceLevel = stackTraceLevel;
  }

  /** Notification that the connection was accepted by the client. */
  public void connectionAccepted() {
    // let's make sure we're talking to the right client with some tiny handshaking.
    server.sendMessage(ProtocolConstants.HANDSHAKE_TYPE, ProtocolConstants.HANDSHAKE_SERVER);
    continueConnection = true;
    waitingOnHandshakeCompletion = true;
  }

  /** Gives your protocol a chance to store this server so it can access its functions. */
  public void initialize(ProcessLauncherServer server) {
    this.server = server;
  }

  /** Call this to stop communication */
  protected void closeConnection() {
    this.continueConnection = false;
  }

  /**
   * Notification that a message has been received. If we just connected, we'll do a quick handshake
   * to verify the client, then we just pass the rest on our our output panel.
   *
   * @param message the message that was received.
   */
  public void messageReceived(MessageObject message) {
    lastMessageReceived = message;
    if (waitingOnHandshakeCompletion) // are we still handshaking?
    {
      if (ProtocolConstants.HANDSHAKE_CLIENT.equalsIgnoreCase(message.getMessage())) {
        waitingOnHandshakeCompletion = false; // we've received what we expected
        hasCompletedConnection = true; // and we're now connected
        if (message.getData() != null) {
          killGradleServerPort = (Integer) message.getData();
          killGradleClientProcotol = new KillGradleClientProtocol();
          killGradleClient = new ClientProcess(killGradleClientProcotol);
          killGradleClient.start(killGradleServerPort);
          handShakeCompleted();
        } else {
          addStatus("Invalid handshaking. Missing port number. Stopping connection");
          server.sendMessage("?", "Invalid client handshake protocol!");
          closeConnection();
        }
      } else {
        addStatus("Invalid handshaking. Stopping connection");
        server.sendMessage("?", "Invalid client handshake protocol!");
        closeConnection();
      }
    } else // otherwise, its just a normal message, the protocol should handle it.
    {
      try {
        handleMessageReceived(message);
      } catch (Throwable e) {
        logger.error("Problem while handing message :\n" + message, e);
      }
    }
  }

  /** This provides you with a chance to do something when the handshaking is complete */
  protected void handShakeCompleted() {}

  /**
   * Notification that a message was received that we didn't process. Implement this to handle the
   * specifics of your protocol. Basically, the base class handles the handshake. The rest of the
   * conversation is up to you.
   *
   * @param message the message we received.
   * @return true if we handled the message, false if not. If we don't know it, we won't return an
   *     acknowlegement.
   */
  protected abstract boolean handleMessageReceived(MessageObject message);

  /**
   * Call this to mark the build as completed (whether successfully or not). This is used to
   * determine if the client has exited prematurely which indicates a problem.
   */
  public void setHasReceivedBuildCompleteNotification() {
    this.hasReceivedBuildCompleteNotification = true;
  }

  /**
   * Notification of any status that might be helpful to the user.
   *
   * @param status a status message
   */
  protected abstract void addStatus(String status);

  public class MyExecutionInfo implements ExecutionInfo {
    public String[] commandLineArguments;
    public File workingDirectory;
    public HashMap<String, String> environmentVariables = new HashMap<String, String>();
    public File initStriptPath;

    public String[] getCommandLineArguments() {
      return commandLineArguments;
    }

    public File getWorkingDirectory() {
      return workingDirectory;
    }

    public HashMap<String, String> getEnvironmentVariables() {
      return environmentVariables;
    }

    public void setCommandLineArguments(String[] commandLineArguments) {
      this.commandLineArguments = commandLineArguments;
    }

    public void setWorkingDirectory(File workingDirectory) {
      this.workingDirectory = workingDirectory;
    }

    public void addEnvironmentVariable(String name, String value) {
      this.environmentVariables.put(name, value);
    }

    public void processExecutionComplete() {
      if (initStriptPath != null) {
        initStriptPath.delete();
      }
    }
  }

  /**
   * Fill in the ExecutionInfo object with information needed to execute the other process.
   *
   * @param serverPort the port the server is listening on. The client should send messages here
   * @return an executionInfo object containing information about what we execute.
   */
  public ExecutionInfo getExecutionInfo(int serverPort) {
    MyExecutionInfo executionInfo = new MyExecutionInfo();

    // set some environment variables that need to be passed to the script.
    executionInfo.addEnvironmentVariable("GRADLE_HOME", getGradleHomeDirectory().getAbsolutePath());
    executionInfo.addEnvironmentVariable("JAVA_HOME", System.getProperty("java.home"));

    executionInfo.setWorkingDirectory(currentDirectory);

    List<String> executionCommandLine = new ArrayList<String>();

    // put the file to execute on the command line
    File gradleExecutableFile = getGradleExecutableFile();
    executionCommandLine.add(gradleExecutableFile.getAbsolutePath());

    // add the port number we're listenening on
    executionCommandLine.add(
        "-D" + ProtocolConstants.PORT_NUMBER_SYSTEM_PROPERTY + "=" + Integer.toString(serverPort));

    CommandLineAssistant commandLineAssistant = new CommandLineAssistant();

    // add whatever the user ran
    String[] individualCommandLineArguments = commandLineAssistant.breakUpCommandLine(commandLine);
    executionCommandLine.addAll(Arrays.asList(individualCommandLineArguments));

    File initStriptPath = getInitScriptFile();
    if (initStriptPath != null) {
      executionCommandLine.add("-" + DefaultCommandLine2StartParameterConverter.INIT_SCRIPT);
      executionCommandLine.add(initStriptPath.getAbsolutePath());
      executionInfo.initStriptPath = initStriptPath;
    }

    // add the log level if its not present
    if (!commandLineAssistant.hasLogLevelDefined(individualCommandLineArguments)) {
      String logLevelText =
          commandLineAssistant
              .getCommandLine2StartParameterConverter()
              .getLogLevelCommandLine(logLevel);
      if (logLevelText != null && !"".equals(logLevelText)) {
        executionCommandLine.add('-' + logLevelText);
      }
    }

    // add the stack trace level if its not present
    if (!commandLineAssistant.hasShowStacktraceDefined(individualCommandLineArguments)) {
      String stackTraceLevelText =
          commandLineAssistant
              .getCommandLine2StartParameterConverter()
              .getShowStacktraceCommandLine(stackTraceLevel);
      if (stackTraceLevelText != null) {
        executionCommandLine.add('-' + stackTraceLevelText);
      }
    }

    executionInfo.setCommandLineArguments(
        executionCommandLine.toArray(new String[executionCommandLine.size()]));
    return executionInfo;
  }

  /**
   * @return the file that should be used to execute gradle. If we've been given a custom file, we
   *     use that, otherwise, we use the batch or shell script inside the gradle home's bin
   *     directory.
   */
  protected File getGradleExecutableFile() {
    if (customGradleExecutor != null) {
      return customGradleExecutor;
    }

    return new File(gradleHomeDirectory, "bin" + File.separator + getDefaultGradleExecutableName());
  }

  /**
   * This determines what we're going to execute. Its different based on the OS.
   *
   * @return whatever we're going to execute.
   */
  private String getDefaultGradleExecutableName() {
    return OperatingSystem.current().getScriptName("gradle");
  }

  /** Notification that the client has stopped all communications. */
  public void clientCommunicationStopped() {
    // we don't really care
  }

  /**
   * Notification that the client has shutdown. Note: this can occur before communciations has ever
   * started. You SHOULD get this notification before receiving serverExited, even if the client
   * fails to launch or locks up.
   *
   * @param returnCode the return code of the client application
   * @param output the standard error and standard output of the client application
   */
  public void clientExited(int returnCode, String output) {
    server.requestShutdown();

    boolean wasPremature = false;
    String message;

    if (!hasCompletedConnection) // if we never connected, report it
    {
      message = "Failed to connect to gradle process for command '" + commandLine + "'\n" + output;
      wasPremature = true;
    } else if (!hasReceivedBuildCompleteNotification) // this may happen if the client doesn't
                                                      // execute properly or it was killed/canceled.
                                                      // This is just so we don't lose our output
                                                      // (which may yeild clues to the problem).
    {
      message = output;
      wasPremature = true;
    } else {
      message = output;
    }

    reportClientExit(wasPremature, returnCode, message);
  }

  /**
   * This is called if the client exits prematurely. That is, we never connected to it or it didn't
   * finish. This can happen because of setup issues or errors that occur in gradle.
   *
   * @param returnCode the return code of the application
   * @param message Whatever information we can gleen about what went wrong.
   */
  protected abstract void reportClientExit(boolean wasPremature, int returnCode, String output);

  /**
   * This is called before we execute a command. Here, return an init script for this protocol. An
   * init script is a gradle script that gets run before the other scripts are processed. This is
   * useful here for initiating the gradle client that talks to the server.
   *
   * @return The path to an init script. Null if you have no init script.
   */
  public abstract File getInitScriptFile();

  /**
   * If you do have an init script that's a resource, this will extract it based on the name and
   * write it to a temporary file and delete it on exit.
   *
   * @param resourceClass the class associated with the resource
   * @param resourceName the name (minus extension or '.') of the resource
   */
  protected File extractInitScriptFile(Class resourceClass, String resourceName) {
    File file = null;
    try {
      file = File.createTempFile(resourceName, INIT_SCRIPT_EXTENSION);
    } catch (IOException e) {
      logger.error("Creating init script file temp file", e);
      return null;
    }
    file.deleteOnExit();

    if (extractResourceAsFile(resourceClass, resourceName + INIT_SCRIPT_EXTENSION, file)) {
      return file;
    }

    logger.error("Internal error! Failed to extract init script for executing commands!");

    return null;
  }

  /**
   * This extracts the given class' resource to the specified file if it doesn't already exist.
   *
   * @param resourceClass the class associated with the resource
   * @param name the resource's name
   * @param file where to put the resource
   * @return true if successful, false if not.
   */
  public boolean extractResourceAsFile(Class resourceClass, String name, File file) {
    InputStream stream = resourceClass.getResourceAsStream(name);
    if (stream == null) {
      return false;
    }

    byte[] bytes = new byte[0];
    try {
      bytes = IOUtils.toByteArray(stream);
    } catch (IOException e) {
      logger.error("Extracting resource as file", e);
      return false;
    }

    FileOutputStream fileOutputStream = null;
    try {
      fileOutputStream = new FileOutputStream(file);
      IOUtils.write(bytes, fileOutputStream);
      return true;
    } catch (IOException e) {
      logger.error("Extracting resource as file (writing bytes)", e);
      return false;
    } finally {
      IOUtils.closeQuietly(fileOutputStream);
    }
  }

  protected File getGradleHomeDirectory() {
    return gradleHomeDirectory;
  }

  /**
   * Notification that a read failure occurred. This really only exists for debugging purposes when
   * things go wrong.
   */
  public void readFailureOccurred() {
    logger.debug("Last message received: " + lastMessageReceived);
  }

  public void aboutToKillProcess() {
    killGradle();
  }

  public void killGradle() {
    if (killGradleClientProcotol != null) {
      killGradleClientProcotol.sendKillMessage();
    }
  }
}
public class DefaultDaemonStarter implements DaemonStarter {

  private static final Logger LOGGER = Logging.getLogger(DefaultDaemonStarter.class);

  private final DaemonDir daemonDir;
  private final DaemonParameters daemonParameters;
  private final DaemonGreeter daemonGreeter;
  private final DaemonStartListener listener;
  private final JvmVersionValidator versionValidator;

  public DefaultDaemonStarter(
      DaemonDir daemonDir,
      DaemonParameters daemonParameters,
      DaemonGreeter daemonGreeter,
      DaemonStartListener listener,
      JvmVersionValidator versionValidator) {
    this.daemonDir = daemonDir;
    this.daemonParameters = daemonParameters;
    this.daemonGreeter = daemonGreeter;
    this.listener = listener;
    this.versionValidator = versionValidator;
  }

  public DaemonStartupInfo startDaemon() {
    String daemonUid = UUID.randomUUID().toString();

    GradleInstallation gradleInstallation = CurrentGradleInstallation.get();
    ModuleRegistry registry = new DefaultModuleRegistry(gradleInstallation);
    ClassPath classpath;
    List<File> searchClassPath;
    if (gradleInstallation == null) {
      // When not running from a Gradle distro, need runtime impl for launcher plus the search path
      // to look for other modules
      classpath = new DefaultClassPath();
      for (Module module : registry.getModule("gradle-launcher").getAllRequiredModules()) {
        classpath = classpath.plus(module.getClasspath());
      }
      searchClassPath = registry.getAdditionalClassPath().getAsFiles();
    } else {
      // When running from a Gradle distro, only need launcher jar. The daemon can find everything
      // from there.
      classpath = registry.getModule("gradle-launcher").getImplementationClasspath();
      searchClassPath = Collections.emptyList();
    }
    if (classpath.isEmpty()) {
      throw new IllegalStateException(
          "Unable to construct a bootstrap classpath when starting the daemon");
    }

    versionValidator.validate(daemonParameters);

    List<String> daemonArgs = new ArrayList<String>();
    daemonArgs.add(daemonParameters.getEffectiveJvm().getJavaExecutable().getAbsolutePath());

    List<String> daemonOpts = daemonParameters.getEffectiveJvmArgs();
    daemonArgs.addAll(daemonOpts);
    daemonArgs.add("-cp");
    daemonArgs.add(CollectionUtils.join(File.pathSeparator, classpath.getAsFiles()));

    if (Boolean.getBoolean("org.gradle.daemon.debug")) {
      daemonArgs.add("-Xdebug");
      daemonArgs.add("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005");
    }
    LOGGER.debug("Using daemon args: {}", daemonArgs);

    daemonArgs.add(GradleDaemon.class.getName());
    // Version isn't used, except by a human looking at the output of jps.
    daemonArgs.add(GradleVersion.current().getVersion());

    // Serialize configuration to daemon via the process' stdin
    ByteArrayOutputStream serializedConfig = new ByteArrayOutputStream();
    FlushableEncoder encoder =
        new KryoBackedEncoder(new EncodedStream.EncodedOutput(serializedConfig));
    try {
      encoder.writeString(daemonParameters.getGradleUserHomeDir().getAbsolutePath());
      encoder.writeString(daemonDir.getBaseDir().getAbsolutePath());
      encoder.writeSmallInt(daemonParameters.getIdleTimeout());
      encoder.writeString(daemonUid);
      encoder.writeSmallInt(daemonOpts.size());
      for (String daemonOpt : daemonOpts) {
        encoder.writeString(daemonOpt);
      }
      encoder.writeSmallInt(searchClassPath.size());
      for (File file : searchClassPath) {
        encoder.writeString(file.getAbsolutePath());
      }
      encoder.flush();
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
    ByteArrayInputStream stdInput = new ByteArrayInputStream(serializedConfig.toByteArray());

    DaemonStartupInfo daemonInfo = startProcess(daemonArgs, daemonDir.getVersionedDir(), stdInput);
    listener.daemonStarted(daemonInfo);
    return daemonInfo;
  }

  private DaemonStartupInfo startProcess(List<String> args, File workingDir, InputStream stdInput) {
    LOGGER.info("Starting daemon process: workingDir = {}, daemonArgs: {}", workingDir, args);
    Clock clock = new Clock();
    try {
      GFileUtils.mkdirs(workingDir);

      DaemonOutputConsumer outputConsumer = new DaemonOutputConsumer(stdInput);
      ExecHandle handle = new DaemonExecHandleBuilder().build(args, workingDir, outputConsumer);

      handle.start();
      LOGGER.debug("Gradle daemon process is starting. Waiting for the daemon to detach...");
      handle.waitForFinish();
      LOGGER.debug("Gradle daemon process is now detached.");

      return daemonGreeter.parseDaemonOutput(outputConsumer.getProcessOutput());
    } catch (GradleException e) {
      throw e;
    } catch (Exception e) {
      throw new GradleException("Could not start Gradle daemon.", e);
    } finally {
      LOGGER.info("An attempt to start the daemon took {}.", clock.getTime());
    }
  }
}
Esempio n. 14
0
/** Access to daemon registry files. Useful also for testing. */
public class PersistentDaemonRegistry implements DaemonRegistry {
  private final PersistentStateCache<DaemonRegistryContent> cache;
  private final Lock lock = new ReentrantLock();
  private final File registryFile;

  private static final Logger LOGGER = Logging.getLogger(PersistentDaemonRegistry.class);

  public PersistentDaemonRegistry(File registryFile, FileLockManager fileLockManager) {
    this.registryFile = registryFile;
    cache =
        new FileIntegrityViolationSuppressingPersistentStateCacheDecorator<DaemonRegistryContent>(
            new SimpleStateCache<DaemonRegistryContent>(
                registryFile,
                new OnDemandFileAccess(registryFile, "daemon addresses registry", fileLockManager),
                new DefaultSerializer<DaemonRegistryContent>()));
  }

  public List<DaemonInfo> getAll() {
    lock.lock();
    try {
      DaemonRegistryContent content = cache.get();
      if (content == null) {
        // when no daemon process has started yet
        return new LinkedList<DaemonInfo>();
      }
      return content.getInfos();
    } finally {
      lock.unlock();
    }
  }

  public List<DaemonInfo> getIdle() {
    lock.lock();
    try {
      List<DaemonInfo> out = new LinkedList<DaemonInfo>();
      List<DaemonInfo> all = getAll();
      for (DaemonInfo d : all) {
        if (d.isIdle()) {
          out.add(d);
        }
      }
      return out;
    } finally {
      lock.unlock();
    }
  }

  public List<DaemonInfo> getBusy() {
    lock.lock();
    try {
      List<DaemonInfo> out = new LinkedList<DaemonInfo>();
      List<DaemonInfo> all = getAll();
      for (DaemonInfo d : all) {
        if (!d.isIdle()) {
          out.add(d);
        }
      }
      return out;
    } finally {
      lock.unlock();
    }
  }

  public void remove(final Address address) {
    lock.lock();
    LOGGER.debug("Removing daemon address: {}", address);
    try {
      cache.update(
          new PersistentStateCache.UpdateAction<DaemonRegistryContent>() {
            public DaemonRegistryContent update(DaemonRegistryContent oldValue) {
              if (oldValue == null) {
                return oldValue;
              }
              oldValue.removeInfo(address);
              return oldValue;
            }
          });
    } finally {
      lock.unlock();
    }
  }

  public void markBusy(final Address address) {
    lock.lock();
    LOGGER.debug("Marking busy by address: {}", address);
    try {
      cache.update(
          new PersistentStateCache.UpdateAction<DaemonRegistryContent>() {
            public DaemonRegistryContent update(DaemonRegistryContent oldValue) {
              DaemonInfo daemonInfo = oldValue != null ? oldValue.getInfo(address) : null;
              if (daemonInfo != null) {
                daemonInfo.setIdle(false);
              }
              // Else, has been removed by something else - ignore
              return oldValue;
            }
          });
    } finally {
      lock.unlock();
    }
  }

  public void markIdle(final Address address) {
    lock.lock();
    LOGGER.debug("Marking idle by address: {}", address);
    try {
      cache.update(
          new PersistentStateCache.UpdateAction<DaemonRegistryContent>() {
            public DaemonRegistryContent update(DaemonRegistryContent oldValue) {
              DaemonInfo daemonInfo = oldValue != null ? oldValue.getInfo(address) : null;
              if (daemonInfo != null) {
                daemonInfo.setIdle(true);
              }
              // Else, has been removed by something else - ignore
              return oldValue;
            }
          });
    } finally {
      lock.unlock();
    }
  }

  public void store(final DaemonInfo info) {
    final Address address = info.getAddress();
    final DaemonContext daemonContext = info.getContext();
    final String password = info.getPassword();
    final boolean idle = info.isIdle();

    lock.lock();
    LOGGER.debug("Storing daemon address: {}, context: {}", address, daemonContext);
    try {
      cache.update(
          new PersistentStateCache.UpdateAction<DaemonRegistryContent>() {
            public DaemonRegistryContent update(DaemonRegistryContent oldValue) {
              if (oldValue == null) {
                // it means the registry didn't exist yet
                oldValue = new DaemonRegistryContent();
              }
              DaemonInfo daemonInfo = new DaemonInfo(address, daemonContext, password, idle);
              oldValue.setStatus(address, daemonInfo);
              return oldValue;
            }
          });
    } finally {
      lock.unlock();
    }
  }

  public String toString() {
    return String.format("PersistentDaemonRegistry[file=%s]", registryFile);
  }
}
Esempio n. 15
0
public class DaemonStarter implements Runnable {

  private static final Logger LOGGER = Logging.getLogger(DaemonStarter.class);

  private final File userHomeDir;
  private final int idleTimeout;

  public DaemonStarter(File userHomeDir, int idleTimeout) {
    this.userHomeDir = userHomeDir;
    this.idleTimeout = idleTimeout;
  }

  public void run() {
    DefaultModuleRegistry registry = new DefaultModuleRegistry();
    Set<File> bootstrapClasspath = new LinkedHashSet<File>();
    bootstrapClasspath.addAll(registry.getModule("gradle-launcher").getImplementationClasspath());
    if (registry.getGradleHome() == null) {
      // Running from the classpath - chuck in everything we can find
      bootstrapClasspath.addAll(registry.getFullClasspath());
    }
    if (bootstrapClasspath.isEmpty()) {
      throw new IllegalStateException(
          "Unable to construct a bootstrap classpath when starting the daemon");
    }

    List<String> daemonArgs = new ArrayList<String>();
    daemonArgs.add(Jvm.current().getJavaExecutable().getAbsolutePath());
    daemonArgs.add("-Xmx1024m");
    daemonArgs.add("-XX:MaxPermSize=256m");
    daemonArgs.add("-XX:MaxPermSize=256m");
    // Useful for debugging purposes - simply uncomment and connect to debug
    //        daemonArgs.add("-Xdebug");
    //        daemonArgs.add("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5006");
    daemonArgs.add("-cp");
    daemonArgs.add(GUtil.join(bootstrapClasspath, File.pathSeparator));
    daemonArgs.add(GradleDaemon.class.getName());
    daemonArgs.add(String.format("-%s", DefaultCommandLineConverter.GRADLE_USER_HOME));
    daemonArgs.add(userHomeDir.getAbsolutePath());

    DaemonIdleTimeout idleTimeout =
        new DaemonIdleTimeout(System.getenv("GRADLE_OPTS"), this.idleTimeout);
    daemonArgs.add(idleTimeout.toSysArg());

    LOGGER.info(
        "starting daemon process: workingDir = {}, daemonArgs: {}", userHomeDir, daemonArgs);
    startProcess(daemonArgs, userHomeDir);
  }

  private void startProcess(List<String> args, File workingDir) {
    try {
      workingDir.mkdirs();
      if (OperatingSystem.current().isWindows()) {
        StringBuilder commandLine = new StringBuilder();
        for (String arg : args) {
          commandLine.append('"');
          commandLine.append(arg);
          commandLine.append("\" ");
        }

        new WindowsProcessStarter().start(workingDir, commandLine.toString());
      } else {
        ProcessBuilder builder = new ProcessBuilder(args);
        builder.directory(workingDir);
        Process process = builder.start();
        process.getOutputStream().close();
        process.getErrorStream().close();
        process.getInputStream().close();
      }
    } catch (Exception e) {
      throw new GradleException("Could not start Gradle daemon.", e);
    }
  }
}
 protected AbstractGradleExecuter(
     GradleDistribution distribution, TestDirectoryProvider testDirectoryProvider) {
   this.distribution = distribution;
   this.testDirectoryProvider = testDirectoryProvider;
   logger = Logging.getLogger(getClass());
 }
Esempio n. 17
0
public class DefaultJavaCompilerFactory implements JavaCompilerFactory {
  private static final Logger LOGGER = Logging.getLogger(DefaultJavaCompilerFactory.class);

  private final ProjectInternal project;
  private final Factory<AntBuilder> antBuilderFactory;
  private final JavaCompilerFactory inProcessCompilerFactory;
  private final CompilerDaemonManager compilerDaemonManager;
  private boolean jointCompilation;

  public DefaultJavaCompilerFactory(
      ProjectInternal project,
      Factory<AntBuilder> antBuilderFactory,
      JavaCompilerFactory inProcessCompilerFactory,
      CompilerDaemonManager compilerDaemonManager) {
    this.project = project;
    this.antBuilderFactory = antBuilderFactory;
    this.inProcessCompilerFactory = inProcessCompilerFactory;
    this.compilerDaemonManager = compilerDaemonManager;
  }

  /**
   * If true, the Java compiler to be created is used for joint compilation together with another
   * language's compiler in the compiler daemon. In that case, the other language's normalizing and
   * daemon compilers should be used.
   */
  public void setJointCompilation(boolean flag) {
    jointCompilation = flag;
  }

  public Compiler<JavaCompileSpec> create(CompileOptions options) {
    fallBackToAntIfNecessary(options);

    if (options.isUseAnt()) {
      return new AntJavaCompiler(antBuilderFactory);
    }

    Compiler<JavaCompileSpec> result = createTargetCompiler(options);
    if (!jointCompilation) {
      result = new NormalizingJavaCompiler(result);
    }
    return result;
  }

  private void fallBackToAntIfNecessary(CompileOptions options) {
    if (options.isUseAnt()) {
      return;
    }

    if (options.getCompiler() != null) {
      LOGGER.warn(
          "Falling back to Ant javac task ('CompileOptions.useAnt = true') because 'CompileOptions.compiler' is set.");
      options.setUseAnt(true);
    }
  }

  private Compiler<JavaCompileSpec> createTargetCompiler(CompileOptions options) {
    if (options.isFork() && options.getForkOptions().getExecutable() != null) {
      return new CommandLineJavaCompiler(
          createSerializableTempFileProvider(), project.getProjectDir());
    }

    Compiler<JavaCompileSpec> compiler = inProcessCompilerFactory.create(options);
    if (options.isFork() && !jointCompilation) {
      return new DaemonJavaCompiler(project, compiler, compilerDaemonManager);
    }

    return compiler;
  }

  private TemporaryFileProvider createSerializableTempFileProvider() {
    return new DefaultTemporaryFileProvider(new FileFactory(project.getBuildDir()));
  }

  private static class FileFactory implements Factory<File>, Serializable {
    private final File file;

    private FileFactory(File file) {
      this.file = file;
    }

    public File create() {
      return file;
    }
  }
}
public class ShortCircuitTaskArtifactStateRepository implements TaskArtifactStateRepository {
  private static final Logger LOGGER =
      Logging.getLogger(ShortCircuitTaskArtifactStateRepository.class);
  private final StartParameter startParameter;
  private final TaskArtifactStateRepository repository;

  public ShortCircuitTaskArtifactStateRepository(
      StartParameter startParameter, TaskArtifactStateRepository repository) {
    this.startParameter = startParameter;
    this.repository = repository;
  }

  public TaskArtifactState getStateFor(final TaskInternal task) {
    if (task.getOutputs().getHasOutput()) {
      return new ShortCircuitArtifactState(task, repository.getStateFor(task));
    }
    LOGGER.info(
        String.format(
            "%s has not declared any outputs, assuming that it is out-of-date.",
            StringUtils.capitalize(task.toString())));
    return new NoHistoryArtifactState();
  }

  private static class NoHistoryArtifactState implements TaskArtifactState, TaskExecutionHistory {
    public boolean isUpToDate() {
      return false;
    }

    public void beforeTask() {}

    public void afterTask() {}

    public void finished() {}

    public TaskExecutionHistory getExecutionHistory() {
      return this;
    }

    public FileCollection getOutputFiles() {
      throw new UnsupportedOperationException();
    }
  }

  private class ShortCircuitArtifactState implements TaskArtifactState {
    private final TaskInternal task;
    private final TaskArtifactState state;

    public ShortCircuitArtifactState(TaskInternal task, TaskArtifactState state) {
      this.task = task;
      this.state = state;
    }

    public boolean isUpToDate() {
      return !startParameter.isRerunTasks()
          && task.getOutputs().getUpToDateSpec().isSatisfiedBy(task)
          && state.isUpToDate();
    }

    public TaskExecutionHistory getExecutionHistory() {
      return state.getExecutionHistory();
    }

    public void beforeTask() {
      state.beforeTask();
    }

    public void afterTask() {
      state.afterTask();
    }

    public void finished() {
      state.finished();
    }
  }
}
Esempio n. 19
0
/**
 * Executes tests. Supports JUnit (3.8.x or 4.x) or TestNG tests.
 *
 * <p>An example with a blend of various settings
 *
 * <pre autoTested=''>
 * apply plugin: 'java' // adds 'test' task
 *
 * test {
 *   //configuring a system property for tests
 *   systemProperty 'some.prop', 'value'
 *
 *   //tuning the included/excluded tests
 *   include 'org/foo/**'
 *   exclude 'org/boo/**'
 *
 *   //makes the standard streams (err and out) visible at console when running tests
 *   testLogging.showStandardStreams = true
 *
 *   //tweaking memory settings for the forked vm that runs tests
 *   jvmArgs '-Xms128m', '-Xmx512m', '-XX:MaxPermSize=128m'
 *
 *   //listening to test execution events
 *   beforeTest { descriptor ->
 *      logger.lifecycle("Running test: " + descriptor)
 *   }
 *   onOutput { descriptor, event ->
 *      logger.lifecycle("Test: " + descriptor + " produced standard out/err: " + event.message )
 *   }
 * }
 * </pre>
 *
 * @author Hans Dockter
 */
public class Test extends ConventionTask
    implements JavaForkOptions, PatternFilterable, VerificationTask {
  private static final Logger LOGGER = Logging.getLogger(Test.class);

  private TestExecuter testExecuter;
  private final DefaultJavaForkOptions options;
  private List<File> testSrcDirs = new ArrayList<File>();
  private File testClassesDir;
  private File testResultsDir;
  private File testReportDir;
  private PatternFilterable patternSet = new PatternSet();
  private boolean ignoreFailures;
  private FileCollection classpath;
  private TestFramework testFramework;
  private boolean testReport = true;
  private boolean scanForTestClasses = true;
  private long forkEvery;
  private int maxParallelForks = 1;
  private ListenerBroadcast<TestListener> testListenerBroadcaster;
  private final ListenerBroadcast<TestOutputListener> testOutputListenerBroadcaster;
  private OutputEventListener outputListener;
  private final TestLoggingContainer testLogging = new DefaultTestLoggingContainer();

  public Test() {
    testListenerBroadcaster =
        getServices().get(ListenerManager.class).createAnonymousBroadcaster(TestListener.class);
    testOutputListenerBroadcaster =
        getServices()
            .get(ListenerManager.class)
            .createAnonymousBroadcaster(TestOutputListener.class);
    this.testExecuter =
        new DefaultTestExecuter(
            getServices().getFactory(WorkerProcessBuilder.class),
            getServices().get(ActorFactory.class));
    outputListener = getServices().get(OutputEventListener.class);

    options = new DefaultJavaForkOptions(getServices().get(FileResolver.class));
    options.setEnableAssertions(true);
  }

  void setTestExecuter(TestExecuter testExecuter) {
    this.testExecuter = testExecuter;
  }

  /** {@inheritDoc} */
  @Input
  public File getWorkingDir() {
    return options.getWorkingDir();
  }

  /** {@inheritDoc} */
  public void setWorkingDir(Object dir) {
    options.setWorkingDir(dir);
  }

  /** {@inheritDoc} */
  public Test workingDir(Object dir) {
    options.workingDir(dir);
    return this;
  }

  /** {@inheritDoc} */
  @Input
  public String getExecutable() {
    return options.getExecutable();
  }

  /** {@inheritDoc} */
  public Test executable(Object executable) {
    options.executable(executable);
    return this;
  }

  /** {@inheritDoc} */
  public void setExecutable(Object executable) {
    options.setExecutable(executable);
  }

  /** {@inheritDoc} */
  @Input
  public Map<String, Object> getSystemProperties() {
    return options.getSystemProperties();
  }

  /** {@inheritDoc} */
  public void setSystemProperties(Map<String, ?> properties) {
    options.setSystemProperties(properties);
  }

  /** {@inheritDoc} */
  public Test systemProperties(Map<String, ?> properties) {
    options.systemProperties(properties);
    return this;
  }

  /** {@inheritDoc} */
  public Test systemProperty(String name, Object value) {
    options.systemProperty(name, value);
    return this;
  }

  /** {@inheritDoc} */
  @Input
  public FileCollection getBootstrapClasspath() {
    return options.getBootstrapClasspath();
  }

  /** {@inheritDoc} */
  public void setBootstrapClasspath(FileCollection classpath) {
    options.setBootstrapClasspath(classpath);
  }

  /** {@inheritDoc} */
  public Test bootstrapClasspath(Object... classpath) {
    options.bootstrapClasspath(classpath);
    return this;
  }

  /** {@inheritDoc} */
  public String getMinHeapSize() {
    return options.getMinHeapSize();
  }

  /** {@inheritDoc} */
  public String getDefaultCharacterEncoding() {
    return options.getDefaultCharacterEncoding();
  }

  /** {@inheritDoc} */
  public void setDefaultCharacterEncoding(String defaultCharacterEncoding) {
    options.setDefaultCharacterEncoding(defaultCharacterEncoding);
  }

  /** {@inheritDoc} */
  public void setMinHeapSize(String heapSize) {
    options.setMinHeapSize(heapSize);
  }

  /** {@inheritDoc} */
  public String getMaxHeapSize() {
    return options.getMaxHeapSize();
  }

  /** {@inheritDoc} */
  public void setMaxHeapSize(String heapSize) {
    options.setMaxHeapSize(heapSize);
  }

  /** {@inheritDoc} */
  @Input
  public List<String> getJvmArgs() {
    return options.getJvmArgs();
  }

  /** {@inheritDoc} */
  public void setJvmArgs(Iterable<?> arguments) {
    options.setJvmArgs(arguments);
  }

  /** {@inheritDoc} */
  public Test jvmArgs(Iterable<?> arguments) {
    options.jvmArgs(arguments);
    return this;
  }

  /** {@inheritDoc} */
  public Test jvmArgs(Object... arguments) {
    options.jvmArgs(arguments);
    return this;
  }

  /** {@inheritDoc} */
  @Input
  public boolean getEnableAssertions() {
    return options.getEnableAssertions();
  }

  /** {@inheritDoc} */
  public void setEnableAssertions(boolean enabled) {
    options.setEnableAssertions(enabled);
  }

  /** {@inheritDoc} */
  public boolean getDebug() {
    return options.getDebug();
  }

  /** {@inheritDoc} */
  public void setDebug(boolean enabled) {
    options.setDebug(enabled);
  }

  /** {@inheritDoc} */
  public List<String> getAllJvmArgs() {
    return options.getAllJvmArgs();
  }

  /** {@inheritDoc} */
  public void setAllJvmArgs(Iterable<?> arguments) {
    options.setAllJvmArgs(arguments);
  }

  /** {@inheritDoc} */
  public Map<String, Object> getEnvironment() {
    return options.getEnvironment();
  }

  /** {@inheritDoc} */
  public Test environment(Map<String, ?> environmentVariables) {
    options.environment(environmentVariables);
    return this;
  }

  /** {@inheritDoc} */
  public Test environment(String name, Object value) {
    options.environment(name, value);
    return this;
  }

  /** {@inheritDoc} */
  public void setEnvironment(Map<String, ?> environmentVariables) {
    options.setEnvironment(environmentVariables);
  }

  /** {@inheritDoc} */
  public Test copyTo(ProcessForkOptions target) {
    options.copyTo(target);
    return this;
  }

  /** {@inheritDoc} */
  public Test copyTo(JavaForkOptions target) {
    options.copyTo(target);
    return this;
  }

  @TaskAction
  public void executeTests() {
    for (LogLevel level : LogLevel.values()) {
      if (!LOGGER.isEnabled(level)) {
        continue;
      }
      TestLogging levelLogging = testLogging.get(level);
      TestExceptionFormatter exceptionFormatter = getExceptionFormatter(testLogging);
      TestEventLogger eventLogger =
          new TestEventLogger(outputListener, level, levelLogging, exceptionFormatter);
      addTestListener(eventLogger);
      addTestOutputListener(eventLogger);
    }

    ProgressLoggerFactory progressLoggerFactory = getServices().get(ProgressLoggerFactory.class);
    TestCountLogger testCountLogger = new TestCountLogger(progressLoggerFactory);
    addTestListener(testCountLogger);

    TestResultProcessor resultProcessor =
        new TestListenerAdapter(
            getTestListenerBroadcaster().getSource(), testOutputListenerBroadcaster.getSource());
    testExecuter.execute(this, resultProcessor);

    testFramework.report();

    testFramework = null;

    if (!getIgnoreFailures() && testCountLogger.hadFailures()) {
      throw new GradleException(
          "There were failing tests. See the report at " + getTestReportUrl() + ".");
    }
  }

  private TestExceptionFormatter getExceptionFormatter(TestLogging testLogging) {
    switch (testLogging.getExceptionFormat()) {
      case SHORT:
        return new ShortExceptionFormatter(testLogging);
      case FULL:
        return new FullExceptionFormatter(testLogging);
      default:
        throw new AssertionError();
    }
  }

  private String getTestReportUrl() {
    // File.toURI().toString() leads to an URL like this on Mac: file:/reports/index.html
    // This URL is not recognized by the Mac terminal (too few leading slashes). We solve
    // this be creating an URI with an empty authority.
    File indexFile = new File(getTestReportDir(), "index.html");
    try {
      if (OperatingSystem.current().isWindows()) {
        return indexFile.toURI().toString();
      }
      return new URI("file", "", indexFile.toString(), null, null).toString();
    } catch (URISyntaxException e) {
      throw UncheckedException.throwAsUncheckedException(e);
    }
  }

  /**
   * Returns the {@link org.gradle.api.tasks.testing.TestListener} broadcaster. This broadcaster
   * will send messages to all listeners that have been registered with the ListenerManager.
   */
  ListenerBroadcast<TestListener> getTestListenerBroadcaster() {
    return testListenerBroadcaster;
  }

  /**
   * Registers a test listener with this task. Consider also the following handy methods for quicker
   * hooking into test execution: {@link #beforeTest(groovy.lang.Closure)}, {@link
   * #afterTest(groovy.lang.Closure)}, {@link #beforeSuite(groovy.lang.Closure)}, {@link
   * #afterSuite(groovy.lang.Closure)}
   *
   * <p>This listener will NOT be notified of tests executed by other tasks. To get that behavior,
   * use {@link org.gradle.api.invocation.Gradle#addListener(Object)}.
   *
   * @param listener The listener to add.
   */
  public void addTestListener(TestListener listener) {
    testListenerBroadcaster.add(listener);
  }

  /**
   * Registers a output listener with this task. Quicker way of hooking into output events is using
   * the {@link #onOutput(groovy.lang.Closure)} method.
   *
   * @param listener The listener to add.
   */
  public void addTestOutputListener(TestOutputListener listener) {
    testOutputListenerBroadcaster.add(listener);
  }

  /**
   * Unregisters a test listener with this task. This method will only remove listeners that were
   * added by calling {@link #addTestListener(org.gradle.api.tasks.testing.TestListener)} on this
   * task. If the listener was registered with Gradle using {@link
   * org.gradle.api.invocation.Gradle#addListener(Object)} this method will not do anything.
   * Instead, use {@link org.gradle.api.invocation.Gradle#removeListener(Object)}.
   *
   * @param listener The listener to remove.
   */
  public void removeTestListener(TestListener listener) {
    testListenerBroadcaster.remove(listener);
  }

  /**
   * Unregisters a test output listener with this task. This method will only remove listeners that
   * were added by calling {@link
   * #addTestOutputListener(org.gradle.api.tasks.testing.TestOutputListener)} on this task. If the
   * listener was registered with Gradle using {@link
   * org.gradle.api.invocation.Gradle#addListener(Object)} this method will not do anything.
   * Instead, use {@link org.gradle.api.invocation.Gradle#removeListener(Object)}.
   *
   * @param listener The listener to remove.
   */
  public void removeTestOutputListener(TestOutputListener listener) {
    testOutputListenerBroadcaster.remove(listener);
  }

  /**
   * Adds a closure to be notified before a test suite is executed. A {@link
   * org.gradle.api.tasks.testing.TestDescriptor} instance is passed to the closure as a parameter.
   *
   * <p>This method is also called before any test suites are executed. The provided descriptor will
   * have a null parent suite.
   *
   * @param closure The closure to call.
   */
  public void beforeSuite(Closure closure) {
    testListenerBroadcaster.add("beforeSuite", closure);
  }

  /**
   * Adds a closure to be notified after a test suite has executed. A {@link
   * org.gradle.api.tasks.testing.TestDescriptor} and {@link TestResult} instance are passed to the
   * closure as a parameter.
   *
   * <p>This method is also called after all test suites are executed. The provided descriptor will
   * have a null parent suite.
   *
   * @param closure The closure to call.
   */
  public void afterSuite(Closure closure) {
    testListenerBroadcaster.add("afterSuite", closure);
  }

  /**
   * Adds a closure to be notified before a test is executed. A {@link
   * org.gradle.api.tasks.testing.TestDescriptor} instance is passed to the closure as a parameter.
   *
   * @param closure The closure to call.
   */
  public void beforeTest(Closure closure) {
    testListenerBroadcaster.add("beforeTest", closure);
  }

  /**
   * Adds a closure to be notified after a test has executed. A {@link
   * org.gradle.api.tasks.testing.TestDescriptor} and {@link TestResult} instance are passed to the
   * closure as a parameter.
   *
   * @param closure The closure to call.
   */
  public void afterTest(Closure closure) {
    testListenerBroadcaster.add("afterTest", closure);
  }

  /**
   * Adds a closure to be notified when output from the test received. A {@link
   * org.gradle.api.tasks.testing.TestDescriptor} and {@link
   * org.gradle.api.tasks.testing.TestOutputEvent} instance are passed to the closure as a
   * parameter.
   *
   * <pre autoTested=''>
   * apply plugin: 'java'
   *
   * test {
   *   onOutput { descriptor, event ->
   *     if (event.destination == TestOutputEvent.Destination.StdErr) {
   *       logger.error("Test: " + descriptor + ", error: " + event.message)
   *     }
   *   }
   * }
   * </pre>
   *
   * @param closure The closure to call.
   */
  public void onOutput(Closure closure) {
    testOutputListenerBroadcaster.add("onOutput", closure);
  }

  /**
   * Adds include patterns for the files in the test classes directory (e.g. '**&#2F;*Test.class')).
   *
   * @see #setIncludes(Iterable)
   */
  public Test include(String... includes) {
    patternSet.include(includes);
    return this;
  }

  /**
   * Adds include patterns for the files in the test classes directory (e.g. '**&#2F;*Test.class')).
   *
   * @see #setIncludes(Iterable)
   */
  public Test include(Iterable<String> includes) {
    patternSet.include(includes);
    return this;
  }

  /** {@inheritDoc} */
  public Test include(Spec<FileTreeElement> includeSpec) {
    patternSet.include(includeSpec);
    return this;
  }

  /** {@inheritDoc} */
  public Test include(Closure includeSpec) {
    patternSet.include(includeSpec);
    return this;
  }

  /**
   * Adds exclude patterns for the files in the test classes directory (e.g. '**&#2F;*Test.class')).
   *
   * @see #setExcludes(Iterable)
   */
  public Test exclude(String... excludes) {
    patternSet.exclude(excludes);
    return this;
  }

  /**
   * Adds exclude patterns for the files in the test classes directory (e.g. '**&#2F;*Test.class')).
   *
   * @see #setExcludes(Iterable)
   */
  public Test exclude(Iterable<String> excludes) {
    patternSet.exclude(excludes);
    return this;
  }

  /** {@inheritDoc} */
  public Test exclude(Spec<FileTreeElement> excludeSpec) {
    patternSet.exclude(excludeSpec);
    return this;
  }

  /** {@inheritDoc} */
  public Test exclude(Closure excludeSpec) {
    patternSet.exclude(excludeSpec);
    return this;
  }

  /**
   * Returns the root folder for the compiled test sources.
   *
   * @return All test class directories to be used.
   */
  public File getTestClassesDir() {
    return testClassesDir;
  }

  /**
   * Sets the root folder for the compiled test sources.
   *
   * @param testClassesDir The root folder
   */
  public void setTestClassesDir(File testClassesDir) {
    this.testClassesDir = testClassesDir;
  }

  /**
   * Returns the root folder for the test results.
   *
   * @return the test result directory, containing the internal test results, mostly in xml form.
   */
  @OutputDirectory
  public File getTestResultsDir() {
    return testResultsDir;
  }

  /**
   * Sets the root folder for the test results.
   *
   * @param testResultsDir The root folder
   */
  public void setTestResultsDir(File testResultsDir) {
    this.testResultsDir = testResultsDir;
  }

  /**
   * Returns the root folder for the test reports.
   *
   * @return the test report directory, containing the test report mostly in HTML form.
   */
  @OutputDirectory
  public File getTestReportDir() {
    return testReportDir;
  }

  /**
   * Sets the root folder for the test reports.
   *
   * @param testReportDir The root folder
   */
  public void setTestReportDir(File testReportDir) {
    this.testReportDir = testReportDir;
  }

  /**
   * Returns the include patterns for test execution.
   *
   * @see #include(String...)
   */
  public Set<String> getIncludes() {
    return patternSet.getIncludes();
  }

  /**
   * Sets the include patterns for test execution.
   *
   * @param includes The patterns list
   * @see #include(String...)
   */
  public Test setIncludes(Iterable<String> includes) {
    patternSet.setIncludes(includes);
    return this;
  }

  /**
   * Returns the exclude patterns for test execution.
   *
   * @see #exclude(String...)
   */
  public Set<String> getExcludes() {
    return patternSet.getExcludes();
  }

  /**
   * Sets the exclude patterns for test execution.
   *
   * @param excludes The patterns list
   * @see #exclude(String...)
   */
  public Test setExcludes(Iterable<String> excludes) {
    patternSet.setExcludes(excludes);
    return this;
  }

  /** {@inheritDoc} */
  @Input
  public boolean getIgnoreFailures() {
    return ignoreFailures;
  }

  /** {@inheritDoc} */
  public void setIgnoreFailures(boolean ignoreFailures) {
    this.ignoreFailures = ignoreFailures;
  }

  public TestFramework getTestFramework() {
    return testFramework(null);
  }

  public TestFramework testFramework(Closure testFrameworkConfigure) {
    if (testFramework == null) {
      useJUnit(testFrameworkConfigure);
    }

    return testFramework;
  }

  /**
   * Returns the test options options.
   *
   * <p>Be sure to call the appropriate {@link #useJUnit()} or {@link #useTestNG()} method before
   * using this method.
   *
   * @return The testframework options.
   */
  @Nested
  public TestFrameworkOptions getOptions() {
    return options(null);
  }

  public TestFrameworkOptions options(Closure testFrameworkConfigure) {
    TestFrameworkOptions options = getTestFramework().getOptions();
    ConfigureUtil.configure(testFrameworkConfigure, testFramework.getOptions());
    return options;
  }

  TestFramework useTestFramework(TestFramework testFramework) {
    return useTestFramework(testFramework, null);
  }

  private TestFramework useTestFramework(
      TestFramework testFramework, Closure testFrameworkConfigure) {
    if (testFramework == null) {
      throw new IllegalArgumentException("testFramework is null!");
    }

    this.testFramework = testFramework;

    if (testFrameworkConfigure != null) {
      ConfigureUtil.configure(testFrameworkConfigure, this.testFramework.getOptions());
    }

    return this.testFramework;
  }

  /** Specifies that JUnit should be used to execute the tests. */
  public void useJUnit() {
    useJUnit(null);
  }

  /**
   * Specifies that JUnit should be used to execute the tests.
   *
   * @param testFrameworkConfigure A closure used to configure the JUnit options. This closure is
   *     passed an instance of type {@link org.gradle.api.tasks.testing.junit.JUnitOptions}.
   */
  public void useJUnit(Closure testFrameworkConfigure) {
    useTestFramework(new JUnitTestFramework(this), testFrameworkConfigure);
  }

  /** Specifies that TestNG should be used to execute the tests. */
  public void useTestNG() {
    useTestNG(null);
  }

  /**
   * Specifies that TestNG should be used to execute the tests.
   *
   * @param testFrameworkConfigure A closure used to configure the TestNG options. This closure is
   *     passed an instance of type {@link org.gradle.api.tasks.testing.testng.TestNGOptions}.
   */
  public void useTestNG(Closure testFrameworkConfigure) {
    useTestFramework(new TestNGTestFramework(this), testFrameworkConfigure);
  }

  /** Returns the classpath to use to execute the tests. */
  @InputFiles
  public FileCollection getClasspath() {
    return classpath;
  }

  public void setClasspath(FileCollection classpath) {
    this.classpath = classpath;
  }

  /** Specifies whether the test report should be generated. */
  @Input
  public boolean isTestReport() {
    return testReport;
  }

  public void setTestReport(boolean testReport) {
    this.testReport = testReport;
  }

  public void enableTestReport() {
    this.testReport = true;
  }

  public void disableTestReport() {
    this.testReport = false;
  }

  /** Returns the directories containing the test source. */
  @InputFiles
  public List<File> getTestSrcDirs() {
    return testSrcDirs;
  }

  public void setTestSrcDirs(List<File> testSrcDir) {
    this.testSrcDirs = testSrcDir;
  }

  /**
   * Specifies whether test classes should be detected. When {@code true} the classes which match
   * the include and exclude patterns are scanned for test classes, and any found are executed. When
   * {@code false} the classes which match the include and exclude patterns are executed.
   */
  @Input
  public boolean isScanForTestClasses() {
    return scanForTestClasses;
  }

  public void setScanForTestClasses(boolean scanForTestClasses) {
    this.scanForTestClasses = scanForTestClasses;
  }

  /**
   * Returns the maximum number of test classes to execute in a forked test process. The forked test
   * process will be restarted when this limit is reached. The default value is 0 (no maximum).
   *
   * @return The maximum number of test classes. Returns 0 when there is no maximum.
   */
  public long getForkEvery() {
    return forkEvery;
  }

  /**
   * Sets the maximum number of test classes to execute in a forked test process. Use null or 0 to
   * use no maximum.
   *
   * @param forkEvery The maximum number of test classes. Use null or 0 to specify no maximum.
   */
  public void setForkEvery(Long forkEvery) {
    if (forkEvery != null && forkEvery < 0) {
      throw new IllegalArgumentException("Cannot set forkEvery to a value less than 0.");
    }
    this.forkEvery = forkEvery == null ? 0 : forkEvery;
  }

  /**
   * Returns the maximum number of forked test processes to execute in parallel. The default value
   * is 1 (no parallel test execution).
   *
   * @return The maximum number of forked test processes.
   */
  public int getMaxParallelForks() {
    return getDebug() ? 1 : maxParallelForks;
  }

  /**
   * Sets the maximum number of forked test processes to execute in parallel. Set to 1 to disable
   * parallel test execution.
   *
   * @param maxParallelForks The maximum number of forked test processes.
   */
  public void setMaxParallelForks(int maxParallelForks) {
    if (maxParallelForks < 1) {
      throw new IllegalArgumentException("Cannot set maxParallelForks to a value less than 1.");
    }
    this.maxParallelForks = maxParallelForks;
  }

  /**
   * Returns the classes files to scan for test classes.
   *
   * @return The candidate class files.
   */
  @InputFiles
  @Input // Also marked as input to force tests to run when the set of candidate class files changes
  public FileTree getCandidateClassFiles() {
    return getProject().fileTree(getTestClassesDir()).matching(patternSet);
  }

  /**
   * Allows configuring the logging of the test execution, for example log eagerly the standard
   * output, etc.
   *
   * <pre autoTested=''>
   * apply plugin: 'java'
   *
   * //makes the standard streams (err and out) visible at console when running tests
   * test.testLogging.showStandardStreams = true
   * </pre>
   *
   * @return test logging configuration
   */
  public TestLogging getTestLogging() {
    return testLogging;
  }

  /**
   * Allows configuring the logging of the test execution, for example log eagerly the standard
   * output, etc.
   *
   * <pre autoTested=''>
   * apply plugin: 'java'
   *
   * //makes the standard streams (err and out) visible at console when running tests
   * test.testLogging {
   *   showStandardStreams = true
   * }
   * </pre>
   *
   * @param closure configure closure
   */
  public void testLogging(Closure closure) {
    ConfigureUtil.configure(closure, testLogging);
  }
}
Esempio n. 20
0
/** This tab contains general settings for the plugin. */
public class SetupTab implements GradleTab, GradlePluginLord.SettingsObserver {
  private final Logger logger = Logging.getLogger(SetupTab.class);

  private static final String STACK_TRACE_LEVEL_CLIENT_PROPERTY =
      "stack-trace-level-client-property";
  private static final String SETUP = "setup";
  private static final String STACK_TRACE_LEVEL = "stack-trace-level";
  private static final String SHOW_OUTPUT_ON_ERROR = "show-output-on-error";
  private static final String LOG_LEVEL = "log-level";
  private static final String CURRENT_DIRECTORY = "current-directory";
  private static final String CUSTOM_GRADLE_EXECUTOR = "custom-gradle-executor";

  private GradlePluginLord gradlePluginLord;
  private OutputUILord outputUILord;
  private SettingsNode settingsNode;

  private JPanel mainPanel;

  private JRadioButton showNoStackTraceRadioButton;
  private JRadioButton showStackTrackRadioButton;
  private JRadioButton showFullStackTrackRadioButton;

  private JComboBox logLevelComboBox;

  private JCheckBox onlyShowOutputOnErrorCheckBox;

  private ButtonGroup stackTraceButtonGroup;

  private JTextField currentDirectoryTextField;

  private JCheckBox useCustomGradleExecutorCheckBox;
  private JTextField customGradleExecutorField;
  private JButton browseForCustomGradleExecutorButton;

  private JPanel customPanelPlaceHolder;

  public SetupTab(
      GradlePluginLord gradlePluginLord, OutputUILord outputUILord, SettingsNode settingsNode) {
    this.gradlePluginLord = gradlePluginLord;
    this.outputUILord = outputUILord;
    this.settingsNode = settingsNode.addChildIfNotPresent(SETUP);
  }

  public String getName() {
    return "Setup";
  }

  public Component createComponent() {
    setupUI();

    return mainPanel;
  }

  /**
   * Notification that this component is about to be shown. Do whatever initialization you choose.
   */
  public void aboutToShow() {
    updatePluginLordSettings();
    gradlePluginLord.addSettingsObserver(this, true);
  }

  private void setupUI() {
    mainPanel = new JPanel();
    mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));

    mainPanel.add(createCurrentDirectoryPanel());
    mainPanel.add(Box.createVerticalStrut(10));
    mainPanel.add(createLogLevelPanel());
    mainPanel.add(Box.createVerticalStrut(10));
    mainPanel.add(createStackTracePanel());
    mainPanel.add(Box.createVerticalStrut(10));
    mainPanel.add(createOptionsPanel());
    mainPanel.add(Box.createVerticalStrut(10));
    mainPanel.add(createCustomExecutorPanel());
    mainPanel.add(Box.createVerticalStrut(10));

    // add a panel that can be used to add custom things to the setup tab
    customPanelPlaceHolder = new JPanel(new BorderLayout());
    mainPanel.add(customPanelPlaceHolder);

    // Glue alone doesn't work in this situation. This forces everything to the top.
    JPanel expandingPanel = new JPanel(new BorderLayout());
    expandingPanel.add(Box.createVerticalGlue(), BorderLayout.CENTER);
    mainPanel.add(expandingPanel);

    mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
  }

  private Component createCurrentDirectoryPanel() {
    currentDirectoryTextField = new JTextField();
    currentDirectoryTextField.setEditable(false);

    String currentDirectory = settingsNode.getValueOfChild(CURRENT_DIRECTORY, null);
    if (currentDirectory == null || "".equals(currentDirectory.trim())) {
      currentDirectory = gradlePluginLord.getCurrentDirectory().getAbsolutePath();
    }

    currentDirectoryTextField.setText(currentDirectory);
    gradlePluginLord.setCurrentDirectory(new File(currentDirectory));

    JButton browseButton =
        new JButton(
            new AbstractAction("Browse...") {
              public void actionPerformed(ActionEvent e) {
                File file = browseForDirectory(gradlePluginLord.getCurrentDirectory());
                if (file != null) {
                  setCurrentDirectory(file);
                }
              }
            });

    JPanel panel = new JPanel();
    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

    panel.add(Utility.addLeftJustifiedComponent(new JLabel("Current Directory")));
    panel.add(createSideBySideComponent(currentDirectoryTextField, browseButton));

    return panel;
  }

  private void setCurrentDirectory(File file) {

    if (file == null) {
      currentDirectoryTextField.setText("");
      settingsNode.setValueOfChild(CURRENT_DIRECTORY, "");
    } else {
      currentDirectoryTextField.setText(file.getAbsolutePath());
      settingsNode.setValueOfChild(CURRENT_DIRECTORY, file.getAbsolutePath());
    }

    if (gradlePluginLord.setCurrentDirectory(file)) {
      // refresh the tasks only if we actually changed the current directory
      gradlePluginLord.addRefreshRequestToQueue();
    }
  }

  /**
   * this creates a panel where the right component is its preferred size. This is useful for
   * putting on a button on the right and a text field on the left.
   */
  public static JComponent createSideBySideComponent(
      Component leftComponent, Component rightComponent) {
    JPanel xLayoutPanel = new JPanel();
    xLayoutPanel.setLayout(new BoxLayout(xLayoutPanel, BoxLayout.X_AXIS));

    Dimension preferredSize = leftComponent.getPreferredSize();
    leftComponent.setMaximumSize(new Dimension(Integer.MAX_VALUE, preferredSize.height));

    xLayoutPanel.add(leftComponent);
    xLayoutPanel.add(Box.createHorizontalStrut(5));
    xLayoutPanel.add(rightComponent);

    return xLayoutPanel;
  }

  /** Browses for a file using the text value from the text field as the current value. */
  private File browseForDirectory(File initialFile) {

    if (initialFile == null) {
      initialFile = SystemProperties.getInstance().getCurrentDir();
    }

    JFileChooser chooser = new JFileChooser(initialFile);
    chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
    chooser.setMultiSelectionEnabled(false);

    File file = null;
    if (chooser.showOpenDialog(mainPanel) == JFileChooser.APPROVE_OPTION) {
      file = chooser.getSelectedFile();
    }

    return file;
  }

  /** Creates a panel that has a combo box to select a log level */
  private Component createLogLevelPanel() {
    JPanel panel = new JPanel();
    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

    logLevelComboBox = new JComboBox(getLogLevelWrappers());

    panel.add(Utility.addLeftJustifiedComponent(new JLabel("Log Level")));
    panel.add(Utility.addLeftJustifiedComponent(logLevelComboBox));

    // initialize our value
    String logLevelName = settingsNode.getValueOfChild(LOG_LEVEL, null);
    LogLevel logLevel = gradlePluginLord.getLogLevel();
    if (logLevelName != null) {
      try {
        logLevel = LogLevel.valueOf(logLevelName);
      } catch (
          IllegalArgumentException
              e) // this may happen if the enum changes. We don't want this to stop the whole UI
      {
        logger.error("Converting log level text to log level enum '" + logLevelName + "'", e);
      }
    }

    gradlePluginLord.setLogLevel(logLevel);
    setLogLevelComboBoxSetting(logLevel);

    logLevelComboBox.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            LogLevelWrapper wrapper = (LogLevelWrapper) logLevelComboBox.getSelectedItem();
            if (wrapper != null) {
              gradlePluginLord.setLogLevel(wrapper.logLevel);
              settingsNode.setValueOfChild(LOG_LEVEL, wrapper.logLevel.name());
            }
          }
        });

    return panel;
  }

  /**
   * This creates an array of wrapper objects suitable for passing to the constructor of the log
   * level combo box.
   */
  private Vector<LogLevelWrapper> getLogLevelWrappers() {
    Collection<LogLevel> collection = new LoggingCommandLineConverter().getLogLevels();

    Vector<LogLevelWrapper> wrappers = new Vector<LogLevelWrapper>();

    Iterator<LogLevel> iterator = collection.iterator();

    while (iterator.hasNext()) {
      LogLevel level = iterator.next();
      wrappers.add(new LogLevelWrapper(level));
    }

    Collections.sort(
        wrappers,
        new Comparator<LogLevelWrapper>() {
          public int compare(LogLevelWrapper o1, LogLevelWrapper o2) {
            return o1.toString().compareToIgnoreCase(o2.toString());
          }
        });

    return wrappers;
  }

  /**
   * This exists solely for overriding toString to something nicer. We'll captilize the first
   * letter. The rest become lower case. Ultimately, this should probably move into LogLevel. We'll
   * also put the log level shortcut in parenthesis
   */
  private class LogLevelWrapper {
    private LogLevel logLevel;
    private String toString;

    private LogLevelWrapper(LogLevel logLevel) {
      this.logLevel = logLevel;

      String temp =
          logLevel
              .toString()
              .toLowerCase()
              .replace('_', ' '); // replace underscores in the name with spaces
      this.toString = Character.toUpperCase(temp.charAt(0)) + temp.substring(1);

      // add the command line character to the end (so if an error message says use a log level, you
      // can easily translate)
      String commandLineCharacter =
          new LoggingCommandLineConverter().getLogLevelCommandLine(logLevel);
      if (commandLineCharacter != null && !commandLineCharacter.equals("")) {
        this.toString += " (-" + commandLineCharacter + ")";
      }
    }

    public String toString() {
      return toString;
    }
  }

  /**
   * Sets the log level combo box to the specified log level.
   *
   * @param logLevel the log level in question.
   */
  private void setLogLevelComboBoxSetting(LogLevel logLevel) {
    DefaultComboBoxModel model = (DefaultComboBoxModel) logLevelComboBox.getModel();
    for (int index = 0; index < model.getSize(); index++) {
      LogLevelWrapper wrapper = (LogLevelWrapper) model.getElementAt(index);
      if (wrapper.logLevel == logLevel) {
        logLevelComboBox.setSelectedIndex(index);
        return;
      }
    }
  }

  /**
   * Creates a panel with stack trace level radio buttons that allow you to specify how much info is
   * given when an error occurs.
   */
  private Component createStackTracePanel() {
    JPanel panel = new JPanel();
    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

    panel.setBorder(BorderFactory.createTitledBorder("Stack Trace Output"));

    showNoStackTraceRadioButton = new JRadioButton("Exceptions Only");
    showStackTrackRadioButton =
        new JRadioButton(
            "Standard Stack Trace (-"
                + LoggingCommandLineConverter.STACKTRACE
                + ")"); // add the command line character to the end (so if an error message says
                        // use a stack trace level, you can easily translate)
    showFullStackTrackRadioButton =
        new JRadioButton("Full Stack Trace (-" + LoggingCommandLineConverter.FULL_STACKTRACE + ")");

    showNoStackTraceRadioButton.putClientProperty(
        STACK_TRACE_LEVEL_CLIENT_PROPERTY, ShowStacktrace.INTERNAL_EXCEPTIONS);
    showStackTrackRadioButton.putClientProperty(
        STACK_TRACE_LEVEL_CLIENT_PROPERTY, ShowStacktrace.ALWAYS);
    showFullStackTrackRadioButton.putClientProperty(
        STACK_TRACE_LEVEL_CLIENT_PROPERTY, ShowStacktrace.ALWAYS_FULL);

    stackTraceButtonGroup = new ButtonGroup();
    stackTraceButtonGroup.add(showNoStackTraceRadioButton);
    stackTraceButtonGroup.add(showStackTrackRadioButton);
    stackTraceButtonGroup.add(showFullStackTrackRadioButton);

    showNoStackTraceRadioButton.setSelected(true);

    ActionListener radioButtonListener =
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            updateStackTraceSetting(true);
          }
        };

    showNoStackTraceRadioButton.addActionListener(radioButtonListener);
    showStackTrackRadioButton.addActionListener(radioButtonListener);
    showFullStackTrackRadioButton.addActionListener(radioButtonListener);

    panel.add(Utility.addLeftJustifiedComponent(showNoStackTraceRadioButton));
    panel.add(Utility.addLeftJustifiedComponent(showStackTrackRadioButton));
    panel.add(Utility.addLeftJustifiedComponent(showFullStackTrackRadioButton));

    String stackTraceLevel =
        settingsNode.getValueOfChild(STACK_TRACE_LEVEL, getSelectedStackTraceLevel().name());
    if (stackTraceLevel != null) {
      try {
        setSelectedStackTraceLevel(ShowStacktrace.valueOf(stackTraceLevel));
        updateStackTraceSetting(false); // false because we're serializing this in
      } catch (
          Exception
              e) { // this can happen if the stack trace levels change because you're moving between
                   // versions.
        logger.error(
            "Converting stack trace level text to stack trace level enum '" + stackTraceLevel + "'",
            e);
      }
    }

    return panel;
  }

  /** This stores the current stack trace setting (based on the UI controls) in the plugin. */
  private void updateStackTraceSetting(boolean saveSetting) {
    ShowStacktrace stackTraceLevel = getSelectedStackTraceLevel();
    gradlePluginLord.setStackTraceLevel(stackTraceLevel);

    if (saveSetting) {
      settingsNode.setValueOfChild(STACK_TRACE_LEVEL, stackTraceLevel.name());
    }
  }

  /**
   * Sets the selected strack trace level on the radio buttons. The radio buttons store their stack
   * trace level as a client property and I'll look for a match using that. This way, we don't have
   * to edit this if new levels are created.
   *
   * @param newStackTraceLevel the new stack trace level.
   */
  private void setSelectedStackTraceLevel(ShowStacktrace newStackTraceLevel) {
    Enumeration<AbstractButton> buttonEnumeration = stackTraceButtonGroup.getElements();
    while (buttonEnumeration.hasMoreElements()) {
      JRadioButton radioButton = (JRadioButton) buttonEnumeration.nextElement();
      ShowStacktrace level =
          (ShowStacktrace) radioButton.getClientProperty(STACK_TRACE_LEVEL_CLIENT_PROPERTY);
      if (newStackTraceLevel == level) {
        radioButton.setSelected(true);
        return;
      }
    }
  }

  /**
   * Returns the currently selected stack trace level. The radio buttons store their stack trace
   * level as a client property so once we get the selected button, we know the level. This way, we
   * don't have to edit this if new levels are created. Unfortunately, Swing doesn't have an easy
   * way to get the actual button from the group.
   *
   * @return the selected stack trace level
   */
  private ShowStacktrace getSelectedStackTraceLevel() {
    ButtonModel selectedButtonModel = stackTraceButtonGroup.getSelection();
    if (selectedButtonModel != null) {
      Enumeration<AbstractButton> buttonEnumeration = stackTraceButtonGroup.getElements();
      while (buttonEnumeration.hasMoreElements()) {
        JRadioButton radioButton = (JRadioButton) buttonEnumeration.nextElement();
        if (radioButton.getModel() == selectedButtonModel) {
          ShowStacktrace level =
              (ShowStacktrace) radioButton.getClientProperty(STACK_TRACE_LEVEL_CLIENT_PROPERTY);
          return level;
        }
      }
    }

    return ShowStacktrace.INTERNAL_EXCEPTIONS;
  }

  private Component createOptionsPanel() {
    JPanel panel = new JPanel();
    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

    onlyShowOutputOnErrorCheckBox = new JCheckBox("Only Show Output When Errors Occur");

    onlyShowOutputOnErrorCheckBox.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            updateShowOutputOnErrorsSetting();
            settingsNode.setValueOfChildAsBoolean(
                SHOW_OUTPUT_ON_ERROR, onlyShowOutputOnErrorCheckBox.isSelected());
          }
        });

    // initialize its default value
    boolean valueAsBoolean =
        settingsNode.getValueOfChildAsBoolean(
            SHOW_OUTPUT_ON_ERROR, onlyShowOutputOnErrorCheckBox.isSelected());
    onlyShowOutputOnErrorCheckBox.setSelected(valueAsBoolean);

    updateShowOutputOnErrorsSetting();

    panel.add(Utility.addLeftJustifiedComponent(onlyShowOutputOnErrorCheckBox));

    return panel;
  }

  private void updateShowOutputOnErrorsSetting() {
    boolean value = onlyShowOutputOnErrorCheckBox.isSelected();

    outputUILord.setOnlyShowOutputOnErrors(value);
  }

  private Component createCustomExecutorPanel() {
    useCustomGradleExecutorCheckBox = new JCheckBox("Use Custom Gradle Executor");

    customGradleExecutorField = new JTextField();
    customGradleExecutorField.setEditable(false);

    browseForCustomGradleExecutorButton =
        new JButton(
            new AbstractAction("Browse...") {
              public void actionPerformed(ActionEvent e) {
                browseForCustomGradleExecutor();
              }
            });

    String customExecutorPath = settingsNode.getValueOfChild(CUSTOM_GRADLE_EXECUTOR, null);
    if (customExecutorPath == null) {
      setCustomGradleExecutor(null);
    } else {
      setCustomGradleExecutor(new File(customExecutorPath));
    }

    JPanel panel = new JPanel();
    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

    panel.add(Utility.addLeftJustifiedComponent(useCustomGradleExecutorCheckBox));
    JComponent sideBySideComponent =
        createSideBySideComponent(customGradleExecutorField, browseForCustomGradleExecutorButton);
    sideBySideComponent.setBorder(BorderFactory.createEmptyBorder(0, 30, 0, 0)); // indent it
    panel.add(sideBySideComponent);

    useCustomGradleExecutorCheckBox.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            if (useCustomGradleExecutorCheckBox
                .isSelected()) { // if they checked it, browse for a custom executor immediately
              browseForCustomGradleExecutor();
            } else {
              setCustomGradleExecutor(null);
            }
          }
        });

    return panel;
  }

  /** Call this to browse for a custom gradle executor. */
  private void browseForCustomGradleExecutor() {
    File startingDirectory = new File(SystemProperties.getInstance().getUserHome());
    File currentFile = gradlePluginLord.getCustomGradleExecutor();
    if (currentFile != null) {
      startingDirectory = currentFile.getAbsoluteFile();
    } else {
      if (gradlePluginLord.getCurrentDirectory() != null) {
        startingDirectory = gradlePluginLord.getCurrentDirectory();
      }
    }

    JFileChooser chooser = new JFileChooser(startingDirectory);
    chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
    chooser.setMultiSelectionEnabled(false);

    File file = null;
    if (chooser.showOpenDialog(mainPanel) == JFileChooser.APPROVE_OPTION) {
      file = chooser.getSelectedFile();
    }

    if (file != null) {
      setCustomGradleExecutor(file);
    } else { // if they canceled, and they have no custom gradle executor specified, then we must
             // clear things
      // This will reset the UI back to 'not using a custom executor'. We can't have them check the
      // field and not have a value here.
      if (gradlePluginLord.getCustomGradleExecutor() == null) {
        setCustomGradleExecutor(null);
      }
    }
  }

  /**
   * Call this to set a custom gradle executor. We'll enable all fields appropriately and setup the
   * foundation settings. We'll also fire off a refresh.
   *
   * @param file the file to use as a custom executor. Null not to use one.
   */
  private void setCustomGradleExecutor(File file) {
    String storagePath;
    boolean isUsingCustom = false;
    if (file == null) {
      isUsingCustom = false;
      storagePath = null;
    } else {
      isUsingCustom = true;
      storagePath = file.getAbsolutePath();
    }

    // set the executor in the foundation
    if (gradlePluginLord.setCustomGradleExecutor(file)) {
      // refresh the tasks only if we actually changed the executor
      gradlePluginLord.addRefreshRequestToQueue();
    }

    // set the UI values
    useCustomGradleExecutorCheckBox.setSelected(isUsingCustom);
    customGradleExecutorField.setText(storagePath);

    // enable the UI appropriately.
    browseForCustomGradleExecutorButton.setEnabled(isUsingCustom);
    customGradleExecutorField.setEnabled(isUsingCustom);

    // store the settings
    settingsNode.setValueOfChild(CUSTOM_GRADLE_EXECUTOR, storagePath);
  }

  /**
   * This adds the specified component to the setup panel. It is added below the last 'default'
   * item. You can only add 1 component here, so if you need to add multiple things, you'll have to
   * handle adding that to yourself to the one component.
   *
   * @param component the component to add.
   */
  public void setCustomPanel(JComponent component) {
    customPanelPlaceHolder.add(component, BorderLayout.CENTER);
    customPanelPlaceHolder.invalidate();
    mainPanel.validate();
  }

  /**
   * Notification that some settings have changed for the plugin. Settings such as current
   * directory, gradle home directory, etc. This is useful for UIs that need to update their UIs
   * when this is changed by other means.
   */
  public void settingsChanged() {
    updatePluginLordSettings();
  }

  /**
   * Called upon start up and whenever GradlePluginLord settings are changed. We'll update our
   * values. Note: this actually gets called several times in a row for each settings during
   * initialization. Its not optimal, but functional and I didn't want to deal with numerous,
   * specific-field notifications.
   */
  private void updatePluginLordSettings() {
    setCustomGradleExecutor(gradlePluginLord.getCustomGradleExecutor());

    setCurrentDirectory(gradlePluginLord.getCurrentDirectory());

    setSelectedStackTraceLevel(gradlePluginLord.getStackTraceLevel());

    setLogLevelComboBoxSetting(gradlePluginLord.getLogLevel());
  }
}
public class ResolutionResultsStoreFactory implements Closeable {
  private static final Logger LOG = Logging.getLogger(ResolutionResultsStoreFactory.class);
  private static final int DEFAULT_MAX_SIZE = 2000000000; // 2 gigs

  private final TemporaryFileProvider temp;
  private int maxSize;

  private CachedStoreFactory oldModelCache;
  private CachedStoreFactory newModelCache;

  private AtomicInteger storeSetBaseId = new AtomicInteger(0);

  public ResolutionResultsStoreFactory(TemporaryFileProvider temp) {
    this(temp, DEFAULT_MAX_SIZE);
  }

  /**
   * @param temp
   * @param maxSize - indicates the approx. maximum size of the binary store that will trigger
   *     rolling of the file
   */
  ResolutionResultsStoreFactory(TemporaryFileProvider temp, int maxSize) {
    this.temp = temp;
    this.maxSize = maxSize;
  }

  private final Map<String, DefaultBinaryStore> stores = new HashMap<String, DefaultBinaryStore>();
  private final CompositeStoppable cleanUpLater = new CompositeStoppable();

  private synchronized DefaultBinaryStore createBinaryStore(String storeKey) {
    DefaultBinaryStore store = stores.get(storeKey);
    if (store == null || isFull(store)) {
      File storeFile = temp.createTemporaryFile("gradle", ".bin");
      storeFile.deleteOnExit();
      store = new DefaultBinaryStore(storeFile);
      stores.put(storeKey, store);
      cleanUpLater.add(store);
    }
    return store;
  }

  private synchronized CachedStoreFactory getOldModelCache() {
    if (oldModelCache == null) {
      oldModelCache = new CachedStoreFactory("Resolution result");
      cleanUpLater.add(oldModelCache);
    }
    return oldModelCache;
  }

  private synchronized CachedStoreFactory getNewModelCache() {
    if (newModelCache == null) {
      newModelCache = new CachedStoreFactory("Resolution result");
      cleanUpLater.add(newModelCache);
    }
    return newModelCache;
  }

  public StoreSet createStoreSet() {
    return new StoreSet() {
      int storeSetId = storeSetBaseId.getAndIncrement();
      int binaryStoreId;

      public DefaultBinaryStore nextBinaryStore() {
        // one binary store per id+threadId
        String storeKey = Thread.currentThread().getId() + "-" + binaryStoreId++;
        return createBinaryStore(storeKey);
      }

      public Store<ResolvedComponentResult> newModelCache() {
        return getOldModelCache().createCachedStore(storeSetId);
      }

      public Store<TransientConfigurationResults> oldModelCache() {
        return getNewModelCache().createCachedStore(storeSetId);
      }
    };
  }

  // offset based implementation is only safe up to certain figure
  // because of the int max value
  // for large streams/files (huge builds), we need to roll the file
  // otherwise the stream.size() returns max integer and the offset is no longer correct
  private boolean isFull(DefaultBinaryStore store) {
    return store.getSize() > maxSize;
  }

  public void close() {
    try {
      Clock clock = new Clock();
      cleanUpLater.stop();
      LOG.debug("Deleted {} resolution results binary files in {}", stores.size(), clock.getTime());
    } finally {
      oldModelCache = null;
      newModelCache = null;
      stores.clear();
    }
  }

  private class DefaultStoreSet implements StoreSet {
    final int storeSetId;
    int binaryStoreId;

    public DefaultStoreSet(int storeSetId) {
      this.storeSetId = storeSetId;
    }

    public DefaultBinaryStore nextBinaryStore() {
      // one binary store per id+threadId
      String storeKey = Thread.currentThread().getId() + "-" + binaryStoreId++;
      return createBinaryStore(storeKey);
    }

    public Store<ResolvedComponentResult> newModelCache() {
      return getOldModelCache().createCachedStore(storeSetId);
    }

    public Store<TransientConfigurationResults> oldModelCache() {
      return getNewModelCache().createCachedStore(storeSetId);
    }
  }
}
Esempio n. 22
0
/**
 * This class has nothing to do with plugins inside of gradle, but are related to making a plugin
 * that uses gradle, such as for an IDE. It is also used by the standalone IDE (that way the
 * standalone UI and plugin UIs are kept in synch).
 *
 * <p>This is the class that stores most of the information that the Gradle plugin works directly
 * with. It is meant to simplify creating a plugin that uses gradle. It maintains a queue of
 * commands to execute and executes them in a separate process due to some complexities with gradle
 * and its dependencies classpaths and potential memory issues.
 *
 * @author mhunsicker
 */
public class GradlePluginLord {
  private final Logger logger = Logging.getLogger(GradlePluginLord.class);

  private File gradleHomeDirectory; // the directory where gradle is installed
  private File currentDirectory; // the directory of your gradle-based project
  private File
      customGradleExecutor; // probably will be null. This allows a user to specify a different
                            // batch file or shell script to initiate gradle.

  private List<ProjectView> projects = new ArrayList<ProjectView>();

  private FavoritesEditor
      favoritesEditor; // an editor for the current favorites. The user can edit this at any time,
                       // hence we're using an editor.

  private ExecutionQueue<Request> executionQueue;
  private LinkedBlockingQueue<Request> currentlyExecutingRequests =
      new LinkedBlockingQueue<Request>();

  private boolean
      isStarted; // this flag is mostly to prevent initialization from firing off repeated refresh
                 // requests.

  private StartParameter.ShowStacktrace stackTraceLevel =
      StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS;
  private LogLevel logLevel = LogLevel.LIFECYCLE;

  private ObserverLord<GeneralPluginObserver> generalObserverLord =
      new ObserverLord<GeneralPluginObserver>();
  private ObserverLord<RequestObserver> requestObserverLord = new ObserverLord<RequestObserver>();
  private ObserverLord<SettingsObserver> settingsObserverLord =
      new ObserverLord<SettingsObserver>();

  private ObserverLord<CommandLineArgumentAlteringListener> commandLineArgumentObserverLord =
      new ObserverLord<CommandLineArgumentAlteringListener>();

  private long nextRequestID = 1; // a unique number assigned to requests

  public List<ProjectView> getProjects() {
    return Collections.unmodifiableList(projects);
  }

  /**
   * Sets the current projects. This is only supposed to be called by internal gradle classes.
   *
   * @param newProjects
   */
  public void setProjects(final List<ProjectView> newProjects) {
    projects.clear();
    if (newProjects != null) {
      projects.addAll(newProjects);
    }

    generalObserverLord.notifyObservers(
        new ObserverLord.ObserverNotification<GeneralPluginObserver>() {
          public void notify(GeneralPluginObserver observer) {
            observer.projectsAndTasksReloaded(newProjects != null);
          }
        });
  }

  public interface GeneralPluginObserver {
    /** Notification that we're about to reload the projects and tasks. */
    public void startingProjectsAndTasksReload();

    /**
     * Notification that the projects and tasks have been reloaded. You may want to repopulate or
     * update your views.
     *
     * @param wasSuccessful true if they were successfully reloaded. False if an error occurred so
     *     we no longer can show the projects and tasks (probably an error in a .gradle file).
     */
    public void projectsAndTasksReloaded(boolean wasSuccessful);
  }

  public interface RequestObserver {
    public void executionRequestAdded(ExecutionRequest request);

    public void refreshRequestAdded(RefreshTaskListRequest request);

    /**
     * Notification that a command is about to be executed. This is mostly useful for IDE's that may
     * need to save their files.
     */
    public void aboutToExecuteRequest(Request request);

    /**
     * Notification that the command has completed execution.
     *
     * @param request the original request containing the command that was executed
     * @param result the result of the command
     * @param output the output from gradle executing the command
     */
    public void requestExecutionComplete(Request request, int result, String output);
  }

  public interface SettingsObserver {

    /**
     * Notification that some settings have changed for the plugin. Settings such as current
     * directory, gradle home directory, etc. This is useful for UIs that need to update their UIs
     * when this is changed by other means.
     */
    public void settingsChanged();
  }

  public GradlePluginLord() {
    favoritesEditor = new FavoritesEditor();

    // create the queue that executes the commands. The contents of this interaction are where we
    // actually launch gradle.
    executionQueue = new ExecutionQueue<Request>(new ExecutionQueueInteraction());

    currentDirectory = new File(System.getProperty("user.dir"));

    String gradleHomeProperty = System.getProperty("gradle.home");
    if (gradleHomeProperty != null) {
      gradleHomeDirectory = new File(gradleHomeProperty);
    } else {
      gradleHomeDirectory = new DefaultClassPathProvider().getGradleHome();
    }
  }

  public File getGradleHomeDirectory() {
    return gradleHomeDirectory;
  }

  /**
   * sets the gradle home directory. You can't just set this here. You must also set the
   * "gradle.home" system property. This code could do this for you, but at this time, I didn't want
   * this to have side effects and setting "gradle.home" can affect other things and there may be
   * some timing issues.
   *
   * @param gradleHomeDirectory the new home directory
   */
  public void setGradleHomeDirectory(File gradleHomeDirectory) {
    if (areEqual(
        this.gradleHomeDirectory,
        gradleHomeDirectory)) // already set to this. This prevents recursive notifications.
    {
      return;
    }
    this.gradleHomeDirectory = gradleHomeDirectory;
    notifySettingsChanged();
  }

  /** @return the root directory of your gradle project. */
  public File getCurrentDirectory() {
    return currentDirectory;
  }

  /**
   * @param currentDirectory the new root directory of your gradle project.
   * @returns true if we changed the current directory, false if not (it was already set to this)
   */
  public boolean setCurrentDirectory(File currentDirectory) {
    if (areEqual(
        this.currentDirectory,
        currentDirectory)) // already set to this. This prevents recursive notifications.
    {
      return false;
    }
    this.currentDirectory = currentDirectory;
    notifySettingsChanged();
    return true;
  }

  public File getCustomGradleExecutor() {
    return customGradleExecutor;
  }

  public boolean setCustomGradleExecutor(File customGradleExecutor) {
    if (areEqual(
        this.customGradleExecutor,
        customGradleExecutor)) // already set to this. This prevents recursive notifications.
    {
      return false;
    }
    this.customGradleExecutor = customGradleExecutor;
    notifySettingsChanged();
    return true;
  }

  public FavoritesEditor getFavoritesEditor() {
    return favoritesEditor;
  }

  /** this allows you to change how much information is given when an error occurs. */
  public void setStackTraceLevel(StartParameter.ShowStacktrace stackTraceLevel) {
    if (areEqual(
        this.stackTraceLevel,
        stackTraceLevel)) // already set to this. This prevents recursive notifications.
    {
      return;
    }
    this.stackTraceLevel = stackTraceLevel;
    notifySettingsChanged();
  }

  public StartParameter.ShowStacktrace getStackTraceLevel() {
    return stackTraceLevel;
  }

  public LogLevel getLogLevel() {
    return logLevel;
  }

  public void setLogLevel(LogLevel logLevel) {
    if (logLevel == null) {
      return;
    }

    if (areEqual(
        this.logLevel, logLevel)) // already set to this. This prevents recursive notifications.
    {
      return;
    }
    this.logLevel = logLevel;
    notifySettingsChanged();
  }

  /** Call this to start execution. This is done after you've initialized everything. */
  public void startExecutionQueue() {
    isStarted = true;
  }

  /**
   * This gives requests of the queue and then executes them by kicking off gradle in a separate
   * process. Most of the code here is tedious setup code needed to start the server. The server is
   * what starts gradle and opens a socket for interprocess communication so we can receive messages
   * back from gradle.
   */
  private class ExecutionQueueInteraction implements ExecutionQueue.ExecutionInteraction<Request> {
    /**
     * When this is called, execute the given request.
     *
     * @param request the request to execute.
     */
    public void execute(final Request request) {

      // mark this request as being currently executed
      currentlyExecutingRequests.add(request);

      notifyAboutToExecuteRequest(request);

      // I'm just putting these in temp variables for easier debugging
      File currentDirectory = getCurrentDirectory();
      File gradleHomeDirectory = getGradleHomeDirectory();
      File customGradleExecutor = getCustomGradleExecutor();

      // the protocol handles the command line to launch gradle and messaging between us and said
      // externally launched gradle.
      ProcessLauncherServer.Protocol serverProtocol =
          request.createServerProtocol(
              logLevel,
              stackTraceLevel,
              currentDirectory,
              gradleHomeDirectory,
              customGradleExecutor);

      // the server kicks off gradle as an external process and manages the communication with said
      // process
      ProcessLauncherServer server = new ProcessLauncherServer(serverProtocol);
      request.setProcessLauncherServer(server);

      // we need to know when this command is finished executing so we can mark it as complete and
      // notify any observers.
      server.addServerObserver(
          new ProcessLauncherServer.ServerObserver() {
            public void clientExited(int result, String output) {
              currentlyExecutingRequests.remove(request);
              notifyRequestExecutionComplete(request, result, output);
            }

            public void serverExited() {}
          },
          false);

      server.start();
    }
  }

  private void notifyAboutToExecuteRequest(final Request request) {
    requestObserverLord.notifyObservers(
        new ObserverLord.ObserverNotification<RequestObserver>() {
          public void notify(RequestObserver observer) {
            try { // wrap this in a try/catch block so exceptions in the observer doesn't stop
                  // everything
              observer.aboutToExecuteRequest(request);
            } catch (Exception e) {
              logger.error("notifying aboutToExecuteCommand() " + e.getMessage());
            }
          }
        });
  }

  private void notifyRequestExecutionComplete(
      final Request request, final int result, final String output) {
    requestObserverLord.notifyObservers(
        new ObserverLord.ObserverNotification<RequestObserver>() {
          public void notify(RequestObserver observer) {
            try { // wrap this in a try/catch block so exceptions in the observer doesn't stop
                  // everything
              observer.requestExecutionComplete(request, result, output);
            } catch (Exception e) {
              logger.error("notifying requestExecutionComplete() " + e.getMessage());
            }
          }
        });
  }

  /**
   * Adds an observer for various events. See PluginObserver.
   *
   * @param observer your observer
   * @param inEventQueue true if you want to be notified in the Event Dispatch Thread.
   */
  public void addGeneralPluginObserver(GeneralPluginObserver observer, boolean inEventQueue) {
    generalObserverLord.addObserver(observer, inEventQueue);
  }

  public void removeGeneralPluginObserver(GeneralPluginObserver observer) {
    generalObserverLord.removeObserver(observer);
  }

  public void addRequestObserver(RequestObserver observer, boolean inEventQueue) {
    requestObserverLord.addObserver(observer, inEventQueue);
  }

  public void removeRequestObserver(RequestObserver observer) {
    requestObserverLord.removeObserver(observer);
  }

  public void addSettingsObserver(SettingsObserver observer, boolean inEventQueue) {
    settingsObserverLord.addObserver(observer, inEventQueue);
  }

  public void removeSettingsObserver(SettingsObserver observer) {
    settingsObserverLord.removeObserver(observer);
  }

  private void notifySettingsChanged() {
    settingsObserverLord.notifyObservers(
        new ObserverLord.ObserverNotification<SettingsObserver>() {
          public void notify(SettingsObserver observer) {
            observer.settingsChanged();
          }
        });
  }

  /**
   * Determines if two are objects are equal and considers them both being null as equal
   *
   * @param object1 the first object
   * @param object2 the second object
   * @return true if they're both null or both equal.
   */
  private boolean areEqual(Object object1, Object object2) {
    if (object1 == null || object2 == null) {
      return object2
          == object1; // yes, we're not using '.equals', we're making sure they both equal null
                      // because one of them is null!
    }

    return object1.equals(object2);
  }

  /**
   * Determines if all required setup is complete based on the current settings.
   *
   * @return true if a setup is complete, false if not.
   */
  public boolean isSetupComplete() {
    // return gradleWrapper.getGradleHomeDirectory() != null &&
    //       gradleWrapper.getGradleHomeDirectory().exists() &&
    return getCurrentDirectory() != null && getCurrentDirectory().exists();
  }

  public Request addExecutionRequestToQueue(String fullCommandLine, String displayName) {
    return addExecutionRequestToQueue(fullCommandLine, displayName, false);
  }

  /**
   * This executes a task in a background thread. This creates or uses an existing OutputPanel to
   * display the results.
   *
   * @param task the task to execute.
   * @param forceOutputToBeShown overrides the user setting onlyShowOutputOnErrors so that the
   *     output is shown regardless
   */
  public Request addExecutionRequestToQueue(
      final TaskView task, boolean forceOutputToBeShown, String... additionCommandLineOptions) {
    if (task == null) {
      return null;
    }

    String fullCommandLine =
        CommandLineAssistant.appendAdditionalCommandLineOptions(task, additionCommandLineOptions);
    return addExecutionRequestToQueue(
        fullCommandLine, task.getFullTaskName(), forceOutputToBeShown);
  }

  /**
   * This executes all the tasks together in a background thread. That is, all tasks are passed to a
   * single gradle call at once. This creates or uses an existing OutputPanel to display the
   * results.
   *
   * @param tasks the tasks to execute
   * @param forceOutputToBeShown overrides the user setting onlyShowOutputOnErrors so that the
   *     output is shown regardless
   * @param additionCommandLineOptions additional command line options to exeucte.
   */
  public Request addExecutionRequestToQueue(
      final List<TaskView> tasks,
      boolean forceOutputToBeShown,
      String... additionCommandLineOptions) {

    if (tasks == null || tasks.isEmpty()) {
      return null;
    }

    if (tasks.size() == 1) { // if there's only 1, just treat it as one
      return addExecutionRequestToQueue(
          tasks.get(0), forceOutputToBeShown, additionCommandLineOptions);
    }

    String singleCommandLine = CommandLineAssistant.combineTasks(tasks, additionCommandLineOptions);
    return addExecutionRequestToQueue(
        singleCommandLine, tasks.get(0).getName() + "...", forceOutputToBeShown);
  }

  /**
   * Executes several favorites commands at once as a single command. This has the affect of simply
   * concatenating all the favorite command lines into a single line.
   *
   * @param favorites a list of favorites. If just one favorite, it executes it normally. If
   *     multiple favorites, it executes them all at once as a single command.
   */
  public Request addExecutionRequestToQueue(List<FavoriteTask> favorites) {
    if (favorites.isEmpty()) {
      return null;
    }

    FavoriteTask firstFavoriteTask = favorites.get(0);
    String displayName;
    String fullCommandLine;
    boolean alwaysShowOutput = firstFavoriteTask.alwaysShowOutput();

    if (favorites.size() == 1) {
      displayName = firstFavoriteTask.getDisplayName();
      fullCommandLine = firstFavoriteTask.getFullCommandLine();
    } else {
      displayName = "Multiple (" + firstFavoriteTask.getDisplayName() + ", ... )";
      fullCommandLine = FavoritesEditor.combineFavoriteCommandLines(favorites);
    }

    return addExecutionRequestToQueue(fullCommandLine, displayName, alwaysShowOutput);
  }

  /**
   * Call this to execute a task in a background thread. This creates or uses an existing
   * OutputPanel to display the results. This version takes text instead of a task object.
   *
   * @param fullCommandLine the full command line to pass to gradle.
   * @param displayName what we show on the tab.
   * @param forceOutputToBeShown overrides the user setting onlyShowOutputOnErrors so that the
   *     output is shown regardless
   */
  public Request addExecutionRequestToQueue(
      String fullCommandLine, String displayName, boolean forceOutputToBeShown) {
    if (!isStarted) {
      return null;
    }

    if (fullCommandLine == null) {
      return null;
    }

    // here we'll give the UI a chance to add things to the command line.
    fullCommandLine = alterCommandLine(fullCommandLine);

    final ExecutionRequest request =
        new ExecutionRequest(
            getNextRequestID(), fullCommandLine, displayName, forceOutputToBeShown, executionQueue);
    requestObserverLord.notifyObservers(
        new ObserverLord.ObserverNotification<RequestObserver>() {
          public void notify(RequestObserver observer) {
            observer.executionRequestAdded(request);
          }
        });
    executionQueue.addRequestToQueue(request);
    return request;
  }

  private synchronized long getNextRequestID() {
    return nextRequestID++;
  }

  /**
   * This will refresh the project/task tree.
   *
   * @return the Request that was created. Null if no request created.
   */
  public Request addRefreshRequestToQueue() {
    return addRefreshRequestToQueue(null);
  }

  /**
   * This will refresh the project/task tree. This version allows you to specify additional
   * arguments to be passed to gradle during the refresh (such as -b to specify a build file)
   *
   * @param additionalCommandLineArguments the arguments to add, or null if none.
   * @return the Request that was created. Null if no request created.
   */
  public Request addRefreshRequestToQueue(String additionalCommandLineArguments) {
    if (!isStarted) {
      return null;
    }

    if (hasRequestOfType(RefreshTaskListRequest.TYPE)) {
      return null; // we're already doing a refresh.
    }

    // we'll request a task list since there is no way to do a no op. We're not really interested
    // in what's being executed, just the ability to get the task list (which must be populated as
    // part of executing anything).
    String fullCommandLine = ImplicitTasksConfigurer.TASKS_TASK;

    if (additionalCommandLineArguments != null) {
      fullCommandLine += ' ' + additionalCommandLineArguments;
    }

    // here we'll give the UI a chance to add things to the command line.
    fullCommandLine = alterCommandLine(fullCommandLine);

    final RefreshTaskListRequest request =
        new RefreshTaskListRequest(getNextRequestID(), fullCommandLine, executionQueue, this);
    executionQueue.addRequestToQueue(request);
    // TODO - fix this race condition - request may already have completed
    requestObserverLord.notifyObservers(
        new ObserverLord.ObserverNotification<RequestObserver>() {
          public void notify(RequestObserver observer) {
            observer.refreshRequestAdded(request);
          }
        });
    return request;
  }

  /**
   * This is where we notify listeners and give them a chance to add things to the command line.
   *
   * @param fullCommandLine the full command line
   * @return the new command line.
   */
  private String alterCommandLine(String fullCommandLine) {
    CommandLineArgumentAlteringNotification notification =
        new CommandLineArgumentAlteringNotification(fullCommandLine);
    commandLineArgumentObserverLord.notifyObservers(notification);

    return notification.getFullCommandLine();
  }

  //
  /**
   * This class notifies the listeners and modifies the command line by adding additional commands
   * to it. Each listener will be given the 'new' full command line, so the order you add things
   * becomes important.
   */
  private class CommandLineArgumentAlteringNotification
      implements ObserverLord.ObserverNotification<CommandLineArgumentAlteringListener> {
    private StringBuilder fullCommandLineBuilder;

    private CommandLineArgumentAlteringNotification(String fullCommandLine) {
      this.fullCommandLineBuilder = new StringBuilder(fullCommandLine);
    }

    public void notify(CommandLineArgumentAlteringListener observer) {
      String additions =
          observer.getAdditionalCommandLineArguments(fullCommandLineBuilder.toString());
      if (additions != null) {
        fullCommandLineBuilder.append(' ').append(additions);
      }
    }

    public String getFullCommandLine() {
      return fullCommandLineBuilder.toString();
    }
  }

  /**
   * This allows you to add a listener that can add additional command line arguments whenever
   * gradle is executed. This is useful if you've customized your gradle build and need to specify,
   * for example, an init script.
   *
   * <p>param listener the listener that modifies the command line arguments.
   */
  public void addCommandLineArgumentAlteringListener(CommandLineArgumentAlteringListener listener) {
    commandLineArgumentObserverLord.addObserver(listener, false);
  }

  public void removeCommandLineArgumentAlteringListener(
      CommandLineArgumentAlteringListener listener) {
    commandLineArgumentObserverLord.removeObserver(listener);
  }

  /**
   * This code was copied from BuildExceptionReporter.reportBuildFailure in gradle's source, then
   * modified slightly to compensate for the fact that we're not driven by options or logging things
   * to a logger object.
   */
  public static String getGradleExceptionMessage(
      Throwable failure, StartParameter.ShowStacktrace stackTraceLevel) {
    if (failure == null) {
      return "";
    }

    Formatter formatter = new Formatter();

    formatter.format("%nBuild failed.%n");

    if (stackTraceLevel == StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS) {
      formatter.format("Use the stack trace options to get more details.");
    }

    if (failure != null) {
      formatter.format("%n");

      if (failure instanceof LocationAwareException) {
        LocationAwareException scriptException = (LocationAwareException) failure;
        formatter.format("%s%n%n", scriptException.getLocation());
        formatter.format("%s", scriptException.getOriginalMessage());

        for (Throwable cause : scriptException.getReportableCauses()) {
          formatter.format("%nCause: %s", getMessage(cause));
        }
      } else {
        formatter.format("%s", getMessage(failure));
      }

      if (stackTraceLevel != StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS) {
        formatter.format("%n%nException is:\n");
        if (stackTraceLevel == StartParameter.ShowStacktrace.ALWAYS_FULL) {
          return formatter.toString() + getStackTraceAsText(failure);
        }

        return formatter.toString() + getStackTraceAsText(StackTraceUtils.deepSanitize(failure));
      }
    }

    return formatter.toString();
  }

  private static String getStackTraceAsText(Throwable t) {
    StringBuilder builder = new StringBuilder();
    StackTraceElement[] stackTraceElements = t.getStackTrace();

    for (int index = 0; index < stackTraceElements.length; index++) {
      StackTraceElement stackTraceElement = stackTraceElements[index];
      builder.append("   ").append(stackTraceElement.toString()).append('\n');
    }

    return builder.toString();
  }

  // tries to get a message from a Throwable. Something there's a message and sometimes there's not.
  private static String getMessage(Throwable throwable) {
    String message = throwable.getMessage();
    if (!GUtil.isTrue(message)) {
      message = String.format("%s (no error message)", throwable.getClass().getName());
    }

    if (throwable.getCause() != null) {
      message += "\nCaused by: " + getMessage(throwable.getCause());
    }

    return message;
  }

  /**
   * Determines if there are tasks executing or waiting to execute. We only care about execution
   * requests, not refresh requests.
   *
   * @return true if this is busy, false if not.
   */
  public boolean isBusy() {
    return hasRequestOfType(ExecutionRequest.TYPE);
  }

  /**
   * Determines if we have an request of the specified type
   *
   * @param type the sought type of request.
   * @return true if it has the request, false if not.
   */
  private boolean hasRequestOfType(Request.Type type) {
    Iterator<Request> iterator = currentlyExecutingRequests.iterator();
    while (iterator.hasNext()) {
      ExecutionQueue.Request request = iterator.next();
      if (request.getType() == type) {
        return true;
      }
    }

    List<Request> pendingRequests = executionQueue.getRequests();
    iterator = pendingRequests.iterator();
    while (iterator.hasNext()) {
      ExecutionQueue.Request request = iterator.next();
      if (request.getType() == type) {
        return true;
      }
    }

    return false;
  }
}
Esempio n. 23
0
/** @author Andrea Di Giorgi */
public class FileUtil extends com.liferay.gradle.util.FileUtil {

  public static FileTree getJarsFileTree(Project project, File dir, String... excludes) {

    Map<String, Object> args = new HashMap<>();

    args.put("dir", dir);

    if (ArrayUtil.isNotEmpty(excludes)) {
      args.put("excludes", Arrays.asList(excludes));
    }

    args.put("include", "*.jar");

    return project.fileTree(args);
  }

  public static String getRelativePath(Project project, File file) {
    String relativePath = project.relativePath(file);

    return relativePath.replace('\\', '/');
  }

  public static boolean hasSourceFiles(Task task, Spec<File> spec) {
    TaskInputs taskInputs = task.getInputs();

    FileCollection fileCollection = taskInputs.getSourceFiles();

    fileCollection = fileCollection.filter(spec);

    if (fileCollection.isEmpty()) {
      return false;
    }

    return true;
  }

  public static FileCollection join(FileCollection... fileCollections) {
    FileCollection joinedFileCollection = null;

    for (FileCollection fileCollection : fileCollections) {
      if (joinedFileCollection == null) {
        joinedFileCollection = fileCollection;
      } else {
        joinedFileCollection = joinedFileCollection.plus(fileCollection);
      }
    }

    return joinedFileCollection;
  }

  public static void touchFile(File file, long time) {
    boolean success = file.setLastModified(time);

    if (!success) {
      _logger.error("Unable to touch " + file);
    }
  }

  public static void touchFiles(Project project, File dir, long time, String... includes) {

    Map<String, Object> args = new HashMap<>();

    args.put("dir", dir);
    args.put("includes", Arrays.asList(includes));

    FileTree fileTree = project.fileTree(args);

    for (File file : fileTree) {
      touchFile(file, time);
    }
  }

  public static void unzip(Project project, final File file, final File destinationDir) {

    Closure<Void> closure =
        new Closure<Void>(null) {

          @SuppressWarnings("unused")
          public void doCall(AntBuilder antBuilder) {
            _invokeAntMethodUnzip(antBuilder, file, destinationDir);
          }
        };

    project.ant(closure);
  }

  private static void _invokeAntMethodUnzip(AntBuilder antBuilder, File file, File destinationDir) {

    Map<String, Object> args = new HashMap<>();

    args.put("dest", destinationDir);
    args.put("src", file);

    antBuilder.invokeMethod("unzip", args);
  }

  private static final Logger _logger = Logging.getLogger(FileUtil.class);
}
Esempio n. 24
0
/**
 * Provides the mechanics of connecting to a daemon, starting one via a given runnable if no
 * suitable daemons are already available.
 */
public class DefaultDaemonConnector implements DaemonConnector {
  private static final Logger LOGGER = Logging.getLogger(DefaultDaemonConnector.class);
  public static final int DEFAULT_CONNECT_TIMEOUT = 30000;
  public static final String STARTING_DAEMON_MESSAGE =
      "Starting a new Gradle Daemon for this build (subsequent builds will be faster).";
  public static final String DISABLE_STARTING_DAEMON_MESSAGE_PROPERTY =
      "org.gradle.daemon.disable-starting-message";
  private final DaemonRegistry daemonRegistry;
  protected final OutgoingConnector connector;
  private final DaemonStarter daemonStarter;
  private long connectTimeout = DefaultDaemonConnector.DEFAULT_CONNECT_TIMEOUT;

  public DefaultDaemonConnector(
      DaemonRegistry daemonRegistry, OutgoingConnector connector, DaemonStarter daemonStarter) {
    this.daemonRegistry = daemonRegistry;
    this.connector = connector;
    this.daemonStarter = daemonStarter;
  }

  public void setConnectTimeout(long connectTimeout) {
    this.connectTimeout = connectTimeout;
  }

  public long getConnectTimeout() {
    return connectTimeout;
  }

  public DaemonRegistry getDaemonRegistry() {
    return daemonRegistry;
  }

  public DaemonClientConnection maybeConnect(ExplainingSpec<DaemonContext> constraint) {
    return findConnection(daemonRegistry.getAll(), constraint);
  }

  public DaemonClientConnection maybeConnect(DaemonInstanceDetails daemon) {
    try {
      return connectToDaemon(daemon, new CleanupOnStaleAddress(daemon, true));
    } catch (ConnectException e) {
      LOGGER.debug("Cannot connect to daemon {} due to {}. Ignoring.", daemon, e);
      return null;
    }
  }

  public DaemonClientConnection connect(ExplainingSpec<DaemonContext> constraint) {
    DaemonClientConnection connection = findConnection(daemonRegistry.getIdle(), constraint);
    if (connection != null) {
      return connection;
    }

    if (!Boolean.getBoolean(DISABLE_STARTING_DAEMON_MESSAGE_PROPERTY)) {
      LOGGER.lifecycle(STARTING_DAEMON_MESSAGE);
    }
    return startDaemon(constraint);
  }

  private DaemonClientConnection findConnection(
      List<DaemonInfo> daemons, ExplainingSpec<DaemonContext> constraint) {
    for (DaemonInfo daemon : daemons) {
      if (!constraint.isSatisfiedBy(daemon.getContext())) {
        LOGGER.debug(
            "Found daemon {} however its context does not match the desired criteria.\n"
                + constraint.whyUnsatisfied(daemon.getContext())
                + "\n"
                + "  Looking for a different daemon...",
            daemon);
        continue;
      }

      try {
        return connectToDaemon(daemon, new CleanupOnStaleAddress(daemon, true));
      } catch (ConnectException e) {
        LOGGER.debug(
            "Cannot connect to daemon {} due to {}. Trying a different daemon...", daemon, e);
      }
    }
    return null;
  }

  public DaemonClientConnection startDaemon(ExplainingSpec<DaemonContext> constraint) {
    final DaemonStartupInfo startupInfo = daemonStarter.startDaemon();
    LOGGER.debug("Started Gradle daemon {}", startupInfo);
    long expiry = System.currentTimeMillis() + connectTimeout;
    do {
      DaemonClientConnection daemonConnection = connectToDaemonWithId(startupInfo, constraint);
      if (daemonConnection != null) {
        return daemonConnection;
      }
      try {
        Thread.sleep(200L);
      } catch (InterruptedException e) {
        throw UncheckedException.throwAsUncheckedException(e);
      }
    } while (System.currentTimeMillis() < expiry);

    throw new DaemonConnectionException(
        "Timeout waiting to connect to the Gradle daemon.\n" + startupInfo.describe());
  }

  private DaemonClientConnection connectToDaemonWithId(
      DaemonStartupInfo daemon, ExplainingSpec<DaemonContext> constraint) throws ConnectException {
    // Look for 'our' daemon among the busy daemons - a daemon will start in busy state so that
    // nobody else will grab it.
    for (DaemonInfo daemonInfo : daemonRegistry.getBusy()) {
      if (daemonInfo.getUid().equals(daemon.getUid())) {
        try {
          if (!constraint.isSatisfiedBy(daemonInfo.getContext())) {
            throw new DaemonConnectionException(
                "The newly created daemon process has a different context than expected."
                    + "\nIt won't be possible to reconnect to this daemon. Context mismatch: "
                    + "\n"
                    + constraint.whyUnsatisfied(daemonInfo.getContext()));
          }
          return connectToDaemon(daemonInfo, new CleanupOnStaleAddress(daemonInfo, false));
        } catch (ConnectException e) {
          throw new DaemonConnectionException(
              "Could not connect to the Gradle daemon.\n" + daemon.describe(), e);
        }
      }
    }
    return null;
  }

  private DaemonClientConnection connectToDaemon(
      DaemonInstanceDetails daemon,
      DaemonClientConnection.StaleAddressDetector staleAddressDetector)
      throws ConnectException {
    RemoteConnection<Message> connection;
    try {
      MessageSerializer<Message> serializer =
          new KryoBackedMessageSerializer<Message>(
              Serializers.stateful(DaemonMessageSerializer.create()));
      connection = connector.connect(daemon.getAddress()).create(serializer);
    } catch (ConnectException e) {
      staleAddressDetector.maybeStaleAddress(e);
      throw e;
    }
    return new DaemonClientConnection(connection, daemon, staleAddressDetector);
  }

  private class CleanupOnStaleAddress implements DaemonClientConnection.StaleAddressDetector {
    private final DaemonInstanceDetails daemon;
    private final boolean exposeAsStale;

    public CleanupOnStaleAddress(DaemonInstanceDetails daemon, boolean exposeAsStale) {
      this.daemon = daemon;
      this.exposeAsStale = exposeAsStale;
    }

    public boolean maybeStaleAddress(Exception failure) {
      LOGGER.info("{}{}", DaemonMessages.REMOVING_DAEMON_ADDRESS_ON_FAILURE, daemon);
      daemonRegistry.remove(daemon.getAddress());
      return exposeAsStale;
    }
  }
}
Esempio n. 25
0
@ThreadSafe
public class DefaultCacheAccess implements CacheAccess {

  private static final Logger LOG = Logging.getLogger(DefaultCacheAccess.class);

  private final String cacheDisplayName;
  private final File lockFile;
  private final FileLockManager lockManager;
  private final FileAccess fileAccess = new UnitOfWorkFileAccess();
  private final Set<MultiProcessSafePersistentIndexedCache> caches =
      new HashSet<MultiProcessSafePersistentIndexedCache>();
  private final Lock lock = new ReentrantLock();
  private final Condition condition = lock.newCondition();
  private Thread owner;
  private LockOptions lockOptions;
  private FileLock fileLock;
  private FileLock.State stateAtOpen;
  private boolean contended;
  private final CacheAccessOperationsStack operations;
  private int cacheClosedCount;

  public DefaultCacheAccess(String cacheDisplayName, File lockFile, FileLockManager lockManager) {
    this.cacheDisplayName = cacheDisplayName;
    this.lockFile = lockFile;
    this.lockManager = lockManager;
    this.operations = new CacheAccessOperationsStack();
  }

  /**
   * Opens this cache access with the given lock mode. Calling this with {@link
   * org.gradle.cache.internal.FileLockManager.LockMode#Exclusive} will lock the cache for exclusive
   * access from all other threads (including those in this process and all other processes), until
   * {@link #close()} is called.
   *
   * @param lockOptions
   */
  public void open(LockOptions lockOptions) {
    lock.lock();
    try {
      if (owner != null) {
        throw new IllegalStateException(
            String.format("Cannot open the %s, as it is already in use.", cacheDisplayName));
      }
      this.lockOptions = lockOptions;
      if (lockOptions.getMode() == FileLockManager.LockMode.None) {
        return;
      }
      if (fileLock != null) {
        throw new IllegalStateException("File lock " + lockFile + " is already open.");
      }
      fileLock = lockManager.lock(lockFile, lockOptions, cacheDisplayName);
      stateAtOpen = fileLock.getState();
      takeOwnership(String.format("Access %s", cacheDisplayName));
      lockManager.allowContention(fileLock, whenContended());
    } finally {
      lock.unlock();
    }
  }

  private void closeFileLock() {
    try {
      cacheClosedCount++;
      try {
        // Close the caches and then notify them of the final state, in case the caches do work on
        // close
        new CompositeStoppable().add(caches).stop();
        FileLock.State state = fileLock.getState();
        for (MultiProcessSafePersistentIndexedCache cache : caches) {
          cache.onEndWork(state);
        }
      } finally {
        fileLock.close();
      }
    } finally {
      fileLock = null;
      stateAtOpen = null;
      contended = false;
    }
  }

  public void close() {
    lock.lock();
    try {
      if (fileLock != null) {
        closeFileLock();
      }
      if (cacheClosedCount != 1) {
        LOG.debug("Cache {} was closed {} times.", cacheDisplayName, cacheClosedCount);
      }
    } finally {
      lockOptions = null;
      owner = null;
      lock.unlock();
    }
  }

  public FileLock getFileLock() {
    return fileLock;
  }

  public void useCache(String operationDisplayName, Runnable action) {
    useCache(operationDisplayName, Factories.toFactory(action));
  }

  public <T> T useCache(String operationDisplayName, Factory<? extends T> factory) {
    if (lockOptions != null && lockOptions.getMode() == FileLockManager.LockMode.Shared) {
      throw new UnsupportedOperationException("Not implemented yet.");
    }

    takeOwnership(operationDisplayName);
    boolean wasStarted = false;
    try {
      wasStarted = onStartWork();
      return factory.create();
    } finally {
      lock.lock();
      try {
        try {
          if (wasStarted) {
            onEndWork();
          }
        } finally {
          releaseOwnership();
        }
      } finally {
        lock.unlock();
      }
    }
  }

  private void takeOwnership(String operationDisplayName) {
    lock.lock();
    try {
      while (owner != null && owner != Thread.currentThread()) {
        try {
          condition.await();
        } catch (InterruptedException e) {
          throw UncheckedException.throwAsUncheckedException(e);
        }
      }
      owner = Thread.currentThread();
      operations.pushCacheAction(operationDisplayName);
    } finally {
      lock.unlock();
    }
  }

  private void releaseOwnership() {
    lock.lock();
    try {
      operations.popCacheAction();
      if (!operations.isInCacheAction()) {
        owner = null;
        condition.signalAll();
      }
    } finally {
      lock.unlock();
    }
  }

  public <T> T longRunningOperation(String operationDisplayName, Factory<? extends T> action) {
    boolean wasEnded = startLongRunningOperation(operationDisplayName);
    try {
      return action.create();
    } finally {
      finishLongRunningOperation(wasEnded);
    }
  }

  private boolean startLongRunningOperation(String operationDisplayName) {
    boolean wasEnded;
    lock.lock();
    try {
      if (operations.isInCacheAction()) {
        checkThreadIsOwner();
        wasEnded = onEndWork();
        owner = null;
        condition.signalAll();
      } else {
        wasEnded = false;
      }
      operations.pushLongRunningOperation(operationDisplayName);
    } finally {
      lock.unlock();
    }
    return wasEnded;
  }

  private void finishLongRunningOperation(boolean wasEnded) {
    lock.lock();
    try {
      operations.popLongRunningOperation();
      if (operations.isInCacheAction()) {
        restoreOwner();
        if (wasEnded) {
          onStartWork();
        }
      }
    } finally {
      lock.unlock();
    }
  }

  private void checkThreadIsOwner() {
    lock.lock();
    try {
      if (owner != Thread.currentThread()) {
        throw new IllegalStateException(
            String.format(
                "Cannot start long running operation, as the %s has not been locked.",
                cacheDisplayName));
      }
    } finally {
      lock.unlock();
    }
  }

  private void restoreOwner() {
    lock.lock();
    try {
      while (owner != null) {
        try {
          condition.await();
        } catch (InterruptedException e) {
          throw UncheckedException.throwAsUncheckedException(e);
        }
      }
      owner = Thread.currentThread();
    } finally {
      lock.unlock();
    }
  }

  public void longRunningOperation(String operationDisplayName, Runnable action) {
    longRunningOperation(operationDisplayName, Factories.toFactory(action));
  }

  public <K, V> MultiProcessSafePersistentIndexedCache<K, V> newCache(
      final PersistentIndexedCacheParameters<K, V> parameters) {
    Factory<BTreePersistentIndexedCache<K, V>> indexedCacheFactory =
        new Factory<BTreePersistentIndexedCache<K, V>>() {
          public BTreePersistentIndexedCache<K, V> create() {
            return doCreateCache(
                parameters.getCacheFile(),
                parameters.getKeySerializer(),
                parameters.getValueSerializer());
          }
        };
    MultiProcessSafePersistentIndexedCache<K, V> indexedCache =
        parameters.decorate(
            new DefaultMultiProcessSafePersistentIndexedCache<K, V>(
                indexedCacheFactory, fileAccess));

    lock.lock();
    try {
      caches.add(indexedCache);
      if (fileLock != null) {
        indexedCache.onStartWork(operations.getDescription(), stateAtOpen);
      }
    } finally {
      lock.unlock();
    }
    return indexedCache;
  }

  <K, V> BTreePersistentIndexedCache<K, V> doCreateCache(
      final File cacheFile,
      final Serializer<K> keySerializer,
      final Serializer<V> valueSerializer) {
    return new BTreePersistentIndexedCache<K, V>(cacheFile, keySerializer, valueSerializer);
  }

  private boolean onStartWork() {
    if (fileLock != null) {
      return false;
    }
    fileLock =
        lockManager.lock(
            lockFile,
            lockOptions.withMode(Exclusive),
            cacheDisplayName,
            operations.getDescription());
    stateAtOpen = fileLock.getState();
    for (UnitOfWorkParticipant cache : caches) {
      cache.onStartWork(operations.getDescription(), stateAtOpen);
    }

    lockManager.allowContention(fileLock, whenContended());

    return true;
  }

  private boolean onEndWork() {
    if (fileLock == null) {
      return false;
    }
    if (contended || fileLock.getMode() == Shared) {
      closeFileLock();
    }
    return true;
  }

  private FileLock getLock() {
    lock.lock();
    try {
      if ((Thread.currentThread() != owner && owner != null) || fileLock == null) {
        throw new IllegalStateException(
            String.format(
                "The %s has not been locked for this thread. File lock: %s, owner: %s",
                cacheDisplayName, fileLock != null, owner));
      }
    } finally {
      lock.unlock();
    }
    return fileLock;
  }

  private class UnitOfWorkFileAccess extends AbstractFileAccess {
    @Override
    public String toString() {
      return cacheDisplayName;
    }

    public <T> T readFile(Factory<? extends T> action) throws LockTimeoutException {
      return getLock().readFile(action);
    }

    public void updateFile(Runnable action) throws LockTimeoutException {
      getLock().updateFile(action);
    }

    public void writeFile(Runnable action) throws LockTimeoutException {
      getLock().writeFile(action);
    }
  }

  Runnable whenContended() {
    return new Runnable() {
      public void run() {
        lock.lock();
        try {
          LOG.debug(
              "Detected file lock contention of {} (fileLock={}, contended={}, owner={})",
              cacheDisplayName,
              fileLock != null,
              contended,
              owner);
          if (fileLock == null) {
            // the lock may have been closed
            return;
          }
          if (owner != null) {
            contended = true;
            return;
          }

          takeOwnership("Other process requested access to " + cacheDisplayName);
          try {
            closeFileLock();
          } finally {
            releaseOwnership();
          }
        } finally {
          lock.unlock();
        }
      }
    };
  }

  Thread getOwner() {
    return owner;
  }

  FileAccess getFileAccess() {
    return fileAccess;
  }
}
  private DefaultGradleLauncher doNewInstance(
      StartParameter startParameter, BuildRequestMetaData requestMetaData) {
    final BuildScopeServices serviceRegistry =
        new BuildScopeServices(sharedServices, startParameter);
    serviceRegistry.add(BuildRequestMetaData.class, requestMetaData);
    serviceRegistry.add(BuildClientMetaData.class, requestMetaData.getClient());
    ListenerManager listenerManager = serviceRegistry.get(ListenerManager.class);
    LoggingManagerInternal loggingManager =
        serviceRegistry.newInstance(LoggingManagerInternal.class);
    loggingManager.setLevel(startParameter.getLogLevel());

    // this hooks up the ListenerManager and LoggingConfigurer so you can call Gradle.addListener()
    // with a StandardOutputListener.
    loggingManager.addStandardOutputListener(
        listenerManager.getBroadcaster(StandardOutputListener.class));
    loggingManager.addStandardErrorListener(
        listenerManager.getBroadcaster(StandardOutputListener.class));

    listenerManager.useLogger(
        new TaskExecutionLogger(serviceRegistry.get(ProgressLoggerFactory.class)));
    if (tracker.getCurrentBuild() == null) {
      listenerManager.useLogger(
          new BuildLogger(
              Logging.getLogger(BuildLogger.class),
              serviceRegistry.get(StyledTextOutputFactory.class),
              startParameter,
              requestMetaData));
    }
    listenerManager.addListener(tracker);
    listenerManager.addListener(new BuildCleanupListener(serviceRegistry));

    listenerManager.addListener(serviceRegistry.get(ProfileEventAdapter.class));
    if (startParameter.isProfile()) {
      listenerManager.addListener(new ReportGeneratingProfileListener());
    }
    ScriptUsageLocationReporter usageLocationReporter = new ScriptUsageLocationReporter();
    listenerManager.addListener(usageLocationReporter);
    DeprecationLogger.useLocationReporter(usageLocationReporter);

    GradleInternal gradle =
        serviceRegistry
            .get(Instantiator.class)
            .newInstance(
                DefaultGradle.class, tracker.getCurrentBuild(), startParameter, serviceRegistry);
    return new DefaultGradleLauncher(
        gradle,
        serviceRegistry.get(InitScriptHandler.class),
        new SettingsHandler(
            new DefaultSettingsFinder(new BuildLayoutFactory()),
            serviceRegistry.get(SettingsProcessor.class),
            new BuildSourceBuilder(
                this,
                serviceRegistry.get(ClassLoaderRegistry.class),
                serviceRegistry.get(CacheRepository.class))),
        serviceRegistry.get(BuildLoader.class),
        serviceRegistry.get(BuildConfigurer.class),
        gradle.getBuildListenerBroadcaster(),
        serviceRegistry.get(ExceptionAnalyser.class),
        loggingManager,
        listenerManager.getBroadcaster(ModelConfigurationListener.class),
        listenerManager.getBroadcaster(TasksCompletionListener.class),
        gradle.getServices().get(BuildExecuter.class));
  }
Esempio n. 27
0
/** @author Andrea Di Giorgi */
public class FileUtil extends com.liferay.gradle.util.FileUtil {

  public static SortedSet<File> flattenAndSort(Iterable<File> files) throws IOException {

    final SortedSet<File> sortedFiles = new TreeSet<>(new FileComparator());

    for (File file : files) {
      if (file.isDirectory()) {
        Files.walkFileTree(
            file.toPath(),
            new SimpleFileVisitor<Path>() {

              @Override
              public FileVisitResult visitFile(Path path, BasicFileAttributes basicFileAttributes)
                  throws IOException {

                sortedFiles.add(path.toFile());

                return FileVisitResult.CONTINUE;
              }
            });
      } else {
        sortedFiles.add(file);
      }
    }

    return sortedFiles;
  }

  public static String getDigest(File file) {
    String digest;

    try {

      // Ignore EOL character differences between operating systems

      List<String> lines = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8);

      digest = Integer.toHexString(lines.hashCode());
    } catch (IOException ioe) {

      // File is not a text file

      if (_logger.isDebugEnabled()) {
        _logger.debug(file + " is not a text file", ioe);
      }

      HashValue hashValue = HashUtil.sha1(file);

      digest = hashValue.asHexString();
    }

    if (_logger.isInfoEnabled()) {
      _logger.info("Digest of " + file + " is " + digest);
    }

    return digest;
  }

  public static String getDigest(
      Project project, Iterable<File> files, boolean excludeIgnoredFiles) {

    Clock clock = null;

    if (_logger.isInfoEnabled()) {
      clock = new Clock();
    }

    StringBuilder sb = new StringBuilder();

    SortedSet<File> sortedFiles = null;

    try {
      sortedFiles = flattenAndSort(files);
    } catch (IOException ioe) {
      throw new GradleException("Unable to flatten files", ioe);
    }

    if (excludeIgnoredFiles) {
      removeIgnoredFiles(project, sortedFiles);
    }

    for (File file : sortedFiles) {
      if (!file.exists()) {
        continue;
      }

      if (".DS_Store".equals(file.getName())) {
        continue;
      }

      sb.append(getDigest(file));
      sb.append(_DIGEST_SEPARATOR);
    }

    if (sb.length() == 0) {
      throw new GradleException("At least one file is required");
    }

    sb.setLength(sb.length() - 1);

    if (_logger.isInfoEnabled() && (clock != null)) {
      _logger.info("Getting the digest took " + clock.getTimeInMs() + " ms");
    }

    return sb.toString();
  }

  public static boolean removeIgnoredFiles(Project project, SortedSet<File> files) {

    if (files.isEmpty()) {
      return false;
    }

    File rootDir = null;

    File firstFile = files.first();

    if (files.size() == 1) {
      rootDir = firstFile.getParentFile();
    } else {
      String dirName =
          StringUtil.getCommonPrefix(
              '/', _getCanonicalPath(firstFile), _getCanonicalPath(files.last()));

      if (Validator.isNotNull(dirName)) {
        rootDir = new File(dirName);
      }
    }

    if (rootDir == null) {
      if (_logger.isWarnEnabled()) {
        _logger.warn(
            "Unable to remove ignored files, common parent directory " + "cannot be found");
      }

      return false;
    }

    String result =
        _getGitResult(
            project,
            rootDir,
            "ls-files",
            "--cached",
            "--deleted",
            "--exclude-standard",
            "--modified",
            "--others",
            "-z");

    if (Validator.isNull(result)) {
      if (_logger.isWarnEnabled()) {
        _logger.warn("Unable to remove ignored files, Git returned an empty " + "result");
      }

      return false;
    }

    String[] committedFileNames = result.split("\\000");

    Set<File> committedFiles = new HashSet<>();

    for (String fileName : committedFileNames) {
      committedFiles.add(new File(rootDir, fileName));
    }

    return files.retainAll(committedFiles);
  }

  private static String _getCanonicalPath(File file) {
    try {
      String canonicalPath = file.getCanonicalPath();

      if (File.separatorChar != '/') {
        canonicalPath = canonicalPath.replace(File.separatorChar, '/');
      }

      return canonicalPath;
    } catch (IOException ioe) {
      throw new UncheckedIOException("Unable to get canonical path of " + file, ioe);
    }
  }

  private static String _getGitResult(
      Project project, final File workingDir, final String... args) {

    final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

    try {
      project.exec(
          new Action<ExecSpec>() {

            @Override
            public void execute(ExecSpec execSpec) {
              execSpec.setArgs(Arrays.asList(args));
              execSpec.setExecutable("git");
              execSpec.setStandardOutput(byteArrayOutputStream);
              execSpec.setWorkingDir(workingDir);
            }
          });
    } catch (ExecException ee) {
      if (_logger.isInfoEnabled()) {
        _logger.info(ee.getMessage(), ee);
      }
    }

    return byteArrayOutputStream.toString();
  }

  private static final char _DIGEST_SEPARATOR = '-';

  private static final Logger _logger = Logging.getLogger(FileUtil.class);

  private static class FileComparator implements Comparator<File> {

    @Override
    public int compare(File file1, File file2) {
      String canonicalPath1 = _getCanonicalPath(file1);
      String canonicalPath2 = _getCanonicalPath(file2);

      return canonicalPath1.compareTo(canonicalPath2);
    }
  }
}
Esempio n. 28
0
/**
 * Provides the mechanics of connecting to a daemon, starting one via a given runnable if no
 * suitable daemons are already available.
 */
public class DefaultDaemonConnector implements DaemonConnector {
  private static final Logger LOGGER = Logging.getLogger(DefaultDaemonConnector.class);
  public static final int DEFAULT_CONNECT_TIMEOUT = 30000;
  private final DaemonRegistry daemonRegistry;
  private final OutgoingConnector<Object> connector;
  private final DaemonStarter daemonStarter;
  private long connectTimeout = DefaultDaemonConnector.DEFAULT_CONNECT_TIMEOUT;

  public DefaultDaemonConnector(
      DaemonRegistry daemonRegistry,
      OutgoingConnector<Object> connector,
      DaemonStarter daemonStarter) {
    this.daemonRegistry = daemonRegistry;
    this.connector = connector;
    this.daemonStarter = daemonStarter;
  }

  public void setConnectTimeout(long connectTimeout) {
    this.connectTimeout = connectTimeout;
  }

  public long getConnectTimeout() {
    return connectTimeout;
  }

  public DaemonRegistry getDaemonRegistry() {
    return daemonRegistry;
  }

  public DaemonClientConnection maybeConnect(ExplainingSpec<DaemonContext> constraint) {
    return findConnection(daemonRegistry.getAll(), constraint);
  }

  public DaemonClientConnection connect(ExplainingSpec<DaemonContext> constraint) {
    DaemonClientConnection connection = findConnection(daemonRegistry.getIdle(), constraint);
    if (connection != null) {
      return connection;
    }

    return createConnection(constraint);
  }

  private DaemonClientConnection findConnection(
      List<DaemonInfo> daemonInfos, ExplainingSpec<DaemonContext> constraint) {
    for (DaemonInfo daemonInfo : daemonInfos) {
      if (!constraint.isSatisfiedBy(daemonInfo.getContext())) {
        LOGGER.debug(
            "Found daemon (address: {}, idle: {}) however it's context does not match the desired criteria.\n"
                + constraint.whyUnsatisfied(daemonInfo.getContext())
                + "\n"
                + "  Looking for a different daemon...",
            daemonInfo.getAddress(),
            daemonInfo.isIdle());
        continue;
      }

      try {
        return connectToDaemon(daemonInfo, null);
      } catch (ConnectException e) {
        LOGGER.debug(
            "Cannot connect to the daemon at "
                + daemonInfo.getAddress()
                + " due to "
                + e
                + ". Trying a different daemon...");
      }
    }
    return null;
  }

  public DaemonClientConnection createConnection(ExplainingSpec<DaemonContext> constraint) {
    LOGGER.info("Starting Gradle daemon");
    final DaemonStartupInfo startupInfo = daemonStarter.startDaemon();
    LOGGER.debug("Started Gradle Daemon: {}", startupInfo);
    long expiry = System.currentTimeMillis() + connectTimeout;
    do {
      try {
        Thread.sleep(200L);
      } catch (InterruptedException e) {
        throw UncheckedException.throwAsUncheckedException(e);
      }
      DaemonClientConnection daemonConnection = connectToDaemonWithId(startupInfo, constraint);
      if (daemonConnection != null) {
        return daemonConnection;
      }
    } while (System.currentTimeMillis() < expiry);

    throw new GradleException(
        "Timeout waiting to connect to Gradle daemon.\n" + startupInfo.describe());
  }

  private DaemonClientConnection connectToDaemonWithId(
      DaemonStartupInfo startupInfo, ExplainingSpec<DaemonContext> constraint)
      throws ConnectException {
    // Look for 'our' daemon among the busy daemons - a daemon will start in busy state so that
    // nobody else will grab it.
    for (DaemonInfo daemonInfo : daemonRegistry.getBusy()) {
      if (daemonInfo.getContext().getUid().equals(startupInfo.getUid())) {
        try {
          if (!constraint.isSatisfiedBy(daemonInfo.getContext())) {
            throw new GradleException(
                "The newly created daemon process has a different context than expected."
                    + "\nIt won't be possible to reconnect to this daemon. Context mismatch: "
                    + "\n"
                    + constraint.whyUnsatisfied(daemonInfo.getContext()));
          }
          return connectToDaemon(daemonInfo, startupInfo.getDiagnostics());
        } catch (ConnectException e) {
          throw new GradleException(
              "The forked daemon process died before we could connect.\n" + startupInfo.describe(),
              e);
        }
      }
    }
    return null;
  }

  private DaemonClientConnection connectToDaemon(
      final DaemonInfo daemonInfo, DaemonDiagnostics diagnostics) throws ConnectException {
    Runnable onFailure =
        new Runnable() {
          public void run() {
            LOGGER.info(DaemonMessages.REMOVING_DAEMON_ADDRESS_ON_FAILURE + daemonInfo);
            try {
              daemonRegistry.remove(daemonInfo.getAddress());
            } catch (Exception e) {
              // If we cannot remove then the file is corrupt or the registry is empty. We can
              // ignore it here.
              LOGGER.info(
                  "Problem removing the address from the registry due to: "
                      + e
                      + ". It will be cleaned up later.");
              // TODO SF, actually we probably want always safely remove so it would be good to
              // reduce the duplication.
            }
          }
        };
    Connection<Object> connection;
    try {
      connection = connector.connect(daemonInfo.getAddress());
    } catch (ConnectException e) {
      onFailure.run();
      throw e;
    }
    return new DaemonClientConnection(
        connection, daemonInfo.getContext().getUid(), diagnostics, onFailure);
  }
}
Esempio n. 29
0
/**
 * This is a server that talks to a client via sockets (Rudimentary form of Inter-Process
 * Communication (IPC)). This does the work of locating a free socket and starting the connection.
 * To use this, you really only have to define a Protocol that handles the actual messages. You'll
 * want to make your client startup a ClientProcess object that implements a corresponding Protocol.
 *
 * @author mhunsicker
 */
public class Server<P extends Server.Protocol, O extends Server.ServerObserver> {
  private final Logger logger = Logging.getLogger(Server.class);

  private ServerSocket serverSocket;
  private boolean isServerRunning;
  private boolean hasRequestedShutdown;

  private ObjectSocketWrapper clientSocket;
  protected P protocol;
  private Thread communicationThread;
  private int port;

  protected ObserverLord<O> observerLord = new ObserverLord<O>();

  //

  /** Implement this to define the behavior of the communication on the server side. */
  public interface Protocol<S extends Server> {
    /** Gives your protocol a chance to store this server so it can access its functions. */
    public void initialize(S server);

    /** Notification that the connection was accepted by the client. */
    public void connectionAccepted();

    /**
     * @return true if we should keep the connection alive. False if we should stop communication.
     */
    public boolean continueConnection();

    /**
     * Notification that a message has been received.
     *
     * @param message the message that was received.
     */
    public void messageReceived(MessageObject message);

    /** Notification that the client has stopped all communications. */
    public void clientCommunicationStopped();

    /**
     * Notification that a read failure occurred. This really only exists for debugging purposes
     * when things go wrong.
     */
    void readFailureOccurred();
  }

  //
  public interface ServerObserver {
    /** Notification that the server has shutdown. */
    public void serverExited();
  }

  public Server(P protocol) {
    this.protocol = protocol;
    protocol.initialize(this);
  }

  public int getPort() {
    return port;
  }

  /**
   * Call this to start the server.
   *
   * @return true if we started, false if not.
   */
  public boolean start() {
    port = connect();
    if (port == -1) {
      return false;
    }

    communicationThread =
        new Thread(
            new Runnable() {
              public void run() {
                listenForConnections();
              }
            });

    communicationThread.start();

    communicationsStarted();

    return true;
  }

  /**
   * this exists solely so it can be overridden. Its an internal notification that communcations
   * have started. You may want to do some extra processing now.
   */
  protected void communicationsStarted() {}

  /**
   * This attempts to open a free port. We'll search for an open port until we find one.
   *
   * @return the port we opened or -1 if we couldn't open one.
   */
  private int connect() {
    try {
      serverSocket = new ServerSocket(0);
      return serverSocket.getLocalPort();
    } catch (IOException e) {
      logger.error("Could not listen on port: " + port, e);
      return -1;
    }
  }

  /**
   * This sits in a loop and listens for connections. Once a connection has been made, we'll call
   * another function to process it.
   */
  private void listenForConnections() {
    int consecutiveFailures = 0;
    while (!hasRequestedShutdown) {
      Socket socket = null;
      try {
        serverSocket.setSoTimeout(
            2000); // attempt to connect for a few seconds, then try again (so we'll get any
                   // shutdown requests).
        socket = serverSocket.accept();

        clientSocket = new ObjectSocketWrapper(socket);
        protocol.connectionAccepted();
        consecutiveFailures = 0; // reset our consecutive failures.
        serverSocket.setSoTimeout(0);

        processCommunications();

        clientSocket.close();
      } catch (IOException e) {
        consecutiveFailures++;
        if (consecutiveFailures
            >= 20) // if we fail too many times, we'll request to shutdown. It's obviously not
                   // working. This is an arbitrary number.
        {
          requestShutdown();
        }

        if (consecutiveFailures
            > 8) // the first few usually fail while we're waiting for the process to startup.
        {
          logger.error("Accept failed (" + consecutiveFailures + ").");
        }
      } catch (Throwable t) { // something really bad happened, shut down
        logger.error("Listening for connections", t);
        requestShutdown();
      }
    }

    isServerRunning = false;

    stop();
    notifyServerExited();
  }

  /**
   * This is called once a connection is made. We'll listen for messages from the client, notifying
   * the protocol of them to do whatever it needs.
   */
  private void processCommunications() {
    boolean hasClientStopped = false;
    int failureCount = 0;
    while (!hasClientStopped && protocol.continueConnection() && !hasRequestedShutdown) {
      Object object = clientSocket.readObject();

      if (object == null) {
        if (!hasRequestedShutdown) // if we're trying to shutdown, we can get errors here. Just
                                   // ignore them and move on
        {
          failureCount++;
          protocol.readFailureOccurred();
          if (failureCount == 3) // after 3 failures, assume the client went away.
          {
            hasClientStopped = true;
            protocol.clientCommunicationStopped();
          }
        }
      } else {
        failureCount = 0; // reset our failures

        if (object instanceof String) {
          protocol.messageReceived(new MessageObject("?", object.toString(), null));
        } else if (object instanceof MessageObject) {
          protocol.messageReceived((MessageObject) object);
        }
      }
    }
  }

  public void requestShutdown() {
    hasRequestedShutdown = true;
  }

  public boolean isServerRunning() {
    return isServerRunning;
  }

  /**
   * Call this to send a message. The protocal and the client must understand the message and
   * message type.
   *
   * @param messageType the message type. Whatever the client and server want.
   * @param message the message being sent.
   */
  public void sendMessage(String messageType, String message) {
    clientSocket.sendObject(new MessageObject(messageType, message, null));
  }

  /**
   * Call this to send a message with some binary data. The protocal and the client must understand
   * the message, message type, and data.
   *
   * @param messageType the message type. Whatever the client and server want.
   * @param message the message being sent
   * @param data the data being sent. Must be serializable.
   */
  public void sendMessage(String messageType, String message, Serializable data) {
    clientSocket.sendObject(new MessageObject(messageType, message, data));
  }

  public void stop() {
    try {
      serverSocket.close();
    } catch (IOException e) {
      logger.error("Closing socket", e);
    }
  }

  private void notifyServerExited() {
    observerLord.notifyObservers(
        new ObserverLord.ObserverNotification<O>() {
          public void notify(ServerObserver observer) {
            observer.serverExited();
          }
        });
  }

  public void addServerObserver(O observer, boolean inEventQueue) {
    observerLord.addObserver(observer, inEventQueue);
  }

  public void removeServerObserver(O observer) {
    observerLord.removeObserver(observer);
  }
}
Esempio n. 30
0
/**
 * A long-lived build server that accepts commands via a communication channel.
 *
 * <p>Daemon instances are single use and have a start/stop debug. They are also threadsafe.
 *
 * <p>See {@link org.gradle.launcher.daemon.client.DaemonClient} for a description of the daemon
 * communication protocol.
 */
public class Daemon implements Stoppable {
  private static final Logger LOGGER = Logging.getLogger(Daemon.class);

  private final DaemonServerConnector connector;
  private final DaemonRegistry daemonRegistry;
  private final DaemonContext daemonContext;
  private final DaemonCommandExecuter commandExecuter;
  private final ExecutorFactory executorFactory;
  private final String password;

  private DaemonStateCoordinator stateCoordinator;

  private final Lock lifecyleLock = new ReentrantLock();

  private Address connectorAddress;
  private DomainRegistryUpdater registryUpdater;
  private DefaultIncomingConnectionHandler connectionHandler;

  /**
   * Creates a new daemon instance.
   *
   * @param connector The provider of server connections for this daemon
   * @param daemonRegistry The registry that this daemon should advertise itself in
   */
  public Daemon(
      DaemonServerConnector connector,
      DaemonRegistry daemonRegistry,
      DaemonContext daemonContext,
      String password,
      DaemonCommandExecuter commandExecuter,
      ExecutorFactory executorFactory) {
    this.connector = connector;
    this.daemonRegistry = daemonRegistry;
    this.daemonContext = daemonContext;
    this.password = password;
    this.commandExecuter = commandExecuter;
    this.executorFactory = executorFactory;
  }

  public String getUid() {
    return daemonContext.getUid();
  }

  public Address getAddress() {
    return connectorAddress;
  }

  /**
   * Starts the daemon, receiving connections asynchronously (i.e. returns immediately).
   *
   * @throws IllegalStateException if this daemon is already running, or has already been stopped.
   */
  public void start() {
    LOGGER.info("start() called on daemon - {}", daemonContext);
    lifecyleLock.lock();
    try {
      if (stateCoordinator != null) {
        throw new IllegalStateException("cannot start daemon as it is already running");
      }

      registryUpdater = new DomainRegistryUpdater(daemonRegistry, daemonContext, password);

      Runtime.getRuntime()
          .addShutdownHook(
              new Thread() {
                public void run() {
                  try {
                    daemonRegistry.remove(connectorAddress);
                  } catch (Exception e) {
                    LOGGER.debug(
                        "VM shutdown hook was unable to remove the daemon address from the registry. It will be cleaned up later.",
                        e);
                  }
                }
              });

      Runnable onStartCommand =
          new Runnable() {
            public void run() {
              registryUpdater.onStartActivity();
            }
          };

      Runnable onFinishCommand =
          new Runnable() {
            public void run() {
              registryUpdater.onCompleteActivity();
            }
          };

      // Start the pipeline in reverse order:
      // 1. mark daemon as running
      // 2. start handling incoming commands
      // 3. start accepting incoming connections
      // 4. advertise presence in registry

      stateCoordinator = new DaemonStateCoordinator(onStartCommand, onFinishCommand);
      connectionHandler =
          new DefaultIncomingConnectionHandler(
              commandExecuter, daemonContext, stateCoordinator, executorFactory);
      connectorAddress = connector.start(connectionHandler);
      LOGGER.debug("Daemon starting at: " + new Date() + ", with address: " + connectorAddress);
      registryUpdater.onStart(connectorAddress);
    } finally {
      lifecyleLock.unlock();
    }
  }

  /**
   * Stops the daemon, blocking until any current requests/connections have been satisfied.
   *
   * <p>This is the semantically the same as sending the daemon the Stop command.
   *
   * <p>This method does not quite conform to the semantics of the Stoppable contract in that it
   * will NOT wait for any executing builds to stop before returning. This is by design as we
   * currently have no way of gracefully stopping a build process and blocking until it's done would
   * not allow us to tear down the jvm like we need to. This may change in the future if we create a
   * way to interrupt a build.
   *
   * <p>What will happen though is that the daemon will immediately disconnect from any clients and
   * remove itself from the registry.
   */
  public void stop() {
    LOGGER.debug("stop() called on daemon");
    lifecyleLock.lock();
    try {
      if (stateCoordinator == null) {
        throw new IllegalStateException("cannot stop daemon as it has not been started.");
      }

      LOGGER.info(DaemonMessages.REMOVING_PRESENCE_DUE_TO_STOP);

      // Stop the pipeline:
      // 1. mark daemon as stopped, so that any incoming requests will be rejected with 'daemon
      // unavailable'
      // 2. remove presence from registry
      // 3. stop accepting new connections
      // 4. wait for commands in progress to finish (except for abandoned long running commands,
      // like running a build)

      CompositeStoppable.stoppable(stateCoordinator, registryUpdater, connector, connectionHandler)
          .stop();
    } finally {
      lifecyleLock.unlock();
    }
  }

  /**
   * Waits for the daemon to be idle for the specified number of milliseconds, then requests that
   * the daemon stop.
   *
   * @throws DaemonStoppedException if the daemon is explicitly stopped instead of idling out.
   */
  public void requestStopOnIdleTimeout(int idleTimeout, TimeUnit idleTimeoutUnits)
      throws DaemonStoppedException {
    LOGGER.debug("requestStopOnIdleTimeout({} {}) called on daemon", idleTimeout, idleTimeoutUnits);
    DaemonStateCoordinator stateCoordinator;
    lifecyleLock.lock();
    try {
      if (this.stateCoordinator == null) {
        throw new IllegalStateException("cannot stop daemon as it has not been started.");
      }
      stateCoordinator = this.stateCoordinator;
    } finally {
      lifecyleLock.unlock();
    }

    stateCoordinator.stopOnIdleTimeout(idleTimeout, idleTimeoutUnits);
  }
}