1 /* 2 * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package bldr; 27 28 import com.sun.source.util.JavacTask; 29 import org.w3c.dom.Attr; 30 import org.w3c.dom.Document; 31 import org.w3c.dom.Element; 32 import org.w3c.dom.Node; 33 import org.w3c.dom.NodeList; 34 import org.xml.sax.SAXException; 35 36 import javax.tools.Diagnostic; 37 import javax.tools.DiagnosticListener; 38 import javax.tools.JavaCompiler; 39 import javax.tools.JavaFileObject; 40 import javax.tools.SimpleJavaFileObject; 41 import javax.tools.ToolProvider; 42 import javax.xml.parsers.DocumentBuilderFactory; 43 import javax.xml.parsers.ParserConfigurationException; 44 import javax.xml.transform.OutputKeys; 45 import javax.xml.transform.TransformerFactory; 46 import javax.xml.transform.dom.DOMSource; 47 import javax.xml.transform.stream.StreamResult; 48 import javax.xml.xpath.XPath; 49 import javax.xml.xpath.XPathConstants; 50 import javax.xml.xpath.XPathExpression; 51 import javax.xml.xpath.XPathExpressionException; 52 import javax.xml.xpath.XPathFactory; 53 import java.io.BufferedReader; 54 import java.io.File; 55 import java.io.IOException; 56 import java.io.InputStream; 57 import java.io.InputStreamReader; 58 import java.io.PrintWriter; 59 import java.io.StringWriter; 60 import java.net.MalformedURLException; 61 import java.net.URI; 62 import java.net.URISyntaxException; 63 import java.net.URL; 64 import java.net.URLEncoder; 65 import java.nio.charset.StandardCharsets; 66 import java.nio.file.Files; 67 import java.nio.file.Path; 68 import java.nio.file.StandardOpenOption; 69 import java.util.ArrayList; 70 import java.util.Arrays; 71 import java.util.Comparator; 72 import java.util.HashMap; 73 import java.util.HashSet; 74 import java.util.List; 75 import java.util.Map; 76 import java.util.Objects; 77 import java.util.Optional; 78 import java.util.Set; 79 import java.util.function.BiConsumer; 80 import java.util.function.Consumer; 81 import java.util.function.Function; 82 import java.util.function.Predicate; 83 import java.util.jar.JarEntry; 84 import java.util.jar.JarOutputStream; 85 import java.util.regex.Matcher; 86 import java.util.regex.Pattern; 87 import java.util.stream.Stream; 88 import java.util.zip.ZipFile; 89 90 import static java.io.IO.print; 91 import static java.io.IO.println; 92 93 public class Bldr { 94 public sealed interface PathHolder permits ClassPathEntry, DirPathHolder, FilePathHolder, SourcePathEntry { 95 default Path path(String subdir) { 96 return path().resolve(subdir); 97 } 98 99 default String fileName() { 100 return path().getFileName().toString(); 101 } 102 103 default Matcher pathMatcher(Pattern pattern) { 104 return pattern.matcher(path().toString()); 105 } 106 107 default boolean matches(Pattern pattern) { 108 return pathMatcher(pattern).matches(); 109 } 110 111 default boolean matches(String pattern) { 112 return pathMatcher(Pattern.compile(pattern)).matches(); 113 } 114 default boolean failsToMatch(String pattern) { 115 return !pathMatcher(Pattern.compile(pattern)).matches(); 116 } 117 118 boolean exists(); 119 120 Path path(); 121 } 122 123 public sealed interface DirPathHolder<T extends DirPathHolder<T>> extends PathHolder 124 permits BuildDirHolder, DirEntry, SourceDir { 125 126 default Stream<Path> find() { 127 try { 128 return Files.walk(path()); 129 } catch (IOException e) { 130 throw new RuntimeException(e); 131 } 132 } 133 134 default Stream<Path> find(Predicate<Path> predicate) { 135 return find().filter(predicate); 136 } 137 138 default Stream<Path> findFiles() { 139 return find(Files::isRegularFile); 140 } 141 142 default Stream<Path> findDirs() { 143 return find(Files::isDirectory); 144 } 145 146 default Stream<Path> findFiles(Predicate<Path> predicate) { 147 return findFiles().filter(predicate); 148 } 149 150 default Stream<Path> findFilesBySuffix(String suffix) { 151 return findFiles(p -> p.toString().endsWith(suffix)); 152 } 153 154 default Stream<SearchableTextFile> findTextFiles(String... suffixes) { 155 return findFiles() 156 .map(SearchableTextFile::new) 157 .filter(searchableTextFile -> searchableTextFile.hasSuffix(suffixes)); 158 } 159 160 default Stream<Path> findDirs(Predicate<Path> predicate) { 161 return find(Files::isDirectory).filter(predicate); 162 } 163 164 default boolean exists() { 165 return Files.isDirectory(path()); 166 } 167 168 default BuildDir buildDir(String name) { 169 return BuildDir.of(path().resolve(name)); 170 } 171 172 default SourceDir sourceDir(String s) { 173 return SourceDir.of(path().resolve(s)); 174 } 175 176 default CppSourceFile cppSourceFile(String s) { 177 return CppSourceFile.of(path().resolve(s)); 178 } 179 180 default XMLFile xmlFile(String s) { 181 return XMLFile.of(path().resolve(s)); 182 } 183 184 default TestNGSuiteFile testNGSuiteFile(String s) { 185 return TestNGSuiteFile.of(path().resolve(s)); 186 } 187 } 188 189 public sealed interface FilePathHolder extends PathHolder { 190 default boolean exists() { 191 return Files.isRegularFile(path()); 192 } 193 } 194 195 public sealed interface Executable extends FilePathHolder { 196 default boolean exists() { 197 return Files.exists(path()) && Files.isRegularFile(path()) && Files.isExecutable(path()); 198 } 199 } 200 201 202 public interface ClassPathEntryProvider { 203 List<ClassPathEntry> classPathEntries(); 204 } 205 206 public sealed interface ClassPathEntry extends PathHolder, ClassPathEntryProvider { 207 } 208 209 interface PathHolderList<T extends PathHolder> { 210 List<T> entries(); 211 212 default String charSeparated() { 213 StringBuilder sb = new StringBuilder(); 214 entries().forEach(pathHolder -> { 215 if (!sb.isEmpty()) { 216 sb.append(File.pathSeparatorChar); 217 } 218 sb.append(pathHolder.path()); 219 }); 220 return sb.toString(); 221 } 222 } 223 224 public record ClassPath(List<ClassPathEntry> classPathEntries) 225 implements PathHolderList<ClassPathEntry>, ClassPathEntryProvider { 226 public static ClassPath of() { 227 return new ClassPath(new ArrayList<>()); 228 } 229 230 public static ClassPath ofOrUse(ClassPath classPath) { 231 return classPath == null ? of() : classPath; 232 } 233 234 public ClassPath add(List<ClassPathEntryProvider> classPathEntryProviders) { 235 classPathEntryProviders.forEach( 236 classPathEntryProvider -> 237 this.classPathEntries.addAll(classPathEntryProvider.classPathEntries())); 238 return this; 239 } 240 241 public ClassPath add(ClassPathEntryProvider... classPathEntryProviders) { 242 return add(List.of(classPathEntryProviders)); 243 } 244 245 @Override 246 public String toString() { 247 return charSeparated(); 248 } 249 250 @Override 251 public List<ClassPathEntry> classPathEntries() { 252 return this.classPathEntries; 253 } 254 255 @Override 256 public List<ClassPathEntry> entries() { 257 return this.classPathEntries; 258 } 259 } 260 261 public record SourcePath(List<SourceDir> entries) 262 implements PathHolderList<SourceDir> { 263 public static SourcePath of() { 264 return new SourcePath(new ArrayList<>()); 265 } 266 267 public static SourcePath ofOrUse(SourcePath sourcePath) { 268 return sourcePath == null ? of() : sourcePath; 269 } 270 271 public SourcePath add(List<SourceDir> sourcePathEntries) { 272 entries.addAll(sourcePathEntries); 273 return this; 274 } 275 276 public SourcePath add(SourceDir... sourcePathEntries) { 277 add(Arrays.asList(sourcePathEntries)); 278 return this; 279 } 280 281 public SourcePath add(SourcePath... sourcePaths) { 282 List.of(sourcePaths).forEach(sourcePath -> add(sourcePath.entries)); 283 return this; 284 } 285 286 @Override 287 public String toString() { 288 return charSeparated(); 289 } 290 291 public Stream<Path> javaFiles() { 292 List<Path> javaFiles = new ArrayList<>(); 293 entries.forEach(entry -> entry.javaFiles().forEach(javaFiles::add)); 294 return javaFiles.stream(); 295 } 296 } 297 298 public record DirPath(List<DirPathHolder<?>> entries) 299 implements PathHolderList<DirPathHolder<?>> { 300 public static DirPath of() { 301 return new DirPath(new ArrayList<>()); 302 } 303 304 public static DirPath ofOrUse(DirPath dirPath) { 305 return dirPath == null ? of() : dirPath; 306 } 307 308 public DirPath add(List<DirPathHolder<?>> dirPathHolders) { 309 entries.addAll(dirPathHolders); 310 return this; 311 } 312 313 public DirPath add(DirPathHolder<?>... dirPathHolders) { 314 add(Arrays.asList(dirPathHolders)); 315 return this; 316 } 317 318 public DirPath add(DirPath... dirPaths) { 319 List.of(dirPaths).forEach(dirPath -> add(dirPath.entries)); 320 return this; 321 } 322 323 @Override 324 public String toString() { 325 return charSeparated(); 326 } 327 } 328 329 public record CMakeBuildDir(Path path) implements BuildDirHolder<CMakeBuildDir> { 330 public static CMakeBuildDir of(Path path) { 331 return new CMakeBuildDir(path); 332 } 333 334 @Override 335 public CMakeBuildDir create() { 336 return CMakeBuildDir.of(mkdir(path())); 337 } 338 339 @Override 340 public CMakeBuildDir remove() { 341 return CMakeBuildDir.of(rmdir(path())); 342 } 343 } 344 345 public sealed interface BuildDirHolder<T extends BuildDirHolder<T>> extends DirPathHolder<T> { 346 T create(); 347 348 T remove(); 349 350 default T clean() { 351 remove(); 352 return create(); 353 } 354 355 default Path mkdir(Path path) { 356 try { 357 return Files.createDirectories(path); 358 } catch (IOException e) { 359 throw new RuntimeException(e); 360 } 361 } 362 363 default Path rmdir(Path path) { 364 try { 365 if (Files.exists(path)) { 366 Files.walk(path) 367 .sorted(Comparator.reverseOrder()) 368 .map(Path::toFile) 369 .forEach(File::delete); 370 } 371 } catch (IOException ioe) { 372 System.out.println(ioe); 373 } 374 return path; 375 } 376 } 377 378 379 public record ClassDir(Path path) implements ClassPathEntry, BuildDirHolder<ClassDir> { 380 public static ClassDir of(Path path) { 381 return new ClassDir(path); 382 } 383 384 public static ClassDir temp() { 385 try { 386 return of(Files.createTempDirectory("javacClasses")); 387 } catch (IOException e) { 388 throw new RuntimeException(e); 389 } 390 } 391 392 @Override 393 public ClassDir create() { 394 return ClassDir.of(mkdir(path())); 395 } 396 397 @Override 398 public ClassDir remove() { 399 return ClassDir.of(rmdir(path())); 400 } 401 402 @Override 403 public List<ClassPathEntry> classPathEntries() { 404 return List.of(this); 405 } 406 } 407 408 public record RepoDir(Path path) implements BuildDirHolder<RepoDir> { 409 public static RepoDir of(Path path) { 410 return new RepoDir(path); 411 } 412 413 @Override 414 public RepoDir create() { 415 return RepoDir.of(mkdir(path())); 416 } 417 418 @Override 419 public RepoDir remove() { 420 return RepoDir.of(rmdir(path())); 421 } 422 423 public JarFile jarFile(String name) { 424 return JarFile.of(path().resolve(name)); 425 } 426 427 public ClassPathEntryProvider classPathEntries(String... specs) { 428 var repo = new MavenStyleRepository(this); 429 return repo.classPathEntries(specs); 430 } 431 } 432 433 public record DirEntry(Path path) implements DirPathHolder<DirEntry> { 434 public static DirEntry of(Path path) { 435 return new DirEntry(path); 436 } 437 438 public static DirEntry of(String string) { 439 return of(Path.of(string)); 440 } 441 442 public static DirEntry ofExisting(String string) { 443 return of(assertExists(Path.of(string))); 444 } 445 446 public static DirEntry current() { 447 return of(Path.of(System.getProperty("user.dir"))); 448 } 449 450 public DirEntry parent() { 451 return of(path().getParent()); 452 } 453 454 public DirEntry dir(String subdir) { 455 return DirEntry.of(path(subdir)); 456 } 457 458 public FileEntry file(String fileName) { 459 return FileEntry.of(path(fileName)); 460 } 461 462 public DirEntry existingDir(String subdir) { 463 return assertExists(DirEntry.of(path(subdir))); 464 } 465 466 public Stream<DirEntry> subDirs() { 467 return Stream.of(Objects.requireNonNull(path().toFile().listFiles(File::isDirectory))) 468 .map(d -> DirEntry.of(d.getPath())); 469 } 470 471 // public Stream<DirEntry> subDirs(Predicate<DirEntry> predicate) { 472 // return subDirs().filter(predicate); 473 // } 474 475 public XMLFile pom( 476 String comment, Consumer<XMLNode.PomXmlBuilder> pomXmlBuilderConsumer) { 477 XMLFile xmlFile = xmlFile("pom.xml"); 478 XMLNode.createPom(comment, pomXmlBuilderConsumer).write(xmlFile); 479 return xmlFile; 480 } 481 482 public BuildDir existingBuildDir(String subdir) { 483 return assertExists(BuildDir.of(path(subdir))); 484 } 485 } 486 487 public interface SourcePathEntryProvider { 488 List<SourcePathEntry> sourcePathEntries(); 489 } 490 491 public sealed interface SourcePathEntry extends PathHolder, SourcePathEntryProvider { 492 } 493 494 public record SourceDir(Path path) implements SourcePathEntry, DirPathHolder<SourceDir> { 495 public static SourceDir of(Path path) { 496 return new SourceDir(path); 497 } 498 499 public Stream<Path> javaFiles() { 500 return findFilesBySuffix(".java"); 501 } 502 503 @Override 504 public List<SourcePathEntry> sourcePathEntries() { 505 return List.of(this); 506 } 507 } 508 509 public record RootDirAndSubPath(DirPathHolder<?> root, Path path) { 510 Path relativize() { 511 return root().path().relativize(path()); 512 } 513 } 514 515 public record BuildDir(Path path) implements ClassPathEntry, BuildDirHolder<BuildDir> { 516 public static BuildDir of(Path path) { 517 return new BuildDir(path); 518 } 519 520 public JarFile jarFile(String name) { 521 return JarFile.of(path().resolve(name)); 522 } 523 524 public ClassPathEntryProvider jarFiles(String... names) { 525 var classPath = ClassPath.of(); 526 Stream.of(names).forEach(name -> classPath.add(JarFile.of(path().resolve(name)))); 527 return classPath; 528 } 529 530 531 public CMakeBuildDir cMakeBuildDir(String name) { 532 return CMakeBuildDir.of(path().resolve(name)); 533 } 534 535 public ClassDir classDir(String name) { 536 return ClassDir.of(path().resolve(name)); 537 } 538 539 public RepoDir repoDir(String name) { 540 return RepoDir.of(path().resolve(name)); 541 } 542 543 @Override 544 public BuildDir create() { 545 return BuildDir.of(mkdir(path())); 546 } 547 548 549 @Override 550 public BuildDir remove() { 551 return BuildDir.of(rmdir(path())); 552 } 553 554 public BuildDir dir(String subdir) { 555 return BuildDir.of(path(subdir)); 556 } 557 558 public ObjectFile objectFile(String name) { 559 return ObjectFile.of(path().resolve(name)); 560 } 561 562 public ExecutableFile executableFile(String name) { 563 return ExecutableFile.of(path().resolve(name)); 564 } 565 566 public SharedLibraryFile sharedLibraryFile(String name) { 567 return SharedLibraryFile.of(path().resolve(name)); 568 } 569 570 @Override 571 public List<ClassPathEntry> classPathEntries() { 572 return List.of(this); 573 } 574 575 public SearchableTextFile textFile(String file, List<String> strings) { 576 SearchableTextFile textFile = SearchableTextFile.of(path().resolve(file)); 577 try { 578 PrintWriter pw = new PrintWriter(Files.newOutputStream(textFile.path(), StandardOpenOption.CREATE)); 579 strings.forEach(pw::print); 580 pw.close(); 581 return textFile; 582 } catch (IOException e) { 583 throw new RuntimeException(e); 584 } 585 } 586 587 public SearchableTextFile textFile(String file, Consumer<StringBuilder> stringBuilderConsumer) { 588 SearchableTextFile textFile = SearchableTextFile.of(path().resolve(file)); 589 var sb = new StringBuilder(); 590 stringBuilderConsumer.accept(sb); 591 try { 592 Files.writeString(textFile.path, sb.toString()); 593 return textFile; 594 } catch (IOException e) { 595 throw new RuntimeException(e); 596 } 597 } 598 599 600 public CMakeLists cmakeLists(Consumer<StringBuilder> stringBuilderConsumer) { 601 var sb = new StringBuilder(); 602 stringBuilderConsumer.accept(sb); 603 var ret = CMakeLists.of(path().resolve("CMakeLists.txt")); 604 try { 605 Files.writeString(ret.path, sb.toString()); 606 return ret; 607 } catch (IOException e) { 608 throw new RuntimeException(e); 609 } 610 } 611 } 612 613 public record FileEntry(Path path) implements FilePathHolder { 614 public static FileEntry of(Path path) { 615 return new FileEntry(path); 616 } 617 } 618 619 public record JarFile(Path path) implements ClassPathEntry, FilePathHolder { 620 public static JarFile of(Path path) { 621 return new JarFile(path); 622 } 623 624 @Override 625 public List<ClassPathEntry> classPathEntries() { 626 return List.of(this); 627 } 628 } 629 630 public sealed interface TextFile extends FilePathHolder { 631 632 static Path tempContaining(String suffix, String text) { 633 try { 634 var path = Files.createTempFile(Files.createTempDirectory("bldr"), "data", suffix); 635 Files.newOutputStream(path).write(text.getBytes()); 636 return path; 637 } catch (IOException e) { 638 throw new RuntimeException(e); 639 } 640 } 641 } 642 643 644 public sealed interface SourceFile extends TextFile { 645 } 646 647 public static final class CMakeLists implements SourceFile { 648 Path path; 649 650 CMakeLists(Path path) { 651 this.path = path; 652 } 653 654 public static CMakeLists of(Path path) { 655 return new CMakeLists(path); 656 } 657 658 @Override 659 public Path path() { 660 return path; 661 } 662 } 663 664 public static final class JavaSourceFile extends SimpleJavaFileObject implements SourceFile { 665 Path path; 666 667 public CharSequence getCharContent(boolean ignoreEncodingErrors) { 668 try { 669 return Files.readString(Path.of(toUri())); 670 } catch (IOException e) { 671 throw new RuntimeException(e); 672 } 673 } 674 675 JavaSourceFile(Path path) { 676 super(path.toUri(), Kind.SOURCE); 677 this.path = path; 678 } 679 680 @Override 681 public Path path() { 682 return path; 683 } 684 } 685 686 public record JExtractExecutable(Path path) implements Executable { 687 public static JExtractExecutable of(Path path) { 688 return new JExtractExecutable(path); 689 } 690 } 691 692 693 public record ObjectFile(Path path) implements FilePathHolder { 694 public static ObjectFile of(Path path) { 695 return new ObjectFile(path); 696 } 697 } 698 699 public record ExecutableFile(Path path) implements FilePathHolder { 700 public static ExecutableFile of(Path path) { 701 return new ExecutableFile(path); 702 } 703 } 704 705 public record SharedLibraryFile(Path path) implements FilePathHolder { 706 public static SharedLibraryFile of(Path path) { 707 return new SharedLibraryFile(path); 708 } 709 } 710 711 public record CppSourceFile(Path path) implements SourceFile { 712 public static CppSourceFile of(Path path) { 713 return new CppSourceFile(path); 714 } 715 } 716 717 public record CppHeaderSourceFile(Path path) implements SourceFile { 718 } 719 720 public record XMLFile(Path path) implements TextFile { 721 public static XMLFile of(Path path) { 722 return new XMLFile(path); 723 } 724 725 public static XMLFile containing(String text) { 726 return XMLFile.of(TextFile.tempContaining("xml", text)); 727 } 728 } 729 730 public record TestNGSuiteFile(Path path) implements TextFile { 731 public static TestNGSuiteFile of(Path path) { 732 return new TestNGSuiteFile(path); 733 } 734 735 public static TestNGSuiteFile containing(String text) { 736 return TestNGSuiteFile.of(TextFile.tempContaining("xml", text)); 737 } 738 } 739 740 public interface OS { 741 String arch(); 742 743 String name(); 744 745 String version(); 746 747 String MacName = "Mac OS X"; 748 String LinuxName = "Linux"; 749 750 record Linux(String arch, String name, String version) implements OS { 751 } 752 753 record Mac(String arch, String name, String version) implements OS { 754 public Path appLibFrameworks() { 755 return Path.of( 756 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/" 757 + "MacOSX.sdk/System/Library/Frameworks"); 758 } 759 760 public Path frameworkHeader(String frameworkName, String headerFileName) { 761 return appLibFrameworks().resolve(frameworkName + ".framework/Headers/" + headerFileName); 762 } 763 764 public Path libFrameworks() { 765 return Path.of("/System/Library/Frameworks"); 766 } 767 768 public Path frameworkLibrary(String frameworkName) { 769 return libFrameworks().resolve(frameworkName + ".framework/" + frameworkName); 770 } 771 } 772 773 static OS get() { 774 String arch = System.getProperty("os.arch"); 775 String name = System.getProperty("os.name"); 776 String version = System.getProperty("os.version"); 777 return switch (name) { 778 case "Mac OS X" -> new Mac(arch, name, version); 779 case "Linux" -> new Linux(arch, name, version); 780 default -> throw new IllegalStateException("No os mapping for " + name); 781 }; 782 } 783 } 784 785 public static OS os = OS.get(); 786 787 public record Java(String version, DirEntry home, int specVersion) { 788 } 789 790 public static Java java = 791 new Java( 792 System.getProperty("java.version"), 793 DirEntry.of(System.getProperty("java.home")), 794 Integer.parseInt(System.getProperty("java.specification.version")) 795 ); 796 797 public record User(DirEntry home, DirEntry pwd) { 798 } 799 800 public static User user = 801 new User(DirEntry.of(System.getProperty("user.home")), DirEntry.of(System.getProperty("user.dir"))); 802 803 804 public abstract sealed static class Builder<T extends Builder<T>> permits CMakeBuilder, FormatBuilder, JExtractBuilder, JarBuilder, JarBuilder.ManifestBuilder, JavaOpts, TestNGBuilder { 805 public Builder<?> parent; 806 public boolean verbose; 807 public boolean quiet; 808 809 @SuppressWarnings("unchecked") 810 T self() { 811 return (T) this; 812 } 813 814 protected T dontCallThisCopy(T other) { 815 this.verbose = other.verbose; 816 this.quiet = other.quiet; 817 return self(); 818 } 819 820 public T quiet(boolean quiet) { 821 this.quiet = quiet; 822 return self(); 823 } 824 825 public T quiet() { 826 quiet(true); 827 return self(); 828 } 829 830 public T verbose(boolean verbose) { 831 this.verbose = verbose; 832 return self(); 833 } 834 835 public T verbose() { 836 verbose(true); 837 return self(); 838 } 839 840 public T when(boolean condition, Consumer<T> consumer) { 841 if (condition) { 842 consumer.accept(self()); 843 } 844 return self(); 845 } 846 847 public <P extends PathHolder> T whenExists(P pathHolder, Consumer<T> consumer) { 848 if (Files.exists(pathHolder.path())) { 849 consumer.accept(self()); 850 } 851 return self(); 852 } 853 854 public <P extends PathHolder> T whenExists(P pathHolder, BiConsumer<P, T> consumer) { 855 if (Files.exists(pathHolder.path())) { 856 consumer.accept(pathHolder, self()); 857 } 858 return self(); 859 } 860 861 public T either(boolean condition, Consumer<T> trueConsumer, Consumer<T> falseConsumer) { 862 if (condition) { 863 trueConsumer.accept(self()); 864 } else { 865 falseConsumer.accept(self()); 866 } 867 return self(); 868 } 869 870 Builder(Builder<?> parent) { 871 this.parent = parent; 872 } 873 874 Builder() { 875 this(null); 876 } 877 878 public T mac(Consumer<OS.Mac> macConsumer) { 879 if (Bldr.os instanceof OS.Mac mac) { 880 macConsumer.accept(mac); 881 } 882 return self(); 883 } 884 885 public T linux(Consumer<OS.Linux> linuxConsumer) { 886 if (Bldr.os instanceof OS.Linux linux) { 887 linuxConsumer.accept(linux); 888 } 889 return self(); 890 } 891 892 public T os(Consumer<OS.Mac> macConsumer, Consumer<OS.Linux> linuxConsumer) { 893 switch (Bldr.os) { 894 case OS.Linux linux -> linuxConsumer.accept(linux); 895 case OS.Mac mac -> macConsumer.accept(mac); 896 default -> throw new IllegalStateException("Unexpected value: " + Bldr.os); 897 } 898 ; 899 return self(); 900 } 901 } 902 903 public abstract static sealed class Result<T extends Builder<T>> permits JExtractResult, JarResult, JavaResult, JavacResult { 904 public boolean ok; 905 public T builder; 906 907 Result(T builder) { 908 this.builder = builder; 909 } 910 } 911 912 public static class Strings { 913 public List<String> strings = new ArrayList<>(); 914 915 Strings() { 916 } 917 918 Strings(Strings strings) { 919 add(strings); 920 } 921 922 Strings(List<String> strings) { 923 add(strings); 924 } 925 926 Strings(String... strings) { 927 add(strings); 928 } 929 930 public Strings add(List<String> strings) { 931 this.strings.addAll(strings); 932 return this; 933 } 934 935 public Strings add(String... strings) { 936 add(Arrays.asList(strings)); 937 return this; 938 } 939 940 public Strings add(Strings strings) { 941 add(strings.strings); 942 return this; 943 } 944 945 public String spaceSeparated() { 946 StringBuilder stringBuilder = new StringBuilder(); 947 strings.forEach(opt -> stringBuilder.append(stringBuilder.isEmpty() ? "" : " ").append(opt)); 948 return stringBuilder.toString(); 949 } 950 } 951 952 953 public static sealed class JavaOpts<T extends JavaOpts<T>> extends Builder<T> { 954 public DirEntry jdk = java.home; 955 public Boolean enablePreview; 956 public Strings modules; 957 958 record FromModulePackageToModule(String fromModule, String pkg, String toModule) { 959 } 960 961 List<FromModulePackageToModule> exports; 962 963 protected T dontCallThisCopy(T other) { 964 super.dontCallThisCopy(other); 965 if (other.jdk != null) { 966 this.jdk = other.jdk; 967 } 968 if (other.enablePreview != null) { 969 this.enablePreview = other.enablePreview; 970 } 971 if (other.modules != null) { 972 this.modules = new Strings(other.modules); 973 } 974 if (other.exports != null) { 975 this.exports = new ArrayList<>(other.exports); 976 } 977 978 return self(); 979 } 980 981 public JavaOpts(Builder<?> parent) { 982 super(parent); 983 } 984 985 public JavaOpts() { 986 super(); 987 } 988 989 static public JavaOpts<?> of() { 990 return new JavaOpts<>(); 991 } 992 993 public T jdk(DirEntry jdk) { 994 this.jdk = jdk; 995 return self(); 996 } 997 998 public T add_exports(String fromModule, String pkg, String toModule) { 999 if (this.exports == null) { 1000 this.exports = new ArrayList<>(); 1001 } 1002 exports.add(new FromModulePackageToModule(fromModule, pkg, toModule)); 1003 return self(); 1004 } 1005 1006 public T add_modules(String... modules) { 1007 if (this.modules == null) { 1008 this.modules = new Strings(); 1009 } 1010 this.modules.add(modules); 1011 1012 return self(); 1013 } 1014 1015 public T add_exports(String fromModule, List<String> packages, String toModule) { 1016 1017 packages.forEach(p -> add_exports(fromModule, p, toModule)); 1018 return self(); 1019 } 1020 1021 public T add_exports(String fromModule, String[] packages, String toModule) { 1022 return add_exports(fromModule, Arrays.asList(packages), toModule); 1023 } 1024 1025 public T add_exports_to_all_unnamed(String fromModule, String... packages) { 1026 return add_exports(fromModule, Arrays.asList(packages), "ALL-UNNAMED"); 1027 } 1028 1029 public T enable_preview() { 1030 this.enablePreview = true; 1031 return self(); 1032 } 1033 1034 1035 } 1036 1037 public abstract sealed static class JavaToolBuilder<T extends JavaToolBuilder<T>> extends JavaOpts<T> permits JavacBuilder, JavaBuilder { 1038 public ClassPath classPath; 1039 1040 protected T dontCallThisCopy(T other) { 1041 super.dontCallThisCopy(other); 1042 if (other.classPath != null) { 1043 this.classPath = ClassPath.of().add(other.classPath); 1044 } 1045 return self(); 1046 } 1047 1048 public JavaToolBuilder(Builder<?> parent) { 1049 super(parent); 1050 } 1051 1052 public JavaToolBuilder() { 1053 super(); 1054 } 1055 1056 public T class_path(List<ClassPathEntryProvider> classPathEntryProviders) { 1057 this.classPath = ClassPath.ofOrUse(this.classPath).add(classPathEntryProviders); 1058 return self(); 1059 } 1060 1061 public T class_path(ClassPathEntryProvider... classPathEntryProviders) { 1062 return class_path(List.of(classPathEntryProviders)); 1063 } 1064 } 1065 1066 public static final class JavacBuilder extends JavaToolBuilder<JavacBuilder> { 1067 public DirEntry mavenStyleRoot; 1068 public ClassDir classDir; 1069 public SourcePath sourcePath; 1070 public ClassPath modulePath; 1071 public SourcePath moduleSourcePath; 1072 public Integer source; 1073 public List<Predicate<JavaSourceFile>> exclusionFilters; 1074 1075 protected JavacBuilder dontCallThisCopy(JavacBuilder other) { 1076 super.dontCallThisCopy(other); 1077 if (other.mavenStyleRoot != null) { 1078 throw new RuntimeException("You are copying a JavacBuilder which is already bound to maven style dir"); 1079 } 1080 if (other.sourcePath != null) { 1081 throw new RuntimeException("You are copying a JavacBuilder which is already bound to a SourcePath"); 1082 } 1083 if (other.moduleSourcePath != null) { 1084 throw new RuntimeException("You are copying a JavacBuilder which is already bound to a ModuleSourcePath"); 1085 } 1086 1087 if (other.source != null) { 1088 this.source = other.source; 1089 } 1090 1091 if (other.classPath != null) { 1092 ClassPath.ofOrUse(this.classPath).add(other.classPath); 1093 } 1094 return this; 1095 } 1096 1097 public JavacBuilder source(int version) { 1098 this.source = version; 1099 return self(); 1100 } 1101 1102 public JavacBuilder current_source() { 1103 return source(Bldr.java.specVersion); 1104 } 1105 1106 public JavacBuilder maven_style_root(DirEntry mavenStyleRoot) { 1107 this.mavenStyleRoot = mavenStyleRoot; 1108 return this; 1109 } 1110 1111 public JavacBuilder class_dir(Path classDir) { 1112 this.classDir = ClassDir.of(classDir); 1113 return this; 1114 } 1115 1116 public JavacBuilder class_dir(ClassDir classDir) { 1117 this.classDir = classDir; 1118 return this; 1119 } 1120 1121 public JavacBuilder d(ClassDir classDir) { 1122 this.classDir = classDir; 1123 return this; 1124 } 1125 1126 public JavacBuilder source_path(List<SourceDir> sourcePaths) { 1127 this.sourcePath = SourcePath.ofOrUse(this.sourcePath).add(sourcePaths); 1128 return this; 1129 } 1130 1131 public JavacBuilder source_path(SourceDir... sourcePathEntries) { 1132 return source_path(List.of(sourcePathEntries)); 1133 } 1134 1135 public JavacBuilder source_path(SourcePath sourcePath) { 1136 return source_path(sourcePath.entries); 1137 } 1138 1139 public JavacBuilder module_source_path(List<SourceDir> moduleSourcePathEntries) { 1140 this.moduleSourcePath = SourcePath.ofOrUse(this.moduleSourcePath).add(moduleSourcePathEntries); 1141 return this; 1142 } 1143 1144 public JavacBuilder module_source_path(SourceDir... moduleSourcePathEntries) { 1145 return module_source_path(List.of(moduleSourcePathEntries)); 1146 } 1147 1148 public JavacBuilder module_source_path(SourcePath moduleSourcePath) { 1149 return module_source_path(moduleSourcePath.entries()); 1150 } 1151 1152 public JavacBuilder() { 1153 super(); 1154 } 1155 1156 public JavacBuilder(JarBuilder jarBuilder) { 1157 super(jarBuilder); 1158 } 1159 1160 public JavacBuilder exclude(Predicate<JavaSourceFile> javaSourceFileFilter) { 1161 this.exclusionFilters = (this.exclusionFilters == null ? new ArrayList<>() : this.exclusionFilters); 1162 this.exclusionFilters.add(javaSourceFileFilter); 1163 return self(); 1164 } 1165 } 1166 1167 public static final class JavacResult extends Result<JavacBuilder> { 1168 Strings opts = new Strings(); 1169 List<JavaSourceFile> sourceFiles = new ArrayList<>(); 1170 List<JavaFileObject> classes = new ArrayList<>(); 1171 public ClassDir classDir; 1172 1173 JavacResult(JavacBuilder builder) { 1174 super(builder); 1175 } 1176 } 1177 1178 public static JavacResult javac(JavacBuilder javacBuilder) { 1179 JavacResult result = new JavacResult(javacBuilder); 1180 1181 try { 1182 if (javacBuilder.source != null) { 1183 result.opts.add("--source", javacBuilder.source.toString()); 1184 } 1185 1186 if (javacBuilder.enablePreview != null && javacBuilder.enablePreview) { 1187 result.opts.add("--enable-preview"); 1188 } 1189 if (javacBuilder.modules != null) { 1190 javacBuilder.modules.strings.forEach(module -> 1191 result.opts.add("--add-modules", module) 1192 ); 1193 } 1194 1195 if (javacBuilder.exports != null) { 1196 javacBuilder.exports.forEach(fpt -> { 1197 result.opts.add("--add-exports=" + fpt.fromModule + "/" + fpt.pkg + "=" + fpt.toModule); 1198 }); 1199 } 1200 1201 result.classDir = javacBuilder.classDir == null ? ClassDir.temp() : javacBuilder.classDir; 1202 result.opts.add("-d", result.classDir.path().toString()); 1203 if (javacBuilder.classPath != null) { 1204 result.opts.add("--class-path", javacBuilder.classPath.charSeparated()); 1205 } else if (javacBuilder.modulePath != null) { 1206 //https://dev.java/learn/modules/building/ 1207 result.opts.add("--module-path", javacBuilder.modulePath.charSeparated()); 1208 } else { 1209 // println("Warning no class path or module path "); 1210 //throw new RuntimeException("No class path or module path provided"); 1211 } 1212 var mavenStyleRoot = 1213 ((javacBuilder.parent instanceof JarBuilder jarBuilder) && jarBuilder.mavenStyleRoot instanceof DirEntry fromJarBuilder) 1214 ? fromJarBuilder 1215 : javacBuilder.mavenStyleRoot; 1216 1217 1218 if (mavenStyleRoot == null) { 1219 if (javacBuilder.sourcePath != null && !javacBuilder.sourcePath.entries.isEmpty()) { 1220 result.opts.add("--source-path", javacBuilder.sourcePath.charSeparated()); 1221 result.sourceFiles.addAll(javacBuilder.sourcePath.javaFiles().map(JavaSourceFile::new).toList()); 1222 } else if (javacBuilder.moduleSourcePath != null && !javacBuilder.moduleSourcePath.entries.isEmpty()) { 1223 result.opts.add("--module-source-path", javacBuilder.moduleSourcePath.charSeparated()); 1224 result.sourceFiles.addAll(javacBuilder.moduleSourcePath.javaFiles().map(JavaSourceFile::new).toList()); 1225 } else { 1226 throw new RuntimeException("No source path or module source path specified"); 1227 } 1228 } else { 1229 var sourcePath = SourcePath.of().add(SourceDir.of(mavenStyleRoot.path.resolve("src/main/java"))); 1230 result.sourceFiles.addAll(sourcePath.javaFiles().map(JavaSourceFile::new).toList()); 1231 if (result.sourceFiles.isEmpty()) { 1232 throw new RuntimeException("No sources"); 1233 } 1234 result.opts.add("--source-path", sourcePath.charSeparated()); 1235 1236 if (javacBuilder.sourcePath != null && !javacBuilder.sourcePath.entries.isEmpty()) { 1237 throw new RuntimeException("You have specified --source-path AND provided maven_style_root "); 1238 } 1239 } 1240 boolean[] failed = {false}; 1241 1242 DiagnosticListener<JavaFileObject> diagnosticListener = 1243 (diagnostic) -> { 1244 if (diagnostic.getKind().equals(Diagnostic.Kind.ERROR)) { 1245 failed[0] = true; 1246 } 1247 if (!diagnostic.getKind().equals(Diagnostic.Kind.NOTE)) { 1248 System.out.println("javac " 1249 + diagnostic.getKind() 1250 + " " 1251 + ((JavaSourceFile) (diagnostic.getSource())).path().toString() 1252 + " " 1253 + diagnostic.getLineNumber() 1254 + ":" 1255 + diagnostic.getColumnNumber() 1256 + " " 1257 + diagnostic.getMessage(null)); 1258 } 1259 }; 1260 1261 JavaCompiler javac = ToolProvider.getSystemJavaCompiler(); 1262 if (javacBuilder.exclusionFilters != null) { 1263 javacBuilder.exclusionFilters.forEach(p -> { 1264 result.sourceFiles = result.sourceFiles.stream().filter( 1265 javaSourceFile -> { 1266 var kill = p.test(javaSourceFile); 1267 if (kill) { 1268 println("Excluded " + javaSourceFile); 1269 } 1270 return !kill; 1271 } 1272 ).toList(); 1273 }); 1274 } 1275 if (javacBuilder.verbose || javacBuilder.parent instanceof JarBuilder jarBuilder && jarBuilder.verbose) { 1276 print("javac " + result.opts.spaceSeparated()); 1277 result.sourceFiles.forEach(s -> print(s + " ")); 1278 println(""); 1279 } 1280 JavaCompiler.CompilationTask compilationTask = 1281 (javac.getTask( 1282 new PrintWriter(System.err), 1283 javac.getStandardFileManager(diagnosticListener, null, null), 1284 diagnosticListener, 1285 result.opts.strings, 1286 null, 1287 result.sourceFiles 1288 )); 1289 JavacTask javacTask = (JavacTask) compilationTask; 1290 1291 javacTask.generate().forEach(javaFileObject -> { 1292 result.classes.add(javaFileObject); 1293 }); 1294 if (failed[0]) { 1295 throw new RuntimeException("javac failed"); 1296 } 1297 return result; 1298 } catch (IOException e) { 1299 throw new RuntimeException(e); 1300 } 1301 } 1302 1303 public static JavacBuilder javacBuilder(Consumer<JavacBuilder> javacBuilderConsumer) { 1304 JavacBuilder javacBuilder = new JavacBuilder(); 1305 javacBuilderConsumer.accept(javacBuilder); 1306 return javacBuilder; 1307 } 1308 1309 public static JavacResult javac(Consumer<JavacBuilder> javacBuilderConsumer) { 1310 return javac(javacBuilder(javacBuilderConsumer)); 1311 } 1312 1313 public static final class JavaBuilder extends JavaToolBuilder<JavaBuilder> { 1314 public String mainClass; 1315 public DirPath libraryPath; 1316 public boolean startOnFirstThread; 1317 public Strings args = new Strings(); 1318 public Strings nativeAccessModules = new Strings(); 1319 private boolean headless; 1320 1321 public JavaBuilder enable_native_access(String module) { 1322 nativeAccessModules.add(module); 1323 return self(); 1324 } 1325 1326 public JavaBuilder enable_native_access_to_all_unnamed() { 1327 return enable_native_access("ALL-UNNAMED"); 1328 } 1329 1330 public JavaBuilder args(List<String> args) { 1331 this.args.add(args); 1332 return self(); 1333 } 1334 1335 public JavaBuilder args(String... args) { 1336 args(Arrays.asList(args)); 1337 return self(); 1338 } 1339 1340 public JavaBuilder main_class(String mainClass) { 1341 this.mainClass = mainClass; 1342 return this; 1343 } 1344 1345 public JavaBuilder library_path(List<DirPathHolder<?>> libraryPathEntries) { 1346 this.libraryPath = DirPath.ofOrUse(this.libraryPath).add(libraryPathEntries); 1347 return this; 1348 } 1349 1350 public JavaBuilder library_path(DirPath libraryPathEntries) { 1351 this.libraryPath = DirPath.ofOrUse(this.libraryPath).add(libraryPathEntries); 1352 return this; 1353 } 1354 1355 public JavaBuilder library_path(DirPathHolder<?>... libraryPathEntries) { 1356 return this.library_path(List.of(libraryPathEntries)); 1357 } 1358 1359 public JavaBuilder start_on_first_thread() { 1360 this.startOnFirstThread = true; 1361 return this; 1362 } 1363 1364 public void headless() { 1365 this.headless = true; 1366 } 1367 } 1368 1369 public static final class JavaResult extends Result<JavaBuilder> { 1370 Strings opts = new Strings(); 1371 1372 JavaResult(JavaBuilder javaBuilder) { 1373 super(javaBuilder); 1374 } 1375 } 1376 1377 public static JavaBuilder java(JavaBuilder javaBuilder) { 1378 JavaResult result = new JavaResult(javaBuilder); 1379 result.opts.add(javaBuilder.jdk.path().resolve("bin/java").toString()); 1380 if (javaBuilder.enablePreview != null && javaBuilder.enablePreview) { 1381 result.opts.add("--enable-preview"); 1382 } 1383 if (javaBuilder.modules != null) { 1384 javaBuilder.modules.strings.forEach(module -> 1385 result.opts.add("--add-modules", module) 1386 ); 1387 } 1388 1389 if (javaBuilder.exports != null) { 1390 javaBuilder.exports.forEach(fpt -> { 1391 result.opts.add("--add-exports=" + fpt.fromModule + "/" + fpt.pkg + "=" + fpt.toModule); 1392 }); 1393 } 1394 if (javaBuilder.headless) { 1395 result.opts.add("-Dheadless=true"); 1396 } 1397 if (javaBuilder.startOnFirstThread) { 1398 result.opts.add("-XstartOnFirstThread"); 1399 } 1400 1401 javaBuilder.nativeAccessModules.strings.forEach(module -> 1402 result.opts.add("--enable-native-access=" + module) 1403 ); 1404 1405 if (javaBuilder.classPath != null) { 1406 result.opts.add("--class-path", javaBuilder.classPath.charSeparated()); 1407 } 1408 if (javaBuilder.libraryPath != null) { 1409 result.opts.add("-Djava.library.path=" + javaBuilder.libraryPath.charSeparated()); 1410 } 1411 result.opts.add(javaBuilder.mainClass); 1412 result.opts.add(javaBuilder.args.strings); 1413 1414 try { 1415 var processBuilder = new ProcessBuilder().inheritIO().command(result.opts.strings); 1416 var process = processBuilder.start(); 1417 if (javaBuilder.verbose) { 1418 println(result.opts.spaceSeparated()); 1419 } 1420 process.waitFor(); 1421 } catch (InterruptedException | IOException ie) { 1422 System.out.println(ie); 1423 } 1424 1425 return javaBuilder; 1426 } 1427 1428 public static JavaBuilder java(Consumer<JavaBuilder> javaBuilderConsumer) { 1429 JavaBuilder javaBuilder = new JavaBuilder(); 1430 javaBuilderConsumer.accept(javaBuilder); 1431 return java(javaBuilder); 1432 } 1433 1434 public static JavaBuilder javaBuilder() { 1435 return new JavaBuilder(); 1436 } 1437 1438 public static final class FormatBuilder extends Builder<FormatBuilder> { 1439 public SourcePath sourcePath; 1440 1441 public FormatBuilder source_path(List<SourceDir> sourcePaths) { 1442 this.sourcePath = SourcePath.ofOrUse(this.sourcePath).add(sourcePaths); 1443 return this; 1444 } 1445 1446 public FormatBuilder source_path(SourceDir... sourcePaths) { 1447 return source_path(List.of(sourcePaths)); 1448 } 1449 } 1450 1451 public static void format(RepoDir repoDir, Consumer<FormatBuilder> formatBuilderConsumer) { 1452 var formatBuilder = new FormatBuilder(); 1453 formatBuilderConsumer.accept(formatBuilder); 1454 var classPathEntries = repoDir.classPathEntries("com.google.googlejavaformat/google-java-format"); 1455 1456 java($ -> $ 1457 .verbose() 1458 .enable_preview() 1459 .enable_native_access("ALL-UNNAMED") 1460 .add_exports("java.base", "jdk.internal", "ALL-UNNAMED") 1461 .add_exports( 1462 "jdk.compiler", 1463 List.of( 1464 "com.sun.tools.javac.api", 1465 "com.sun.tools.javac.code", 1466 "com.sun.tools.javac.file", 1467 "com.sun.tools.javac.main", 1468 "com.sun.tools.javac.parser", 1469 "com.sun.tools.javac.tree", 1470 "com.sun.tools.javac.util"), 1471 "ALL-UNNAMED") 1472 .class_path(classPathEntries) 1473 .main_class("com.google.googlejavaformat.java.Main") 1474 .args("-r") 1475 .args(formatBuilder.sourcePath.javaFiles().map(Path::toString).toList())); 1476 } 1477 1478 public static final class TestNGBuilder extends Builder<TestNGBuilder> { 1479 public SourcePath sourcePath; 1480 public ClassPath classPath; 1481 private SuiteBuilder suiteBuilder; 1482 private JarFile testJar; 1483 1484 public TestNGBuilder class_path(List<ClassPathEntryProvider> classPathEntryProviders) { 1485 this.classPath = ClassPath.ofOrUse(this.classPath).add(classPathEntryProviders); 1486 return this; 1487 } 1488 1489 public TestNGBuilder class_path(ClassPathEntryProvider... classPathEntryProviders) { 1490 class_path(List.of(classPathEntryProviders)); 1491 return this; 1492 } 1493 1494 public TestNGBuilder source_path(List<SourceDir> sourcePathEntries) { 1495 this.sourcePath = SourcePath.ofOrUse(this.sourcePath).add(sourcePathEntries); 1496 return this; 1497 } 1498 1499 public TestNGBuilder source_path(SourceDir... sourcePathEntries) { 1500 return source_path(List.of(sourcePathEntries)); 1501 } 1502 1503 public TestNGBuilder testJar(JarFile testJar) { 1504 this.testJar = testJar; 1505 return self(); 1506 } 1507 1508 public static class SuiteBuilder { 1509 String name; 1510 1511 SuiteBuilder name(String name) { 1512 this.name = name; 1513 return this; 1514 } 1515 1516 List<TestBuilder> testBuilders = new ArrayList<>(); 1517 1518 public static class TestBuilder { 1519 String name; 1520 List<String> classNames; 1521 1522 TestBuilder name(String name) { 1523 this.name = name; 1524 return this; 1525 } 1526 1527 public TestBuilder classes(List<String> classNames) { 1528 this.classNames = this.classNames == null ? new ArrayList<>() : this.classNames; 1529 this.classNames.addAll(classNames); 1530 return this; 1531 } 1532 1533 public TestBuilder classes(String... classNames) { 1534 return classes(List.of(classNames)); 1535 } 1536 } 1537 1538 public void test(String testName, Consumer<TestBuilder> testBuilderConsumer) { 1539 TestBuilder testBuilder = new TestBuilder(); 1540 testBuilder.name(testName); 1541 testBuilderConsumer.accept(testBuilder); 1542 testBuilders.add(testBuilder); 1543 } 1544 } 1545 1546 public TestNGBuilder suite(String suiteName, Consumer<SuiteBuilder> suiteBuilderConsumer) { 1547 this.suiteBuilder = new SuiteBuilder(); 1548 suiteBuilder.name(suiteName); 1549 suiteBuilderConsumer.accept(suiteBuilder); 1550 return self(); 1551 } 1552 } 1553 1554 public static void testng(RepoDir repoDir, Consumer<TestNGBuilder> testNGBuilderConsumer) { 1555 var testNGBuilder = new TestNGBuilder(); 1556 testNGBuilderConsumer.accept(testNGBuilder); 1557 1558 var text = 1559 XMLNode.create( 1560 "suite", 1561 $ -> { 1562 $.attr("name", testNGBuilder.suiteBuilder.name); 1563 testNGBuilder.suiteBuilder.testBuilders.forEach( 1564 tb -> { 1565 $.element( 1566 "test", 1567 $$ -> 1568 $$.attr("name", tb.name) 1569 .element( 1570 "classes", 1571 $$$ -> 1572 tb.classNames.forEach( 1573 className -> 1574 $$$.element( 1575 "class", 1576 $$$$ -> $$$$.attr("name", className))))); 1577 }); 1578 }) 1579 .toString(); 1580 1581 println(text); 1582 1583 TestNGSuiteFile testNGSuiteFile = TestNGSuiteFile.containing(text); 1584 var mavenJars = repoDir.classPathEntries("org.testng/testng", "org.slf4j/slf4j-api"); 1585 1586 1587 var testJarResult = 1588 jar(jar -> jar 1589 .jarFile(testNGBuilder.testJar) 1590 .javac(javac -> javac 1591 .source(24) 1592 .enable_preview() 1593 .class_path(testNGBuilder.classPath, mavenJars) 1594 .source_path(testNGBuilder.sourcePath) 1595 ) 1596 ); 1597 1598 java( 1599 $ -> 1600 $.enable_preview() 1601 .add_exports_to_all_unnamed("java.base", "jdk.internal") 1602 .enable_native_access("ALL-UNNAMED") 1603 .class_path(testNGBuilder.classPath, mavenJars, testJarResult) 1604 .main_class("org.testng.TestNG") 1605 .args(testNGSuiteFile.path().toString())); 1606 } 1607 1608 public static final class JarBuilder extends Builder<JarBuilder> { 1609 public static class Manifest { 1610 public String mainClass; 1611 public String[] classPath; 1612 public String version; 1613 public String createdBy; 1614 public String buildBy; 1615 1616 public void writeTo(JarOutputStream jarStream) { 1617 PrintWriter printWriter = new PrintWriter(jarStream); 1618 if (version != null) { 1619 printWriter.println("Manifest-Version: " + version); 1620 } 1621 if (mainClass != null) { 1622 printWriter.println("Main-Class: " + mainClass); 1623 } 1624 if (classPath != null) { 1625 printWriter.print("Class-Path:"); 1626 for (String s : classPath) { 1627 printWriter.print(" "); 1628 printWriter.print(s); 1629 } 1630 printWriter.println(); 1631 } 1632 printWriter.flush(); 1633 } 1634 } 1635 1636 public static final class ManifestBuilder extends Builder<ManifestBuilder> { 1637 1638 Manifest manifest; 1639 1640 public ManifestBuilder main_class(String mainClass) { 1641 this.manifest.mainClass = mainClass; 1642 return self(); 1643 } 1644 1645 public ManifestBuilder version(String version) { 1646 this.manifest.version = version; 1647 return self(); 1648 } 1649 1650 public ManifestBuilder created_by(String createdBy) { 1651 this.manifest.createdBy = createdBy; 1652 return self(); 1653 } 1654 1655 public ManifestBuilder build_by(String buildBy) { 1656 this.manifest.buildBy = buildBy; 1657 return self(); 1658 } 1659 1660 public ManifestBuilder class_path(String... classPath) { 1661 this.manifest.classPath = classPath; 1662 return self(); 1663 } 1664 1665 public ManifestBuilder class_path(ClassPathEntry... classPathEntries) { 1666 this.manifest.classPath = Stream.of(classPathEntries).map(classPathEntry -> classPathEntry.path().getFileName().toString()).toArray(String[]::new); 1667 return self(); 1668 } 1669 1670 ManifestBuilder(Manifest manifest) { 1671 this.manifest = manifest; 1672 } 1673 } 1674 1675 public DirEntry mavenStyleRoot; 1676 public JarFile jar; 1677 public JavacResult javacResult; 1678 public DirPath dirList; 1679 // public String mainClass; 1680 public Manifest manifest; 1681 1682 public JarBuilder jarFile(JarFile jar) { 1683 this.jar = jar; 1684 return self(); 1685 } 1686 1687 public JarBuilder maven_style_root(DirEntry mavenStyleRoot) { 1688 this.mavenStyleRoot = mavenStyleRoot; 1689 return this; 1690 } 1691 1692 public JarBuilder manifest(Consumer<ManifestBuilder> manifestBuilderConsumer) { 1693 this.manifest = this.manifest == null ? new Manifest() : this.manifest; 1694 var manifestBuilder = new ManifestBuilder(manifest); 1695 manifestBuilderConsumer.accept(manifestBuilder); 1696 return self(); 1697 } 1698 1699 private JarBuilder javac(JavacBuilder javacBuilder) { 1700 this.javacResult = Bldr.javac(javacBuilder); 1701 1702 this.dirList = 1703 (this.dirList == null) 1704 ? DirPath.of().add(this.javacResult.classDir) 1705 : this.dirList.add(this.javacResult.classDir); 1706 if (mavenStyleRoot != null) { 1707 var resources = mavenStyleRoot.dir("src/main/resources"); 1708 if (resources.exists()) { 1709 this.dirList.add(resources); 1710 } 1711 } 1712 return self(); 1713 } 1714 1715 public JavacBuilder javacBuilder(Consumer<JavacBuilder> javacBuilderConsumer) { 1716 JavacBuilder javacBuilder = new JavacBuilder(this); 1717 javacBuilderConsumer.accept(javacBuilder); 1718 return javacBuilder; 1719 } 1720 1721 public JavacBuilder javacBuilder(JavacBuilder copyMe, Consumer<JavacBuilder> javacBuilderConsumer) { 1722 JavacBuilder javacBuilder = new JavacBuilder(this); 1723 javacBuilder.dontCallThisCopy(copyMe); 1724 javacBuilderConsumer.accept(javacBuilder); 1725 return javacBuilder; 1726 } 1727 1728 public JarBuilder javac(Consumer<JavacBuilder> javacBuilderConsumer) { 1729 return javac(javacBuilder(javacBuilderConsumer)); 1730 } 1731 1732 public JarBuilder javac(JavacBuilder copyMe, Consumer<JavacBuilder> javacBuilderConsumer) { 1733 return javac(javacBuilder(copyMe, javacBuilderConsumer)); 1734 } 1735 1736 @SuppressWarnings("unchecked") 1737 public <P extends DirPathHolder<P>> JarBuilder dir_list(P... holders) { 1738 Arrays.asList(holders).forEach(holder -> 1739 this.dirList = DirPath.ofOrUse(this.dirList).add(holder) 1740 ); 1741 return self(); 1742 } 1743 1744 @SuppressWarnings("unchecked") 1745 public <P extends DirPathHolder<P>> JarBuilder add(P... holders) { 1746 Arrays.asList(holders).forEach(holder -> 1747 this.dirList = DirPath.ofOrUse(this.dirList).add(holder) 1748 ); 1749 return self(); 1750 } 1751 } 1752 1753 public static final class JarResult extends Result<JarBuilder> implements ClassPathEntryProvider { 1754 public Strings opts = new Strings(); 1755 public List<RootDirAndSubPath> pathsToJar = new ArrayList<>(); 1756 public List<Path> paths = new ArrayList<>(); 1757 public JarFile jarFile; 1758 1759 1760 public JarResult(JarBuilder jarBuilder) { 1761 super(jarBuilder); 1762 this.jarFile = jarBuilder.jar; 1763 } 1764 1765 @Override 1766 public List<ClassPathEntry> classPathEntries() { 1767 return List.of(jarFile); 1768 } 1769 1770 @Override 1771 public String toString() { 1772 return jarFile.path.toString(); 1773 } 1774 } 1775 1776 public static JarResult jar(JarBuilder jarBuilder) { 1777 1778 JarResult result = new JarResult(jarBuilder); 1779 try { 1780 1781 var jarStream = new JarOutputStream(Files.newOutputStream(jarBuilder.jar.path())); 1782 if (jarBuilder.dirList == null) { 1783 throw new RuntimeException("Nothing to jar "); 1784 } 1785 if (jarBuilder.manifest != null) { 1786 // We must add manifest 1787 var entry = new JarEntry("META-INF/MANIFEST.MF"); 1788 // entry.setTime(Files.getLastModifiedTime(rootAndPath.path()).toMillis()); 1789 1790 jarStream.putNextEntry(entry); 1791 jarBuilder.manifest.writeTo(jarStream); 1792 jarStream.closeEntry(); 1793 1794 } 1795 jarBuilder.dirList.entries.forEach( 1796 root -> 1797 root.findFiles() 1798 .map(path -> new RootDirAndSubPath(root, path)) 1799 .forEach(result.pathsToJar::add)); 1800 result.pathsToJar.stream() 1801 .sorted(Comparator.comparing(RootDirAndSubPath::path)) 1802 .forEach( 1803 rootAndPath -> { 1804 try { 1805 result.paths.add(rootAndPath.path); 1806 var entry = new JarEntry(rootAndPath.relativize().toString()); 1807 entry.setTime(Files.getLastModifiedTime(rootAndPath.path()).toMillis()); 1808 jarStream.putNextEntry(entry); 1809 Files.newInputStream(rootAndPath.path()).transferTo(jarStream); 1810 jarStream.closeEntry(); 1811 if (jarBuilder.verbose) { 1812 println("INFO: adding " + rootAndPath.relativize().toString()); 1813 } 1814 } catch (IOException e) { 1815 throw new RuntimeException(e); 1816 } 1817 }); 1818 jarStream.finish(); 1819 jarStream.close(); 1820 if (jarBuilder.verbose) { 1821 println("INFO: created " + jarBuilder.jar.path.toString()); 1822 } 1823 return result; 1824 } catch (IOException e) { 1825 throw new RuntimeException(e); 1826 } 1827 } 1828 1829 public static JarBuilder jarBuilder(Consumer<JarBuilder> jarBuilderConsumer) { 1830 JarBuilder jarBuilder = new JarBuilder(); 1831 jarBuilderConsumer.accept(jarBuilder); 1832 return jarBuilder; 1833 } 1834 1835 public static JarBuilder jarBuilder(JarBuilder copyMe, Consumer<JarBuilder> jarBuilderConsumer) { 1836 JarBuilder jarBuilder = new JarBuilder(); 1837 jarBuilder.dontCallThisCopy(copyMe); 1838 jarBuilderConsumer.accept(jarBuilder); 1839 return jarBuilder; 1840 } 1841 1842 public static JarResult jar(Consumer<JarBuilder> jarBuilderConsumer) { 1843 return jar(jarBuilder(jarBuilderConsumer)); 1844 } 1845 1846 public static JarResult jar(JarBuilder copyMe, Consumer<JarBuilder> jarBuilderConsumer) { 1847 return jar(jarBuilder(copyMe, jarBuilderConsumer)); 1848 } 1849 1850 public static final class CMakeBuilder extends Builder<CMakeBuilder> { 1851 public List<String> libraries = new ArrayList<>(); 1852 public CMakeBuildDir cmakeBuildDir; 1853 public DirEntry sourceDir; 1854 private Path output; 1855 public BuildDir copyToDir; 1856 public List<String> opts = new ArrayList<>(); 1857 1858 public CMakeBuilder opts(List<String> opts) { 1859 this.opts.addAll(opts); 1860 return self(); 1861 } 1862 1863 public CMakeBuilder opts(String... opts) { 1864 opts(Arrays.asList(opts)); 1865 return self(); 1866 } 1867 1868 public CMakeBuilder() { 1869 opts.add("cmake"); 1870 } 1871 1872 public CMakeBuilder build_dir(CMakeBuildDir cmakeBuildDir) { 1873 this.cmakeBuildDir = cmakeBuildDir; 1874 opts("-B", cmakeBuildDir.path.toString()); 1875 return this; 1876 } 1877 1878 public CMakeBuilder copy_to(BuildDir copyToDir) { 1879 this.copyToDir = copyToDir; 1880 opts("-DHAT_TARGET=" + this.copyToDir.path().toString()); 1881 return this; 1882 } 1883 1884 public CMakeBuilder source_dir(DirEntry sourceDir) { 1885 this.sourceDir = sourceDir; 1886 opts("-S", sourceDir.path().toString()); 1887 return this; 1888 } 1889 1890 public CMakeBuilder build(CMakeBuildDir cmakeBuildDir) { 1891 this.cmakeBuildDir = cmakeBuildDir; 1892 opts("--build", cmakeBuildDir.path().toString()); 1893 return this; 1894 } 1895 } 1896 1897 public static void cmake(Consumer<CMakeBuilder> cmakeBuilderConsumer) { 1898 CMakeBuilder cmakeBuilder = new CMakeBuilder(); 1899 cmakeBuilderConsumer.accept(cmakeBuilder); 1900 cmakeBuilder.cmakeBuildDir.create(); 1901 try { 1902 var processBuilder = new ProcessBuilder().inheritIO().command(cmakeBuilder.opts); 1903 var process = processBuilder.start(); 1904 if (cmakeBuilder.verbose) { 1905 print(cmakeBuilder.opts); 1906 } 1907 process.waitFor(); 1908 } catch (InterruptedException | IOException ie) { 1909 System.out.println(ie); 1910 } 1911 } 1912 1913 static Path unzip(Path in, Path dir) { 1914 try { 1915 Files.createDirectories(dir); 1916 ZipFile zip = new ZipFile(in.toFile()); 1917 zip.entries() 1918 .asIterator() 1919 .forEachRemaining( 1920 entry -> { 1921 try { 1922 String currentEntry = entry.getName(); 1923 1924 Path destFile = dir.resolve(currentEntry); 1925 // destFile = new File(newPath, destFile.getName()); 1926 Path destinationParent = destFile.getParent(); 1927 Files.createDirectories(destinationParent); 1928 // create the parent directory structure if needed 1929 1930 if (!entry.isDirectory()) { 1931 zip.getInputStream(entry).transferTo(Files.newOutputStream(destFile)); 1932 } 1933 } catch (IOException ioe) { 1934 throw new RuntimeException(ioe); 1935 } 1936 }); 1937 zip.close(); 1938 1939 } catch (IOException e) { 1940 throw new RuntimeException(e); 1941 } 1942 return dir; 1943 } 1944 1945 1946 public static final class JExtractBuilder extends Builder<JExtractBuilder> { 1947 public Strings compileFlags = new Strings(); 1948 public List<Path> libraries = new ArrayList<>(); 1949 public List<Path> headers = new ArrayList<>(); 1950 private String targetPackage; 1951 private String headerClassName; 1952 private BuildDir output; 1953 1954 protected JExtractBuilder dontCallThisCopy(JExtractBuilder other) { 1955 this.compileFlags = new Strings(other.compileFlags); 1956 if (other.targetPackage != null) { 1957 throw new RuntimeException("You are copying jextract builder already bound to a target package"); 1958 } 1959 if (other.output != null) { 1960 throw new RuntimeException("You are copying jextract builder already bound to output directory"); 1961 } 1962 if (!other.libraries.isEmpty()) { 1963 throw new RuntimeException("You are copying jextract builder already bound to library(ies)"); 1964 } 1965 if (!other.headers.isEmpty()) { 1966 throw new RuntimeException("You are copying jextract builder already bound to headers library(ies)"); 1967 } 1968 return self(); 1969 } 1970 1971 public JExtractBuilder target_package(String targetPackage) { 1972 this.targetPackage = targetPackage; 1973 return self(); 1974 } 1975 1976 public JExtractBuilder header_class_name(String headerClassName) { 1977 this.headerClassName = headerClassName; 1978 return self(); 1979 } 1980 1981 public JExtractBuilder output(BuildDir output) { 1982 this.output = output; 1983 return self(); 1984 } 1985 1986 public JExtractBuilder library(Path... libraries) { 1987 this.libraries.addAll(Arrays.asList(libraries)); 1988 return self(); 1989 } 1990 1991 public JExtractBuilder compile_flag(String... compileFlags) { 1992 this.compileFlags.add(compileFlags); 1993 return self(); 1994 } 1995 1996 public JExtractBuilder header(Path header) { 1997 this.headers.add(header); 1998 return self(); 1999 } 2000 2001 public JExtractBuilder capability(Capabilities.Jextractable jextractable, BuildDir stageDir) { 2002 output(jextractable.stage(stageDir)) 2003 .target_package(jextractable.packageName()) 2004 .header_class_name(jextractable.headerClassName()); 2005 jextractable.inversionOfControl(this); 2006 return self(); 2007 } 2008 } 2009 2010 public static final class JExtractResult extends Result<JExtractBuilder> { 2011 public Strings opts = new Strings(); 2012 2013 JExtractResult(JExtractBuilder builder) { 2014 super(builder); 2015 } 2016 } 2017 2018 public static JExtractResult jextract(Capabilities.JExtract jextract, Consumer<JExtractBuilder> jextractBuilderConsumer) { 2019 return jextract(JExtractExecutable.of(jextract.path()),jextractBuilderConsumer); 2020 } 2021 2022 2023 public static JExtractResult jextract(JExtractExecutable executable, Consumer<JExtractBuilder> jextractBuilderConsumer) { 2024 2025 var exePath = executable.path; 2026 var homePath = exePath.getParent().getParent(); 2027 2028 JExtractBuilder jExtractBuilder = new JExtractBuilder(); 2029 JExtractResult result = new JExtractResult(jExtractBuilder); 2030 jextractBuilderConsumer.accept(jExtractBuilder); 2031 result.opts.add(executable.path().toString()); 2032 2033 if (jExtractBuilder.targetPackage != null) { 2034 result.opts.add("--target-package", jExtractBuilder.targetPackage); 2035 } 2036 if (jExtractBuilder.output != null) { 2037 jExtractBuilder.output.create(); 2038 result.opts.add("--output", jExtractBuilder.output.path().toString()); 2039 } 2040 for (Path library : jExtractBuilder.libraries) { 2041 result.opts.add("--library", ":" + library); 2042 } 2043 2044 2045 if (jExtractBuilder.headers.isEmpty()) { 2046 throw new RuntimeException("No headers specified"); 2047 } 2048 for (Path header : jExtractBuilder.headers) { 2049 if (jExtractBuilder.headerClassName != null) { 2050 result.opts.add("--header-class-name", jExtractBuilder.headerClassName); 2051 System.out.println("header and class name: " + header.toString() + " with " + jExtractBuilder.headerClassName); 2052 } else { 2053 System.out.println("header: " + header.toString() + " no header className"); 2054 } 2055 result.opts.add(header.toString()); 2056 } 2057 2058 2059 if (jExtractBuilder.compileFlags != null && !jExtractBuilder.compileFlags.strings.isEmpty()) { 2060 jExtractBuilder.output.textFile("compile_flags.txt", jExtractBuilder.compileFlags.strings); 2061 } 2062 2063 if (jExtractBuilder.verbose) { 2064 println(result.opts.spaceSeparated()); 2065 } 2066 var processBuilder = new ProcessBuilder(); 2067 if (jExtractBuilder.output != null) { 2068 processBuilder.directory(jExtractBuilder.output.path().toFile()); 2069 } 2070 processBuilder.inheritIO().command(result.opts.strings); 2071 try { 2072 processBuilder.start().waitFor(); 2073 } catch (InterruptedException | IOException ie) { 2074 throw new RuntimeException(ie); 2075 } 2076 return result; 2077 } 2078 2079 public record SearchableTextFile(Path path) implements TextFile { 2080 static SearchableTextFile of(Path path) { 2081 return new SearchableTextFile(path); 2082 } 2083 2084 public Stream<Line> lines() { 2085 try { 2086 int num[] = new int[]{1}; 2087 return Files.readAllLines(path(), StandardCharsets.UTF_8).stream() 2088 .map(line -> new Line(line, num[0]++)); 2089 } catch (IOException ioe) { 2090 System.out.println(ioe); 2091 return new ArrayList<Line>().stream(); 2092 } 2093 } 2094 2095 public boolean grep(Pattern pattern) { 2096 return lines().anyMatch(line -> pattern.matcher(line.line).matches()); 2097 } 2098 2099 public boolean hasSuffix(String... suffixes) { 2100 var suffixSet = Set.of(suffixes); 2101 int dotIndex = path().toString().lastIndexOf('.'); 2102 return dotIndex == -1 || suffixSet.contains(path().toString().substring(dotIndex + 1)); 2103 } 2104 } 2105 2106 public record Line(String line, int num) { 2107 public boolean grep(Pattern pattern) { 2108 return pattern.matcher(line()).matches(); 2109 } 2110 } 2111 2112 public static Path curl(URL url, Path file) { 2113 try { 2114 println("Downloading " + url + "->" + file); 2115 url.openStream().transferTo(Files.newOutputStream(file)); 2116 } catch (IOException e) { 2117 throw new RuntimeException(e); 2118 } 2119 return file; 2120 } 2121 2122 public static Optional<Path> which(String execName) { 2123 // which and whereis had issues. 2124 return Arrays.asList(System.getenv("PATH").split(File.pathSeparator)).stream() 2125 .map(dirName -> Path.of(dirName).resolve(execName).normalize()) 2126 .filter(Files::isExecutable) 2127 .findFirst(); 2128 } 2129 2130 public static boolean canExecute(String execName) { 2131 return which(execName).isPresent(); 2132 } 2133 2134 public static Path untar(Path tarFile, Path dir) { 2135 try { 2136 new ProcessBuilder() 2137 .inheritIO() 2138 .command("tar", "xvf", tarFile.toString(), "--directory", tarFile.getParent().toString()) 2139 .start() 2140 .waitFor(); 2141 return dir; 2142 } catch ( 2143 InterruptedException 2144 e) { // We get IOException if the executable not found, at least on Mac so interuppted 2145 // means it exists 2146 return null; 2147 } catch (IOException e) { // We get IOException if the executable not found, at least on Mac 2148 // throw new RuntimeException(e); 2149 return null; 2150 } 2151 } 2152 2153 public static Optional<Path> fromPATH(String name) { 2154 return Arrays.stream(System.getenv("PATH").split(File.pathSeparator)) 2155 .map(dirName -> Path.of(dirName).resolve(name).normalize()) 2156 .filter(Files::isExecutable).findFirst(); 2157 } 2158 2159 2160 public static <T extends PathHolder> T assertExists(T testme) { 2161 if (Files.exists(testme.path())) { 2162 return testme; 2163 } else { 2164 throw new IllegalStateException("FAILED: " + testme.path() + " does not exist"); 2165 } 2166 } 2167 2168 public static <T extends Path> T assertExists(T path) { 2169 if (Files.exists(path)) { 2170 return path; 2171 } else { 2172 throw new IllegalStateException("FAILED: " + path + " does not exist"); 2173 } 2174 } 2175 2176 public static class CMakeProbe implements Capabilities.Probe { 2177 public interface CMakeVar<T> { 2178 String name(); 2179 2180 T value(); 2181 } 2182 2183 public record CMakeTypedVar(String name, String type, String value, String comment) 2184 implements CMakeVar<String> { 2185 static final Regex regex = Regex.of("^_*(?:CMAKE_)?([A-Za-z0-9_]+):([^=]*)=(.*)$"); 2186 2187 CMakeTypedVar(Matcher matcher, String comment) { 2188 this( 2189 "CMAKE_" + matcher.group(1).trim(), 2190 matcher.group(2).trim(), 2191 matcher.group(3).trim(), 2192 comment.substring(2).trim()); 2193 } 2194 2195 static boolean onMatch(String line, String comment, Consumer<CMakeTypedVar> consumer) { 2196 return regex.matches(line, matcher -> consumer.accept(new CMakeTypedVar(matcher, comment))); 2197 } 2198 } 2199 2200 public record CMakeSimpleVar(String name, String value) implements CMakeVar { 2201 static final Regex regex = Regex.of("^_*(?:CMAKE_)?([A-Za-z0-9_]+)=\\{<\\{(.*)\\}>\\}$"); 2202 2203 CMakeSimpleVar(Matcher matcher) { 2204 this( 2205 "CMAKE_" + matcher.group(1).trim(), 2206 (matcher.group(2).isEmpty()) ? "" : matcher.group(2).trim()); 2207 } 2208 2209 static boolean onMatch(String line, String comment, Consumer<CMakeSimpleVar> consumer) { 2210 return regex.matches(line, matcher -> consumer.accept(new CMakeSimpleVar(matcher))); 2211 } 2212 } 2213 2214 public record CMakeDirVar(String name, DirPathHolder value) implements CMakeVar { 2215 static final Regex regex = Regex.of("^_*(?:CMAKE_)?([A-Za-z0-9_]+)=\\{<\\{(.*)\\}>\\}$"); 2216 2217 static boolean onMatch(String line, String comment, Consumer<CMakeSimpleVar> consumer) { 2218 return regex.matches(line, matcher -> consumer.accept(new CMakeSimpleVar(matcher))); 2219 } 2220 } 2221 2222 public record CMakeContentVar(String name, String value) implements CMakeVar { 2223 static final Regex startRegex = Regex.of("^_*(?:CMAKE_)?([A-Za-z0-9_]+)=\\{<\\{(.*)$"); 2224 static final Regex endRegex = Regex.of("^(.*)\\}>\\}$"); 2225 } 2226 2227 public record CMakeRecipeVar(String name, String value) implements CMakeVar<String> { 2228 static final Regex varPattern = Regex.of("<([^>]*)>"); 2229 static final Regex regex = Regex.of("^_*(?:CMAKE_)?([A-Za-z0-9_]+)=\\{<\\{<(.*)>\\}>\\}$"); 2230 2231 CMakeRecipeVar(Matcher matcher) { 2232 this( 2233 "CMAKE_" + matcher.group(1).trim(), 2234 "<" + ((matcher.group(2).isEmpty()) ? "" : matcher.group(2).trim()) + ">"); 2235 } 2236 2237 public String expandRecursively(Map<String, CMakeVar<?>> varMap, String value) { // recurse 2238 String result = value; 2239 if (varPattern.pattern().matcher(value) instanceof Matcher matcher && matcher.find()) { 2240 var v = matcher.group(1); 2241 if (varMap.containsKey(v)) { 2242 String replacement = varMap.get(v).value().toString(); 2243 result = 2244 expandRecursively( 2245 varMap, 2246 value.substring(0, matcher.start()) 2247 + replacement 2248 + value.substring(matcher.end())); 2249 } 2250 } 2251 return result; 2252 } 2253 2254 public String expand(Map<String, CMakeVar<?>> vars) { 2255 return expandRecursively(vars, value()); 2256 } 2257 2258 static boolean onMatch(String line, String comment, Consumer<CMakeRecipeVar> consumer) { 2259 return regex.matches(line, matcher -> consumer.accept(new CMakeRecipeVar(matcher))); 2260 } 2261 } 2262 2263 BuildDir dir; 2264 2265 Map<String, CMakeVar<?>> varMap = new HashMap<>(); 2266 2267 public CMakeProbe(BuildDir dir, Capabilities capabilities) { 2268 this.dir = BuildDir.of(dir.path("cmakeprobe")); 2269 this.dir.clean(); 2270 2271 try { 2272 this.dir.cmakeLists(cmakeLists -> { 2273 cmakeLists.append( 2274 """ 2275 cmake_minimum_required(VERSION 3.21) 2276 project(cmakeprobe) 2277 set(CMAKE_CXX_STANDARD 14) 2278 """ 2279 ); 2280 2281 capabilities.capabilities() 2282 .filter(capability -> capability instanceof Capabilities.CMakeProbeable) 2283 .map(capability -> (Capabilities.CMakeProbeable) capability) 2284 .forEach(p -> 2285 cmakeLists.append(p.cmakeStanza()).append("\n") 2286 ); 2287 cmakeLists.append( 2288 """ 2289 get_cmake_property(_variableNames VARIABLES ${VarNames}) 2290 foreach(VarName ${_variableNames}) 2291 message("${VarName}={<{${${VarName}}}>}") 2292 endforeach() 2293 """ 2294 ); 2295 }); 2296 2297 var cmakeProcessBuilder = 2298 new ProcessBuilder() 2299 .directory(this.dir.path().toFile()) 2300 .redirectErrorStream(true) 2301 .command("cmake", "-LAH") 2302 .start(); 2303 List<String> stdinlines = 2304 new BufferedReader(new InputStreamReader(cmakeProcessBuilder.getInputStream())) 2305 .lines() 2306 .toList(); 2307 cmakeProcessBuilder.waitFor(); 2308 this.dir.textFile("rawlines", sb -> { 2309 stdinlines.forEach(line -> sb.append(line).append("\n")); 2310 // stderrlines.forEach(line-> sb.append("ERR").append(line).append("\n")); 2311 }); 2312 2313 String comment = null; 2314 String contentName = null; 2315 StringBuilder content = null; 2316 2317 for (String line : stdinlines) { 2318 if (line.startsWith("//")) { 2319 comment = line; 2320 content = null; 2321 2322 } else if (comment != null) { 2323 if (CMakeTypedVar.onMatch( 2324 line, 2325 comment, 2326 v -> { 2327 if (varMap.containsKey(v.name())) { 2328 var theVar = varMap.get(v.name()); 2329 if (theVar.value().equals(v.value())) { 2330 /* println( 2331 "replacing duplicate variable with typed variant with the name same value" 2332 + v 2333 + theVar);*/ 2334 } else { 2335 throw new IllegalStateException( 2336 "Duplicate variable name different value: " + v + theVar); 2337 } 2338 varMap.put(v.name(), v); 2339 } else { 2340 varMap.put(v.name(), v); 2341 } 2342 })) { 2343 } else { 2344 println("failed to parse " + line); 2345 } 2346 comment = null; 2347 content = null; 2348 contentName = null; 2349 } else if (!line.isEmpty()) { 2350 if (content != null) { 2351 if (CMakeContentVar.endRegex.pattern().matcher(line) instanceof Matcher matcher 2352 && matcher.matches()) { 2353 content.append("\n").append(matcher.group(1)); 2354 var v = new CMakeContentVar(contentName, content.toString()); 2355 contentName = null; 2356 content = null; 2357 varMap.put(v.name(), v); 2358 } else { 2359 content.append("\n").append(line); 2360 } 2361 } else if (!line.endsWith("}>}") 2362 && CMakeContentVar.startRegex.pattern().matcher(line) instanceof Matcher matcher 2363 && matcher.matches()) { 2364 contentName = "CMAKE_" + matcher.group(1); 2365 content = new StringBuilder(matcher.group(2)); 2366 } else if (CMakeRecipeVar.regex.pattern().matcher(line) instanceof Matcher matcher 2367 && matcher.matches()) { 2368 CMakeVar<String> v = new CMakeRecipeVar(matcher); 2369 if (varMap.containsKey(v.name())) { 2370 var theVar = varMap.get(v.name()); 2371 if (theVar.value().equals(v.value())) { 2372 // println("Skipping duplicate variable name different value: " + v + theVar); 2373 } else { 2374 throw new IllegalStateException( 2375 "Duplicate variable name different value: " + v + theVar); 2376 } 2377 varMap.put(v.name(), v); 2378 } else { 2379 varMap.put(v.name(), v); 2380 } 2381 } else if (CMakeSimpleVar.regex.pattern().matcher(line) instanceof Matcher matcher 2382 && matcher.matches()) { 2383 var v = new CMakeSimpleVar(matcher); 2384 if (varMap.containsKey(v.name())) { 2385 var theVar = varMap.get(v.name()); 2386 if (theVar.value().equals(v.value())) { 2387 // println("Skipping duplicate variable name different value: " + v + theVar); 2388 } else { 2389 //throw new IllegalStateException( 2390 // "Duplicate variable name different vars: " + v + theVar); 2391 } 2392 // note we don't replace a Typed with a Simple 2393 } else { 2394 varMap.put(v.name(), v); 2395 } 2396 } else { 2397 // println("Skipping " + line); 2398 } 2399 } 2400 } 2401 2402 } catch (IOException ioe) { 2403 throw new RuntimeException(ioe); 2404 } catch (InterruptedException e) { 2405 throw new RuntimeException(e); 2406 } 2407 this.dir.textFile("vars", sb -> { 2408 varMap.values().forEach(v -> sb.append(v.name()).append("<{<").append(v.value().toString()).append(">}>").append("\n")); 2409 }); 2410 2411 capabilities 2412 .capabilities() 2413 .filter(capability -> capability instanceof Capabilities.CMakeProbeable) 2414 .map(capability -> (Capabilities.CMakeProbeable) capability) 2415 .forEach(capability -> capability.accept(this)); 2416 2417 } 2418 2419 ObjectFile cxxCompileObject( 2420 ObjectFile target, CppSourceFile source, List<String> frameworks) { 2421 CMakeRecipeVar compileObject = (CMakeRecipeVar) varMap.get("CMAKE_CXX_COMPILE_OBJECT"); 2422 Map<String, CMakeVar<?>> localVars = new HashMap<>(varMap); 2423 localVars.put("DEFINES", new CMakeSimpleVar("DEFINES", "")); 2424 localVars.put("INCLUDES", new CMakeSimpleVar("INCLUDES", "")); 2425 localVars.put("FLAGS", new CMakeSimpleVar("FLAGS", "")); 2426 localVars.put("OBJECT", new CMakeSimpleVar("OBJECT", target.path().toString())); 2427 localVars.put("SOURCE", new CMakeSimpleVar("SOURCE", source.path().toString())); 2428 String executable = compileObject.expand(localVars); 2429 println(executable); 2430 return target; 2431 } 2432 2433 ExecutableFile cxxLinkExecutable( 2434 ExecutableFile target, List<ObjectFile> objFiles, List<String> frameworks) { 2435 CMakeRecipeVar linkExecutable = (CMakeRecipeVar) varMap.get("CMAKE_CXX_LINK_EXECUTABLE"); 2436 Map<String, CMakeVar<?>> localVars = new HashMap<>(varMap); 2437 String executable = linkExecutable.expand(localVars); 2438 println(executable); 2439 return target; 2440 } 2441 2442 SharedLibraryFile cxxCreateSharedLibrary( 2443 SharedLibraryFile target, List<ObjectFile> objFiles, List<String> frameworks) { 2444 CMakeRecipeVar createSharedLibrary = 2445 (CMakeRecipeVar) varMap.get("CMAKE_CXX_CREATE_SHARED_LIBRARY"); 2446 Map<String, CMakeVar<?>> localVars = new HashMap<>(varMap); 2447 String executable = createSharedLibrary.expand(localVars); 2448 println(executable); 2449 return target; 2450 } 2451 2452 2453 public String value(String key) { 2454 var v = varMap.get(key); 2455 return v.value().toString(); 2456 } 2457 2458 public boolean hasKey(String includeDirKey) { 2459 return varMap.containsKey(includeDirKey); 2460 } 2461 2462 } 2463 2464 public interface CapabilityHolder { 2465 Capabilities.Capability capability(); 2466 } 2467 2468 public static class Capabilities { 2469 2470 interface Probe { 2471 2472 } 2473 2474 public static abstract class Capability implements CapabilityHolder { 2475 final public String name; 2476 2477 protected Capability(String name) { 2478 this.name = name; 2479 } 2480 2481 public String name() { 2482 return name; 2483 } 2484 2485 public abstract boolean available(); 2486 2487 @Override 2488 public Capability capability() { 2489 return this; 2490 } 2491 } 2492 2493 public interface CMakeProbeable extends Consumer<Bldr.CMakeProbe> { 2494 2495 // void setCmakeProbe(Bldr.CMakeProbe cmakeProbe); 2496 String cmakeStanza(); 2497 } 2498 2499 public interface Jextractable { 2500 2501 2502 String name(); 2503 2504 2505 default BuildDir stage(BuildDir stage) { 2506 return stage.buildDir(packageName() + "_jextracted"); 2507 } 2508 2509 2510 default String packageName() { 2511 return name().toLowerCase(); 2512 } 2513 2514 2515 default String headerClassName() { 2516 return packageName() + "_h"; 2517 } 2518 2519 default JarFile jarFile(BuildDir buildDir) { 2520 return buildDir.jarFile("hat-jextracted-"+packageName() + "-1.0.jar"); 2521 } 2522 2523 void inversionOfControl(JExtractBuilder jextractBuilder); 2524 } 2525 2526 public Map<String, Capability> capabilityMap = new HashMap<>(); 2527 2528 public static Capabilities of(CapabilityHolder... capabilityHolders) { 2529 return new Capabilities(capabilityHolders); 2530 } 2531 2532 public Stream<Capability> capabilities() { 2533 return capabilityMap.values().stream(); 2534 } 2535 2536 public Stream<Capability> capabilities(Predicate<Capability> filter) { 2537 return capabilities().filter(filter); 2538 } 2539 2540 public boolean capabilityIsAvailable(String name) { 2541 return capabilities().anyMatch(c -> c.name.equalsIgnoreCase(name)); 2542 } 2543 2544 private Capabilities(CapabilityHolder... capabilityHolders) { 2545 List.of(capabilityHolders).forEach(capabilityHolder -> 2546 capabilityMap.put(capabilityHolder.capability().name, capabilityHolder.capability()) 2547 ); 2548 2549 } 2550 2551 public static final class OpenCL extends Capability implements CMakeProbeable, Jextractable { 2552 public static String includeDirKey = "CMAKE_OpenCL_INCLUDE_DIR"; 2553 public static String libKey = "CMAKE_OpenCL_LIBRARY"; 2554 public static String foundKey = "CMAKE_OPENCL_FOUND"; 2555 public static String osxSysroot = "CMAKE_OSX_SYSROOT"; 2556 2557 public OpenCL() { 2558 super("OpenCL"); 2559 } 2560 2561 public static OpenCL of() { 2562 return new OpenCL(); 2563 } 2564 2565 @Override 2566 public String cmakeStanza() { 2567 return 2568 """ 2569 find_package(OpenCL) 2570 if(OPENCL_FOUND) 2571 if (APPLE) 2572 set(OPENCL_INCLUDE_DIR "-framework OpenCL") 2573 set(OPENCL_LIBRARY_DIR "-framework OpenCL") 2574 else() 2575 set(OPENCL_LIB "OpenCL") 2576 endif() 2577 endif() 2578 """; 2579 } 2580 2581 public String appLibFrameworks() { 2582 return cmakeProbe.value(osxSysroot); 2583 } 2584 2585 @Override 2586 public boolean available() { 2587 return cmakeProbe.hasKey(foundKey) && cmakeProbe.value(foundKey).equals("TRUE"); 2588 } 2589 2590 public String lib() { 2591 return cmakeProbe.value(libKey); 2592 } 2593 2594 public String includeDir() { 2595 return cmakeProbe.value(includeDirKey); 2596 } 2597 2598 public Bldr.CMakeProbe cmakeProbe; 2599 2600 @Override 2601 public void accept(Bldr.CMakeProbe cmakeProbe) { 2602 this.cmakeProbe = cmakeProbe; 2603 } 2604 2605 2606 @Override 2607 public void inversionOfControl(JExtractBuilder jextractBuilder) { 2608 jextractBuilder.os(mac -> jextractBuilder 2609 .compile_flag("-F" 2610 + appLibFrameworks() + "/System/library/Frameworks") 2611 .library(mac.frameworkLibrary("OpenCL")) 2612 .header(Path.of(includeDir()).resolve("Headers/opencl.h")), 2613 linux -> { 2614 throw new IllegalStateException("Linux not handled yet"); 2615 }); 2616 } 2617 } 2618 2619 public static final class OpenGL extends Capability implements CMakeProbeable, Jextractable { 2620 public static String glutIncludeDirKey = "CMAKE_GLUT_INCLUDE_DIR"; 2621 public static String openGLIncludeDirKey = "CMAKE_OPENGL_INCLUDE_DIR"; 2622 public static String libKey = "CMAKE_OPENGL_LIBRARY"; 2623 public static String osxSysroot = "CMAKE_OSX_SYSROOT"; 2624 2625 public OpenGL() { 2626 super("OpenGL"); 2627 } 2628 2629 public static OpenGL of() { 2630 return new OpenGL(); 2631 } 2632 2633 @Override 2634 public boolean available() { 2635 return cmakeProbe.hasKey(openGLIncludeDirKey); 2636 } 2637 2638 public DirEntry openglIncludeDir() { 2639 return DirEntry.of(Path.of(cmakeProbe.value(openGLIncludeDirKey)) + "/Headers"); 2640 } 2641 2642 public DirEntry glutIncludeDir() { 2643 return DirEntry.of(cmakeProbe.value(osxSysroot)+"/System/Library/Frameworks/GLUT.framework/Headers"); 2644 } 2645 2646 public String appLibFrameworks() { 2647 return cmakeProbe.value(osxSysroot); 2648 } 2649 2650 public String lib() { 2651 return cmakeProbe.value(libKey); 2652 } 2653 2654 public Path lib(String frameworkName) { 2655 return Path.of(cmakeProbe.value(libKey).split(";")[0]).resolve(frameworkName + ".framework/" + frameworkName); 2656 } 2657 2658 @Override 2659 public String cmakeStanza() { 2660 return 2661 """ 2662 find_package(OpenGL) 2663 if(OPENGL_FOUND) 2664 if (APPLE) 2665 set(OPENGL_FRAMEWORK "-framework OpenGL") 2666 else() 2667 set(OPENCL_LIB "OpenCL") 2668 endif() 2669 else() 2670 message("NO OPENGL FOUND") 2671 endif() 2672 """; 2673 } 2674 2675 public Bldr.CMakeProbe cmakeProbe; 2676 2677 @Override 2678 public void accept(Bldr.CMakeProbe cmakeProbe) { 2679 2680 this.cmakeProbe = cmakeProbe; 2681 /* 2682 cmakeProbe.varMap.forEach((k, v) -> { 2683 if (k.toUpperCase().contains("OPENGL")) { 2684 println(k); 2685 } 2686 if (k.toUpperCase().contains("GLUT")) { 2687 println(k); 2688 } 2689 }); */ 2690 2691 } 2692 @Override public void inversionOfControl(JExtractBuilder jextractBuilder){ 2693 jextractBuilder 2694 .os(mac -> jextractBuilder 2695 .compile_flag("-F" 2696 + appLibFrameworks() + "/System/library/Frameworks") 2697 .library(mac.frameworkLibrary("OpenGL")) 2698 .library(mac.frameworkLibrary("GLUT")) 2699 .header(glutIncludeDir().dir("glut.h").path()), 2700 linux -> { 2701 throw new IllegalStateException("Linux not handled yet"); 2702 } 2703 ); 2704 } 2705 } 2706 2707 public static final class HIP extends Capability implements CMakeProbeable, Jextractable { 2708 public HIP() { 2709 super("HIP"); 2710 } 2711 2712 public static HIP of() { 2713 return new HIP(); 2714 } 2715 2716 @Override 2717 public boolean available() { 2718 return false; 2719 } 2720 2721 @Override 2722 public String cmakeStanza() { 2723 return 2724 """ 2725 find_package(HIP) 2726 if(HIP_FOUND) 2727 2728 else() 2729 message("NO HIP FOUND") 2730 endif() 2731 """; 2732 } 2733 2734 public Bldr.CMakeProbe cmakeProbe; 2735 2736 @Override 2737 public void accept(Bldr.CMakeProbe cmakeProbe) { 2738 2739 this.cmakeProbe = cmakeProbe; 2740 } 2741 2742 @Override 2743 public void inversionOfControl(JExtractBuilder jextractBuilder) { 2744 2745 } 2746 } 2747 2748 public static final class CUDA extends Capability implements CMakeProbeable, Jextractable { 2749 public static String sdkRootDirKey = "CMAKE_CUDA_SDK_ROOT_DIR"; 2750 public static String sdkRootDirNotFoundValue = "CUDA_SDK_ROOT_DIR-NOTFOUND"; 2751 2752 public CUDA() { 2753 super("CUDA"); 2754 } 2755 2756 public static CUDA of() { 2757 return new CUDA(); 2758 } 2759 2760 @Override 2761 public boolean available() { 2762 return cmakeProbe.hasKey(sdkRootDirKey) && !cmakeProbe.value(sdkRootDirKey).equals(sdkRootDirNotFoundValue); 2763 } 2764 2765 @Override 2766 public String cmakeStanza() { 2767 return 2768 """ 2769 find_package(CUDAToolkit) 2770 if(CUDAToolkit_FOUND) 2771 set(CUDA_FOUND true) 2772 set(CUDA_INCLUDE_DIR ${CUDAToolkit_INCLUDE_DIR}) 2773 set(CUDA_LIBRARY_DIR ${CUDAToolkit_LIBRARY_DIR}) 2774 set(CUDA_LIBRARIES "-lcudart -lcuda") 2775 else() 2776 message("NO CUDA FOUND") 2777 endif() 2778 """; 2779 } 2780 2781 public Bldr.CMakeProbe cmakeProbe; 2782 2783 @Override 2784 public void accept(Bldr.CMakeProbe cmakeProbe) { 2785 this.cmakeProbe = cmakeProbe; 2786 } 2787 2788 @Override 2789 public void inversionOfControl(JExtractBuilder jextractBuilder) { 2790 2791 } 2792 } 2793 2794 public static final class JExtract extends Capability implements Executable { 2795 public JExtractExecutable executable; 2796 2797 JExtract() { 2798 super("JExtract"); 2799 var optionalExe = fromPATH("jextract"); 2800 if (optionalExe.isEmpty()) { 2801 // println("jextract not in path"); 2802 } else { 2803 executable = JExtractExecutable.of(optionalExe.get()); 2804 } 2805 2806 } 2807 2808 JExtract(Path executable) { 2809 super("JExtract"); 2810 this.executable = JExtractExecutable.of(executable); 2811 } 2812 2813 @Override 2814 public boolean available() { 2815 return executable != null && executable.exists(); 2816 } 2817 2818 public static JExtract of() { 2819 return new JExtract(); 2820 } 2821 2822 public static JExtract of(Path executable) { 2823 return new JExtract(executable); 2824 } 2825 2826 2827 @Override 2828 public Path path() { 2829 return executable.path; 2830 } 2831 } 2832 2833 public static final class CMake extends Capability implements Executable { 2834 public JExtractExecutable executable; 2835 public Bldr.CMakeProbe cmakeProbe; 2836 2837 CMake() { 2838 super("CMake"); 2839 var optionalExe = fromPATH("cmake"); 2840 if (optionalExe.isEmpty()) { 2841 println("cmake not in path"); 2842 } else { 2843 executable = JExtractExecutable.of(optionalExe.get()); 2844 } 2845 } 2846 2847 @Override 2848 public boolean available() { 2849 return executable != null && executable.exists(); 2850 } 2851 2852 public static CMake of() { 2853 return new CMake(); 2854 } 2855 2856 public void probe(BuildDir buildDir, Capabilities capabilities) { 2857 this.cmakeProbe = new Bldr.CMakeProbe(buildDir, capabilities); 2858 } 2859 2860 @Override 2861 public Path path() { 2862 return executable.path(); 2863 } 2864 } 2865 2866 } 2867 2868 public record Regex(Pattern pattern) { 2869 Regex(String regex) { 2870 this(Pattern.compile(regex)); 2871 } 2872 2873 public static Regex of(String regexString) { 2874 return new Regex(regexString); 2875 } 2876 2877 boolean matches(String text, Consumer<Matcher> matcherConsumer) { 2878 if (pattern().matcher(text) instanceof Matcher matcher && matcher.matches()) { 2879 matcherConsumer.accept(matcher); 2880 return true; 2881 } else { 2882 return false; 2883 } 2884 } 2885 } 2886 2887 public static class XMLNode { 2888 Element element; 2889 List<XMLNode> children = new ArrayList<>(); 2890 Map<String, String> attrMap = new HashMap<>(); 2891 2892 public static class AbstractXMLBuilder<T extends AbstractXMLBuilder<T>> { 2893 final public Element element; 2894 2895 @SuppressWarnings("unchecked") 2896 public T self() { 2897 return (T) this; 2898 } 2899 2900 public T attr(String name, String value) { 2901 element.setAttribute(name, value); 2902 return self(); 2903 } 2904 2905 public T attr(URI uri, String name, String value) { 2906 element.setAttributeNS(uri.toString(), name, value); 2907 return self(); 2908 } 2909 2910 public T element(String name, Function<Element, T> factory, Consumer<T> xmlBuilderConsumer) { 2911 var node = element.getOwnerDocument().createElement(name); 2912 element.appendChild(node); 2913 var builder = factory.apply(node); 2914 xmlBuilderConsumer.accept(builder); 2915 return self(); 2916 } 2917 2918 public T element( 2919 URI uri, String name, Function<Element, T> factory, Consumer<T> xmlBuilderConsumer) { 2920 var node = element.getOwnerDocument().createElementNS(uri.toString(), name); 2921 element.appendChild(node); 2922 var builder = factory.apply(node); 2923 xmlBuilderConsumer.accept(builder); 2924 return self(); 2925 } 2926 2927 AbstractXMLBuilder(Element element) { 2928 this.element = element; 2929 } 2930 2931 public T text(String thisText) { 2932 var node = element.getOwnerDocument().createTextNode(thisText); 2933 element.appendChild(node); 2934 return self(); 2935 } 2936 2937 public T comment(String thisComment) { 2938 var node = element.getOwnerDocument().createComment(thisComment); 2939 element.appendChild(node); 2940 return self(); 2941 } 2942 2943 <L> T forEach(List<L> list, BiConsumer<T, L> biConsumer) { 2944 list.forEach(l -> biConsumer.accept(self(), l)); 2945 return self(); 2946 } 2947 2948 <L> T forEach(Stream<L> stream, BiConsumer<T, L> biConsumer) { 2949 stream.forEach(l -> biConsumer.accept(self(), l)); 2950 return self(); 2951 } 2952 2953 <L> T forEach(Stream<L> stream, Consumer<L> consumer) { 2954 stream.forEach(consumer); 2955 return self(); 2956 } 2957 2958 protected T then(Consumer<T> xmlBuilderConsumer) { 2959 xmlBuilderConsumer.accept(self()); 2960 return self(); 2961 } 2962 } 2963 2964 public static class PomXmlBuilder extends AbstractXMLBuilder<PomXmlBuilder> { 2965 PomXmlBuilder(Element element) { 2966 super(element); 2967 } 2968 2969 public PomXmlBuilder element(String name, Consumer<PomXmlBuilder> xmlBuilderConsumer) { 2970 return element(name, PomXmlBuilder::new, xmlBuilderConsumer); 2971 } 2972 2973 public PomXmlBuilder element(URI uri, String name, Consumer<PomXmlBuilder> xmlBuilderConsumer) { 2974 return element(uri, name, PomXmlBuilder::new, xmlBuilderConsumer); 2975 } 2976 2977 public PomXmlBuilder modelVersion(String s) { 2978 return element("modelVersion", $ -> $.text(s)); 2979 } 2980 2981 public PomXmlBuilder pom(String groupId, String artifactId, String version) { 2982 return modelVersion("4.0.0").packaging("pom").ref(groupId, artifactId, version); 2983 } 2984 2985 public PomXmlBuilder jar(String groupId, String artifactId, String version) { 2986 return modelVersion("4.0.0").packaging("jar").ref(groupId, artifactId, version); 2987 } 2988 2989 public PomXmlBuilder groupId(String s) { 2990 return element("groupId", $ -> $.text(s)); 2991 } 2992 2993 public PomXmlBuilder artifactId(String s) { 2994 return element("artifactId", $ -> $.text(s)); 2995 } 2996 2997 public PomXmlBuilder packaging(String s) { 2998 return element("packaging", $ -> $.text(s)); 2999 } 3000 3001 public PomXmlBuilder version(String s) { 3002 return element("version", $ -> $.text(s)); 3003 } 3004 3005 public PomXmlBuilder build(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3006 return element("build", pomXmlBuilderConsumer); 3007 } 3008 3009 public PomXmlBuilder plugins(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3010 return element("plugins", pomXmlBuilderConsumer); 3011 } 3012 3013 public PomXmlBuilder plugin( 3014 String groupId, 3015 String artifactId, 3016 String version, 3017 Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3018 return element( 3019 "plugin", $ -> $.ref(groupId, artifactId, version).then(pomXmlBuilderConsumer)); 3020 } 3021 3022 public PomXmlBuilder antPlugin(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3023 return plugin( 3024 "org.apache.maven.plugins", 3025 "maven-antrun-plugin", 3026 "1.8", 3027 pomXmlBuilderConsumer); 3028 } 3029 3030 public PomXmlBuilder surefirePlugin(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3031 return plugin( 3032 "org.apache.maven.plugins", 3033 "maven-surefire-plugin", 3034 "3.1.2", 3035 pomXmlBuilderConsumer); 3036 } 3037 3038 public PomXmlBuilder compilerPlugin( 3039 Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3040 return plugin( 3041 "org.apache.maven.plugins", 3042 "maven-compiler-plugin", 3043 "3.11.0", pomXmlBuilderConsumer 3044 ); 3045 } 3046 3047 public PomXmlBuilder execPlugin(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3048 return plugin("org.codehaus.mojo", "exec-maven-plugin", "3.1.0", pomXmlBuilderConsumer); 3049 } 3050 3051 3052 public PomXmlBuilder plugin( 3053 String groupId, String artifactId, Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3054 return element("plugin", $ -> $.groupIdArtifactId(groupId, artifactId).then(pomXmlBuilderConsumer)); 3055 } 3056 3057 public PomXmlBuilder plugin(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3058 return element("plugin", pomXmlBuilderConsumer); 3059 } 3060 3061 public PomXmlBuilder parent(String groupId, String artifactId, String version) { 3062 return parent(parent -> parent.ref(groupId, artifactId, version)); 3063 } 3064 3065 public PomXmlBuilder parent(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3066 return element("parent", pomXmlBuilderConsumer); 3067 } 3068 3069 public PomXmlBuilder pluginManagement(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3070 return element("pluginManagement", pomXmlBuilderConsumer); 3071 } 3072 3073 public PomXmlBuilder file(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3074 return element("file", pomXmlBuilderConsumer); 3075 } 3076 3077 public PomXmlBuilder activation(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3078 return element("activation", pomXmlBuilderConsumer); 3079 } 3080 3081 public PomXmlBuilder profiles(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3082 return element("profiles", pomXmlBuilderConsumer); 3083 } 3084 3085 public PomXmlBuilder profile(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3086 return element("profile", pomXmlBuilderConsumer); 3087 } 3088 3089 public PomXmlBuilder arguments(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3090 return element("arguments", pomXmlBuilderConsumer); 3091 } 3092 3093 public PomXmlBuilder executions(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3094 return element("executions", pomXmlBuilderConsumer); 3095 } 3096 3097 public PomXmlBuilder execution(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3098 return element("execution", pomXmlBuilderConsumer); 3099 } 3100 3101 public PomXmlBuilder execIdPhaseConf( 3102 String id, String phase, Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3103 return execution(execution -> execution.id(id).phase(phase).goals(gs -> gs.goal("exec")).configuration(pomXmlBuilderConsumer)); 3104 } 3105 3106 public PomXmlBuilder exec( 3107 String phase, String executable, Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3108 return execIdPhaseConf( 3109 executable + "-" + phase, 3110 phase, 3111 conf -> conf.executable(executable).arguments(pomXmlBuilderConsumer)); 3112 } 3113 3114 public PomXmlBuilder cmake( 3115 String id, String phase, Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3116 return execIdPhaseConf( 3117 id, phase, conf -> conf.executable("cmake").arguments(pomXmlBuilderConsumer)); 3118 } 3119 3120 public PomXmlBuilder cmake(String id, String phase, String... args) { 3121 return execIdPhaseConf( 3122 id, 3123 phase, 3124 conf -> 3125 conf.executable("cmake") 3126 .arguments(arguments -> arguments.forEach(Stream.of(args), arguments::argument))); 3127 } 3128 3129 public PomXmlBuilder jextract(String id, String phase, String... args) { 3130 return execIdPhaseConf( 3131 id, 3132 phase, 3133 conf -> 3134 conf.executable("jextract") 3135 .arguments(arguments -> arguments.forEach(Stream.of(args), arguments::argument))); 3136 } 3137 3138 public PomXmlBuilder ant( 3139 String id, String phase, String goal, Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3140 return execution(execution -> execution 3141 .id(id) 3142 .phase(phase) 3143 .goals(gs -> gs.goal(goal)) 3144 .configuration(configuration -> configuration.target(pomXmlBuilderConsumer))); 3145 } 3146 3147 public PomXmlBuilder goals(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3148 return element("goals", pomXmlBuilderConsumer); 3149 } 3150 3151 public PomXmlBuilder target(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3152 return element("target", pomXmlBuilderConsumer); 3153 } 3154 3155 public PomXmlBuilder configuration(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3156 return element("configuration", pomXmlBuilderConsumer); 3157 } 3158 3159 public PomXmlBuilder compilerArgs(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3160 return element("compilerArgs", pomXmlBuilderConsumer); 3161 } 3162 3163 public PomXmlBuilder compilerArgs(String... args) { 3164 return element("compilerArgs", $ -> $.forEach(Stream.of(args), $::arg)); 3165 } 3166 3167 public PomXmlBuilder properties(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3168 return element("properties", pomXmlBuilderConsumer); 3169 } 3170 3171 public PomXmlBuilder dependencies(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3172 return element("dependencies", pomXmlBuilderConsumer); 3173 } 3174 3175 public PomXmlBuilder dependsOn(String groupId, String artifactId, String version) { 3176 return element("dependencies", $ -> $.dependency(groupId, artifactId, version)); 3177 } 3178 3179 public PomXmlBuilder dependsOn(String groupId, String artifactId, String version, String phase) { 3180 return element("dependencies", $ -> $.dependency(groupId, artifactId, version, phase)); 3181 } 3182 3183 public PomXmlBuilder dependency(String groupId, String artifactId, String version) { 3184 return dependency($ -> $.ref(groupId, artifactId, version)); 3185 } 3186 3187 public PomXmlBuilder dependency( 3188 String groupId, String artifactId, String version, String scope) { 3189 return dependency($ -> $.ref(groupId, artifactId, version).scope(scope)); 3190 } 3191 3192 public PomXmlBuilder dependency(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3193 return element("dependency", pomXmlBuilderConsumer); 3194 } 3195 3196 public PomXmlBuilder modules(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3197 return element("modules", pomXmlBuilderConsumer); 3198 } 3199 3200 public PomXmlBuilder modules(List<String> modules) { 3201 return element("modules", $ -> $.forEach(modules.stream(), $::module)); 3202 } 3203 3204 public PomXmlBuilder modules(String... modules) { 3205 return modules(List.of(modules)); 3206 } 3207 3208 public PomXmlBuilder module(String name) { 3209 return element("module", $ -> $.text(name)); 3210 } 3211 3212 public PomXmlBuilder property(String name, String value) { 3213 return element(name, $ -> $.text(value)); 3214 } 3215 3216 public PomXmlBuilder antproperty(String name, String value) { 3217 return element("property", $ -> $.attr("name", name).attr("value", value)); 3218 } 3219 3220 public PomXmlBuilder scope(String s) { 3221 return element("scope", $ -> $.text(s)); 3222 } 3223 3224 public PomXmlBuilder phase(String s) { 3225 return element("phase", $ -> $.text(s)); 3226 } 3227 3228 public PomXmlBuilder argument(String s) { 3229 return element("argument", $ -> $.text(s)); 3230 } 3231 3232 public PomXmlBuilder goal(String s) { 3233 return element("goal", $ -> $.text(s)); 3234 } 3235 3236 public PomXmlBuilder copy(String file, String toDir) { 3237 return element("copy", $ -> $.attr("file", file).attr("toDir", toDir)); 3238 } 3239 3240 public PomXmlBuilder antjar(String basedir, String include, String destfile) { 3241 return element("jar", $ -> $.attr("basedir", basedir).attr("includes", include + "/**").attr("destfile", destfile)); 3242 } 3243 3244 public PomXmlBuilder echo(String message) { 3245 return element("echo", $ -> $.attr("message", message)); 3246 } 3247 3248 public PomXmlBuilder echo(String filename, String message) { 3249 return element("echo", $ -> $.attr("message", message).attr("file", filename)); 3250 } 3251 3252 public PomXmlBuilder mkdir(String dirName) { 3253 return element("mkdir", $ -> $.attr("dir", dirName)); 3254 } 3255 3256 public PomXmlBuilder groupIdArtifactId(String groupId, String artifactId) { 3257 return groupId(groupId).artifactId(artifactId); 3258 } 3259 3260 public PomXmlBuilder ref(String groupId, String artifactId, String version) { 3261 return groupIdArtifactId(groupId, artifactId).version(version); 3262 } 3263 3264 public PomXmlBuilder skip(String string) { 3265 return element("skip", $ -> $.text(string)); 3266 } 3267 3268 public PomXmlBuilder id(String s) { 3269 return element("id", $ -> $.text(s)); 3270 } 3271 3272 public PomXmlBuilder arg(String s) { 3273 return element("arg", $ -> $.text(s)); 3274 } 3275 3276 public PomXmlBuilder argLine(String s) { 3277 return element("argLine", $ -> $.text(s)); 3278 } 3279 3280 public PomXmlBuilder source(String s) { 3281 return element("source", $ -> $.text(s)); 3282 } 3283 3284 public PomXmlBuilder target(String s) { 3285 return element("target", $ -> $.text(s)); 3286 } 3287 3288 public PomXmlBuilder showWarnings(String s) { 3289 return element("showWarnings", $ -> $.text(s)); 3290 } 3291 3292 public PomXmlBuilder showDeprecation(String s) { 3293 return element("showDeprecation", $ -> $.text(s)); 3294 } 3295 3296 public PomXmlBuilder failOnError(String s) { 3297 return element("failOnError", $ -> $.text(s)); 3298 } 3299 3300 public PomXmlBuilder exists(String s) { 3301 return element("exists", $ -> $.text(s)); 3302 } 3303 3304 public PomXmlBuilder activeByDefault(String s) { 3305 return element("activeByDefault", $ -> $.text(s)); 3306 } 3307 3308 public PomXmlBuilder executable(String s) { 3309 return element("executable", $ -> $.text(s)); 3310 } 3311 3312 public PomXmlBuilder workingDirectory(String s) { 3313 return element("workingDirectory", $ -> $.text(s)); 3314 } 3315 } 3316 3317 public static class ImlBuilder extends AbstractXMLBuilder<ImlBuilder> { 3318 3319 ImlBuilder(Element element) { 3320 super(element); 3321 } 3322 3323 public ImlBuilder element(String name, Consumer<ImlBuilder> xmlBuilderConsumer) { 3324 return element(name, ImlBuilder::new, xmlBuilderConsumer); 3325 } 3326 3327 public ImlBuilder element(URI uri, String name, Consumer<ImlBuilder> xmlBuilderConsumer) { 3328 return element(uri, name, ImlBuilder::new, xmlBuilderConsumer); 3329 } 3330 3331 public ImlBuilder modelVersion(String s) { 3332 return element("modelVersion", $ -> $.text(s)); 3333 } 3334 3335 public ImlBuilder groupId(String s) { 3336 return element("groupId", $ -> $.text(s)); 3337 } 3338 3339 public ImlBuilder artifactId(String s) { 3340 return element("artifactId", $ -> $.text(s)); 3341 } 3342 3343 public ImlBuilder packaging(String s) { 3344 return element("packaging", $ -> $.text(s)); 3345 } 3346 3347 public ImlBuilder version(String s) { 3348 return element("version", $ -> $.text(s)); 3349 } 3350 3351 public ImlBuilder build(Consumer<ImlBuilder> pomXmlBuilderConsumer) { 3352 return element("build", pomXmlBuilderConsumer); 3353 } 3354 3355 public ImlBuilder plugins(Consumer<ImlBuilder> pomXmlBuilderConsumer) { 3356 return element("plugins", pomXmlBuilderConsumer); 3357 } 3358 3359 public ImlBuilder plugin( 3360 String groupId, 3361 String artifactId, 3362 String version, 3363 Consumer<ImlBuilder> pomXmlBuilderConsumer) { 3364 return element( 3365 "plugin", 3366 $ -> 3367 $.groupIdArtifactIdVersion(groupId, artifactId, version).then(pomXmlBuilderConsumer)); 3368 } 3369 3370 public ImlBuilder plugin( 3371 String groupId, String artifactId, Consumer<ImlBuilder> pomXmlBuilderConsumer) { 3372 return element( 3373 "plugin", $ -> $.groupIdArtifactId(groupId, artifactId).then(pomXmlBuilderConsumer)); 3374 } 3375 3376 public ImlBuilder plugin(Consumer<ImlBuilder> pomXmlBuilderConsumer) { 3377 return element("plugin", pomXmlBuilderConsumer); 3378 } 3379 3380 public ImlBuilder parent(String groupId, String artifactId, String version) { 3381 return parent(parent -> parent.groupIdArtifactIdVersion(groupId, artifactId, version)); 3382 } 3383 3384 public ImlBuilder parent(Consumer<ImlBuilder> pomXmlBuilderConsumer) { 3385 return element("parent", pomXmlBuilderConsumer); 3386 } 3387 3388 public ImlBuilder pluginManagement(Consumer<ImlBuilder> pomXmlBuilderConsumer) { 3389 return element("pluginManagement", pomXmlBuilderConsumer); 3390 } 3391 3392 public ImlBuilder file(Consumer<ImlBuilder> pomXmlBuilderConsumer) { 3393 return element("file", pomXmlBuilderConsumer); 3394 } 3395 3396 public ImlBuilder activation(Consumer<ImlBuilder> pomXmlBuilderConsumer) { 3397 return element("activation", pomXmlBuilderConsumer); 3398 } 3399 3400 public ImlBuilder profiles(Consumer<ImlBuilder> pomXmlBuilderConsumer) { 3401 return element("profiles", pomXmlBuilderConsumer); 3402 } 3403 3404 public ImlBuilder profile(Consumer<ImlBuilder> pomXmlBuilderConsumer) { 3405 return element("profile", pomXmlBuilderConsumer); 3406 } 3407 3408 public ImlBuilder arguments(Consumer<ImlBuilder> pomXmlBuilderConsumer) { 3409 return element("arguments", pomXmlBuilderConsumer); 3410 } 3411 3412 public ImlBuilder executions(Consumer<ImlBuilder> pomXmlBuilderConsumer) { 3413 return element("executions", pomXmlBuilderConsumer); 3414 } 3415 3416 public ImlBuilder execution(Consumer<ImlBuilder> pomXmlBuilderConsumer) { 3417 return element("execution", pomXmlBuilderConsumer); 3418 } 3419 3420 public ImlBuilder goals(Consumer<ImlBuilder> pomXmlBuilderConsumer) { 3421 return element("goals", pomXmlBuilderConsumer); 3422 } 3423 3424 public ImlBuilder target(Consumer<ImlBuilder> pomXmlBuilderConsumer) { 3425 return element("target", pomXmlBuilderConsumer); 3426 } 3427 3428 public ImlBuilder configuration(Consumer<ImlBuilder> pomXmlBuilderConsumer) { 3429 return element("configuration", pomXmlBuilderConsumer); 3430 } 3431 3432 public ImlBuilder compilerArgs(Consumer<ImlBuilder> pomXmlBuilderConsumer) { 3433 return element("compilerArgs", pomXmlBuilderConsumer); 3434 } 3435 3436 public ImlBuilder properties(Consumer<ImlBuilder> pomXmlBuilderConsumer) { 3437 return element("properties", pomXmlBuilderConsumer); 3438 } 3439 3440 public ImlBuilder dependencies(Consumer<ImlBuilder> pomXmlBuilderConsumer) { 3441 return element("dependencies", pomXmlBuilderConsumer); 3442 } 3443 3444 public ImlBuilder dependency(String groupId, String artifactId, String version) { 3445 return dependency($ -> $.groupIdArtifactIdVersion(groupId, artifactId, version)); 3446 } 3447 3448 public ImlBuilder dependency(String groupId, String artifactId, String version, String scope) { 3449 return dependency($ -> $.groupIdArtifactIdVersion(groupId, artifactId, version).scope(scope)); 3450 } 3451 3452 public ImlBuilder dependency(Consumer<ImlBuilder> pomXmlBuilderConsumer) { 3453 return element("dependency", pomXmlBuilderConsumer); 3454 } 3455 3456 public ImlBuilder modules(Consumer<ImlBuilder> pomXmlBuilderConsumer) { 3457 return element("modules", pomXmlBuilderConsumer); 3458 } 3459 3460 public ImlBuilder module(String name) { 3461 return element("module", $ -> $.text(name)); 3462 } 3463 3464 public ImlBuilder property(String name, String value) { 3465 return element(name, $ -> $.text(value)); 3466 } 3467 3468 public ImlBuilder scope(String s) { 3469 return element("scope", $ -> $.text(s)); 3470 } 3471 3472 public ImlBuilder phase(String s) { 3473 return element("phase", $ -> $.text(s)); 3474 } 3475 3476 public ImlBuilder argument(String s) { 3477 return element("argument", $ -> $.text(s)); 3478 } 3479 3480 public ImlBuilder goal(String s) { 3481 return element("goal", $ -> $.text(s)); 3482 } 3483 3484 public ImlBuilder copy(String file, String toDir) { 3485 return element("copy", $ -> $.attr("file", file).attr("toDir", toDir)); 3486 } 3487 3488 public ImlBuilder groupIdArtifactId(String groupId, String artifactId) { 3489 return groupId(groupId).artifactId(artifactId); 3490 } 3491 3492 public ImlBuilder groupIdArtifactIdVersion(String groupId, String artifactId, String version) { 3493 return groupIdArtifactId(groupId, artifactId).version(version); 3494 } 3495 3496 public ImlBuilder skip(String string) { 3497 return element("skip", $ -> $.text(string)); 3498 } 3499 3500 public ImlBuilder id(String s) { 3501 return element("id", $ -> $.text(s)); 3502 } 3503 3504 public ImlBuilder arg(String s) { 3505 return element("arg", $ -> $.text(s)); 3506 } 3507 3508 public ImlBuilder argLine(String s) { 3509 return element("argLine", $ -> $.text(s)); 3510 } 3511 3512 public ImlBuilder source(String s) { 3513 return element("source", $ -> $.text(s)); 3514 } 3515 3516 public ImlBuilder target(String s) { 3517 return element("target", $ -> $.text(s)); 3518 } 3519 3520 public ImlBuilder showWarnings(String s) { 3521 return element("showWarnings", $ -> $.text(s)); 3522 } 3523 3524 public ImlBuilder showDeprecation(String s) { 3525 return element("showDeprecation", $ -> $.text(s)); 3526 } 3527 3528 public ImlBuilder failOnError(String s) { 3529 return element("failOnError", $ -> $.text(s)); 3530 } 3531 3532 public ImlBuilder exists(String s) { 3533 return element("exists", $ -> $.text(s)); 3534 } 3535 3536 public ImlBuilder activeByDefault(String s) { 3537 return element("activeByDefault", $ -> $.text(s)); 3538 } 3539 3540 public ImlBuilder executable(String s) { 3541 return element("executable", $ -> $.text(s)); 3542 } 3543 } 3544 3545 public static class XMLBuilder extends AbstractXMLBuilder<XMLBuilder> { 3546 XMLBuilder(Element element) { 3547 super(element); 3548 } 3549 3550 public XMLBuilder element(String name, Consumer<XMLBuilder> xmlBuilderConsumer) { 3551 return element(name, XMLBuilder::new, xmlBuilderConsumer); 3552 } 3553 3554 public XMLBuilder element(URI uri, String name, Consumer<XMLBuilder> xmlBuilderConsumer) { 3555 return element(uri, name, XMLBuilder::new, xmlBuilderConsumer); 3556 } 3557 } 3558 3559 static XMLNode create(String nodeName, Consumer<XMLBuilder> xmlBuilderConsumer) { 3560 3561 try { 3562 var doc = 3563 DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); 3564 var element = doc.createElement(nodeName); 3565 doc.appendChild(element); 3566 XMLBuilder xmlBuilder = new XMLBuilder(element); 3567 xmlBuilderConsumer.accept(xmlBuilder); 3568 return new XMLNode(element); 3569 } catch (ParserConfigurationException e) { 3570 throw new RuntimeException(e); 3571 } 3572 } 3573 3574 static XMLNode createIml(String commentText, Consumer<ImlBuilder> imlBuilderConsumer) { 3575 try { 3576 var doc = 3577 DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); 3578 var uri1 = URI.create("http://maven.apache.org/POM/4.0.0"); 3579 var uri2 = URI.create("http://www.w3.org/2001/XMLSchema-instance"); 3580 var uri3 = URI.create("http://maven.apache.org/xsd/maven-4.0.0.xsd"); 3581 var comment = doc.createComment(commentText); 3582 doc.appendChild(comment); 3583 var element = doc.createElementNS(uri1.toString(), "project"); 3584 doc.appendChild(element); 3585 element.setAttributeNS(uri2.toString(), "xsi:schemaLocation", uri1 + " " + uri3); 3586 ImlBuilder imlBuilder = new ImlBuilder(element); 3587 imlBuilderConsumer.accept(imlBuilder); 3588 return new XMLNode(element); 3589 } catch (ParserConfigurationException e) { 3590 throw new RuntimeException(e); 3591 } 3592 } 3593 3594 public static XMLNode createPom( 3595 String commentText, Consumer<PomXmlBuilder> pomXmlBuilderConsumer) { 3596 try { 3597 var doc = 3598 DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); 3599 3600 var uri1 = URI.create("http://maven.apache.org/POM/4.0.0"); 3601 var uri2 = URI.create("http://www.w3.org/2001/XMLSchema-instance"); 3602 var uri3 = URI.create("http://maven.apache.org/xsd/maven-4.0.0.xsd"); 3603 var comment = doc.createComment(commentText); 3604 doc.appendChild(comment); 3605 var element = doc.createElementNS(uri1.toString(), "project"); 3606 doc.appendChild(element); 3607 element.setAttributeNS(uri2.toString(), "xsi:schemaLocation", uri1 + " " + uri3); 3608 PomXmlBuilder pomXmlBuilder = new PomXmlBuilder(element); 3609 pomXmlBuilderConsumer.accept(pomXmlBuilder); 3610 return new XMLNode(element); 3611 } catch (ParserConfigurationException e) { 3612 throw new RuntimeException(e); 3613 } 3614 } 3615 3616 static XMLNode create(URI uri, String nodeName, Consumer<XMLBuilder> xmlBuilderConsumer) { 3617 try { 3618 var doc = 3619 DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); 3620 var element = doc.createElementNS(uri.toString(), nodeName); 3621 doc.appendChild(element); 3622 XMLBuilder xmlBuilder = new XMLBuilder(element); 3623 xmlBuilderConsumer.accept(xmlBuilder); 3624 return new XMLNode(element); 3625 } catch (ParserConfigurationException e) { 3626 throw new RuntimeException(e); 3627 } 3628 } 3629 3630 XMLNode(Element element) { 3631 this.element = element; 3632 this.element.normalize(); 3633 NodeList nodeList = element.getChildNodes(); 3634 for (int i = 0; i < nodeList.getLength(); i++) { 3635 if (nodeList.item(i) instanceof Element e) { 3636 this.children.add(new XMLNode(e)); 3637 } 3638 } 3639 for (int i = 0; i < element.getAttributes().getLength(); i++) { 3640 if (element.getAttributes().item(i) instanceof Attr attr) { 3641 this.attrMap.put(attr.getName(), attr.getValue()); 3642 } 3643 } 3644 } 3645 3646 public boolean hasAttr(String name) { 3647 return attrMap.containsKey(name); 3648 } 3649 3650 public String attr(String name) { 3651 return attrMap.get(name); 3652 } 3653 3654 static Document parse(InputStream is) { 3655 try { 3656 return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is); 3657 } catch (ParserConfigurationException | SAXException | IOException e) { 3658 throw new RuntimeException(e); 3659 } 3660 } 3661 3662 static Document parse(Path path) { 3663 try { 3664 return parse(Files.newInputStream(path)); 3665 } catch (IOException e) { 3666 throw new RuntimeException(e); 3667 } 3668 } 3669 3670 XMLNode(Path path) { 3671 this(parse(path).getDocumentElement()); 3672 } 3673 3674 XMLNode(File file) { 3675 this(parse(file.toPath()).getDocumentElement()); 3676 } 3677 3678 XMLNode(URL url) throws Throwable { 3679 this(parse(url.openStream()).getDocumentElement()); 3680 } 3681 3682 void write(StreamResult streamResult) throws Throwable { 3683 var transformer = TransformerFactory.newInstance().newTransformer(); 3684 transformer.setOutputProperty(OutputKeys.INDENT, "yes"); 3685 transformer.setOutputProperty(OutputKeys.METHOD, "xml"); 3686 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); 3687 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); 3688 transformer.transform(new DOMSource(element.getOwnerDocument()), streamResult); 3689 } 3690 3691 void write(File file) { 3692 try { 3693 write(new StreamResult(file)); 3694 } catch (Throwable t) { 3695 throw new RuntimeException(t); 3696 } 3697 } 3698 3699 public void write(XMLFile xmlFile) { 3700 try { 3701 write(new StreamResult(xmlFile.path().toFile())); 3702 } catch (Throwable t) { 3703 throw new RuntimeException(t); 3704 } 3705 } 3706 3707 @Override 3708 public String toString() { 3709 var stringWriter = new StringWriter(); 3710 try { 3711 var transformer = TransformerFactory.newInstance().newTransformer(); 3712 transformer.setOutputProperty(OutputKeys.INDENT, "yes"); 3713 transformer.setOutputProperty(OutputKeys.METHOD, "xml"); 3714 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); 3715 transformer.transform(new DOMSource(element), new StreamResult(stringWriter)); 3716 return stringWriter.toString(); 3717 } catch (Throwable e) { 3718 throw new RuntimeException(e); 3719 } 3720 } 3721 3722 XPathExpression xpath(String expression) { 3723 XPath xpath = XPathFactory.newInstance().newXPath(); 3724 try { 3725 return xpath.compile(expression); 3726 } catch (XPathExpressionException e) { 3727 throw new RuntimeException(e); 3728 } 3729 } 3730 3731 Node node(XPathExpression xPathExpression) { 3732 try { 3733 return (Node) xPathExpression.evaluate(this.element, XPathConstants.NODE); 3734 } catch (XPathExpressionException e) { 3735 throw new RuntimeException(e); 3736 } 3737 } 3738 3739 Optional<Node> optionalNode(XPathExpression xPathExpression) { 3740 var nodes = nodes(xPathExpression).toList(); 3741 return switch (nodes.size()) { 3742 case 0 -> Optional.empty(); 3743 case 1 -> Optional.of(nodes.getFirst()); 3744 default -> throw new IllegalStateException("Expected 0 or 1 but got more"); 3745 }; 3746 } 3747 3748 String str(XPathExpression xPathExpression) { 3749 try { 3750 return (String) xPathExpression.evaluate(this.element, XPathConstants.STRING); 3751 } catch (XPathExpressionException e) { 3752 throw new RuntimeException(e); 3753 } 3754 } 3755 3756 String xpathQueryString(String xpathString) { 3757 try { 3758 return (String) xpath(xpathString).evaluate(this.element, XPathConstants.STRING); 3759 } catch (XPathExpressionException e) { 3760 throw new RuntimeException(e); 3761 } 3762 } 3763 3764 NodeList nodeList(XPathExpression xPathExpression) { 3765 try { 3766 return (NodeList) xPathExpression.evaluate(this.element, XPathConstants.NODESET); 3767 } catch (XPathExpressionException e) { 3768 throw new RuntimeException(e); 3769 } 3770 } 3771 3772 Stream<Node> nodes(XPathExpression xPathExpression) { 3773 var nodeList = nodeList(xPathExpression); 3774 List<Node> nodes = new ArrayList<>(); 3775 for (int i = 0; i < nodeList.getLength(); i++) { 3776 nodes.add(nodeList.item(i)); 3777 } 3778 return nodes.stream(); 3779 } 3780 3781 Stream<Element> elements(XPathExpression xPathExpression) { 3782 return nodes(xPathExpression) 3783 .filter(n -> n instanceof Element) 3784 .map(n -> (Element) n); 3785 } 3786 3787 Stream<XMLNode> xmlNodes(XPathExpression xPathExpression) { 3788 return elements(xPathExpression).map(e -> new XMLNode(e)); 3789 } 3790 } 3791 3792 public static class MavenStyleRepository { 3793 private final String repoBase = "https://repo1.maven.org/maven2/"; 3794 private final String searchBase = "https://search.maven.org/solrsearch/"; 3795 public RepoDir dir; 3796 3797 JarFile jarFile(Id id) { 3798 return dir.jarFile(id.artifactAndVersion() + ".jar"); 3799 } 3800 3801 XMLFile pomFile(Id id) { 3802 return dir.xmlFile(id.artifactAndVersion() + ".pom"); 3803 } 3804 3805 public enum Scope { 3806 TEST, 3807 COMPILE, 3808 PROVIDED, 3809 RUNTIME, 3810 SYSTEM; 3811 3812 static Scope of(String name) { 3813 return switch (name.toLowerCase()) { 3814 case "test" -> TEST; 3815 case "compile" -> COMPILE; 3816 case "provided" -> PROVIDED; 3817 case "runtime" -> RUNTIME; 3818 case "system" -> SYSTEM; 3819 default -> COMPILE; 3820 }; 3821 } 3822 } 3823 3824 public record GroupAndArtifactId(GroupId groupId, ArtifactId artifactId) { 3825 3826 public static GroupAndArtifactId of(String groupAndArtifactId) { 3827 int idx = groupAndArtifactId.indexOf('/'); 3828 return of(groupAndArtifactId.substring(0, idx), groupAndArtifactId.substring(idx + 1)); 3829 } 3830 3831 public static GroupAndArtifactId of(GroupId groupId, ArtifactId artifactId) { 3832 return new GroupAndArtifactId(groupId, artifactId); 3833 } 3834 3835 public static GroupAndArtifactId of(String groupId, String artifactId) { 3836 return of(GroupId.of(groupId), ArtifactId.of(artifactId)); 3837 } 3838 3839 String location() { 3840 return groupId().string().replace('.', '/') + "/" + artifactId().string(); 3841 } 3842 3843 @Override 3844 public String toString() { 3845 return groupId() + "/" + artifactId(); 3846 } 3847 } 3848 3849 public sealed interface Id permits DependencyId, MetaDataId { 3850 MavenStyleRepository mavenStyleRepository(); 3851 3852 GroupAndArtifactId groupAndArtifactId(); 3853 3854 VersionId versionId(); 3855 3856 default String artifactAndVersion() { 3857 return groupAndArtifactId().artifactId().string() + '-' + versionId(); 3858 } 3859 3860 default String location() { 3861 return mavenStyleRepository().repoBase + groupAndArtifactId().location() + "/" + versionId(); 3862 } 3863 3864 default URL url(String suffix) { 3865 try { 3866 return new URI(location() + "/" + artifactAndVersion() + "." + suffix).toURL(); 3867 } catch (MalformedURLException | URISyntaxException e) { 3868 throw new RuntimeException(e); 3869 } 3870 } 3871 } 3872 3873 public record DependencyId( 3874 MavenStyleRepository mavenStyleRepository, 3875 GroupAndArtifactId groupAndArtifactId, 3876 VersionId versionId, 3877 Scope scope, 3878 boolean required) 3879 implements Id { 3880 @Override 3881 public String toString() { 3882 return groupAndArtifactId().toString() 3883 + "/" 3884 + versionId() 3885 + ":" 3886 + scope.toString() 3887 + ":" 3888 + (required ? "Required" : "Optiona"); 3889 } 3890 } 3891 3892 public record Pom(MetaDataId metaDataId, XMLNode xmlNode) { 3893 JarFile getJar() { 3894 var jarFile = metaDataId.mavenStyleRepository().jarFile(metaDataId); // ; 3895 metaDataId.mavenStyleRepository.queryAndCache(metaDataId.jarURL(), jarFile); 3896 return jarFile; 3897 } 3898 3899 String description() { 3900 return xmlNode().xpathQueryString("/project/description/text()"); 3901 } 3902 3903 Stream<DependencyId> dependencies() { 3904 return xmlNode() 3905 .nodes(xmlNode.xpath("/project/dependencies/dependency")) 3906 .map(node -> new XMLNode((Element) node)) 3907 .map( 3908 dependency -> 3909 new DependencyId( 3910 metaDataId().mavenStyleRepository(), 3911 GroupAndArtifactId.of( 3912 GroupId.of(dependency.xpathQueryString("groupId/text()")), 3913 ArtifactId.of(dependency.xpathQueryString("artifactId/text()"))), 3914 VersionId.of(dependency.xpathQueryString("version/text()")), 3915 Scope.of(dependency.xpathQueryString("scope/text()")), 3916 !Boolean.parseBoolean(dependency.xpathQueryString("optional/text()")))); 3917 } 3918 3919 Stream<DependencyId> requiredDependencies() { 3920 return dependencies().filter(DependencyId::required); 3921 } 3922 } 3923 3924 public Optional<Pom> pom(Id id) { 3925 return switch (id) { 3926 case MetaDataId metaDataId -> { 3927 if (metaDataId.versionId() == VersionId.UNSPECIFIED) { 3928 // println("what to do when the version is unspecified"); 3929 yield Optional.empty(); 3930 } 3931 try { 3932 yield Optional.of( 3933 new Pom( 3934 metaDataId, 3935 queryAndCache( 3936 metaDataId.pomURL(), metaDataId.mavenStyleRepository.pomFile(metaDataId)))); 3937 } catch (Throwable e) { 3938 throw new RuntimeException(e); 3939 } 3940 } 3941 case DependencyId dependencyId -> { 3942 if (metaData( 3943 id.groupAndArtifactId().groupId().string(), 3944 id.groupAndArtifactId().artifactId().string()) 3945 instanceof Optional<MetaData> optionalMetaData 3946 && optionalMetaData.isPresent()) { 3947 if (optionalMetaData 3948 .get() 3949 .metaDataIds() 3950 .filter(metaDataId -> metaDataId.versionId().equals(id.versionId())) 3951 .findFirst() 3952 instanceof Optional<MetaDataId> metaId 3953 && metaId.isPresent()) { 3954 yield pom(metaId.get()); 3955 } else { 3956 yield Optional.empty(); 3957 } 3958 } else { 3959 yield Optional.empty(); 3960 } 3961 } 3962 3963 }; 3964 } 3965 3966 public Optional<Pom> pom(GroupAndArtifactId groupAndArtifactId) { 3967 var metaData = metaData(groupAndArtifactId).orElseThrow(); 3968 var metaDataId = metaData.latestMetaDataId().orElseThrow(); 3969 return pom(metaDataId); 3970 } 3971 3972 record IdVersions(GroupAndArtifactId groupAndArtifactId, Set<Id> versions) { 3973 static IdVersions of(GroupAndArtifactId groupAndArtifactId) { 3974 return new IdVersions(groupAndArtifactId, new HashSet<>()); 3975 } 3976 } 3977 3978 public static class Dag implements ClassPathEntryProvider { 3979 private final MavenStyleRepository repo; 3980 private final List<GroupAndArtifactId> rootGroupAndArtifactIds; 3981 Map<GroupAndArtifactId, IdVersions> nodes = new HashMap<>(); 3982 Map<IdVersions, List<IdVersions>> edges = new HashMap<>(); 3983 3984 Dag add(Id from, Id to) { 3985 var fromNode = 3986 nodes.computeIfAbsent( 3987 from.groupAndArtifactId(), _ -> IdVersions.of(from.groupAndArtifactId())); 3988 fromNode.versions().add(from); 3989 var toNode = 3990 nodes.computeIfAbsent( 3991 to.groupAndArtifactId(), _ -> IdVersions.of(to.groupAndArtifactId())); 3992 toNode.versions().add(to); 3993 edges.computeIfAbsent(fromNode, k -> new ArrayList<>()).add(toNode); 3994 return this; 3995 } 3996 3997 void removeUNSPECIFIED() { 3998 nodes 3999 .values() 4000 .forEach( 4001 idversions -> { 4002 if (idversions.versions().size() > 1) { 4003 List<Id> versions = new ArrayList<>(idversions.versions()); 4004 idversions.versions().clear(); 4005 idversions 4006 .versions() 4007 .addAll( 4008 versions.stream() 4009 .filter(v -> !v.versionId().equals(VersionId.UNSPECIFIED)) 4010 .toList()); 4011 println(idversions); 4012 } 4013 if (idversions.versions().size() > 1) { 4014 throw new IllegalStateException("more than one version"); 4015 } 4016 }); 4017 } 4018 4019 Dag(MavenStyleRepository repo, List<GroupAndArtifactId> rootGroupAndArtifactIds) { 4020 this.repo = repo; 4021 this.rootGroupAndArtifactIds = rootGroupAndArtifactIds; 4022 4023 Set<Id> unresolved = new HashSet<>(); 4024 rootGroupAndArtifactIds.forEach( 4025 rootGroupAndArtifactId -> { 4026 var metaData = repo.metaData(rootGroupAndArtifactId).orElseThrow(); 4027 var metaDataId = metaData.latestMetaDataId().orElseThrow(); 4028 var optionalPom = repo.pom(rootGroupAndArtifactId); 4029 4030 if (optionalPom.isPresent() && optionalPom.get() instanceof Pom pom) { 4031 pom.requiredDependencies() 4032 .filter(dependencyId -> !dependencyId.scope.equals(Scope.TEST)) 4033 .forEach( 4034 dependencyId -> { 4035 add(metaDataId, dependencyId); 4036 unresolved.add(dependencyId); 4037 }); 4038 } 4039 }); 4040 4041 while (!unresolved.isEmpty()) { 4042 var resolveSet = new HashSet<>(unresolved); 4043 unresolved.clear(); 4044 resolveSet.forEach(id -> { 4045 if (repo.pom(id) instanceof Optional<Pom> p && p.isPresent()) { 4046 p.get() 4047 .requiredDependencies() 4048 .filter(dependencyId -> !dependencyId.scope.equals(Scope.TEST)) 4049 .forEach( 4050 dependencyId -> { 4051 unresolved.add(dependencyId); 4052 add(id, dependencyId); 4053 }); 4054 } 4055 }); 4056 } 4057 removeUNSPECIFIED(); 4058 } 4059 4060 @Override 4061 public List<ClassPathEntry> classPathEntries() { 4062 return classPath().classPathEntries(); 4063 } 4064 4065 ClassPath classPath() { 4066 4067 ClassPath jars = ClassPath.of(); 4068 nodes 4069 .keySet() 4070 .forEach( 4071 id -> { 4072 Optional<Pom> optionalPom = repo.pom(id); 4073 if (optionalPom.isPresent() && optionalPom.get() instanceof Pom pom) { 4074 jars.add(pom.getJar()); 4075 } else { 4076 throw new RuntimeException("No pom for " + id + " needed by " + id); 4077 } 4078 }); 4079 return jars; 4080 } 4081 } 4082 4083 public ClassPathEntryProvider classPathEntries(String... rootGroupAndArtifactIds) { 4084 return classPathEntries(Stream.of(rootGroupAndArtifactIds).map(GroupAndArtifactId::of).toList()); 4085 } 4086 4087 public ClassPathEntryProvider classPathEntries(GroupAndArtifactId... rootGroupAndArtifactIds) { 4088 return classPathEntries(List.of(rootGroupAndArtifactIds)); 4089 } 4090 4091 public ClassPathEntryProvider classPathEntries(List<GroupAndArtifactId> rootGroupAndArtifactIds) { 4092 StringBuilder sb = new StringBuilder(); 4093 rootGroupAndArtifactIds.forEach(groupAndArtifactId -> sb.append(sb.isEmpty() ? "" : "-").append(groupAndArtifactId.groupId + "-" + groupAndArtifactId.artifactId)); 4094 System.out.println(sb); 4095 ClassPathEntryProvider classPathEntries = null; 4096 var pathFileName = sb + "-path.xml"; 4097 var pathFile = dir.xmlFile(pathFileName); 4098 if (pathFile.exists()) { 4099 System.out.println(pathFileName + " exists " + pathFile.path().toString()); 4100 XMLNode path = new XMLNode(pathFile.path()); 4101 ClassPath classPath = ClassPath.of(); 4102 path.nodes(path.xpath("/path/jar/text()")).forEach(e -> 4103 classPath.add(dir.jarFile(e.getNodeValue())) 4104 ); 4105 classPathEntries = classPath; 4106 } else { 4107 var finalClassPathEntries = new Dag(this, rootGroupAndArtifactIds); 4108 XMLNode.create("path", xml -> { 4109 finalClassPathEntries.classPathEntries().forEach(cpe -> 4110 xml.element("jar", jar -> jar.text(dir.path().relativize(cpe.path()).toString())) 4111 ); 4112 }).write(pathFile); 4113 System.out.println("created " + pathFile.path()); 4114 classPathEntries = finalClassPathEntries; 4115 } 4116 return classPathEntries; 4117 } 4118 4119 public record VersionId(Integer maj, Integer min, Integer point, String classifier) 4120 implements Comparable<VersionId> { 4121 static Integer integerOrNull(String s) { 4122 return (s == null || s.isEmpty()) ? null : Integer.parseInt(s); 4123 } 4124 4125 public static Pattern pattern = Pattern.compile("^(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(.*))?)?$"); 4126 static VersionId UNSPECIFIED = new VersionId(null, null, null, null); 4127 4128 static VersionId of(String version) { 4129 Matcher matcher = pattern.matcher(version); 4130 if (matcher.matches()) { 4131 return new VersionId( 4132 integerOrNull(matcher.group(1)), 4133 integerOrNull(matcher.group(2)), 4134 integerOrNull(matcher.group(3)), 4135 matcher.group(4)); 4136 } else { 4137 return UNSPECIFIED; 4138 } 4139 } 4140 4141 int cmp(Integer v1, Integer v2) { 4142 if (v1 == null && v2 == null) { 4143 return 0; 4144 } 4145 if (v1 == null) { 4146 return -v2; 4147 } else if (v2 == null) { 4148 return v1; 4149 } else { 4150 return v1 - v2; 4151 } 4152 } 4153 4154 @Override 4155 public int compareTo(VersionId o) { 4156 if (cmp(maj(), o.maj()) == 0) { 4157 if (cmp(min(), o.min()) == 0) { 4158 if (cmp(point(), o.point()) == 0) { 4159 return classifier().compareTo(o.classifier()); 4160 } else { 4161 return cmp(point(), o.point()); 4162 } 4163 } else { 4164 return cmp(min(), o.min()); 4165 } 4166 } else { 4167 return cmp(maj(), o.maj()); 4168 } 4169 } 4170 4171 @Override 4172 public String toString() { 4173 StringBuilder sb = new StringBuilder(); 4174 if (maj() != null) { 4175 sb.append(maj()); 4176 if (min() != null) { 4177 sb.append(".").append(min()); 4178 if (point() != null) { 4179 sb.append(".").append(point()); 4180 if (classifier() != null) { 4181 sb.append(classifier()); 4182 } 4183 } 4184 } 4185 } else { 4186 sb.append("UNSPECIFIED"); 4187 } 4188 return sb.toString(); 4189 } 4190 } 4191 4192 public record GroupId(String string) { 4193 public static GroupId of(String s) { 4194 return new GroupId(s); 4195 } 4196 4197 @Override 4198 public String toString() { 4199 return string; 4200 } 4201 } 4202 4203 public record ArtifactId(String string) { 4204 static ArtifactId of(String string) { 4205 return new ArtifactId(string); 4206 } 4207 4208 @Override 4209 public String toString() { 4210 return string; 4211 } 4212 } 4213 4214 public record MetaDataId( 4215 MavenStyleRepository mavenStyleRepository, 4216 GroupAndArtifactId groupAndArtifactId, 4217 VersionId versionId, 4218 Set<String> downloadables, 4219 Set<String> tags) 4220 implements Id { 4221 4222 public URL pomURL() { 4223 return url("pom"); 4224 } 4225 4226 public URL jarURL() { 4227 return url("jar"); 4228 } 4229 4230 public XMLNode getPom() { 4231 if (downloadables.contains(".pom")) { 4232 return mavenStyleRepository.queryAndCache( 4233 url("pom"), mavenStyleRepository.dir.xmlFile(artifactAndVersion() + ".pom")); 4234 } else { 4235 throw new IllegalStateException("no pom"); 4236 } 4237 } 4238 4239 @Override 4240 public String toString() { 4241 return groupAndArtifactId().toString() + "." + versionId(); 4242 } 4243 } 4244 4245 public MavenStyleRepository(RepoDir dir) { 4246 this.dir = dir.create(); 4247 } 4248 4249 JarFile queryAndCache(URL query, JarFile jarFile) { 4250 try { 4251 if (!jarFile.exists()) { 4252 print("Querying and caching " + jarFile.fileName()); 4253 println(" downloading " + query); 4254 curl(query, jarFile.path()); 4255 } else { 4256 // println("Using cached " + jarFile.fileName()); 4257 4258 } 4259 } catch (Throwable e) { 4260 throw new RuntimeException(e); 4261 } 4262 return jarFile; 4263 } 4264 4265 XMLNode queryAndCache(URL query, XMLFile xmlFile) { 4266 XMLNode xmlNode = null; 4267 try { 4268 if (!xmlFile.exists()) { 4269 print("Querying and caching " + xmlFile.fileName()); 4270 println(" downloading " + query); 4271 xmlNode = new XMLNode(query); 4272 xmlNode.write(xmlFile.path().toFile()); 4273 } else { 4274 // println("Using cached " + xmlFile.fileName()); 4275 xmlNode = new XMLNode(xmlFile.path()); 4276 } 4277 } catch (Throwable e) { 4278 throw new RuntimeException(e); 4279 } 4280 return xmlNode; 4281 } 4282 4283 public record MetaData( 4284 MavenStyleRepository mavenStyleRepository, 4285 GroupAndArtifactId groupAndArtifactId, 4286 XMLNode xmlNode) { 4287 4288 public Stream<MetaDataId> metaDataIds() { 4289 return xmlNode 4290 .xmlNodes(xmlNode.xpath("/response/result/doc")) 4291 .map( 4292 xmln -> 4293 new MetaDataId( 4294 this.mavenStyleRepository, 4295 GroupAndArtifactId.of( 4296 GroupId.of(xmln.xpathQueryString("str[@name='g']/text()")), 4297 ArtifactId.of(xmln.xpathQueryString("str[@name='a']/text()"))), 4298 VersionId.of(xmln.xpathQueryString("str[@name='v']/text()")), 4299 new HashSet<>( 4300 xmln.nodes(xmln.xpath("arr[@name='ec']/str/text()")) 4301 .map(Node::getNodeValue) 4302 .toList()), 4303 new HashSet<>( 4304 xmln.nodes(xmln.xpath("arr[@name='tags']/str/text()")) 4305 .map(Node::getNodeValue) 4306 .toList()))); 4307 } 4308 4309 public Stream<MetaDataId> sortedMetaDataIds() { 4310 return metaDataIds().sorted(Comparator.comparing(MetaDataId::versionId)); 4311 } 4312 4313 public Optional<MetaDataId> latestMetaDataId() { 4314 return metaDataIds().max(Comparator.comparing(MetaDataId::versionId)); 4315 } 4316 4317 public Optional<MetaDataId> getMetaDataId(VersionId versionId) { 4318 return metaDataIds().filter(id -> versionId.compareTo(id.versionId()) == 0).findFirst(); 4319 } 4320 } 4321 4322 public Optional<MetaData> metaData(String groupId, String artifactId) { 4323 return metaData(GroupAndArtifactId.of(groupId, artifactId)); 4324 } 4325 4326 public Optional<MetaData> metaData(GroupAndArtifactId groupAndArtifactId) { 4327 try { 4328 var query = "g:" + groupAndArtifactId.groupId() + " AND a:" + groupAndArtifactId.artifactId(); 4329 URL rowQueryUrl = 4330 new URI( 4331 searchBase 4332 + "select?q=" 4333 + URLEncoder.encode(query, StandardCharsets.UTF_8) 4334 + "&core=gav&wt=xml&rows=0") 4335 .toURL(); 4336 var rowQueryResponse = new XMLNode(rowQueryUrl); 4337 var numFound = rowQueryResponse.xpathQueryString("/response/result/@numFound"); 4338 4339 URL url = 4340 new URI( 4341 searchBase 4342 + "select?q=" 4343 + URLEncoder.encode(query, StandardCharsets.UTF_8) 4344 + "&core=gav&wt=xml&rows=" 4345 + numFound) 4346 .toURL(); 4347 try { 4348 // println(url); 4349 var xmlNode = 4350 queryAndCache(url, dir.xmlFile(groupAndArtifactId.artifactId() + ".meta.xml")); 4351 if (numFound.isEmpty() || numFound.equals("0")) { 4352 return Optional.empty(); 4353 } else { 4354 return Optional.of(new MetaData(this, groupAndArtifactId, xmlNode)); 4355 } 4356 } catch (Throwable e) { 4357 throw new RuntimeException(e); 4358 } 4359 } catch (Throwable e) { 4360 throw new RuntimeException(e); 4361 } 4362 } 4363 } 4364 4365 public static class IntelliJ { 4366 public static class IntellijArtifact { 4367 DirEntry projectDir; 4368 XMLNode root; 4369 4370 Stream<XMLNode> query(String xpath) { 4371 return root.nodes(root.xpath(xpath)).map(e -> new XMLNode((Element) e)); 4372 } 4373 4374 IntellijArtifact(DirEntry projectDir, XMLNode root) { 4375 this.projectDir = projectDir; 4376 this.root = root; 4377 } 4378 } 4379 4380 public static class Workspace extends IntellijArtifact { 4381 4382 record Application(XMLNode xmlNode) { 4383 } 4384 4385 List<Application> applications; 4386 4387 Workspace(DirEntry projectDir, XMLNode root) { 4388 super(projectDir, root); 4389 this.applications = 4390 query("/project/component[@name='RunManager']/configuration") 4391 .map(Application::new) 4392 .toList(); 4393 } 4394 } 4395 4396 public static class Compiler extends IntellijArtifact { 4397 public record JavacSettings(XMLNode xmlNode) { 4398 public String getAdditionalOptions() { 4399 return xmlNode.xpathQueryString("option[@name='ADDITIONAL_OPTIONS_STRING']/@value"); 4400 } 4401 } 4402 4403 public JavacSettings javacSettings; 4404 4405 Compiler(DirEntry projectDir, XMLNode root) { 4406 super(projectDir, root); 4407 this.javacSettings = 4408 new JavacSettings(query("/project/component[@name='JavacSettings']").findFirst().get()); 4409 } 4410 } 4411 4412 public static class ImlGraph extends IntellijArtifact { 4413 public record Module(Path imlPath, XMLNode xmlNode) { 4414 @Override 4415 public String toString() { 4416 return name(); 4417 } 4418 4419 public String name() { 4420 return imlPath.getFileName().toString(); 4421 } 4422 4423 public SourcePath getSourcePath() { 4424 return null; 4425 } 4426 4427 Stream<XMLNode> query(String xpath) { 4428 return xmlNode.nodes(xmlNode.xpath(xpath)).map(e -> new XMLNode((Element) e)); 4429 } 4430 } 4431 4432 Stream<XMLNode> query(String xpath) { 4433 return root.nodes(root.xpath(xpath)).map(e -> new XMLNode((Element) e)); 4434 } 4435 4436 Set<Module> modules = new HashSet<>(); 4437 public Map<Module, List<Module>> fromToDependencies = new HashMap<>(); 4438 Map<Module, List<Module>> toFromDependencies = new HashMap<>(); 4439 4440 ImlGraph(DirEntry projectDir, XMLNode root) { 4441 super(projectDir, root); 4442 Map<String, Module> nameToModule = new HashMap<>(); 4443 query("/project/component[@name='ProjectModuleManager']/modules/module") 4444 .map( 4445 xmlNode -> 4446 Path.of( 4447 xmlNode 4448 .attrMap 4449 .get("filepath") 4450 .replace("$PROJECT_DIR$", projectDir.path().toString()))) 4451 .map(path -> new Module(path, new XMLNode(path))) 4452 .forEach( 4453 module -> { 4454 modules.add(module); 4455 nameToModule.put(module.name(), module); 4456 }); 4457 modules.forEach( 4458 module -> 4459 module 4460 .xmlNode 4461 .nodes(root.xpath("/module/component/orderEntry[@type='module']")) 4462 .map(e -> new XMLNode((Element) e)) 4463 .forEach( 4464 e -> { 4465 var dep = nameToModule.get(e.attrMap.get("module-name") + ".iml"); 4466 fromToDependencies.computeIfAbsent(module, _ -> new ArrayList<>()).add(dep); 4467 toFromDependencies.computeIfAbsent(dep, _ -> new ArrayList<>()).add(module); 4468 })); 4469 } 4470 } 4471 4472 public static class Project { 4473 public DirEntry intellijDir; 4474 public ImlGraph imlGraph; 4475 public Workspace workSpace; 4476 public Compiler compiler; 4477 4478 public Project(DirEntry intellijDir) { 4479 this.intellijDir = intellijDir; 4480 var ideaDir = intellijDir.existingDir(".idea"); 4481 imlGraph = new ImlGraph(intellijDir, new XMLNode(ideaDir.xmlFile("modules.xml").path())); 4482 workSpace = new Workspace(intellijDir, new XMLNode(ideaDir.xmlFile("workspace.xml").path())); 4483 compiler = new Compiler(intellijDir, new XMLNode(ideaDir.xmlFile("compiler.xml").path())); 4484 } 4485 } 4486 } 4487 }