Browse Source
Allows for updating the depencencies of the project in an automated fashion. Closes gh-9542pull/9549/head
10 changed files with 675 additions and 6 deletions
@ -0,0 +1,7 @@ |
|||||||
|
mutation CreateIssueInput($assigneeId: ID!, $labelIds: [ID!], $milestoneId: ID!, $repositoryId: ID!, $title: String!) { |
||||||
|
createIssue(input: {assigneeIds: [$assigneeId], labelIds: $labelIds, milestoneId: $milestoneId, projectIds: [], repositoryId: $repositoryId, title: $title}) { |
||||||
|
issue { |
||||||
|
number |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,20 @@ |
|||||||
|
query FindCreateIssueInput($owner: String!, $name: String!, $labelQuery: String, $milestoneName: String) { |
||||||
|
repository(owner: $owner, name: $name) { |
||||||
|
id |
||||||
|
labels(query: $labelQuery, first: 1) { |
||||||
|
nodes { |
||||||
|
id |
||||||
|
name |
||||||
|
} |
||||||
|
} |
||||||
|
milestones(query: $milestoneName, states: [OPEN], first: 1) { |
||||||
|
nodes { |
||||||
|
id |
||||||
|
title |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
viewer { |
||||||
|
id |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
query RateLimit { |
||||||
|
rateLimit { |
||||||
|
limit |
||||||
|
cost |
||||||
|
remaining |
||||||
|
resetAt |
||||||
|
} |
||||||
|
} |
||||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,179 @@ |
|||||||
|
package org.springframework.security.convention.versions; |
||||||
|
|
||||||
|
import com.apollographql.apollo.ApolloCall; |
||||||
|
import com.apollographql.apollo.ApolloClient; |
||||||
|
import com.apollographql.apollo.api.Input; |
||||||
|
import com.apollographql.apollo.api.Response; |
||||||
|
import com.apollographql.apollo.exception.ApolloException; |
||||||
|
import okhttp3.Interceptor; |
||||||
|
import okhttp3.OkHttpClient; |
||||||
|
import okhttp3.Request; |
||||||
|
import org.jetbrains.annotations.NotNull; |
||||||
|
import reactor.core.publisher.Mono; |
||||||
|
import reactor.util.retry.Retry; |
||||||
|
import reactor.util.retry.RetrySpec; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.time.Duration; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Optional; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
|
||||||
|
public class GitHubApi { |
||||||
|
|
||||||
|
private final ApolloClient apolloClient; |
||||||
|
|
||||||
|
public GitHubApi(String githubToken) { |
||||||
|
if (githubToken == null) { |
||||||
|
throw new IllegalArgumentException("githubToken is required. You can set it using -PgitHubAccessToken="); |
||||||
|
} |
||||||
|
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); |
||||||
|
clientBuilder.addInterceptor(new AuthorizationInterceptor(githubToken)); |
||||||
|
this.apolloClient = ApolloClient.builder() |
||||||
|
.serverUrl("https://api.github.com/graphql") |
||||||
|
.okHttpClient(clientBuilder.build()) |
||||||
|
.build(); |
||||||
|
} |
||||||
|
|
||||||
|
public Mono<FindCreateIssueResult> findCreateIssueInput(String owner, String name, String milestone) { |
||||||
|
String label = "\"type: dependency-upgrade\""; |
||||||
|
FindCreateIssueInputQuery findCreateIssueInputQuery = new FindCreateIssueInputQuery(owner, name, Input.optional(label), Input.optional(milestone)); |
||||||
|
return Mono.create( sink -> this.apolloClient.query(findCreateIssueInputQuery) |
||||||
|
.enqueue(new ApolloCall.Callback<FindCreateIssueInputQuery.Data>() { |
||||||
|
@Override |
||||||
|
public void onResponse(@NotNull Response<FindCreateIssueInputQuery.Data> response) { |
||||||
|
if (response.hasErrors()) { |
||||||
|
sink.error(new RuntimeException(response.getErrors().stream().map(e -> e.getMessage()).collect(Collectors.joining(" ")))); |
||||||
|
} else { |
||||||
|
FindCreateIssueInputQuery.Data data = response.getData(); |
||||||
|
FindCreateIssueInputQuery.Repository repository = data.repository(); |
||||||
|
List<String> labels = repository.labels().nodes().stream().map(FindCreateIssueInputQuery.Node::id).collect(Collectors.toList()); |
||||||
|
if (labels.isEmpty()) { |
||||||
|
sink.error(new IllegalArgumentException("Could not find label for " + label)); |
||||||
|
return; |
||||||
|
} |
||||||
|
Optional<String> firstMilestoneId = repository.milestones().nodes().stream().map(FindCreateIssueInputQuery.Node1::id).findFirst(); |
||||||
|
if (!firstMilestoneId.isPresent()) { |
||||||
|
sink.error(new IllegalArgumentException("Could not find OPEN milestone id for " + milestone)); |
||||||
|
return; |
||||||
|
} |
||||||
|
String milestoneId = firstMilestoneId.get(); |
||||||
|
String repositoryId = repository.id(); |
||||||
|
String assigneeId = data.viewer().id(); |
||||||
|
sink.success(new FindCreateIssueResult(repositoryId, labels, milestoneId, assigneeId)); |
||||||
|
} |
||||||
|
} |
||||||
|
@Override |
||||||
|
public void onFailure(@NotNull ApolloException e) { |
||||||
|
sink.error(e); |
||||||
|
} |
||||||
|
})); |
||||||
|
} |
||||||
|
|
||||||
|
public static class FindCreateIssueResult { |
||||||
|
private final String repositoryId; |
||||||
|
private final List<String> labelIds; |
||||||
|
private final String milestoneId; |
||||||
|
private final String assigneeId; |
||||||
|
|
||||||
|
public FindCreateIssueResult(String repositoryId, List<String> labelIds, String milestoneId, String assigneeId) { |
||||||
|
this.repositoryId = repositoryId; |
||||||
|
this.labelIds = labelIds; |
||||||
|
this.milestoneId = milestoneId; |
||||||
|
this.assigneeId = assigneeId; |
||||||
|
} |
||||||
|
|
||||||
|
public String getRepositoryId() { |
||||||
|
return repositoryId; |
||||||
|
} |
||||||
|
|
||||||
|
public List<String> getLabelIds() { |
||||||
|
return labelIds; |
||||||
|
} |
||||||
|
|
||||||
|
public String getMilestoneId() { |
||||||
|
return milestoneId; |
||||||
|
} |
||||||
|
|
||||||
|
public String getAssigneeId() { |
||||||
|
return assigneeId; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public Mono<RateLimitQuery.RateLimit> findRateLimit() { |
||||||
|
return Mono.create( sink -> this.apolloClient.query(new RateLimitQuery()) |
||||||
|
.enqueue(new ApolloCall.Callback<RateLimitQuery.Data>() { |
||||||
|
@Override |
||||||
|
public void onResponse(@NotNull Response<RateLimitQuery.Data> response) { |
||||||
|
if (response.hasErrors()) { |
||||||
|
sink.error(new RuntimeException(response.getErrors().stream().map(e -> e.getMessage()).collect(Collectors.joining(" ")))); |
||||||
|
} else { |
||||||
|
sink.success(response.getData().rateLimit()); |
||||||
|
} |
||||||
|
} |
||||||
|
@Override |
||||||
|
public void onFailure(@NotNull ApolloException e) { |
||||||
|
sink.error(e); |
||||||
|
} |
||||||
|
})); |
||||||
|
} |
||||||
|
|
||||||
|
public Mono<Integer> createIssue(String repositoryId, String title, List<String> labelIds, String milestoneId, String assigneeId) { |
||||||
|
CreateIssueInputMutation createIssue = new CreateIssueInputMutation.Builder() |
||||||
|
.repositoryId(repositoryId) |
||||||
|
.title(title) |
||||||
|
.labelIds(labelIds) |
||||||
|
.milestoneId(milestoneId) |
||||||
|
.assigneeId(assigneeId) |
||||||
|
.build(); |
||||||
|
return Mono.create( sink -> this.apolloClient.mutate(createIssue) |
||||||
|
.enqueue(new ApolloCall.Callback<CreateIssueInputMutation.Data>() { |
||||||
|
@Override |
||||||
|
public void onResponse(@NotNull Response<CreateIssueInputMutation.Data> response) { |
||||||
|
if (response.hasErrors()) { |
||||||
|
String message = response.getErrors().stream().map(e -> e.getMessage() + " " + e.getCustomAttributes() + " " + e.getLocations()).collect(Collectors.joining(" ")); |
||||||
|
if (message.contains("was submitted too quickly")) { |
||||||
|
sink.error(new SubmittedTooQuick(message)); |
||||||
|
} else { |
||||||
|
sink.error(new RuntimeException(message)); |
||||||
|
} |
||||||
|
} else { |
||||||
|
sink.success(response.getData().createIssue().issue().number()); |
||||||
|
} |
||||||
|
} |
||||||
|
@Override |
||||||
|
public void onFailure(@NotNull ApolloException e) { |
||||||
|
sink.error(e); |
||||||
|
} |
||||||
|
})) |
||||||
|
.retryWhen( |
||||||
|
RetrySpec.fixedDelay(3, Duration.ofMinutes(1)) |
||||||
|
.filter(SubmittedTooQuick.class::isInstance) |
||||||
|
.doBeforeRetry(r -> System.out.println("Pausing for 1 minute and then retrying due to receiving \"submitted too quickly\" error from GitHub API")) |
||||||
|
) |
||||||
|
.cast(Integer.class); |
||||||
|
} |
||||||
|
|
||||||
|
public static class SubmittedTooQuick extends RuntimeException { |
||||||
|
public SubmittedTooQuick(String message) { |
||||||
|
super(message); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static class AuthorizationInterceptor implements Interceptor { |
||||||
|
|
||||||
|
private final String token; |
||||||
|
|
||||||
|
public AuthorizationInterceptor(String token) { |
||||||
|
this.token = token; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public okhttp3.Response intercept(Chain chain) throws IOException { |
||||||
|
Request request = chain.request().newBuilder() |
||||||
|
.addHeader("Authorization", "Bearer " + this.token).build(); |
||||||
|
return chain.proceed(request); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,168 @@ |
|||||||
|
package org.springframework.security.convention.versions; |
||||||
|
|
||||||
|
import com.github.benmanes.gradle.versions.updates.resolutionstrategy.ComponentSelectionWithCurrent; |
||||||
|
import org.gradle.api.Action; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
import java.util.function.Supplier; |
||||||
|
import java.util.regex.Pattern; |
||||||
|
|
||||||
|
public class UpdateDependenciesExtension { |
||||||
|
private Supplier<List<File>> files; |
||||||
|
|
||||||
|
private UpdateMode updateMode = UpdateMode.COMMIT; |
||||||
|
|
||||||
|
private DependencyExcludes dependencyExcludes = new DependencyExcludes(); |
||||||
|
|
||||||
|
private GitHub gitHub = new GitHub(); |
||||||
|
|
||||||
|
public UpdateDependenciesExtension(Supplier<List<File>> files) { |
||||||
|
this.files = files; |
||||||
|
} |
||||||
|
|
||||||
|
public void setUpdateMode(UpdateMode updateMode) { |
||||||
|
this.updateMode = updateMode; |
||||||
|
} |
||||||
|
|
||||||
|
public UpdateMode getUpdateMode() { |
||||||
|
return updateMode; |
||||||
|
} |
||||||
|
|
||||||
|
GitHub getGitHub() { |
||||||
|
return this.gitHub; |
||||||
|
} |
||||||
|
|
||||||
|
DependencyExcludes getExcludes() { |
||||||
|
return dependencyExcludes; |
||||||
|
} |
||||||
|
|
||||||
|
Supplier<List<File>> getFiles() { |
||||||
|
return files; |
||||||
|
} |
||||||
|
|
||||||
|
public void setFiles(Supplier<List<File>> files) { |
||||||
|
this.files = files; |
||||||
|
} |
||||||
|
|
||||||
|
public void addFiles(Supplier<List<File>> files) { |
||||||
|
Supplier<List<File>> original = this.files; |
||||||
|
setFiles(() -> { |
||||||
|
List<File> result = new ArrayList<>(original.get()); |
||||||
|
result.addAll(files.get()); |
||||||
|
return result; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
public void dependencyExcludes(Action<DependencyExcludes> excludes) { |
||||||
|
excludes.execute(this.dependencyExcludes); |
||||||
|
} |
||||||
|
|
||||||
|
public void gitHub(Action<GitHub> gitHub) { |
||||||
|
gitHub.execute(this.gitHub); |
||||||
|
} |
||||||
|
|
||||||
|
public enum UpdateMode { |
||||||
|
COMMIT, |
||||||
|
GITHUB_ISSUE |
||||||
|
} |
||||||
|
|
||||||
|
public class GitHub { |
||||||
|
private String organization; |
||||||
|
|
||||||
|
private String repository; |
||||||
|
|
||||||
|
private String accessToken; |
||||||
|
|
||||||
|
private String milestone; |
||||||
|
|
||||||
|
public String getOrganization() { |
||||||
|
return organization; |
||||||
|
} |
||||||
|
|
||||||
|
public void setOrganization(String organization) { |
||||||
|
this.organization = organization; |
||||||
|
} |
||||||
|
|
||||||
|
public String getRepository() { |
||||||
|
return repository; |
||||||
|
} |
||||||
|
|
||||||
|
public void setRepository(String repository) { |
||||||
|
this.repository = repository; |
||||||
|
} |
||||||
|
|
||||||
|
public String getAccessToken() { |
||||||
|
return accessToken; |
||||||
|
} |
||||||
|
|
||||||
|
public void setAccessToken(String accessToken) { |
||||||
|
this.accessToken = accessToken; |
||||||
|
} |
||||||
|
|
||||||
|
public String getMilestone() { |
||||||
|
return milestone; |
||||||
|
} |
||||||
|
|
||||||
|
public void setMilestone(String milestone) { |
||||||
|
this.milestone = milestone; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Consider creating some Predicates instead since they are composible |
||||||
|
*/ |
||||||
|
public class DependencyExcludes { |
||||||
|
private List<Action<ComponentSelectionWithCurrent>> actions = new ArrayList<>(); |
||||||
|
|
||||||
|
List<Action<ComponentSelectionWithCurrent>> getActions() { |
||||||
|
return actions; |
||||||
|
} |
||||||
|
|
||||||
|
public DependencyExcludes alphaBetaVersions() { |
||||||
|
this.actions.add(excludeVersionWithRegex("(?i).*?(alpha|beta).*", "an alpha or beta version")); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public DependencyExcludes majorVersionBump() { |
||||||
|
this.actions.add((selection) -> { |
||||||
|
String currentVersion = selection.getCurrentVersion(); |
||||||
|
int separator = currentVersion.indexOf("."); |
||||||
|
String major = separator > 0 ? currentVersion.substring(0, separator) : currentVersion; |
||||||
|
String candidateVersion = selection.getCandidate().getVersion(); |
||||||
|
Pattern calVerPattern = Pattern.compile("\\d\\d\\d\\d.*"); |
||||||
|
boolean isCalVer = calVerPattern.matcher(candidateVersion).matches(); |
||||||
|
if (!isCalVer && !candidateVersion.startsWith(major)) { |
||||||
|
selection.reject("Cannot upgrade to new Major Version"); |
||||||
|
} |
||||||
|
}); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public DependencyExcludes releaseCandidatesVersions() { |
||||||
|
this.actions.add(excludeVersionWithRegex("(?i).*?rc\\d+.*", "a release candidate version")); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public DependencyExcludes milestoneVersions() { |
||||||
|
this.actions.add(excludeVersionWithRegex("(?i).*?m\\d+.*", "a milestone version")); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public DependencyExcludes snapshotVersions() { |
||||||
|
this.actions.add(excludeVersionWithRegex(".*?-SNAPSHOT.*", "a SNAPSHOT version")); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
private Action<ComponentSelectionWithCurrent> excludeVersionWithRegex(String regex, String reason) { |
||||||
|
Pattern pattern = Pattern.compile(regex); |
||||||
|
return (selection) -> { |
||||||
|
String candidateVersion = selection.getCandidate().getVersion(); |
||||||
|
if (pattern.matcher(candidateVersion).matches()) { |
||||||
|
selection.reject(candidateVersion + " is not allowed because it is " + reason); |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,254 @@ |
|||||||
|
package org.springframework.security.convention.versions; |
||||||
|
|
||||||
|
import com.github.benmanes.gradle.versions.reporter.result.DependencyOutdated; |
||||||
|
import com.github.benmanes.gradle.versions.reporter.result.Result; |
||||||
|
import com.github.benmanes.gradle.versions.reporter.result.VersionAvailable; |
||||||
|
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask; |
||||||
|
import com.github.benmanes.gradle.versions.updates.gradle.GradleUpdateResult; |
||||||
|
import com.github.benmanes.gradle.versions.updates.resolutionstrategy.ComponentSelectionRulesWithCurrent; |
||||||
|
import com.github.benmanes.gradle.versions.updates.resolutionstrategy.ComponentSelectionWithCurrent; |
||||||
|
import com.github.benmanes.gradle.versions.updates.resolutionstrategy.ResolutionStrategyWithCurrent; |
||||||
|
import groovy.lang.Closure; |
||||||
|
import org.gradle.api.Action; |
||||||
|
import org.gradle.api.Plugin; |
||||||
|
import org.gradle.api.Project; |
||||||
|
import org.gradle.api.artifacts.component.ModuleComponentIdentifier; |
||||||
|
import reactor.core.publisher.Mono; |
||||||
|
|
||||||
|
import java.io.*; |
||||||
|
import java.nio.file.Files; |
||||||
|
import java.time.Duration; |
||||||
|
import java.util.*; |
||||||
|
import java.util.function.Function; |
||||||
|
import java.util.function.Supplier; |
||||||
|
import java.util.regex.Matcher; |
||||||
|
import java.util.regex.Pattern; |
||||||
|
|
||||||
|
public class UpdateDependenciesPlugin implements Plugin<Project> { |
||||||
|
private GitHubApi gitHubApi; |
||||||
|
|
||||||
|
@Override |
||||||
|
public void apply(Project project) { |
||||||
|
UpdateDependenciesExtension updateDependenciesSettings = project.getExtensions().create("updateDependenciesSettings", UpdateDependenciesExtension.class, defaultFiles(project)); |
||||||
|
if (project.hasProperty("updateMode")) { |
||||||
|
String updateMode = String.valueOf(project.findProperty("updateMode")); |
||||||
|
updateDependenciesSettings.setUpdateMode(UpdateDependenciesExtension.UpdateMode.valueOf(updateMode)); |
||||||
|
} |
||||||
|
if (project.hasProperty("nextVersion")) { |
||||||
|
String nextVersion = String.valueOf(project.findProperty("nextVersion")); |
||||||
|
updateDependenciesSettings.getGitHub().setMilestone(nextVersion); |
||||||
|
} |
||||||
|
if (project.hasProperty("gitHubAccessToken")) { |
||||||
|
String gitHubAccessToken = String.valueOf(project.findProperty("gitHubAccessToken")); |
||||||
|
updateDependenciesSettings.getGitHub().setAccessToken(gitHubAccessToken); |
||||||
|
} |
||||||
|
project.getTasks().register("updateDependencies", DependencyUpdatesTask.class, new Action<DependencyUpdatesTask>() { |
||||||
|
@Override |
||||||
|
public void execute(DependencyUpdatesTask updateDependencies) { |
||||||
|
updateDependencies.setDescription("Update the dependencies"); |
||||||
|
updateDependencies.setCheckConstraints(true); |
||||||
|
updateDependencies.setOutputFormatter(new Closure<Void>(null) { |
||||||
|
@Override |
||||||
|
public Void call(Object argument) { |
||||||
|
Result result = (Result) argument; |
||||||
|
if (gitHubApi == null && updateDependenciesSettings.getUpdateMode() != UpdateDependenciesExtension.UpdateMode.COMMIT) { |
||||||
|
gitHubApi = new GitHubApi(updateDependenciesSettings.getGitHub().getAccessToken()); |
||||||
|
} |
||||||
|
updateDependencies(result, project, updateDependenciesSettings); |
||||||
|
updateGradleVersion(result, project, updateDependenciesSettings); |
||||||
|
return null; |
||||||
|
} |
||||||
|
}); |
||||||
|
updateDependencies.resolutionStrategy(new Action<ResolutionStrategyWithCurrent>() { |
||||||
|
@Override |
||||||
|
public void execute(ResolutionStrategyWithCurrent resolution) { |
||||||
|
resolution.componentSelection(new Action<ComponentSelectionRulesWithCurrent>() { |
||||||
|
@Override |
||||||
|
public void execute(ComponentSelectionRulesWithCurrent components) { |
||||||
|
updateDependenciesSettings.getExcludes().getActions().forEach((action) -> { |
||||||
|
components.all(action); |
||||||
|
}); |
||||||
|
components.all((selection) -> { |
||||||
|
ModuleComponentIdentifier candidate = selection.getCandidate(); |
||||||
|
if (candidate.getGroup().startsWith("org.apache.directory.") && !candidate.getVersion().equals(selection.getCurrentVersion())) { |
||||||
|
selection.reject("org.apache.directory.* has breaking changes in newer versions"); |
||||||
|
} |
||||||
|
}); |
||||||
|
String jaxbBetaRegex = ".*?b\\d+.*"; |
||||||
|
components.withModule("javax.xml.bind:jaxb-api", excludeWithRegex(jaxbBetaRegex, "Reject jaxb-api beta versions")); |
||||||
|
components.withModule("com.sun.xml.bind:jaxb-impl", excludeWithRegex(jaxbBetaRegex, "Reject jaxb-api beta versions")); |
||||||
|
components.withModule("commons-collections:commons-collections", excludeWithRegex("^\\d{3,}.*", "Reject commons-collections date based releases")); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private void updateDependencies(Result result, Project project, UpdateDependenciesExtension updateDependenciesSettings) { |
||||||
|
SortedSet<DependencyOutdated> dependencies = result.getOutdated().getDependencies(); |
||||||
|
if (dependencies.isEmpty()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
Map<String, List<DependencyOutdated>> groups = new LinkedHashMap<>(); |
||||||
|
dependencies.forEach(outdated -> { |
||||||
|
groups.computeIfAbsent(outdated.getGroup(), (key) -> new ArrayList<>()).add(outdated); |
||||||
|
}); |
||||||
|
File gradlePropertiesFile = project.getRootProject().file(Project.GRADLE_PROPERTIES); |
||||||
|
Mono<GitHubApi.FindCreateIssueResult> createIssueResult = createIssueResultMono(updateDependenciesSettings); |
||||||
|
List<File> filesWithDependencies = updateDependenciesSettings.getFiles().get(); |
||||||
|
groups.forEach((group, outdated) -> { |
||||||
|
outdated.forEach((dependency) -> { |
||||||
|
String ga = dependency.getGroup() + ":" + dependency.getName() + ":"; |
||||||
|
String originalDependency = ga + dependency.getVersion(); |
||||||
|
String replacementDependency = ga + updatedVersion(dependency); |
||||||
|
System.out.println("Update " + originalDependency + " to " + replacementDependency); |
||||||
|
filesWithDependencies.forEach((fileWithDependency) -> { |
||||||
|
updateDependencyInlineVersion(fileWithDependency, dependency); |
||||||
|
updateDependencyWithVersionVariable(fileWithDependency, gradlePropertiesFile, dependency); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
// commit
|
||||||
|
DependencyOutdated firstDependency = outdated.get(0); |
||||||
|
String updatedVersion = updatedVersion(firstDependency); |
||||||
|
String title = outdated.size() == 1 ? "Update " + firstDependency.getName() + " to " + updatedVersion : "Update " + firstDependency.getGroup() + " to " + updatedVersion; |
||||||
|
afterGroup(updateDependenciesSettings, project.getRootDir(), title, createIssueResult); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private void afterGroup(UpdateDependenciesExtension updateDependenciesExtension, File rootDir, String title, Mono<GitHubApi.FindCreateIssueResult> createIssueResultMono) { |
||||||
|
|
||||||
|
String commitMessage = title; |
||||||
|
if (updateDependenciesExtension.getUpdateMode() == UpdateDependenciesExtension.UpdateMode.GITHUB_ISSUE) { |
||||||
|
GitHubApi.FindCreateIssueResult createIssueResult = createIssueResultMono.block(); |
||||||
|
RateLimitQuery.RateLimit rateLimit = gitHubApi.findRateLimit().block(); |
||||||
|
rateLimit = gitHubApi.findRateLimit().block(); |
||||||
|
System.out.println("remaining " + rateLimit.remaining() + " reset at " + rateLimit.resetAt()); |
||||||
|
Integer issueNumber = gitHubApi.createIssue(createIssueResult.getRepositoryId(), title, createIssueResult.getLabelIds(), createIssueResult.getMilestoneId(), createIssueResult.getAssigneeId()).delayElement(Duration.ofSeconds(1)).block(); |
||||||
|
commitMessage += "\n\nCloses gh-" + issueNumber; |
||||||
|
} |
||||||
|
runCommand(rootDir, "git", "commit", "-am", commitMessage); |
||||||
|
} |
||||||
|
|
||||||
|
private Mono<GitHubApi.FindCreateIssueResult> createIssueResultMono(UpdateDependenciesExtension updateDependenciesExtension) { |
||||||
|
return Mono.defer(() -> { |
||||||
|
UpdateDependenciesExtension.GitHub gitHub = updateDependenciesExtension.getGitHub(); |
||||||
|
return gitHubApi.findCreateIssueInput(gitHub.getOrganization(), gitHub.getRepository(), gitHub.getMilestone()).cache(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private void updateGradleVersion(Result result, Project project, UpdateDependenciesExtension updateDependenciesSettings) { |
||||||
|
GradleUpdateResult current = result.getGradle().getCurrent(); |
||||||
|
GradleUpdateResult running = result.getGradle().getRunning(); |
||||||
|
if (current.compareTo(running) > 0) { |
||||||
|
String title = "Update Gradle to " + current.getVersion(); |
||||||
|
System.out.println(title); |
||||||
|
runCommand(project.getRootDir(), "./gradlew", "wrapper", "--gradle-version", current.getVersion(), "--no-daemon"); |
||||||
|
afterGroup(updateDependenciesSettings, project.getRootDir(), title, createIssueResultMono(updateDependenciesSettings)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static Supplier<List<File>> defaultFiles(Project project) { |
||||||
|
return () -> { |
||||||
|
List<File> result = new ArrayList<>(); |
||||||
|
result.add(project.getBuildFile()); |
||||||
|
project.getChildProjects().values().forEach((childProject) -> |
||||||
|
result.add(childProject.getBuildFile()) |
||||||
|
); |
||||||
|
result.add(project.getRootProject().file("buildSrc/build.gradle")); |
||||||
|
return result; |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
static void runCommand(File dir, String... args) { |
||||||
|
try { |
||||||
|
Process process = new ProcessBuilder() |
||||||
|
.directory(dir) |
||||||
|
.command(args) |
||||||
|
.start(); |
||||||
|
writeLinesTo(process.getInputStream(), System.out); |
||||||
|
writeLinesTo(process.getErrorStream(), System.out); |
||||||
|
if (process.waitFor() != 0) { |
||||||
|
new RuntimeException("Failed to run " + Arrays.toString(args)); |
||||||
|
} |
||||||
|
} catch (IOException | InterruptedException e) { |
||||||
|
throw new RuntimeException("Failed to run " + Arrays.toString(args), e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static void writeLinesTo(InputStream input, PrintStream out) { |
||||||
|
Scanner scanner = new Scanner(input); |
||||||
|
while(scanner.hasNextLine()) { |
||||||
|
out.println(scanner.nextLine()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
static Action<ComponentSelectionWithCurrent> excludeWithRegex(String regex, String reason) { |
||||||
|
Pattern pattern = Pattern.compile(regex); |
||||||
|
return (selection) -> { |
||||||
|
String candidateVersion = selection.getCandidate().getVersion(); |
||||||
|
if (pattern.matcher(candidateVersion).matches()) { |
||||||
|
selection.reject(candidateVersion + " is not allowed because it is " + reason); |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
static void updateDependencyInlineVersion(File buildFile, DependencyOutdated dependency){ |
||||||
|
String ga = dependency.getGroup() + ":" + dependency.getName() + ":"; |
||||||
|
String originalDependency = ga + dependency.getVersion(); |
||||||
|
String replacementDependency = ga + updatedVersion(dependency); |
||||||
|
replaceFileText(buildFile, buildFileText -> buildFileText.replace(originalDependency, replacementDependency)); |
||||||
|
} |
||||||
|
|
||||||
|
static void replaceFileText(File file, Function<String, String> replaceText) { |
||||||
|
String buildFileText = readString(file); |
||||||
|
String updatedBuildFileText = replaceText.apply(buildFileText); |
||||||
|
writeString(file, updatedBuildFileText); |
||||||
|
} |
||||||
|
|
||||||
|
private static String readString(File file) { |
||||||
|
try { |
||||||
|
byte[] bytes = Files.readAllBytes(file.toPath()); |
||||||
|
return new String(bytes); |
||||||
|
} catch (IOException e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static void writeString(File file, String text) { |
||||||
|
try { |
||||||
|
Files.write(file.toPath(), text.getBytes()); |
||||||
|
} catch (IOException e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static void updateDependencyWithVersionVariable(File scanFile, File gradlePropertiesFile, DependencyOutdated dependency) { |
||||||
|
if (!gradlePropertiesFile.exists()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
replaceFileText(gradlePropertiesFile, (gradlePropertiesText) -> { |
||||||
|
String ga = dependency.getGroup() + ":" + dependency.getName() + ":"; |
||||||
|
Pattern pattern = Pattern.compile("\"" + ga + "\\$\\{?([^'\"]+?)\\}?\""); |
||||||
|
String buildFileText = readString(scanFile); |
||||||
|
Matcher matcher = pattern.matcher(buildFileText); |
||||||
|
while (matcher.find()) { |
||||||
|
String versionVariable = matcher.group(1); |
||||||
|
gradlePropertiesText = gradlePropertiesText.replace(versionVariable + "=" + dependency.getVersion(), versionVariable + "=" + updatedVersion(dependency)); |
||||||
|
} |
||||||
|
return gradlePropertiesText; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private static String updatedVersion(DependencyOutdated dependency) { |
||||||
|
VersionAvailable available = dependency.getAvailable(); |
||||||
|
String release = available.getRelease(); |
||||||
|
if (release != null) { |
||||||
|
return release; |
||||||
|
} |
||||||
|
return available.getMilestone(); |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue