/* * Copyright (c) 2013-2014 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.werval.gradle; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.UncheckedIOException; import java.io.UnsupportedEncodingException; import java.net.URL; import java.nio.charset.Charset; import java.util.List; import java.util.Set; import io.werval.spi.dev.DevShellRebuildException; import io.werval.spi.dev.DevShellSPI.SourceWatcher; import io.werval.spi.dev.DevShellSPIAdapter; import org.gradle.tooling.GradleConnector; import org.gradle.tooling.ProjectConnection; import static io.werval.util.Charsets.UTF_8; import static io.werval.util.Strings.hasText; /** * Gradle DevShellSPI implementation. * <p> * Use the Gradle Tooling API to rebuild the project. */ public final class GradleDevShellSPI extends DevShellSPIAdapter { private static final String BUILD_OUTPUT_STDOUT = "" + " __ __ __\n" + ".-----.| |_.--| |.-----.--.--.| |_\n" + "|__ --|| _| _ || _ | | || _|\n" + "|_____||____|_____||_____|_____||____|\n" + "-------------------------------------------------------------------------------\n" + "\n"; private static final String BUILD_OUTPUT_STDERR = "" + " __ __ \n" + ".-----.| |_.--| |.-----.----.----.\n" + "|__ --|| _| _ || -__| _| _|\n" + "|_____||____|_____||_____|__| |__|\n" + "-------------------------------------------------------------------------------\n" + "\n"; /** * Number of connection retries to attempt. */ private static final int MAX_RETRIES = 3; /** * Names of the Gradle tasks to run to rebuild the sources. */ private final List<String> rebuildTasks; /** * Gradle Tooling API Connector. */ private final GradleConnector connector; /** * Gradle Daemon Connection. */ private ProjectConnection connection; /** * Create a new DevShellSPI for the Gradle plugin. */ public GradleDevShellSPI( URL[] applicationSources, URL[] applicationClassPath, URL[] runtimeClassPath, Set<File> toWatch, SourceWatcher watcher, List<String> rebuildTasks, File gradleHomeDir, File projectDir ) { super( applicationSources, applicationClassPath, runtimeClassPath, toWatch, watcher, false ); this.rebuildTasks = rebuildTasks; this.connector = GradleConnector.newConnector() .useInstallation( gradleHomeDir ) .forProjectDirectory( projectDir ); } @Override protected void doRebuild() throws DevShellRebuildException { effectivelyRebuild( 0 ); } private void effectivelyRebuild( int retries ) { if( connection == null ) { connection = connector.connect(); } ByteArrayOutputStream stdout = new ByteArrayOutputStream(); ByteArrayOutputStream stderr = new ByteArrayOutputStream(); try { connection.newBuild() .withArguments( "-q" ) .setStandardOutput( stdout ) .setStandardError( stderr ) .forTasks( rebuildTasks.toArray( new String[ rebuildTasks.size() ] ) ) .run(); } catch( IllegalStateException disconnected ) { if( retries < MAX_RETRIES ) { connection = null; effectivelyRebuild( retries + 1 ); } else { throw disconnected; } } catch( Exception buildError ) { closeConnection(); String stdoutString = uncheckedToString( stdout, UTF_8 ); String stderrString = uncheckedToString( stderr, UTF_8 ); boolean stdoutHasText = hasText( stdoutString ); boolean stderrHasText = hasText( stderrString ); StringBuilder buildOutput = new StringBuilder(); if( stdoutHasText ) { buildOutput.append( BUILD_OUTPUT_STDOUT ).append( stdoutString ); if( stderrHasText ) { buildOutput.append( "\n\n" ); } } if( stderrHasText ) { buildOutput.append( BUILD_OUTPUT_STDERR ).append( stderrString ); } throw new DevShellRebuildException( "Gradle build error for tasks: " + rebuildTasks, buildError, buildOutput.toString() ); } } @Override protected void doStop() { closeConnection(); } private void closeConnection() { if( connection != null ) { connection.close(); connection = null; } } private static String uncheckedToString( ByteArrayOutputStream baos, Charset charset ) { try { return baos.toString( charset.name() ); } catch( UnsupportedEncodingException ex ) { throw new UncheckedIOException( ex ); } } }