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); } } }
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(); } } }
/** * 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); } }
/** * 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()); } } }
/** 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); } }
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()); }
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(); } } }
/** * 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. '**F;*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. '**F;*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. '**F;*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. '**F;*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); } }
/** 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); } } }
/** * 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; } }
/** @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); }
/** * 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; } } }
@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)); }
/** @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); } } }
/** * 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); } }
/** * 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); } }
/** * 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); } }