Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
1import org.json.JSONArray; 2import org.json.JSONObject; 3 4import java.io.IOException; 5import java.io.PrintWriter; 6import java.net.URI; 7import java.net.http.*; 8import java.nio.file.Files; 9import java.nio.file.Path; 10import java.util.*; 11import java.util.regex.Pattern; 12import java.util.stream.Collectors; 13import java.util.stream.Stream; 14import java.util.stream.StreamSupport; 15 16public class JavaUpdater { 17 18 record GitHubResult(Optional<String> latestVersion, Optional<String> next) { 19 } 20 21 record JsonInfo(String repo, String version, String hash) { 22 public JsonInfo(JSONObject json) { 23 this(json.getString("repo"), json.getString("version"), json.getString("hash")); 24 } 25 26 public String toJsonString(String featureVersion) { 27 return """ 28 \s "%s": { 29 \s "version": "%s", 30 \s "repo": "%s", 31 \s "hash": "%s" 32 \s }\ 33 """.formatted(featureVersion, version, repo, hash); 34 } 35 } 36 37 // Parses the GitHub Link header 38 public static Optional<String> getNextLink(HttpHeaders headers) { 39 var linkHeader = headers.map().get("Link"); 40 if (linkHeader == null || linkHeader.isEmpty()) return null; 41 42 var links = linkHeader.getFirst(); 43 var linksRegex = Pattern.compile("<(.+)>;\\s*rel=\"next\""); 44 return Pattern.compile(",") 45 .splitAsStream(links) 46 .map(x -> linksRegex.matcher(x).results() 47 .map(g -> g.group(1)) 48 .findFirst() 49 ) 50 .filter(Optional::isPresent) 51 .map(Optional::orElseThrow) 52 .findFirst(); 53 } 54 55 // HTTP request helper, sets GITHUB_TOKEN if present 56 private static HttpRequest NewGithubRequest(String url) { 57 var token = System.getenv().get("GITHUB_TOKEN"); 58 var builder = HttpRequest.newBuilder() 59 .uri(URI.create(url)); 60 if (token != null) 61 builder.setHeader("Authorization", "Bearer " + token); 62 return builder.build(); 63 } 64 65 private static GitHubResult getLatestTag(String url) { 66 var request = NewGithubRequest(url); 67 68 var response = 69 HttpClient.newHttpClient().sendAsync(request, HttpResponse.BodyHandlers.ofString()) 70 .join(); 71 72 var json = new JSONArray(response.body()); 73 74 Optional<String> version = StreamSupport.stream(json.spliterator(), false) 75 .map(JSONObject.class::cast) 76 .map(x -> x.getString("name").replaceFirst("jdk-", "")) 77 .filter(x -> x.contains("-ga")) 78 .max(Comparator.comparing(Runtime.Version::parse)); 79 80 return new GitHubResult(version, getNextLink(response.headers())); 81 } 82 83 public String findNewerVersion() { 84 var url = Optional.of("https://api.github.com/repos/openjdk/" + getRepo() + "/tags?per_page=100"); 85 String version = getCurrentVersion(); 86 do { 87 GitHubResult response = getLatestTag(url.orElseThrow()); 88 if (response.latestVersion.isPresent() && response.latestVersion.orElseThrow().equals(version)) { 89 return null; 90 } 91 92 String latestVersion = Stream.of(version, response.latestVersion.orElse(version)) 93 .max(Comparator.comparing(Runtime.Version::parse)).orElseThrow(); 94 95 if (latestVersion != version) 96 return latestVersion; 97 98 url = response.next; 99 } while (url.isPresent()); 100 return null; 101 } 102 103 104 private static String prettyPrint(JSONObject json) { 105 106 Iterable<String> iterable = () -> json.keys(); 107 108 return StreamSupport 109 .stream(iterable.spliterator(), false) 110 .sorted(Comparator.reverseOrder()) 111 .map(majorVersion -> (new JsonInfo(json.getJSONObject(majorVersion))).toJsonString(majorVersion)) 112 .collect( 113 Collectors.joining(",\n", "{\n", "\n}") 114 ); 115 } 116 117 public void updateJsonInfo(String newVersion) { 118 try { 119 JSONObject json = getJsonInfo(); 120 var info = json.getJSONObject(featureNumber); 121 info.put("version", newVersion); 122 info.put("hash", nixHash(newVersion)); 123 124 try (PrintWriter out = new PrintWriter(infoJsonPath)) { 125 out.println(prettyPrint(json)); 126 } 127 128 } catch (Exception e) { 129 throw new RuntimeException(e); 130 } 131 } 132 133 private String nixHash(String version) { 134 try { 135 var process = new ProcessBuilder("nix", "flake", "prefetch", 136 "--extra-experimental-features", "'nix-command flakes'", 137 "--json", "github:openjdk/" + getRepo() + "/jdk-" + version).start(); 138 139 var json = new JSONObject(new String(process.getInputStream().readAllBytes())); 140 process.waitFor(); 141 return json.getString("hash"); 142 } catch (Exception e) { 143 throw new RuntimeException(e); 144 } 145 } 146 147 private final String featureNumber; 148 private final String infoJsonPath; 149 private final JSONObject jsonInfo; 150 151 public String getCurrentVersion() { 152 return this.jsonInfo.getJSONObject(this.featureNumber).getString("version"); 153 } 154 155 public String getRepo() { 156 return this.jsonInfo.getJSONObject(this.featureNumber).getString("repo"); 157 } 158 159 public JSONObject getJsonInfo() { 160 try { 161 String infoStr = Files.readString(Path.of(this.infoJsonPath)); 162 return new JSONObject(infoStr); 163 } catch (IOException e) { 164 throw new RuntimeException(e); 165 } 166 } 167 168 public JavaUpdater(String featureNumber, String infoJsonPath) { 169 this.featureNumber = featureNumber; 170 this.infoJsonPath = infoJsonPath; 171 this.jsonInfo = getJsonInfo(); 172 } 173 174 public static void main(String[] args) { 175 var updater = new JavaUpdater(args[0], args[1]); 176 String newerVersion = updater.findNewerVersion(); 177 if (newerVersion != null) { 178 updater.updateJsonInfo(newerVersion); 179 } 180 } 181}