|
| 1 | +/* |
| 2 | + * Copyright 2002-2022 the original author or authors. |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * https://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | + |
| 17 | +package org.springframework.test.context.aot; |
| 18 | + |
| 19 | +import java.io.File; |
| 20 | +import java.io.IOException; |
| 21 | +import java.io.UncheckedIOException; |
| 22 | +import java.nio.file.Path; |
| 23 | +import java.nio.file.Paths; |
| 24 | +import java.util.Arrays; |
| 25 | +import java.util.Set; |
| 26 | +import java.util.stream.Stream; |
| 27 | + |
| 28 | +import org.springframework.aot.generate.FileSystemGeneratedFiles; |
| 29 | +import org.springframework.aot.generate.GeneratedFiles; |
| 30 | +import org.springframework.aot.generate.GeneratedFiles.Kind; |
| 31 | +import org.springframework.aot.hint.RuntimeHints; |
| 32 | +import org.springframework.aot.nativex.FileNativeConfigurationWriter; |
| 33 | +import org.springframework.util.Assert; |
| 34 | +import org.springframework.util.FileSystemUtils; |
| 35 | + |
| 36 | +/** |
| 37 | + * Command-line application that scans the provided classpath roots for Spring |
| 38 | + * integration test classes and then generates AOT artifacts for those test |
| 39 | + * classes in the provided output directories. |
| 40 | + * |
| 41 | + * <p><strong>For internal use only.</strong> |
| 42 | + * |
| 43 | + * @author Sam Brannen |
| 44 | + * @author Stephane Nicoll |
| 45 | + * @author Andy Wilkinson |
| 46 | + * @author Phillip Webb |
| 47 | + * @since 6.0 |
| 48 | + * @see TestClassScanner |
| 49 | + * @see TestContextAotGenerator |
| 50 | + * @see FileNativeConfigurationWriter |
| 51 | + * @see org.springframework.boot.AotProcessor |
| 52 | + */ |
| 53 | +public class TestAotProcessor { |
| 54 | + |
| 55 | + private final Path[] classpathRoots; |
| 56 | + |
| 57 | + private final Path sourceOutput; |
| 58 | + |
| 59 | + private final Path resourceOutput; |
| 60 | + |
| 61 | + private final Path classOutput; |
| 62 | + |
| 63 | + private final String groupId; |
| 64 | + |
| 65 | + private final String artifactId; |
| 66 | + |
| 67 | + |
| 68 | + /** |
| 69 | + * Create a new processor for the specified test classpath roots and |
| 70 | + * general settings. |
| 71 | + * |
| 72 | + * @param classpathRoots the classpath roots to scan for test classes |
| 73 | + * @param sourceOutput the location of generated sources |
| 74 | + * @param resourceOutput the location of generated resources |
| 75 | + * @param classOutput the location of generated classes |
| 76 | + * @param groupId the group ID of the application, used to locate |
| 77 | + * {@code native-image.properties} |
| 78 | + * @param artifactId the artifact ID of the application, used to locate |
| 79 | + * {@code native-image.properties} |
| 80 | + */ |
| 81 | + public TestAotProcessor(Path[] classpathRoots, Path sourceOutput, Path resourceOutput, Path classOutput, |
| 82 | + String groupId, String artifactId) { |
| 83 | + |
| 84 | + this.classpathRoots = classpathRoots; |
| 85 | + this.sourceOutput = sourceOutput; |
| 86 | + this.resourceOutput = resourceOutput; |
| 87 | + this.classOutput = classOutput; |
| 88 | + this.groupId = groupId; |
| 89 | + this.artifactId = artifactId; |
| 90 | + } |
| 91 | + |
| 92 | + |
| 93 | + /** |
| 94 | + * Trigger processing of the test classes in the configured classpath roots. |
| 95 | + */ |
| 96 | + public void process() { |
| 97 | + deleteExistingOutput(); |
| 98 | + performAotProcessing(); |
| 99 | + } |
| 100 | + |
| 101 | + private void deleteExistingOutput() { |
| 102 | + deleteExistingOutput(this.sourceOutput, this.resourceOutput, this.classOutput); |
| 103 | + } |
| 104 | + |
| 105 | + private void deleteExistingOutput(Path... paths) { |
| 106 | + for (Path path : paths) { |
| 107 | + try { |
| 108 | + FileSystemUtils.deleteRecursively(path); |
| 109 | + } |
| 110 | + catch (IOException ex) { |
| 111 | + throw new UncheckedIOException("Failed to delete existing output in '%s'".formatted(path), ex); |
| 112 | + } |
| 113 | + } |
| 114 | + } |
| 115 | + |
| 116 | + private void performAotProcessing() { |
| 117 | + TestClassScanner scanner = new TestClassScanner(Set.of(this.classpathRoots)); |
| 118 | + Stream<Class<?>> testClasses = scanner.scan(); |
| 119 | + |
| 120 | + GeneratedFiles generatedFiles = new FileSystemGeneratedFiles(this::getRoot); |
| 121 | + TestContextAotGenerator generator = new TestContextAotGenerator(generatedFiles); |
| 122 | + generator.processAheadOfTime(testClasses); |
| 123 | + |
| 124 | + writeHints(generator.getRuntimeHints()); |
| 125 | + } |
| 126 | + |
| 127 | + private Path getRoot(Kind kind) { |
| 128 | + return switch (kind) { |
| 129 | + case SOURCE -> this.sourceOutput; |
| 130 | + case RESOURCE -> this.resourceOutput; |
| 131 | + case CLASS -> this.classOutput; |
| 132 | + }; |
| 133 | + } |
| 134 | + |
| 135 | + private void writeHints(RuntimeHints hints) { |
| 136 | + FileNativeConfigurationWriter writer = |
| 137 | + new FileNativeConfigurationWriter(this.resourceOutput, this.groupId, this.artifactId); |
| 138 | + writer.write(hints); |
| 139 | + } |
| 140 | + |
| 141 | + |
| 142 | + public static void main(String[] args) { |
| 143 | + int requiredArgs = 6; |
| 144 | + Assert.isTrue(args.length >= requiredArgs, () -> |
| 145 | + "Usage: %s <classpathRoots> <sourceOutput> <resourceOutput> <classOutput> <groupId> <artifactId>" |
| 146 | + .formatted(TestAotProcessor.class.getName())); |
| 147 | + Path[] classpathRoots = Arrays.stream(args[0].split(File.pathSeparator)).map(Paths::get).toArray(Path[]::new); |
| 148 | + Path sourceOutput = Paths.get(args[1]); |
| 149 | + Path resourceOutput = Paths.get(args[2]); |
| 150 | + Path classOutput = Paths.get(args[3]); |
| 151 | + String groupId = args[4]; |
| 152 | + String artifactId = args[5]; |
| 153 | + new TestAotProcessor(classpathRoots, sourceOutput, resourceOutput, classOutput, groupId, artifactId).process(); |
| 154 | + } |
| 155 | + |
| 156 | +} |
0 commit comments