InstantRunTransform extends Transform { @Override public void transform(@NonNull TransformInvocation invocation) throws IOException, TransformException, InterruptedException { InstantRunBuildContext instantRunBuildContext = transformScope.getInstantRunBuildContext(); boolean cleanUpClassesThree = instantRunBuildContext.getBuildMode() != InstantRunBuildMode.HOT_WARM; TransformOutputProvider outputProvider = invocation.getOutputProvider(); if (outputProvider == null) { throw new IllegalStateException("InstantRunTransform called with null output"); } List<URL> referencedInputUrls = getAllClassesLocations( invocation.getInputs(), invocation.getReferencedInputs()); ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); try (URLClassLoader urlClassLoader = new NonDelegatingUrlClassloader(referencedInputUrls)) { instantRunBuildContext.startRecording( InstantRunBuildContext.TaskType.INSTANT_RUN_TRANSFORM); Thread.currentThread().setContextClassLoader(urlClassLoader); File classesTwoOutput = outputProvider.getContentLocation("main", TransformManager.CONTENT_CLASS, getScopes(), Format.DIRECTORY); File classesThreeOutput = outputProvider.getContentLocation("enhanced", ImmutableSet.<ContentType>of(ExtendedContentType.CLASSES_ENHANCED), getScopes(), Format.DIRECTORY); if (cleanUpClassesThree) { FileUtils.cleanOutputDir(classesThreeOutput); } for (TransformInput input : invocation.getInputs()) { for (DirectoryInput directoryInput : input.getDirectoryInputs()) { File inputDir = directoryInput.getFile(); if (invocation.isIncremental()) { for (Map.Entry<File, Status> fileEntry : directoryInput.getChangedFiles() .entrySet()) { File inputFile = fileEntry.getKey(); if (!inputFile.getName().endsWith(SdkConstants.DOT_CLASS)) continue; switch (fileEntry.getValue()) { case ADDED: transformToClasses2Format( inputDir, inputFile, classesTwoOutput, Status.ADDED); break; case REMOVED: deleteOutputFile(IncrementalSupportVisitor.VISITOR_BUILDER, inputDir, inputFile, classesTwoOutput); deleteOutputFile(IncrementalChangeVisitor.VISITOR_BUILDER, inputDir, inputFile, classesThreeOutput); break; case CHANGED: transformToClasses2Format( inputDir, inputFile, classesTwoOutput, Status.CHANGED); if (!cleanUpClassesThree) { transformToClasses3Format( inputDir, inputFile, classesThreeOutput); } break; case NOTCHANGED: break; default: throw new IllegalStateException("Unhandled file status " + fileEntry.getValue()); } } } else { for (File file : Files.fileTreeTraverser().breadthFirstTraversal(inputDir)) { if (file.isDirectory()) { continue; } try { transformToClasses2Format( inputDir, file, classesTwoOutput, Status.ADDED); } catch (IOException e) { throw new RuntimeException("Exception while preparing " + file.getAbsolutePath()); } } } } } wrapUpOutputs(classesTwoOutput, classesThreeOutput); } finally { Thread.currentThread().setContextClassLoader(currentClassLoader); instantRunBuildContext.stopRecording( InstantRunBuildContext.TaskType.INSTANT_RUN_TRANSFORM); } } InstantRunTransform(InstantRunVariantScope transformScope); @NonNull @Override String getName(); @NonNull @Override Set<ContentType> getInputTypes(); @NonNull @Override Set<ContentType> getOutputTypes(); @NonNull @Override Set<QualifiedContent.Scope> getScopes(); @NonNull @Override Set<Scope> getReferencedScopes(); @Override boolean isIncremental(); @NonNull @Override Map<String, Object> getParameterInputs(); @NonNull @Override Collection<SecondaryFile> getSecondaryFiles(); @Override void transform(@NonNull TransformInvocation invocation);  }
@Test public void incrementalModeTest() throws TransformException, InterruptedException, IOException { final ImmutableList.Builder<File> filesElectedForClasses2Transformation = ImmutableList.builder(); final ImmutableList.Builder<File> filesElectedForClasses3Transformation = ImmutableList.builder(); InstantRunTransform transform = new InstantRunTransform(variantScope) { @Override protected void transformToClasses2Format( @NonNull File inputDir, @NonNull File inputFile, @NonNull File outputDir, @NonNull Status status) throws IOException { filesElectedForClasses2Transformation.add(inputFile); } @Override protected void transformToClasses3Format(File inputDir, File inputFile, File outputDir) throws IOException { filesElectedForClasses3Transformation.add(inputFile); } @Override protected void wrapUpOutputs(File classes2Folder, File classes3Folder) throws IOException { } }; ImmutableList.Builder<TransformInput> input = ImmutableList.builder(); input.add(new TransformInput() { @NonNull @Override public Collection<DirectoryInput> getDirectoryInputs() { return ImmutableList.<DirectoryInput>of(new DirectoryInputForTests() { @NonNull @Override public Map<File, Status> getChangedFiles() { return ImmutableMap.<File, Status>builder() .put(new File("/tmp/foo/bar/Changed.class"), Status.CHANGED) .put(new File("/tmp/foo/bar/Added.class"), Status.ADDED) .build(); } @NonNull @Override public File getFile() { return new File("/tmp"); } }); } @NonNull @Override public Collection<JarInput> getJarInputs() { return ImmutableList.of(); } }); TransformOutputProvider transformOutput = new TransformOutputProvider() { @Override public void deleteAll() throws IOException { } @NonNull @Override public File getContentLocation(@NonNull String name, @NonNull Set<ContentType> types, @NonNull Set<Scope> scopes, @NonNull Format format) { assertThat(types).hasSize(1); if (types.iterator().next().equals(QualifiedContent.DefaultContentType.CLASSES)) { return new File("out"); } else { return new File("out.3"); } } }; transform.transform(new TransformInvocationBuilder(context) .addInputs(input.build()) .addOutputProvider(transformOutput) .setIncrementalMode(true) .build()); ImmutableList<File> processedFiles = filesElectedForClasses2Transformation.build(); assertEquals("Wrong number of files elected for classes 2 processing", 2, processedFiles.size()); assertEquals("Output File path", FileUtils.toSystemDependentPath("/tmp/foo/bar/Changed.class"), processedFiles.get(0).getPath()); assertEquals("Output File path", FileUtils.toSystemDependentPath("/tmp/foo/bar/Added.class"), processedFiles.get(1).getPath()); processedFiles = filesElectedForClasses3Transformation.build(); assertEquals("Wrong number of files elected for classes 3 processing", 1, processedFiles.size()); assertEquals("Output File path", FileUtils.toSystemDependentPath("/tmp/foo/bar/Changed.class"), processedFiles.get(0).getPath()); }
@Test public void fileDeletionTest() throws IOException, TransformException, InterruptedException { final File tmpFolder = temporaryFolder.newFolder(); final File inputFolder = new File(tmpFolder, "input"); FileUtils.mkdirs(inputFolder); final File originalFile = createEmptyFile(inputFolder, "com/example/A.class"); final File outputFolder = new File(tmpFolder, "output"); final File outputFile = createEmptyFile(outputFolder, "com/example/A.class"); final File outputEnhancedFolder = new File(tmpFolder, "outputEnhanced"); final File outputEnhancedFile = createEmptyFile(outputEnhancedFolder, "com/example/A$override.class"); assertTrue(outputFile.exists()); assertTrue(outputEnhancedFile.exists()); final ImmutableList.Builder<File> filesElectedForClasses2Transformation = ImmutableList.builder(); final ImmutableList.Builder<File> filesElectedForClasses3Transformation = ImmutableList.builder(); InstantRunTransform transform = new InstantRunTransform(variantScope) { @Override protected void transformToClasses2Format( @NonNull File inputDir, @NonNull File inputFile, @NonNull File outputDir, @NonNull Status status) throws IOException { filesElectedForClasses2Transformation.add(inputFile); } @Override protected void transformToClasses3Format(File inputDir, File inputFile, File outputDir) throws IOException { filesElectedForClasses3Transformation.add(inputFile); } @Override protected void wrapUpOutputs(File classes2Folder, File classes3Folder) throws IOException { } }; ImmutableList.Builder<TransformInput> input = ImmutableList.builder(); input.add(new TransformInput() { @NonNull @Override public Collection<DirectoryInput> getDirectoryInputs() { return ImmutableList.<DirectoryInput>of(new DirectoryInputForTests() { @NonNull @Override public Map<File, Status> getChangedFiles() { return ImmutableMap.<File, Status>builder() .put(originalFile, Status.REMOVED) .build(); } @NonNull @Override public File getFile() { return inputFolder; } }); } @NonNull @Override public Collection<JarInput> getJarInputs() { return ImmutableList.of(); } }); TransformOutputProvider transformOutputProvider = new TransformOutputProvider() { @Override public void deleteAll() throws IOException { } @NonNull @Override public File getContentLocation(@NonNull String name, @NonNull Set<ContentType> types, @NonNull Set<Scope> scopes, @NonNull Format format) { assertThat(types).hasSize(1); if (types.iterator().next().equals(QualifiedContent.DefaultContentType.CLASSES)) { return outputFolder; } else { return outputEnhancedFolder; } } }; originalFile.delete(); transform.transform(new TransformInvocationBuilder(context) .addOutputProvider(transformOutputProvider) .addInputs(input.build()) .setIncrementalMode(true) .build()); ImmutableList<File> processedFiles = filesElectedForClasses2Transformation.build(); assertEquals("Wrong number of files elected for processing", 0, processedFiles.size()); assertFalse("Incremental support class file should have been deleted.", outputFile.exists()); assertFalse("Enhanced class file should have been deleted.", outputEnhancedFile.exists()); FileUtils.deletePath(tmpFolder); }