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