Gradle Project Parser - initial implementation (#7)
This commit is contained in:
3
pom.xml
3
pom.xml
@@ -15,6 +15,7 @@
|
||||
<module>spring-rewrite-commons-launcher</module>
|
||||
<module>spring-rewrite-commons-docs</module>
|
||||
<module>spring-rewrite-commons-examples</module>
|
||||
<module>spring-rewrite-commons-gradle</module>
|
||||
</modules>
|
||||
|
||||
<organization>
|
||||
@@ -30,6 +31,8 @@
|
||||
<!-- prod dependencies -->
|
||||
<spring-boot.version>3.1.3</spring-boot.version>
|
||||
<rewrite.version>8.5.1</rewrite.version>
|
||||
<rewrite-kotlin.version>1.8.2</rewrite-kotlin.version>
|
||||
<rewrite-polyglot.version>1.8.9</rewrite-polyglot.version>
|
||||
<rewrite-maven-plugin.version>5.3.2</rewrite-maven-plugin.version>
|
||||
<jaxb-api.version>2.3.1</jaxb-api.version>
|
||||
|
||||
|
||||
16
spring-rewrite-commons-gradle/README.md
Normal file
16
spring-rewrite-commons-gradle/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Gradle Project Parser
|
||||
|
||||
## Structure
|
||||
**Model** Shared data structures between the **plugin** and **parser** modules. It has a utility class `SpringRewriteModelBuilder` to fetch `GradleProjectData` from Gradel Build process
|
||||
|
||||
**Plugin** Gradle plugin that executes the code inside of Gradle Build process. Responsible for creating serializable instance of `SpringRewriteModelBuilder`
|
||||
|
||||
**Parser** Parses Gradle project sources with a help of `GradleProjectData` fetched from Gradle Build process via Gradle Tooling API
|
||||
|
||||
## Building
|
||||
Execute first `mvn clean install -DskipTests` to install **plugin** based on the latest sources into local Maven repository
|
||||
|
||||
Then execute `mvn clean install` - now tests would be able to find the **plugin** module for their execution.
|
||||
|
||||
## Usage
|
||||
See `ProjectParserTest` class under the tests of **parser** module
|
||||
35
spring-rewrite-commons-gradle/pom.xml
Normal file
35
spring-rewrite-commons-gradle/pom.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.rewrite</groupId>
|
||||
<artifactId>spring-rewrite-commons</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>spring-rewrite-commons-gradle</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<gradle.version>8.4</gradle.version>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>rewrite-gradle-model</module>
|
||||
<module>rewrite-gradle-plugin</module>
|
||||
<module>rewrite-gradle-parser</module>
|
||||
</modules>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>gradle</id>
|
||||
<url>https://repo.gradle.org/gradle/libs-releases</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
</project>
|
||||
48
spring-rewrite-commons-gradle/rewrite-gradle-model/pom.xml
Normal file
48
spring-rewrite-commons-gradle/rewrite-gradle-model/pom.xml
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.rewrite</groupId>
|
||||
<artifactId>spring-rewrite-commons-gradle</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>rewrite-gradle-model</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite.gradle.tooling</groupId>
|
||||
<artifactId>model</artifactId>
|
||||
<version>1.1.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.gradle</groupId>
|
||||
<artifactId>gradle-tooling-api</artifactId>
|
||||
<version>${gradle.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2021 - 2023 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
|
||||
*
|
||||
* https://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 org.springframework.rewrite.gradle.model;
|
||||
|
||||
import org.openrewrite.gradle.toolingapi.GradleProject;
|
||||
import org.openrewrite.gradle.toolingapi.GradleSettings;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface GradleProjectData extends GradleProject {
|
||||
|
||||
String getGroup();
|
||||
|
||||
String getVersion();
|
||||
|
||||
GradleSettings getGradleSettings();
|
||||
|
||||
String getGradleVersion();
|
||||
|
||||
boolean isRootProject();
|
||||
|
||||
File getRootProjectDir();
|
||||
|
||||
Collection<GradleProjectData> getSubprojects();
|
||||
|
||||
File getProjectDir();
|
||||
|
||||
File getBuildDir();
|
||||
|
||||
File getBuildscriptFile();
|
||||
|
||||
Map<String, ?> getProperties();
|
||||
|
||||
List<JavaSourceSetData> getJavaSourceSets();
|
||||
|
||||
boolean isMultiPlatformKotlinProject();
|
||||
|
||||
List<KotlinSourceSetData> getKotlinSourceSets();
|
||||
|
||||
Collection<File> getBuildscriptClasspath();
|
||||
|
||||
Collection<File> getSettingsClasspath();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2021 - 2023 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
|
||||
*
|
||||
* https://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 org.springframework.rewrite.gradle.model;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
|
||||
public interface JavaSourceSetData {
|
||||
|
||||
String getName();
|
||||
|
||||
Collection<File> getSources();
|
||||
|
||||
Collection<File> getSourceDirectories();
|
||||
|
||||
Collection<File> getJava();
|
||||
|
||||
Collection<File> getClassesDirs();
|
||||
|
||||
Collection<File> getCompileClasspath();
|
||||
|
||||
Collection<File> getImplementationClasspath();
|
||||
|
||||
JavaVersionData getJavaVersionData();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2021 - 2023 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
|
||||
*
|
||||
* https://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 org.springframework.rewrite.gradle.model;
|
||||
|
||||
public interface JavaVersionData {
|
||||
|
||||
String getCreatedBy();
|
||||
|
||||
String getVmVendor();
|
||||
|
||||
String getSourceCompatibility();
|
||||
|
||||
String getTargetCompatibility();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2021 - 2023 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
|
||||
*
|
||||
* https://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 org.springframework.rewrite.gradle.model;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
|
||||
public interface KotlinSourceSetData {
|
||||
|
||||
String getName();
|
||||
|
||||
Collection<File> getKotlin();
|
||||
|
||||
Collection<File> getCompileClasspath();
|
||||
|
||||
Collection<File> getImplementationClasspath();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2021 - 2023 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
|
||||
*
|
||||
* https://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 org.springframework.rewrite.gradle.model;
|
||||
|
||||
import org.gradle.tooling.GradleConnector;
|
||||
import org.gradle.tooling.ModelBuilder;
|
||||
import org.gradle.tooling.ProjectConnection;
|
||||
import org.gradle.tooling.internal.consumer.DefaultGradleConnector;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SpringRewriteModelBuilder {
|
||||
|
||||
public static <T> T forProjectDirectory(Class<T> type, File projectDir, File buildFile) {
|
||||
DefaultGradleConnector connector = (DefaultGradleConnector) GradleConnector.newConnector();
|
||||
if (Files.exists(projectDir.toPath().resolve("gradle/wrapper/gradle-wrapper.properties"))) {
|
||||
connector.useBuildDistribution();
|
||||
}
|
||||
else {
|
||||
connector.useGradleVersion("8.4");
|
||||
}
|
||||
connector.forProjectDirectory(projectDir);
|
||||
List<String> arguments = new ArrayList<>();
|
||||
if (buildFile != null && buildFile.exists()) {
|
||||
arguments.add("-b");
|
||||
arguments.add(buildFile.getAbsolutePath());
|
||||
}
|
||||
arguments.add("--init-script");
|
||||
Path init = projectDir.toPath().resolve("openrewrite-tooling.gradle").toAbsolutePath();
|
||||
arguments.add(init.toString());
|
||||
try (ProjectConnection connection = connector.connect()) {
|
||||
ModelBuilder<T> customModelBuilder = connection.model(type);
|
||||
try (InputStream is = SpringRewriteModelBuilder.class.getResourceAsStream("/init.gradle")) {
|
||||
if (is == null) {
|
||||
throw new IllegalStateException("Expected to find init.gradle on the classpath");
|
||||
}
|
||||
Files.copy(is, init, StandardCopyOption.REPLACE_EXISTING);
|
||||
customModelBuilder.withArguments(arguments);
|
||||
return customModelBuilder.get();
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
Files.delete(init);
|
||||
}
|
||||
catch (IOException e) {
|
||||
// noinspection ThrowFromFinallyBlock
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static GradleProjectData forProjectDirectory(File projectDir, File buildFile) {
|
||||
return forProjectDirectory(GradleProjectData.class, projectDir, buildFile);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2021 - 2023 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
initscript{
|
||||
repositories{
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
configurations.all{
|
||||
resolutionStrategy{
|
||||
cacheChangingModulesFor 0, 'seconds'
|
||||
cacheDynamicVersionsFor 0, 'seconds'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies{
|
||||
classpath 'org.springframework.rewrite:rewrite-gradle-plugin:latest.integration'
|
||||
classpath 'org.springframework.rewrite:rewrite-gradle-model:latest.integration'
|
||||
}
|
||||
}
|
||||
|
||||
allprojects{
|
||||
apply plugin: org.springframework.rewrite.gradle.plugin.SpringRewritePlugin
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2021 - 2023 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
|
||||
*
|
||||
* https://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 org.springframework.rewrite.gradle.model;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class GradleProjectDataTest {
|
||||
|
||||
@Test
|
||||
void serializable(@TempDir Path dir) throws Exception {
|
||||
try (InputStream is = GradleProjectDataTest.class.getResourceAsStream("/build.gradle")) {
|
||||
Files.write(dir.resolve("build.gradle"), Objects.requireNonNull(is).readAllBytes());
|
||||
}
|
||||
try (InputStream is = GradleProjectDataTest.class.getResourceAsStream("/settings.gradle")) {
|
||||
Files.write(dir.resolve("settings.gradle"), Objects.requireNonNull(is).readAllBytes());
|
||||
}
|
||||
|
||||
GradleProjectData gp = SpringRewriteModelBuilder.forProjectDirectory(GradleProjectData.class, dir.toFile(),
|
||||
dir.resolve("build.gradle").toFile());
|
||||
|
||||
assertThat(gp.getGroup()).isEqualTo("");
|
||||
assertThat(gp.getName()).isEqualTo("sample");
|
||||
assertThat(gp.getVersion()).isEqualTo("unspecified");
|
||||
assertThat(gp.getPlugins().size()).isEqualTo(13);
|
||||
assertThat(gp.getMavenRepositories().get(0).getUri()).isEqualTo("https://repo.maven.apache.org/maven2/");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2021 - 2023 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
plugins{
|
||||
id 'java'
|
||||
}
|
||||
|
||||
repositories{
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies{
|
||||
implementation 'org.apache.commons:commons-lang3:3.4'
|
||||
testImplementation("org.projectlombok:lombok:latest.release")
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2021 - 2023 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
rootProject.name = "sample"
|
||||
124
spring-rewrite-commons-gradle/rewrite-gradle-parser/pom.xml
Normal file
124
spring-rewrite-commons-gradle/rewrite-gradle-parser/pom.xml
Normal file
@@ -0,0 +1,124 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.rewrite</groupId>
|
||||
<artifactId>spring-rewrite-commons-gradle</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>rewrite-gradle-parser</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.rewrite</groupId>
|
||||
<artifactId>rewrite-gradle-model</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/dev.gradleplugins/gradle-api -->
|
||||
<dependency>
|
||||
<groupId>dev.gradleplugins</groupId>
|
||||
<artifactId>gradle-api</artifactId>
|
||||
<version>${gradle.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-core</artifactId>
|
||||
<version>${rewrite.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-gradle</artifactId>
|
||||
<version>${rewrite.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-groovy</artifactId>
|
||||
<version>${rewrite.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-hcl</artifactId>
|
||||
<version>${rewrite.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-java</artifactId>
|
||||
<version>${rewrite.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-java-8</artifactId>
|
||||
<version>${rewrite.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-java-11</artifactId>
|
||||
<version>${rewrite.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-java-17</artifactId>
|
||||
<version>${rewrite.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-json</artifactId>
|
||||
<version>${rewrite.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-kotlin</artifactId>
|
||||
<version>${rewrite-kotlin.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-properties</artifactId>
|
||||
<version>${rewrite.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-protobuf</artifactId>
|
||||
<version>${rewrite.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-xml</artifactId>
|
||||
<version>${rewrite.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-yaml</artifactId>
|
||||
<version>${rewrite.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-polyglot</artifactId>
|
||||
<version>${rewrite-polyglot.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2021 - 2023 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
|
||||
*
|
||||
* https://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 org.springframework.rewrite.gradle;
|
||||
|
||||
import org.openrewrite.style.NamedStyles;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record Options(List<String> exclusions, boolean logCompilationWarningsAndErrors, List<String> plainTextMasks,
|
||||
int sizeThresholdMb, List<NamedStyles> styles) {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,631 @@
|
||||
/*
|
||||
* Copyright 2021 - 2023 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
|
||||
*
|
||||
* https://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 org.springframework.rewrite.gradle;
|
||||
|
||||
import org.openrewrite.ExecutionContext;
|
||||
import org.openrewrite.InMemoryExecutionContext;
|
||||
import org.openrewrite.SourceFile;
|
||||
import org.openrewrite.gradle.GradleParser;
|
||||
import org.openrewrite.gradle.toolingapi.GradleSettings;
|
||||
import org.springframework.rewrite.gradle.model.GradleProjectData;
|
||||
import org.springframework.rewrite.gradle.model.JavaSourceSetData;
|
||||
import org.springframework.rewrite.gradle.model.KotlinSourceSetData;
|
||||
import org.openrewrite.groovy.GroovyParser;
|
||||
import org.openrewrite.internal.StringUtils;
|
||||
import org.openrewrite.internal.lang.Nullable;
|
||||
import org.openrewrite.java.JavaParser;
|
||||
import org.openrewrite.java.internal.JavaTypeCache;
|
||||
import org.openrewrite.java.marker.JavaProject;
|
||||
import org.openrewrite.java.marker.JavaSourceSet;
|
||||
import org.openrewrite.java.marker.JavaVersion;
|
||||
import org.openrewrite.kotlin.KotlinParser;
|
||||
import org.openrewrite.marker.*;
|
||||
import org.openrewrite.marker.ci.BuildEnvironment;
|
||||
import org.openrewrite.polyglot.*;
|
||||
import org.openrewrite.quark.QuarkParser;
|
||||
import org.openrewrite.style.NamedStyles;
|
||||
import org.openrewrite.text.PlainTextParser;
|
||||
import org.openrewrite.tree.ParseError;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.PathMatcher;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.Collections.*;
|
||||
import static java.util.stream.Collectors.*;
|
||||
import static org.openrewrite.PathUtils.separatorsToUnix;
|
||||
import static org.openrewrite.Tree.randomId;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ProjectParser {
|
||||
|
||||
private static final String LOG_INDENT_INCREMENT = " ";
|
||||
|
||||
private static final String GROOVY_PLUGIN = "org.gradle.api.plugins.GroovyPlugin";
|
||||
|
||||
private final Options options;
|
||||
|
||||
private final Logger logger;
|
||||
|
||||
private final AtomicBoolean firstWarningLogged = new AtomicBoolean(false);
|
||||
|
||||
protected final Path baseDir;
|
||||
|
||||
protected final GradleProjectData project;
|
||||
|
||||
private final List<Marker> sharedProvenance;
|
||||
|
||||
public ProjectParser(GradleProjectData project, Options options, Logger logger) {
|
||||
this.baseDir = repositoryRoot(project);
|
||||
this.options = options;
|
||||
this.project = project;
|
||||
this.logger = logger;
|
||||
|
||||
BuildEnvironment buildEnvironment = BuildEnvironment.build(System::getenv);
|
||||
sharedProvenance = Stream
|
||||
.of(buildEnvironment, gitProvenance(baseDir, buildEnvironment), OperatingSystemProvenance.current(),
|
||||
new BuildTool(randomId(), BuildTool.Type.Gradle, project.getGradleVersion()))
|
||||
.filter(Objects::nonNull)
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to determine the root of the git repository for the given project. Many
|
||||
* Gradle builds co-locate the build root with the git repository root, but that is
|
||||
* not required. If no git repository can be located in any folder containing the
|
||||
* build, the build root will be returned.
|
||||
*/
|
||||
static Path repositoryRoot(GradleProjectData project) {
|
||||
Path buildRoot = project.getProjectDir().toPath();
|
||||
Path maybeBaseDir = buildRoot;
|
||||
while (maybeBaseDir != null && !Files.exists(maybeBaseDir.resolve(".git"))) {
|
||||
maybeBaseDir = maybeBaseDir.getParent();
|
||||
}
|
||||
if (maybeBaseDir == null) {
|
||||
return buildRoot;
|
||||
}
|
||||
return maybeBaseDir;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private GitProvenance gitProvenance(Path baseDir, @Nullable BuildEnvironment buildEnvironment) {
|
||||
try {
|
||||
return GitProvenance.fromProjectDirectory(baseDir, buildEnvironment);
|
||||
}
|
||||
catch (Exception e) {
|
||||
// Logging at a low level as this is unlikely to happen except in non-git
|
||||
// projects, where it is expected
|
||||
logger.debug("Unable to determine git provenance", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// By accident, we were inconsistent with the names of these properties between this
|
||||
// and the maven plugin
|
||||
// Check all variants of the name, preferring more-fully-qualified names
|
||||
@Nullable
|
||||
private String getPropertyWithVariantNames(String property) {
|
||||
String maybeProp = System.getProperty("rewrite." + property + "s");
|
||||
if (maybeProp == null) {
|
||||
maybeProp = System.getProperty("rewrite." + property);
|
||||
}
|
||||
if (maybeProp == null) {
|
||||
maybeProp = System.getProperty(property + "s");
|
||||
}
|
||||
if (maybeProp == null) {
|
||||
maybeProp = System.getProperty(property);
|
||||
}
|
||||
return maybeProp;
|
||||
}
|
||||
|
||||
public Collection<Path> listSources() {
|
||||
// Use a sorted collection so that gradle input detection isn't thrown off by
|
||||
// ordering
|
||||
Set<Path> result = new TreeSet<>(
|
||||
omniParser(emptySet()).acceptedPaths(baseDir, project.getProjectDir().toPath()));
|
||||
for (JavaSourceSetData sourceSet : project.getJavaSourceSets()) {
|
||||
sourceSet.getSources()
|
||||
.stream()
|
||||
.map(File::toPath)
|
||||
.map(Path::toAbsolutePath)
|
||||
.map(Path::normalize)
|
||||
.forEach(result::add);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Stream<SourceFile> parse(ExecutionContext ctx) {
|
||||
Stream<SourceFile> builder = Stream.of();
|
||||
Set<Path> alreadyParsed = new HashSet<>();
|
||||
if (project.isRootProject()) {
|
||||
for (GradleProjectData subProject : project.getSubprojects()) {
|
||||
builder = Stream.concat(builder, parse(subProject, alreadyParsed, ctx));
|
||||
}
|
||||
}
|
||||
builder = Stream.concat(builder, parse(project, alreadyParsed, ctx));
|
||||
|
||||
// log parse errors here at the end, so that we don't log parse errors for files
|
||||
// that were excluded
|
||||
return builder.map(this::logParseErrors);
|
||||
}
|
||||
|
||||
public Stream<SourceFile> parse(GradleProjectData subproject, Set<Path> alreadyParsed, ExecutionContext ctx) {
|
||||
String cliPort = System.getenv("MODERNE_CLI_PORT");
|
||||
try (ProgressBar progressBar = StringUtils.isBlank(cliPort) ? new NoopProgressBar()
|
||||
: new RemoteProgressBarSender(Integer.parseInt(cliPort))) {
|
||||
SourceFileStream sourceFileStream = SourceFileStream.build(subproject.getPath(),
|
||||
projectName -> progressBar.intermediateResult(":" + projectName));
|
||||
|
||||
Collection<PathMatcher> exclusions = options.exclusions()
|
||||
.stream()
|
||||
.map(pattern -> subproject.getProjectDir().toPath().getFileSystem().getPathMatcher("glob:" + pattern))
|
||||
.collect(toList());
|
||||
if (isExcluded(exclusions, baseDir.relativize(subproject.getProjectDir().toPath()))) {
|
||||
logger.info("Skipping project {} because it is excluded", subproject.getPath());
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
logger.info("Scanning sources in project {}", subproject.getPath());
|
||||
List<NamedStyles> styles = options.styles();
|
||||
logger.info("Using active styles {}", styles.stream().map(NamedStyles::getName).collect(toList()));
|
||||
List<JavaSourceSetData> sourceSets = subproject.getJavaSourceSets()
|
||||
.stream()
|
||||
.sorted(Comparator.comparingInt(sourceSet -> {
|
||||
if ("main".equals(sourceSet.getName())) {
|
||||
return 0;
|
||||
}
|
||||
else if ("test".equals(sourceSet.getName())) {
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return 2;
|
||||
}
|
||||
}))
|
||||
.toList();
|
||||
List<Marker> projectProvenance;
|
||||
if (sourceSets.isEmpty()) {
|
||||
projectProvenance = sharedProvenance;
|
||||
}
|
||||
else {
|
||||
projectProvenance = new ArrayList<>(sharedProvenance);
|
||||
projectProvenance.add(new JavaProject(randomId(), subproject.getName(), new JavaProject.Publication(
|
||||
subproject.getGroup(), subproject.getName(), subproject.getVersion())));
|
||||
}
|
||||
|
||||
if (subproject.isMultiPlatformKotlinProject()) {
|
||||
sourceFileStream = sourceFileStream
|
||||
.concat(parseMultiplatformKotlinProject(subproject, exclusions, alreadyParsed, ctx));
|
||||
}
|
||||
|
||||
Set<String> sourceDirs = new HashSet<>();
|
||||
for (JavaSourceSetData sourceSet : sourceSets) {
|
||||
Stream<SourceFile> sourceSetSourceFiles = Stream.of();
|
||||
int sourceSetSize = 0;
|
||||
|
||||
JavaTypeCache javaTypeCache = new JavaTypeCache();
|
||||
JavaVersion javaVersion = new JavaVersion(randomId(), sourceSet.getJavaVersionData().getCreatedBy(),
|
||||
sourceSet.getJavaVersionData().getVmVendor(),
|
||||
sourceSet.getJavaVersionData().getSourceCompatibility(),
|
||||
sourceSet.getJavaVersionData().getTargetCompatibility());
|
||||
|
||||
List<Path> unparsedSources = sourceSet.getSources()
|
||||
.stream()
|
||||
.filter(it -> it.exists() && !alreadyParsed.contains(it.toPath()))
|
||||
.flatMap(sourceDir -> {
|
||||
try {
|
||||
return Files.walk(sourceDir.toPath());
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
})
|
||||
.filter(Files::isRegularFile)
|
||||
.map(Path::toAbsolutePath)
|
||||
.map(Path::normalize)
|
||||
.distinct()
|
||||
.toList();
|
||||
List<Path> javaPaths = unparsedSources.stream()
|
||||
.filter(it -> it.toString().endsWith(".java") && !alreadyParsed.contains(it))
|
||||
.collect(toList());
|
||||
|
||||
Collection<File> implementationClasspath = sourceSet.getImplementationClasspath();
|
||||
// The implementation configuration doesn't include build/source
|
||||
// directories from project dependencies
|
||||
// So mash it and our rewriteImplementation together to get everything
|
||||
List<Path> dependencyPaths = Stream
|
||||
.concat(implementationClasspath.stream(), sourceSet.getCompileClasspath().stream())
|
||||
.map(File::toPath)
|
||||
.map(Path::toAbsolutePath)
|
||||
.map(Path::normalize)
|
||||
.distinct()
|
||||
.collect(toList());
|
||||
|
||||
if (!javaPaths.isEmpty()) {
|
||||
alreadyParsed.addAll(javaPaths);
|
||||
Stream<SourceFile> cus = Stream
|
||||
.of((Supplier<JavaParser>) () -> JavaParser.fromJavaVersion()
|
||||
.classpath(dependencyPaths)
|
||||
.styles(options.styles())
|
||||
.typeCache(javaTypeCache)
|
||||
.logCompilationWarningsAndErrors(options.logCompilationWarningsAndErrors())
|
||||
.build())
|
||||
.map(Supplier::get)
|
||||
.flatMap(jp -> jp.parse(javaPaths, baseDir, ctx))
|
||||
.map(cu -> {
|
||||
if (isExcluded(exclusions, cu.getSourcePath()) || cu.getSourcePath()
|
||||
.startsWith(baseDir.relativize(subproject.getBuildDir().toPath()))) {
|
||||
return null;
|
||||
}
|
||||
return cu;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.map(it -> it.withMarkers(it.getMarkers().add(javaVersion)));
|
||||
sourceSetSourceFiles = Stream.concat(sourceSetSourceFiles, cus);
|
||||
sourceSetSize += javaPaths.size();
|
||||
logger.info("Scanned {} Java sources in {}/{}", javaPaths.size(), subproject.getPath(),
|
||||
sourceSet.getName());
|
||||
}
|
||||
|
||||
if (subproject.getPlugins().stream().anyMatch(gpd -> "org.jetbrains.kotlin.jvm".equals(gpd.getId()))) {
|
||||
String excludedProtosPath = subproject.getProjectDir().getPath() + "/protos/build/generated";
|
||||
List<Path> kotlinPaths = unparsedSources.stream()
|
||||
.filter(it -> !it.toString().startsWith(excludedProtosPath))
|
||||
.filter(it -> it.toString().endsWith(".kt"))
|
||||
.collect(toList());
|
||||
|
||||
if (!kotlinPaths.isEmpty()) {
|
||||
alreadyParsed.addAll(kotlinPaths);
|
||||
Stream<SourceFile> cus = Stream
|
||||
.of((Supplier<KotlinParser>) () -> KotlinParser.builder()
|
||||
.classpath(dependencyPaths)
|
||||
.styles(options.styles())
|
||||
.typeCache(javaTypeCache)
|
||||
.logCompilationWarningsAndErrors(options.logCompilationWarningsAndErrors())
|
||||
.build())
|
||||
.map(Supplier::get)
|
||||
.flatMap(kp -> kp.parse(kotlinPaths, baseDir, ctx))
|
||||
.map(cu -> {
|
||||
if (isExcluded(exclusions, cu.getSourcePath())) {
|
||||
return null;
|
||||
}
|
||||
return cu;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.map(it -> it.withMarkers(it.getMarkers().add(javaVersion)));
|
||||
sourceSetSourceFiles = Stream.concat(sourceSetSourceFiles, cus);
|
||||
sourceSetSize += kotlinPaths.size();
|
||||
logger.info("Scanned {} Kotlin sources in {}/{}", kotlinPaths.size(), subproject.getPath(),
|
||||
sourceSet.getName());
|
||||
}
|
||||
}
|
||||
if (subproject.getPlugins()
|
||||
.stream()
|
||||
.anyMatch(gpd -> gpd.getFullyQualifiedClassName().startsWith(GROOVY_PLUGIN))) {
|
||||
List<Path> groovyPaths = unparsedSources.stream()
|
||||
.filter(it -> it.toString().endsWith(".groovy"))
|
||||
.collect(toList());
|
||||
|
||||
if (!groovyPaths.isEmpty()) {
|
||||
// Groovy sources are aware of java types that are intermixed in
|
||||
// the same directory/sourceSet
|
||||
// Include the build directory containing class files so these
|
||||
// definitions are available
|
||||
List<Path> dependenciesWithBuildDirs = Stream
|
||||
.concat(dependencyPaths.stream(), sourceSet.getClassesDirs().stream().map(File::toPath))
|
||||
.collect(toList());
|
||||
|
||||
alreadyParsed.addAll(groovyPaths);
|
||||
|
||||
Stream<SourceFile> cus = Stream
|
||||
.of((Supplier<GroovyParser>) () -> GroovyParser.builder()
|
||||
.classpath(dependenciesWithBuildDirs)
|
||||
.styles(options.styles())
|
||||
.typeCache(javaTypeCache)
|
||||
.logCompilationWarningsAndErrors(false)
|
||||
.build())
|
||||
.map(Supplier::get)
|
||||
.flatMap(gp -> gp.parse(groovyPaths, baseDir, ctx))
|
||||
.map(cu -> {
|
||||
if (isExcluded(exclusions, cu.getSourcePath())) {
|
||||
return null;
|
||||
}
|
||||
return cu;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.map(it -> it.withMarkers(it.getMarkers().add(javaVersion)));
|
||||
sourceSetSourceFiles = Stream.concat(sourceSetSourceFiles, cus);
|
||||
sourceSetSize += groovyPaths.size();
|
||||
logger.info("Scanned {} Groovy sources in {}/{}", groovyPaths.size(), subproject.getPath(),
|
||||
sourceSet.getName());
|
||||
}
|
||||
}
|
||||
|
||||
for (File resourcesDir : sourceSet.getSourceDirectories()) {
|
||||
if (resourcesDir.exists() && !alreadyParsed.contains(resourcesDir.toPath())) {
|
||||
OmniParser omniParser = omniParser(alreadyParsed);
|
||||
List<Path> accepted = omniParser.acceptedPaths(baseDir, resourcesDir.toPath());
|
||||
sourceSetSourceFiles = Stream.concat(sourceSetSourceFiles,
|
||||
omniParser.parse(accepted, baseDir, new InMemoryExecutionContext()));
|
||||
alreadyParsed.addAll(accepted);
|
||||
sourceSetSize += accepted.size();
|
||||
}
|
||||
}
|
||||
|
||||
JavaSourceSet sourceSetProvenance = JavaSourceSet.build(sourceSet.getName(), dependencyPaths,
|
||||
javaTypeCache, false);
|
||||
sourceFileStream = sourceFileStream.concat(sourceSetSourceFiles.map(addProvenance(sourceSetProvenance)),
|
||||
sourceSetSize);
|
||||
// Some source sets get misconfigured to have the same directories as
|
||||
// other source sets
|
||||
// This causes duplicate source files to be parsed, so once a source set
|
||||
// has been parsed exclude it from future parsing
|
||||
for (File file : sourceSet.getSourceDirectories()) {
|
||||
alreadyParsed.add(file.toPath());
|
||||
}
|
||||
}
|
||||
SourceFileStream gradleFiles = parseGradleFiles(exclusions, alreadyParsed, ctx);
|
||||
sourceFileStream = sourceFileStream.concat(gradleFiles, gradleFiles.size());
|
||||
|
||||
SourceFileStream nonProjectResources = parseNonProjectResources(subproject, alreadyParsed, ctx,
|
||||
projectProvenance, sourceFileStream);
|
||||
sourceFileStream = sourceFileStream.concat(nonProjectResources, nonProjectResources.size());
|
||||
|
||||
progressBar.setMax(sourceFileStream.size());
|
||||
return sourceFileStream.map(addProvenance(projectProvenance)).peek(it -> progressBar.step());
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private GradleParser gradleParser() {
|
||||
List<Path> settingsClasspath = project.getSettingsClasspath().stream().map(File::toPath).collect(toList());
|
||||
List<Path> buildscriptClasspath = project.getBuildscriptClasspath()
|
||||
.stream()
|
||||
.map(File::toPath)
|
||||
.collect(toList());
|
||||
|
||||
return GradleParser.builder()
|
||||
.groovyParser(GroovyParser.builder()
|
||||
.typeCache(new JavaTypeCache())
|
||||
.styles(options.styles())
|
||||
.logCompilationWarningsAndErrors(false))
|
||||
.buildscriptClasspath(buildscriptClasspath)
|
||||
.settingsClasspath(settingsClasspath)
|
||||
.build();
|
||||
}
|
||||
|
||||
private SourceFileStream parseGradleFiles(Collection<PathMatcher> exclusions, Set<Path> alreadyParsed,
|
||||
ExecutionContext ctx) {
|
||||
Stream<SourceFile> sourceFiles = Stream.empty();
|
||||
int gradleFileCount = 0;
|
||||
|
||||
GradleParser gradleParser = null;
|
||||
if (project.getBuildscriptFile() != null) {
|
||||
File buildGradleFile = project.getBuildscriptFile();
|
||||
Path buildScriptPath = baseDir.relativize(buildGradleFile.toPath());
|
||||
if (!isExcluded(exclusions, buildScriptPath) && buildGradleFile.exists()) {
|
||||
alreadyParsed.add(buildScriptPath);
|
||||
if (buildScriptPath.toString().endsWith(".gradle")) {
|
||||
gradleParser = gradleParser();
|
||||
sourceFiles = gradleParser.parse(singleton(buildGradleFile.toPath()), baseDir, ctx);
|
||||
}
|
||||
else {
|
||||
sourceFiles = PlainTextParser.builder()
|
||||
.build()
|
||||
.parse(singleton(buildGradleFile.toPath()), baseDir, ctx);
|
||||
}
|
||||
gradleFileCount++;
|
||||
sourceFiles = sourceFiles.map(sourceFile -> sourceFile.withMarkers(sourceFile.getMarkers()
|
||||
.add(org.openrewrite.gradle.marker.GradleProject.fromToolingModel(project))));
|
||||
alreadyParsed.add(project.getBuildscriptFile().toPath());
|
||||
}
|
||||
}
|
||||
|
||||
if (project.isRootProject()) {
|
||||
File settingsGradleFile = new File(project.getProjectDir(), "settings.gradle");
|
||||
File settingsGradleKtsFile = new File(project.getProjectDir(), "settings.gradle.kts");
|
||||
GradleSettings gs = project.getGradleSettings();
|
||||
if (settingsGradleFile.exists()) {
|
||||
Path settingsPath = baseDir.relativize(settingsGradleFile.toPath());
|
||||
if (gradleParser == null) {
|
||||
gradleParser = gradleParser();
|
||||
}
|
||||
if (!isExcluded(exclusions, settingsPath)) {
|
||||
sourceFiles = Stream.concat(sourceFiles,
|
||||
gradleParser.parse(singleton(settingsGradleFile.toPath()), baseDir, ctx).map(sourceFile -> {
|
||||
if (gs == null) {
|
||||
return sourceFile;
|
||||
}
|
||||
return sourceFile.withMarkers(sourceFile.getMarkers()
|
||||
.add(org.openrewrite.gradle.marker.GradleSettings.fromToolingModel(gs)));
|
||||
}));
|
||||
gradleFileCount++;
|
||||
}
|
||||
alreadyParsed.add(settingsGradleFile.toPath());
|
||||
}
|
||||
else if (settingsGradleKtsFile.exists()) {
|
||||
Path settingsPath = baseDir.relativize(settingsGradleKtsFile.toPath());
|
||||
if (!isExcluded(exclusions, settingsPath)) {
|
||||
sourceFiles = Stream.concat(sourceFiles,
|
||||
PlainTextParser.builder()
|
||||
.build()
|
||||
.parse(singleton(settingsGradleKtsFile.toPath()), baseDir, ctx)
|
||||
.map(sourceFile -> {
|
||||
if (gs == null) {
|
||||
return sourceFile;
|
||||
}
|
||||
return sourceFile.withMarkers(sourceFile.getMarkers()
|
||||
.add(org.openrewrite.gradle.marker.GradleSettings.fromToolingModel(gs)));
|
||||
}));
|
||||
gradleFileCount++;
|
||||
}
|
||||
alreadyParsed.add(settingsGradleKtsFile.toPath());
|
||||
}
|
||||
}
|
||||
|
||||
return SourceFileStream.build("", s -> {
|
||||
}).concat(sourceFiles, gradleFileCount);
|
||||
}
|
||||
|
||||
protected SourceFileStream parseNonProjectResources(GradleProjectData subproject, Set<Path> alreadyParsed,
|
||||
ExecutionContext ctx, List<Marker> projectProvenance, Stream<SourceFile> sourceFiles) {
|
||||
// Collect any additional yaml/properties/xml files that are NOT already in a
|
||||
// source set.
|
||||
OmniParser omniParser = omniParser(alreadyParsed);
|
||||
List<Path> accepted = omniParser.acceptedPaths(baseDir, subproject.getProjectDir().toPath());
|
||||
return SourceFileStream.build("", s -> {
|
||||
}).concat(omniParser.parse(accepted, baseDir, ctx), accepted.size());
|
||||
}
|
||||
|
||||
private OmniParser omniParser(Set<Path> alreadyParsed) {
|
||||
return OmniParser
|
||||
.builder(OmniParser.defaultResourceParsers(),
|
||||
PlainTextParser.builder().plainTextMasks(baseDir, options.plainTextMasks()).build(),
|
||||
QuarkParser.builder().build())
|
||||
.exclusionMatchers(pathMatchers(baseDir, mergeExclusions(project, baseDir, options)))
|
||||
.exclusions(alreadyParsed)
|
||||
.sizeThresholdMb(options.sizeThresholdMb())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static Collection<String> mergeExclusions(GradleProjectData project, Path baseDir, Options options) {
|
||||
return Stream
|
||||
.concat(project.getSubprojects()
|
||||
.stream()
|
||||
.map(subproject -> separatorsToUnix(
|
||||
baseDir.relativize(subproject.getProjectDir().toPath()).toString())),
|
||||
options.exclusions().stream())
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
private Collection<PathMatcher> pathMatchers(Path basePath, Collection<String> pathExpressions) {
|
||||
return pathExpressions.stream()
|
||||
.map(o -> basePath.getFileSystem().getPathMatcher("glob:" + o))
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
private SourceFileStream parseMultiplatformKotlinProject(GradleProjectData subproject,
|
||||
Collection<PathMatcher> exclusions, Set<Path> alreadyParsed, ExecutionContext ctx) {
|
||||
SourceFileStream sourceFileStream = SourceFileStream.build(subproject.getPath(), s -> {
|
||||
});
|
||||
for (KotlinSourceSetData sourceSet : project.getKotlinSourceSets()) {
|
||||
List<Path> kotlinPaths = sourceSet.getKotlin()
|
||||
.stream()
|
||||
.filter(it -> it.isFile() && it.getName().endsWith(".kt"))
|
||||
.map(File::toPath)
|
||||
.map(Path::toAbsolutePath)
|
||||
.map(Path::normalize)
|
||||
.collect(toList());
|
||||
|
||||
// The implementation configuration doesn't include build/source directories
|
||||
// from project dependencies
|
||||
// So mash it and our rewriteImplementation together to get everything
|
||||
List<Path> dependencyPaths = Stream
|
||||
.concat(sourceSet.getImplementationClasspath().stream(), sourceSet.getCompileClasspath().stream())
|
||||
.map(File::toPath)
|
||||
.map(Path::toAbsolutePath)
|
||||
.map(Path::normalize)
|
||||
.distinct()
|
||||
.collect(toList());
|
||||
|
||||
if (!kotlinPaths.isEmpty()) {
|
||||
JavaTypeCache javaTypeCache = new JavaTypeCache();
|
||||
KotlinParser kp = KotlinParser.builder()
|
||||
.classpath(dependencyPaths)
|
||||
.styles(options.styles())
|
||||
.typeCache(javaTypeCache)
|
||||
.logCompilationWarningsAndErrors(options.logCompilationWarningsAndErrors())
|
||||
.build();
|
||||
|
||||
Stream<SourceFile> cus = kp.parse(kotlinPaths, baseDir, ctx);
|
||||
alreadyParsed.addAll(kotlinPaths);
|
||||
cus = cus.map(cu -> {
|
||||
if (isExcluded(exclusions, cu.getSourcePath())) {
|
||||
return null;
|
||||
}
|
||||
return cu;
|
||||
}).filter(Objects::nonNull);
|
||||
JavaSourceSet sourceSetProvenance = JavaSourceSet.build(sourceSet.getName(), dependencyPaths,
|
||||
javaTypeCache, false);
|
||||
|
||||
sourceFileStream = sourceFileStream.concat(cus.map(addProvenance(sourceSetProvenance)),
|
||||
kotlinPaths.size());
|
||||
logger.info("Scanned {} Kotlin sources in {}/{}", kotlinPaths.size(), subproject.getPath(),
|
||||
sourceSet.getName());
|
||||
}
|
||||
}
|
||||
|
||||
return sourceFileStream;
|
||||
}
|
||||
|
||||
private SourceFile logParseErrors(SourceFile source) {
|
||||
if (source instanceof ParseError) {
|
||||
if (firstWarningLogged.compareAndSet(false, true)) {
|
||||
logger.warn("There were problems parsing some source files, run with --info to see full stack traces");
|
||||
}
|
||||
logger.warn("There were problems parsing " + source.getSourcePath());
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
private boolean isExcluded(Collection<PathMatcher> exclusions, Path path) {
|
||||
for (PathMatcher excluded : exclusions) {
|
||||
if (excluded.matches(path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// PathMather will not evaluate the path "build.gradle" to be matched by the
|
||||
// pattern "**/build.gradle"
|
||||
// This is counter-intuitive for most users and would otherwise require separate
|
||||
// exclusions for files at the root and files in subdirectories
|
||||
if (!path.isAbsolute() && !path.startsWith(File.separator)) {
|
||||
return isExcluded(exclusions, Paths.get("/" + path));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private <T extends SourceFile> UnaryOperator<T> addProvenance(List<Marker> projectProvenance) {
|
||||
return s -> {
|
||||
Markers m = s.getMarkers();
|
||||
for (Marker marker : projectProvenance) {
|
||||
m = m.addIfAbsent(marker);
|
||||
}
|
||||
return s.withMarkers(m);
|
||||
};
|
||||
}
|
||||
|
||||
private <T extends SourceFile> UnaryOperator<T> addProvenance(Marker sourceSet) {
|
||||
return s -> {
|
||||
Markers m = s.getMarkers();
|
||||
m = m.addIfAbsent(sourceSet);
|
||||
return s.withMarkers(m);
|
||||
};
|
||||
}
|
||||
|
||||
public Path getBaseDir() {
|
||||
return baseDir;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2021 - 2023 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
|
||||
*
|
||||
* https://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 org.springframework.rewrite.gradle;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.openrewrite.InMemoryExecutionContext;
|
||||
import org.openrewrite.SourceFile;
|
||||
import org.openrewrite.style.NamedStyles;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.rewrite.gradle.model.GradleProjectData;
|
||||
import org.springframework.rewrite.gradle.model.SpringRewriteModelBuilder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class ProjectParserTest {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ProjectParserTest.class);
|
||||
|
||||
private static final Options OPTIONS = new Options(Collections.emptyList(), false, Collections.emptyList(),
|
||||
Integer.MAX_VALUE, Collections.emptyList());
|
||||
|
||||
private Path downloadPetclinic(Path dir) throws Exception {
|
||||
unzip(new URL(
|
||||
"https://github.com/spring-projects/spring-petclinic/archive/0aa3adb56f500c41564411c32cd301affe284ecc.zip"),
|
||||
dir);
|
||||
return Files.list(dir)
|
||||
.filter(Files::isDirectory)
|
||||
.filter(p -> p.getFileName().toString().startsWith("spring-petclinic-"))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
private static void unzip(URL url, Path destDir) throws IOException {
|
||||
File dir = destDir.toFile();
|
||||
// create output directory if it doesn't exist
|
||||
if (!dir.exists())
|
||||
dir.mkdirs();
|
||||
InputStream is;
|
||||
// buffer for read and write data to file
|
||||
byte[] buffer = new byte[1024];
|
||||
try {
|
||||
is = url.openStream();
|
||||
ZipInputStream zis = new ZipInputStream(is);
|
||||
ZipEntry ze = zis.getNextEntry();
|
||||
while (ze != null) {
|
||||
if (!ze.isDirectory()) {
|
||||
String fileName = ze.getName();
|
||||
File newFile = new File(destDir + File.separator + fileName);
|
||||
System.out.println("Unzipping to " + newFile.getAbsolutePath());
|
||||
// create directories for sub directories in zip
|
||||
new File(newFile.getParent()).mkdirs();
|
||||
FileOutputStream fos = new FileOutputStream(newFile);
|
||||
int len;
|
||||
while ((len = zis.read(buffer)) > 0) {
|
||||
fos.write(buffer, 0, len);
|
||||
}
|
||||
fos.close();
|
||||
}
|
||||
// close this ZipEntry
|
||||
zis.closeEntry();
|
||||
ze = zis.getNextEntry();
|
||||
}
|
||||
// close last ZipEntry
|
||||
zis.closeEntry();
|
||||
zis.close();
|
||||
is.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void sanity(@TempDir Path dir) throws Exception {
|
||||
Path petClinic = downloadPetclinic(dir.resolve("petclinic"));
|
||||
GradleProjectData gp = SpringRewriteModelBuilder.forProjectDirectory(GradleProjectData.class,
|
||||
petClinic.toFile(), petClinic.resolve("build.gradle").toFile());
|
||||
List<SourceFile> sources = new ProjectParser(gp, OPTIONS, log)
|
||||
.parse(new InMemoryExecutionContext(t -> Assertions.fail("Parser Error", t)))
|
||||
.toList();
|
||||
assertThat(sources.size()).isEqualTo(114);
|
||||
}
|
||||
|
||||
}
|
||||
66
spring-rewrite-commons-gradle/rewrite-gradle-plugin/pom.xml
Normal file
66
spring-rewrite-commons-gradle/rewrite-gradle-plugin/pom.xml
Normal file
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.rewrite</groupId>
|
||||
<artifactId>spring-rewrite-commons-gradle</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>rewrite-gradle-plugin</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<lombok.version>1.18.30</lombok.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.rewrite</groupId>
|
||||
<artifactId>rewrite-gradle-model</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.gradleplugins</groupId>
|
||||
<artifactId>gradle-api</artifactId>
|
||||
<version>${gradle.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.inject</groupId>
|
||||
<artifactId>javax.inject</artifactId>
|
||||
<version>1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2021 - 2023 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
|
||||
*
|
||||
* https://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 org.springframework.rewrite.gradle.plugin;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.tooling.provider.model.ToolingModelBuilder;
|
||||
import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
|
||||
import org.springframework.rewrite.gradle.model.GradleProjectData;
|
||||
import org.springframework.rewrite.gradle.plugin.model.GradleToolingApiProjectBuilder;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class SpringRewritePlugin implements Plugin<Project> {
|
||||
|
||||
private final ToolingModelBuilderRegistry registry;
|
||||
|
||||
@Inject
|
||||
public SpringRewritePlugin(ToolingModelBuilderRegistry registry) {
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
registry.register(new GradleProjectDataBuilder());
|
||||
}
|
||||
|
||||
private static class GradleProjectDataBuilder implements ToolingModelBuilder {
|
||||
|
||||
@Override
|
||||
public boolean canBuild(String modelName) {
|
||||
return modelName.equals(GradleProjectData.class.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object buildAll(String modelName, Project project) {
|
||||
return GradleToolingApiProjectBuilder.createProjectData(project);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* Copyright 2021 - 2023 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
|
||||
*
|
||||
* https://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 org.springframework.rewrite.gradle.plugin.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Value;
|
||||
import org.gradle.api.NamedDomainObjectContainer;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.file.SourceDirectorySet;
|
||||
import org.gradle.api.initialization.Settings;
|
||||
import org.gradle.api.logging.Logger;
|
||||
import org.gradle.api.logging.Logging;
|
||||
import org.gradle.api.plugins.JavaPluginConvention;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.compile.JavaCompile;
|
||||
import org.gradle.invocation.DefaultGradle;
|
||||
import org.gradle.util.GradleVersion;
|
||||
import org.openrewrite.gradle.toolingapi.*;
|
||||
import org.springframework.rewrite.gradle.model.GradleProjectData;
|
||||
import org.springframework.rewrite.gradle.model.JavaSourceSetData;
|
||||
import org.springframework.rewrite.gradle.model.KotlinSourceSetData;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
@Value
|
||||
@AllArgsConstructor
|
||||
class GradleProjectDataImpl implements GradleProjectData, Serializable {
|
||||
|
||||
private static final Logger logger = Logging.getLogger(GradleProjectDataImpl.class);
|
||||
|
||||
private static Class<?>[] SUPPORTED_GRADLE_PROPERTY_VALUE_TYPES = new Class<?>[] { Number.class, Boolean.class,
|
||||
String.class, Character.class };
|
||||
|
||||
String name;
|
||||
|
||||
String path;
|
||||
|
||||
String group;
|
||||
|
||||
String version;
|
||||
|
||||
List<GradlePluginDescriptor> plugins;
|
||||
|
||||
List<MavenRepository> mavenRepositories;
|
||||
|
||||
List<MavenRepository> mavenPluginRepositories;
|
||||
|
||||
Map<String, GradleDependencyConfiguration> nameToConfiguration;
|
||||
|
||||
GradleSettings gradleSettings;
|
||||
|
||||
String gradleVersion;
|
||||
|
||||
boolean rootProject;
|
||||
|
||||
File rootProjectDir;
|
||||
|
||||
Collection<GradleProjectData> subprojects;
|
||||
|
||||
File projectDir;
|
||||
|
||||
File buildDir;
|
||||
|
||||
File buildscriptFile;
|
||||
|
||||
Map<String, ?> properties;
|
||||
|
||||
List<JavaSourceSetData> javaSourceSets;
|
||||
|
||||
boolean multiPlatformKotlinProject;
|
||||
|
||||
List<KotlinSourceSetData> kotlinSourceSets;
|
||||
|
||||
Collection<File> buildscriptClasspath;
|
||||
|
||||
Collection<File> settingsClasspath;
|
||||
|
||||
static GradleProjectDataImpl from(Project project) {
|
||||
GradleProject toolingRewriiteGradleProject = GradleToolingApiProjectBuilder.gradleProject(project);
|
||||
return new GradleProjectDataImpl(project.getName(), project.getPath(), project.getGroup().toString(),
|
||||
project.getVersion().toString(),
|
||||
GradleToolingApiProjectBuilder.pluginDescriptors(project.getPluginManager()),
|
||||
GradleToolingApiProjectBuilder.mapRepositories(project.getRepositories()),
|
||||
GradleToolingApiProjectBuilder.pluginMavenRepos(project),
|
||||
GradleToolingApiProjectBuilder.dependencyConfigurations(project.getConfigurations()),
|
||||
GradleVersion.current().compareTo(GradleVersion.version("4.4")) >= 0 ? GradleToolingApiSettingsBuilder
|
||||
.gradleSettings(((DefaultGradle) project.getGradle()).getSettings()) : null,
|
||||
project.getGradle().getGradleVersion(), project == project.getRootProject(),
|
||||
project.getRootProject().getProjectDir(), subprojects(project.getSubprojects()),
|
||||
project.getProjectDir(), project.getBuildDir(), project.getBuildscript().getSourceFile(),
|
||||
properties(project.getProperties()), javaSourceSets(project), isMultiPlatformKotlinProject(project),
|
||||
kotlinSourceSets(project),
|
||||
project.getBuildscript().getConfigurations().getByName("classpath").resolve(),
|
||||
settingsClasspath(project));
|
||||
}
|
||||
|
||||
private static Collection<GradleProjectData> subprojects(Collection<Project> subprojects) {
|
||||
List<GradleProjectData> sub = new ArrayList(subprojects.size());
|
||||
for (Project s : subprojects) {
|
||||
sub.add(from(s));
|
||||
}
|
||||
return sub;
|
||||
}
|
||||
|
||||
private static Map<String, ?> properties(Map<String, ?> props) {
|
||||
return props.entrySet()
|
||||
.stream()
|
||||
.filter(e -> Arrays.stream(SUPPORTED_GRADLE_PROPERTY_VALUE_TYPES).anyMatch(c -> c.isInstance(e.getValue())))
|
||||
.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
|
||||
}
|
||||
|
||||
private static List<JavaSourceSetData> javaSourceSets(Project project) {
|
||||
JavaPluginConvention javaConvention = (JavaPluginConvention) project.getConvention()
|
||||
.findPlugin(JavaPluginConvention.class);
|
||||
if (javaConvention == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
else {
|
||||
List<JavaSourceSetData> sourceSetData = new ArrayList(javaConvention.getSourceSets().size());
|
||||
for (SourceSet sourceSet : javaConvention.getSourceSets()) {
|
||||
sourceSetData.add(new JavaSourceSetDataImpl(sourceSet.getName(), sourceSet.getAllSource().getFiles(),
|
||||
sourceSet.getResources().getSourceDirectories().getFiles(), sourceSet.getAllJava().getFiles(),
|
||||
sourceSet.getOutput().getClassesDirs().getFiles(), sourceSet.getCompileClasspath().getFiles(),
|
||||
javaSourceSetImplementationClasspath(project, sourceSet),
|
||||
sourceSetJavaVersion(project, sourceSet)));
|
||||
}
|
||||
return sourceSetData;
|
||||
}
|
||||
}
|
||||
|
||||
private static Collection<File> javaSourceSetImplementationClasspath(Project project, SourceSet sourceSet) {
|
||||
// classpath doesn't include the transitive dependencies of the implementation
|
||||
// configuration
|
||||
// These aren't needed for compilation, but we want them so recipes have access to
|
||||
// comprehensive type information
|
||||
// The implementation configuration isn't resolvable, so we need a new
|
||||
// configuration that extends from it
|
||||
Configuration implementation = project.getConfigurations()
|
||||
.getByName(sourceSet.getImplementationConfigurationName());
|
||||
Configuration rewriteImplementation = project.getConfigurations()
|
||||
.maybeCreate("rewrite" + sourceSet.getImplementationConfigurationName());
|
||||
rewriteImplementation.extendsFrom(new Configuration[] { implementation });
|
||||
|
||||
try {
|
||||
return rewriteImplementation.resolve();
|
||||
}
|
||||
catch (Exception e) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
|
||||
private static JavaVersionDataImpl sourceSetJavaVersion(Project project, SourceSet sourceSet) {
|
||||
final JavaCompile javaCompileTask = (JavaCompile) project.getTasks()
|
||||
.getByName(sourceSet.getCompileJavaTaskName());
|
||||
return new JavaVersionDataImpl(System.getProperty("java.runtime.version"), System.getProperty("java.vm.vendor"),
|
||||
javaCompileTask.getSourceCompatibility(), javaCompileTask.getTargetCompatibility());
|
||||
}
|
||||
|
||||
private static boolean isMultiPlatformKotlinProject(Project project) {
|
||||
try {
|
||||
return project.getPlugins().hasPlugin("org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension")
|
||||
|| project.getExtensions().findByName("kotlin") != null && project.getExtensions()
|
||||
.findByName("kotlin")
|
||||
.getClass()
|
||||
.getCanonicalName()
|
||||
.startsWith("org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension");
|
||||
}
|
||||
catch (Throwable t) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<KotlinSourceSetData> kotlinSourceSets(Project project) {
|
||||
NamedDomainObjectContainer sourceSets;
|
||||
try {
|
||||
Object kotlinExtension = project.getExtensions().getByName("kotlin");
|
||||
Class<?> clazz = kotlinExtension.getClass()
|
||||
.getClassLoader()
|
||||
.loadClass("org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension");
|
||||
sourceSets = (NamedDomainObjectContainer) clazz.getMethod("getSourceSets").invoke(kotlinExtension);
|
||||
}
|
||||
catch (Exception e) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
SortedSet<String> sourceSetNames;
|
||||
try {
|
||||
sourceSetNames = (SortedSet<String>) sourceSets.getClass().getMethod("getNames").invoke(sourceSets);
|
||||
}
|
||||
catch (Exception e) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<KotlinSourceSetData> kotlinSourceSetData = new ArrayList(sourceSetNames.size());
|
||||
|
||||
for (String sourceSetName : sourceSetNames) {
|
||||
try {
|
||||
Object sourceSet = sourceSets.getClass()
|
||||
.getMethod("getByName", String.class)
|
||||
.invoke(sourceSets, sourceSetName);
|
||||
final SourceDirectorySet kotlinDirectorySet = (SourceDirectorySet) sourceSet.getClass()
|
||||
.getMethod("getKotlin")
|
||||
.invoke(sourceSet);
|
||||
String implementationName = (String) sourceSet.getClass()
|
||||
.getMethod("getImplementationConfigurationName")
|
||||
.invoke(sourceSet);
|
||||
Configuration implementation = project.getConfigurations().getByName(implementationName);
|
||||
Configuration rewriteImplementation = (Configuration) project.getConfigurations()
|
||||
.maybeCreate("rewrite" + implementationName);
|
||||
rewriteImplementation.extendsFrom(new Configuration[] { implementation });
|
||||
|
||||
Set implementationClasspath;
|
||||
try {
|
||||
implementationClasspath = rewriteImplementation.resolve();
|
||||
}
|
||||
catch (Exception e) {
|
||||
implementationClasspath = Collections.emptySet();
|
||||
}
|
||||
|
||||
String compileName = (String) sourceSet.getClass()
|
||||
.getMethod("getCompileOnlyConfigurationName")
|
||||
.invoke(sourceSet);
|
||||
Configuration compileOnly = project.getConfigurations().getByName(compileName);
|
||||
Configuration rewriteCompileOnly = project.getConfigurations().maybeCreate("rewrite" + compileName);
|
||||
rewriteCompileOnly.setCanBeResolved(true);
|
||||
rewriteCompileOnly.extendsFrom(new Configuration[] { compileOnly });
|
||||
final Set<File> compClasspath = rewriteCompileOnly.getFiles();
|
||||
kotlinSourceSetData.add(new KotlinSourceSetDataImpl(sourceSetName, kotlinDirectorySet.getFiles(),
|
||||
compClasspath, implementationClasspath));
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.warn("Failed to resolve sourceSet from {}:{}. Some type information may be incomplete",
|
||||
project.getPath(), sourceSetName);
|
||||
}
|
||||
}
|
||||
|
||||
return kotlinSourceSetData;
|
||||
}
|
||||
|
||||
private static Collection<File> settingsClasspath(Project project) {
|
||||
if (GradleVersion.current().compareTo(GradleVersion.version("4.4")) >= 0) {
|
||||
try {
|
||||
Settings settings = ((DefaultGradle) project.getGradle()).getSettings();
|
||||
return settings.getBuildscript().getConfigurations().getByName("classpath").resolve();
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
// ignore - return empty list
|
||||
}
|
||||
}
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,483 @@
|
||||
/*
|
||||
* Copyright 2021 - 2023 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
|
||||
*
|
||||
* https://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 org.springframework.rewrite.gradle.plugin.model;
|
||||
|
||||
import lombok.*;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.ConfigurationContainer;
|
||||
import org.gradle.api.artifacts.ResolvedConfiguration;
|
||||
import org.gradle.api.artifacts.repositories.ArtifactRepository;
|
||||
import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
|
||||
import org.gradle.api.initialization.Settings;
|
||||
import org.gradle.api.internal.plugins.PluginManagerInternal;
|
||||
import org.gradle.api.plugins.PluginManager;
|
||||
import org.gradle.invocation.DefaultGradle;
|
||||
import org.gradle.plugin.use.PluginId;
|
||||
import org.gradle.util.GradleVersion;
|
||||
import org.openrewrite.gradle.toolingapi.*;
|
||||
import org.springframework.rewrite.gradle.model.GradleProjectData;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
public class GradleToolingApiProjectBuilder {
|
||||
|
||||
@Builder
|
||||
@Getter
|
||||
static class MavenRepositoryImpl implements MavenRepository, Serializable {
|
||||
|
||||
String id;
|
||||
|
||||
String uri;
|
||||
|
||||
String releases;
|
||||
|
||||
String snapshots;
|
||||
|
||||
boolean knownToExist;
|
||||
|
||||
String username;
|
||||
|
||||
String password;
|
||||
|
||||
Boolean DeriveMetadataIfMissing;
|
||||
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
static class GradlePluginDescriptorImpl implements GradlePluginDescriptor, Serializable {
|
||||
|
||||
String fullyQualifiedClassName;
|
||||
|
||||
String id;
|
||||
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
static class GroupArtifactVersionImpl implements GroupArtifactVersion, Serializable {
|
||||
|
||||
String groupId;
|
||||
|
||||
String artifactId;
|
||||
|
||||
String version;
|
||||
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
static class GroupArtifactImpl implements GroupArtifact, Serializable {
|
||||
|
||||
String groupId;
|
||||
|
||||
String artifactId;
|
||||
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@Builder
|
||||
@EqualsAndHashCode
|
||||
static class DependencyImpl implements Dependency, Serializable {
|
||||
|
||||
GroupArtifactVersion gav;
|
||||
|
||||
String classifier;
|
||||
|
||||
String type;
|
||||
|
||||
String scope;
|
||||
|
||||
List<GroupArtifact> exclusions;
|
||||
|
||||
String optional;
|
||||
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
static class ResolvedGroupArtifactVersionImpl implements ResolvedGroupArtifactVersion, Serializable {
|
||||
|
||||
String artifactId;
|
||||
|
||||
String groupId;
|
||||
|
||||
String version;
|
||||
|
||||
String datedSnapshotVersion;
|
||||
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
static class ResolvedDependencyImpl implements ResolvedDependency, Serializable {
|
||||
|
||||
MavenRepositoryImpl repository;
|
||||
|
||||
ResolvedGroupArtifactVersionImpl gav;
|
||||
|
||||
DependencyImpl requested;
|
||||
|
||||
List<ResolvedDependency> dependencies;
|
||||
|
||||
int depth;
|
||||
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
static class GradleDependencyConfigurationImpl implements GradleDependencyConfiguration, Serializable {
|
||||
|
||||
String name;
|
||||
|
||||
String description;
|
||||
|
||||
boolean transitive;
|
||||
|
||||
boolean canBeConsumed;
|
||||
|
||||
boolean canBeResolved;
|
||||
|
||||
List<GradleDependencyConfiguration> extendsFrom;
|
||||
|
||||
List<Dependency> requested;
|
||||
|
||||
List<ResolvedDependency> resolved;
|
||||
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
static class GradleProjectImpl implements GradleProject, Serializable {
|
||||
|
||||
String name;
|
||||
|
||||
String path;
|
||||
|
||||
List<GradlePluginDescriptor> plugins;
|
||||
|
||||
List<MavenRepository> mavenRepositories;
|
||||
|
||||
List<MavenRepository> mavenPluginRepositories;
|
||||
|
||||
Map<String, GradleDependencyConfiguration> nameToConfiguration;
|
||||
|
||||
}
|
||||
|
||||
static MavenRepositoryImpl GRADLE_PLUGIN_PORTAL = MavenRepositoryImpl.builder()
|
||||
.id("Gradle Central Plugin Repository")
|
||||
.uri("https://plugins.gradle.org/m2")
|
||||
.releases(String.valueOf(true))
|
||||
.snapshots(String.valueOf(true))
|
||||
.build();
|
||||
|
||||
public static GradleProject gradleProject(Project project) {
|
||||
return new GradleProjectImpl(project.getName(), project.getPath(),
|
||||
pluginDescriptors(project.getPluginManager()), mapRepositories(project.getRepositories()),
|
||||
pluginMavenRepos(project), dependencyConfigurations(project.getConfigurations()));
|
||||
}
|
||||
|
||||
static List<MavenRepository> pluginMavenRepos(Project project) {
|
||||
Set<MavenRepository> pluginRepositories = new HashSet<>();
|
||||
if (GradleVersion.current().compareTo(GradleVersion.version("4.4")) >= 0) {
|
||||
Settings settings = ((DefaultGradle) project.getGradle()).getSettings();
|
||||
pluginRepositories.addAll(mapRepositories(settings.getPluginManagement().getRepositories()));
|
||||
pluginRepositories.addAll(mapRepositories(settings.getBuildscript().getRepositories()));
|
||||
}
|
||||
pluginRepositories.addAll(mapRepositories(project.getBuildscript().getRepositories()));
|
||||
if (pluginRepositories.isEmpty()) {
|
||||
pluginRepositories.add(GRADLE_PLUGIN_PORTAL);
|
||||
}
|
||||
|
||||
return new ArrayList<>(pluginRepositories);
|
||||
}
|
||||
|
||||
static List<MavenRepository> mapRepositories(List<ArtifactRepository> repositories) {
|
||||
return repositories.stream()
|
||||
.filter(MavenArtifactRepository.class::isInstance)
|
||||
.map(MavenArtifactRepository.class::cast)
|
||||
.map(repo -> MavenRepositoryImpl.builder()
|
||||
.id(repo.getName())
|
||||
.uri(repo.getUrl().toString())
|
||||
.releases(String.valueOf(true))
|
||||
.snapshots(String.valueOf(true))
|
||||
.build())
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
public static List<GradlePluginDescriptor> pluginDescriptors(PluginManager pluginManager) {
|
||||
if (pluginManager instanceof PluginManagerInternal) {
|
||||
return pluginDescriptors((PluginManagerInternal) pluginManager);
|
||||
}
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
public static List<GradlePluginDescriptor> pluginDescriptors(PluginManagerInternal pluginManager) {
|
||||
return pluginManager.getPluginContainer()
|
||||
.stream()
|
||||
.map(plugin -> new GradlePluginDescriptorImpl(plugin.getClass().getName(),
|
||||
pluginIdForClass(pluginManager, plugin.getClass())))
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
private static String pluginIdForClass(PluginManagerInternal pluginManager, Class<?> pluginClass) {
|
||||
try {
|
||||
Method findPluginIdForClass = PluginManagerInternal.class.getMethod("findPluginIdForClass", Class.class);
|
||||
// noinspection unchecked
|
||||
Optional<PluginId> maybePluginId = (Optional<PluginId>) findPluginIdForClass.invoke(pluginManager,
|
||||
pluginClass);
|
||||
return maybePluginId.map(PluginId::getId).orElse(null);
|
||||
}
|
||||
catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||
// On old versions of gradle that don't have this method, returning null is
|
||||
// fine
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final Map<GroupArtifactImpl, GroupArtifactImpl> groupArtifactCache = new ConcurrentHashMap<>();
|
||||
|
||||
private static GroupArtifactImpl groupArtifact(Dependency dep) {
|
||||
// noinspection ConstantConditions
|
||||
return groupArtifactCache
|
||||
.computeIfAbsent(new GroupArtifactImpl(dep.getGav().getGroupId(), dep.getGav().getArtifactId()), it -> it);
|
||||
}
|
||||
|
||||
private static GroupArtifactImpl groupArtifact(org.gradle.api.artifacts.ResolvedDependency dep) {
|
||||
return groupArtifactCache.computeIfAbsent(new GroupArtifactImpl(dep.getModuleGroup(), dep.getModuleName()),
|
||||
it -> it);
|
||||
}
|
||||
|
||||
private static final Map<GroupArtifactVersionImpl, GroupArtifactVersionImpl> groupArtifactVersionCache = new ConcurrentHashMap<>();
|
||||
|
||||
private static GroupArtifactVersionImpl groupArtifactVersion(org.gradle.api.artifacts.ResolvedDependency dep) {
|
||||
return groupArtifactVersionCache.computeIfAbsent(new GroupArtifactVersionImpl(dep.getModuleGroup(),
|
||||
dep.getModuleName(), unspecifiedToNull(dep.getModuleVersion())), it -> it);
|
||||
}
|
||||
|
||||
private static GroupArtifactVersionImpl groupArtifactVersion(org.gradle.api.artifacts.Dependency dep) {
|
||||
return groupArtifactVersionCache.computeIfAbsent(
|
||||
new GroupArtifactVersionImpl(dep.getGroup(), dep.getName(), unspecifiedToNull(dep.getVersion())),
|
||||
it -> it);
|
||||
}
|
||||
|
||||
private static final Map<ResolvedGroupArtifactVersionImpl, ResolvedGroupArtifactVersionImpl> resolvedGroupArtifactVersionCache = new ConcurrentHashMap<>();
|
||||
|
||||
private static ResolvedGroupArtifactVersionImpl resolvedGroupArtifactVersion(
|
||||
org.gradle.api.artifacts.ResolvedDependency dep) {
|
||||
return resolvedGroupArtifactVersionCache.computeIfAbsent(new ResolvedGroupArtifactVersionImpl(
|
||||
dep.getModuleName(), dep.getModuleGroup(), dep.getModuleVersion(), null), it -> it);
|
||||
}
|
||||
|
||||
/**
|
||||
* Some Gradle dependency functions will have the String "unspecified" to indicate a
|
||||
* missing value. Rewrite's dependency API represents these missing things as "null"
|
||||
*/
|
||||
private static String unspecifiedToNull(String maybeUnspecified) {
|
||||
if ("unspecified".equals(maybeUnspecified)) {
|
||||
return null;
|
||||
}
|
||||
return maybeUnspecified;
|
||||
}
|
||||
|
||||
static Map<String, GradleDependencyConfiguration> dependencyConfigurations(
|
||||
ConfigurationContainer configurationContainer) {
|
||||
Map<String, GradleDependencyConfiguration> results = new HashMap<>();
|
||||
List<Configuration> configurations = new ArrayList<>(configurationContainer);
|
||||
for (Configuration conf : configurations) {
|
||||
try {
|
||||
List<Dependency> requested = conf.getAllDependencies()
|
||||
.stream()
|
||||
.map(dep -> dependency(dep, conf))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<ResolvedDependency> resolved;
|
||||
Map<GroupArtifactImpl, Dependency> gaToRequested = requested.stream()
|
||||
.collect(Collectors.toMap(GradleToolingApiProjectBuilder::groupArtifact, dep -> dep, (a, b) -> a));
|
||||
// Archives and default are redundant with other configurations
|
||||
// Newer versions of gradle display warnings with long stack traces when
|
||||
// attempting to resolve them
|
||||
// Some Scala plugin we don't care about creates configurations that, for
|
||||
// some unknown reason, are difficult to resolve
|
||||
if (conf.isCanBeResolved() && !"archives".equals(conf.getName()) && !"default".equals(conf.getName())
|
||||
&& !conf.getName().startsWith("incrementalScalaAnalysis")) {
|
||||
ResolvedConfiguration resolvedConf = conf.getResolvedConfiguration();
|
||||
Map<GroupArtifactImpl, org.gradle.api.artifacts.ResolvedDependency> gaToResolved = resolvedConf
|
||||
.getFirstLevelModuleDependencies()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(dep -> GradleToolingApiProjectBuilder.groupArtifact(dep), dep -> dep,
|
||||
(a, b) -> a));
|
||||
resolved = resolved(gaToRequested, gaToResolved);
|
||||
}
|
||||
else {
|
||||
resolved = emptyList();
|
||||
}
|
||||
// TODO: Doesn't look like 'transitive' is needed at all for Gradle
|
||||
// tooling model side
|
||||
List<ResolvedDependency> transitive = resolveTransitiveDependencies(resolved, new LinkedHashSet<>());
|
||||
GradleDependencyConfigurationImpl dc = new GradleDependencyConfigurationImpl(conf.getName(),
|
||||
conf.getDescription(), conf.isTransitive(), conf.isCanBeConsumed(), conf.isCanBeResolved(),
|
||||
emptyList(), requested, resolved);
|
||||
results.put(conf.getName(), dc);
|
||||
}
|
||||
catch (Exception e) {
|
||||
GradleDependencyConfigurationImpl dc = new GradleDependencyConfigurationImpl(conf.getName(),
|
||||
conf.getDescription(), conf.isTransitive(), conf.isCanBeConsumed(), conf.isCanBeResolved(),
|
||||
emptyList(), emptyList(), emptyList());
|
||||
results.put(conf.getName(), dc);
|
||||
}
|
||||
}
|
||||
|
||||
// Record the relationships between dependency configurations
|
||||
for (Configuration conf : configurations) {
|
||||
if (conf.getExtendsFrom().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
GradleDependencyConfigurationImpl dc = (GradleDependencyConfigurationImpl) results.get(conf.getName());
|
||||
if (dc != null) {
|
||||
List<GradleDependencyConfiguration> extendsFrom = conf.getExtendsFrom()
|
||||
.stream()
|
||||
.map(it -> results.get(it.getName()))
|
||||
.collect(Collectors.toList());
|
||||
dc.extendsFrom = extendsFrom;
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
static List<ResolvedDependency> resolveTransitiveDependencies(List<ResolvedDependency> resolved,
|
||||
Set<ResolvedDependency> alreadyResolved) {
|
||||
for (ResolvedDependency dependency : resolved) {
|
||||
if (alreadyResolved.add(dependency)) {
|
||||
alreadyResolved.addAll(resolveTransitiveDependencies(dependency.getDependencies(), alreadyResolved));
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(alreadyResolved);
|
||||
}
|
||||
|
||||
private static final Map<GroupArtifactVersionImpl, DependencyImpl> requestedCache = new ConcurrentHashMap<>();
|
||||
|
||||
private static DependencyImpl dependency(org.gradle.api.artifacts.Dependency dep, Configuration configuration) {
|
||||
GroupArtifactVersionImpl gav = groupArtifactVersion(dep);
|
||||
return requestedCache.computeIfAbsent(gav,
|
||||
it -> DependencyImpl.builder()
|
||||
.gav(gav)
|
||||
.type("jar")
|
||||
.scope(configuration.getName())
|
||||
.exclusions(emptyList())
|
||||
.build());
|
||||
}
|
||||
|
||||
private static List<ResolvedDependency> resolved(Map<GroupArtifactImpl, Dependency> gaToRequested,
|
||||
Map<GroupArtifactImpl, org.gradle.api.artifacts.ResolvedDependency> gaToResolved) {
|
||||
Map<ResolvedGroupArtifactVersionImpl, ResolvedDependencyImpl> resolvedCache = new HashMap<>();
|
||||
return gaToResolved.entrySet().stream().map(entry -> {
|
||||
GroupArtifactImpl ga = entry.getKey();
|
||||
org.gradle.api.artifacts.ResolvedDependency resolved = entry.getValue();
|
||||
|
||||
// Gradle knows which repository it got a dependency from, but haven't been
|
||||
// able to find where that info lives
|
||||
ResolvedGroupArtifactVersionImpl resolvedGav = resolvedGroupArtifactVersion(resolved);
|
||||
ResolvedDependencyImpl resolvedDependency = resolvedCache.get(resolvedGav);
|
||||
if (resolvedDependency == null) {
|
||||
resolvedDependency = ResolvedDependencyImpl.builder()
|
||||
.gav(resolvedGav)
|
||||
// There may not be a requested entry if a dependency substitution
|
||||
// rule took effect
|
||||
// the DependencyHandler has the substitution mapping buried inside
|
||||
// it, but not exposed publicly
|
||||
// Possible improvement to dig that out and use it
|
||||
.requested((DependencyImpl) gaToRequested.getOrDefault(ga, dependency(resolved)))
|
||||
.dependencies(resolved.getChildren()
|
||||
.stream()
|
||||
.map(child -> resolved(child, 1, resolvedCache))
|
||||
.collect(toList()))
|
||||
.depth(0)
|
||||
.build();
|
||||
resolvedCache.put(resolvedGav, resolvedDependency);
|
||||
}
|
||||
return resolvedDependency;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* When there is a resolved dependency that cannot be matched up with a requested
|
||||
* dependency, construct a requested dependency corresponding to the exact version
|
||||
* which was resolved. This isn't strictly accurate, but there is no obvious way to
|
||||
* access the resolution of transitive dependencies to figure out what versions are
|
||||
* requested during the resolution process.
|
||||
*/
|
||||
private static DependencyImpl dependency(org.gradle.api.artifacts.ResolvedDependency dep) {
|
||||
GroupArtifactVersionImpl gav = groupArtifactVersion(dep);
|
||||
return requestedCache.computeIfAbsent(gav,
|
||||
it -> DependencyImpl.builder()
|
||||
.gav(gav)
|
||||
.type("jar")
|
||||
.scope(dep.getConfiguration())
|
||||
.exclusions(emptyList())
|
||||
.build());
|
||||
}
|
||||
|
||||
private static ResolvedDependencyImpl resolved(org.gradle.api.artifacts.ResolvedDependency dep, int depth,
|
||||
Map<ResolvedGroupArtifactVersionImpl, ResolvedDependencyImpl> resolvedCache) {
|
||||
ResolvedGroupArtifactVersionImpl resolvedGav = resolvedGroupArtifactVersion(dep);
|
||||
ResolvedDependencyImpl resolvedDependency = resolvedCache.get(resolvedGav);
|
||||
if (resolvedDependency == null) {
|
||||
|
||||
List<ResolvedDependency> dependencies = new ArrayList<>();
|
||||
|
||||
resolvedDependency = ResolvedDependencyImpl.builder()
|
||||
.gav(resolvedGav)
|
||||
.requested(dependency(dep))
|
||||
.dependencies(dependencies)
|
||||
.depth(depth)
|
||||
.build();
|
||||
// we add a temporal resolved dependency in the cache to avoid stackoverflow
|
||||
// with dependencies that have cycles
|
||||
resolvedCache.put(resolvedGav, resolvedDependency);
|
||||
dep.getChildren().forEach(child -> dependencies.add(resolved(child, depth + 1, resolvedCache)));
|
||||
}
|
||||
return resolvedDependency;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static void clearCaches() {
|
||||
requestedCache.clear();
|
||||
groupArtifactCache.clear();
|
||||
groupArtifactVersionCache.clear();
|
||||
resolvedGroupArtifactVersionCache.clear();
|
||||
}
|
||||
|
||||
public static GradleProjectData createProjectData(Project project) {
|
||||
return GradleProjectDataImpl.from(project);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2021 - 2023 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
|
||||
*
|
||||
* https://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 org.springframework.rewrite.gradle.plugin.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Value;
|
||||
import org.gradle.api.initialization.Settings;
|
||||
import org.gradle.api.internal.FeaturePreviews;
|
||||
import org.gradle.initialization.DefaultSettings;
|
||||
import org.gradle.internal.service.ServiceRegistry;
|
||||
import org.gradle.internal.service.UnknownServiceException;
|
||||
import org.gradle.util.GradleVersion;
|
||||
import org.openrewrite.gradle.toolingapi.FeaturePreview;
|
||||
import org.openrewrite.gradle.toolingapi.GradlePluginDescriptor;
|
||||
import org.openrewrite.gradle.toolingapi.GradleSettings;
|
||||
import org.openrewrite.gradle.toolingapi.MavenRepository;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
|
||||
public class GradleToolingApiSettingsBuilder {
|
||||
|
||||
@AllArgsConstructor
|
||||
@Value
|
||||
static class FeaturePreviewImpl implements FeaturePreview, Serializable {
|
||||
|
||||
String name;
|
||||
|
||||
boolean active;
|
||||
|
||||
boolean enabled;
|
||||
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@Value
|
||||
static class GradleSettingsImpl implements GradleSettings, Serializable {
|
||||
|
||||
List<MavenRepository> pluginRepositories;
|
||||
|
||||
List<GradlePluginDescriptor> plugins;
|
||||
|
||||
Map<String, FeaturePreview> featurePreviews;
|
||||
|
||||
}
|
||||
|
||||
public static GradleSettings gradleSettings(Settings settings) {
|
||||
if (settings == null) {
|
||||
return null;
|
||||
}
|
||||
Set<MavenRepository> pluginRepositories = new HashSet<>();
|
||||
pluginRepositories
|
||||
.addAll(GradleToolingApiProjectBuilder.mapRepositories(settings.getPluginManagement().getRepositories()));
|
||||
pluginRepositories
|
||||
.addAll(GradleToolingApiProjectBuilder.mapRepositories(settings.getBuildscript().getRepositories()));
|
||||
if (pluginRepositories.isEmpty()) {
|
||||
pluginRepositories.add(GradleToolingApiProjectBuilder.GRADLE_PLUGIN_PORTAL);
|
||||
}
|
||||
|
||||
return new GradleSettingsImpl(new ArrayList<>(pluginRepositories),
|
||||
GradleToolingApiProjectBuilder.pluginDescriptors(settings.getPluginManager()),
|
||||
featurePreviews((DefaultSettings) settings));
|
||||
}
|
||||
|
||||
private static Map<String, FeaturePreview> featurePreviews(DefaultSettings settings) {
|
||||
if (GradleVersion.current().compareTo(GradleVersion.version("4.6")) < 0) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
Map<String, FeaturePreview> featurePreviews = new HashMap<>();
|
||||
FeaturePreviews gradleFeaturePreviews = getService(settings, FeaturePreviews.class);
|
||||
if (gradleFeaturePreviews != null) {
|
||||
FeaturePreviews.Feature[] gradleFeatures = FeaturePreviews.Feature.values();
|
||||
for (FeaturePreviews.Feature feature : gradleFeatures) {
|
||||
// Unclear how enabled status can be determined in latest gradle APIs
|
||||
featurePreviews.put(feature.name(), new FeaturePreviewImpl(feature.name(), feature.isActive(), false));
|
||||
}
|
||||
}
|
||||
return featurePreviews;
|
||||
}
|
||||
|
||||
private static <T> T getService(DefaultSettings settings,
|
||||
@SuppressWarnings("SameParameterValue") Class<T> serviceType) {
|
||||
try {
|
||||
Method services = settings.getClass().getDeclaredMethod("getServices");
|
||||
services.setAccessible(true);
|
||||
ServiceRegistry serviceRegistry = (ServiceRegistry) services.invoke(settings);
|
||||
return serviceRegistry.get(serviceType);
|
||||
}
|
||||
catch (UnknownServiceException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2021 - 2023 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
|
||||
*
|
||||
* https://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 org.springframework.rewrite.gradle.plugin.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Value;
|
||||
import org.springframework.rewrite.gradle.model.JavaSourceSetData;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Value
|
||||
class JavaSourceSetDataImpl implements JavaSourceSetData, Serializable {
|
||||
|
||||
String name;
|
||||
|
||||
Collection<File> sources;
|
||||
|
||||
Collection<File> sourceDirectories;
|
||||
|
||||
Collection<File> java;
|
||||
|
||||
Collection<File> classesDirs;
|
||||
|
||||
Collection<File> compileClasspath;
|
||||
|
||||
Collection<File> implementationClasspath;
|
||||
|
||||
JavaVersionDataImpl javaVersionData;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2021 - 2023 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
|
||||
*
|
||||
* https://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 org.springframework.rewrite.gradle.plugin.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Value;
|
||||
import org.springframework.rewrite.gradle.model.JavaVersionData;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Value
|
||||
class JavaVersionDataImpl implements JavaVersionData, Serializable {
|
||||
|
||||
String createdBy;
|
||||
|
||||
String vmVendor;
|
||||
|
||||
String sourceCompatibility;
|
||||
|
||||
String targetCompatibility;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2021 - 2023 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
|
||||
*
|
||||
* https://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 org.springframework.rewrite.gradle.plugin.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Value;
|
||||
import org.springframework.rewrite.gradle.model.KotlinSourceSetData;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Value
|
||||
class KotlinSourceSetDataImpl implements KotlinSourceSetData, Serializable {
|
||||
|
||||
String name;
|
||||
|
||||
Collection<File> kotlin;
|
||||
|
||||
Collection<File> compileClasspath;
|
||||
|
||||
Collection<File> implementationClasspath;
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user