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 javax.tools.Diagnostic; 29 import javax.tools.DiagnosticListener; 30 import javax.tools.JavaCompiler; 31 import javax.tools.JavaFileObject; 32 import javax.tools.SimpleJavaFileObject; 33 import java.io.File; 34 import java.io.IOException; 35 import java.io.PrintWriter; 36 import java.net.MalformedURLException; 37 import java.net.URI; 38 import java.net.URISyntaxException; 39 import java.net.URL; 40 import java.nio.charset.StandardCharsets; 41 import java.nio.file.FileVisitOption; 42 import java.nio.file.FileVisitResult; 43 import java.nio.file.FileVisitor; 44 import java.nio.file.Files; 45 import java.nio.file.LinkOption; 46 import java.nio.file.Path; 47 import java.nio.file.SimpleFileVisitor; 48 import java.nio.file.attribute.BasicFileAttributes; 49 import java.nio.file.attribute.PosixFileAttributes; 50 import java.util.ArrayList; 51 import java.util.Arrays; 52 import java.util.Comparator; 53 import java.util.HashMap; 54 import java.util.HashSet; 55 import java.util.List; 56 import java.util.Map; 57 import java.util.Optional; 58 import java.util.Set; 59 import java.util.function.Consumer; 60 import java.util.function.Predicate; 61 import java.util.jar.JarEntry; 62 import java.util.jar.JarOutputStream; 63 import java.util.regex.Matcher; 64 import java.util.regex.Pattern; 65 import java.util.stream.Stream; 66 import java.util.zip.ZipFile; 67 68 import static java.io.IO.print; 69 import static java.io.IO.println; 70 import static java.nio.file.Files.isDirectory; 71 import static java.nio.file.Files.isRegularFile; 72 73 public class Bldr { 74 public interface PathHolder { 75 Path path(); 76 default String name(){ 77 return path().getFileName().toString(); 78 } 79 default Matcher pathMatcher(Pattern pattern) { 80 return pattern.matcher(path().toString()); 81 } 82 83 default boolean matches(Pattern pattern) { 84 return pathMatcher(pattern).matches(); 85 } 86 87 default boolean matches(String pattern) { 88 return pathMatcher(Pattern.compile(pattern)).matches(); 89 } 90 91 } 92 93 public interface TargetDirProvider extends PathHolder { 94 Path targetDir(); 95 } 96 97 public interface JavaSourceDirProvider { 98 Path javaSourceDir(); 99 } 100 101 public interface ResourceDirProvider { 102 DirPathHolder resourcesDir(); 103 } 104 105 public interface DirPathHolder<T extends DirPathHolder<T>> extends PathHolder { 106 default Path path(String subdir){ 107 return path().resolve(subdir); 108 } 109 default Stream<Path> find() { 110 try { 111 return Files.walk(path()); 112 } catch (IOException e) { 113 throw new RuntimeException(e); 114 } 115 } 116 default Stream<Path> find(Predicate<Path> predicate) { 117 return find().filter(predicate); 118 } 119 120 121 default Stream<Path> findFiles() { 122 return find( Files::isRegularFile); 123 } 124 125 default Stream<Path> findDirs() { 126 return find( Files::isDirectory); 127 } 128 129 default Stream<Path> findFiles(Predicate<Path> predicate) { 130 return findFiles().filter(predicate); 131 } 132 133 default Stream<SearchableTextFile> findTextFiles(String... suffixes) { 134 return findFiles().map(SearchableTextFile::new).filter(searchableTextFile -> searchableTextFile.hasSuffix(suffixes)); 135 } 136 137 default Stream<Path> findDirs( Predicate<Path> predicate) { 138 return find( Files::isDirectory).filter(predicate); 139 } 140 141 142 default boolean exists(){ 143 return Files.exists(path()) && Files.isDirectory(path()); 144 } 145 146 147 } 148 149 public interface FilePathHolder extends PathHolder { } 150 151 public interface ClassPathEntry extends PathHolder { } 152 public record CMakeBuildDir(Path path) implements BuildDirHolder<CMakeBuildDir> { 153 public static CMakeBuildDir of(Path path) { 154 return new CMakeBuildDir(path); 155 } 156 157 @Override 158 public CMakeBuildDir create() { 159 return CMakeBuildDir.of(mkdir(path())); 160 } 161 162 @Override 163 public CMakeBuildDir remove() { 164 return CMakeBuildDir.of(rmdir(path())); 165 } 166 } 167 public interface BuildDirHolder<T extends BuildDirHolder<T>> extends DirPathHolder<T> { 168 T create(); 169 T remove(); 170 default void clean(){ 171 remove(); 172 create(); 173 } 174 default Path mkdir(Path path) { 175 try { 176 return Files.createDirectories(path); 177 } catch (IOException e) { 178 throw new RuntimeException(e); 179 } 180 } 181 default Path rmdir(Path path) { 182 try { 183 if (Files.exists(path)) { 184 Files.walk(path).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); 185 } 186 } catch (IOException ioe) { 187 System.out.println(ioe); 188 } 189 return path; 190 } 191 192 } 193 public record ClassDir(Path path) implements ClassPathEntry, BuildDirHolder<ClassDir> { 194 public static ClassDir of(Path path) { 195 return new ClassDir(path); 196 } 197 198 public static ClassDir temp(String javacclasses) { 199 try { 200 return of(Files.createTempDirectory("javacClasses")); 201 } catch (IOException e) { 202 throw new RuntimeException(e); 203 } 204 } 205 206 @Override 207 public ClassDir create(){ 208 return ClassDir.of(mkdir(path())); 209 } 210 @Override 211 public ClassDir remove(){ 212 return ClassDir.of(rmdir(path())); 213 } 214 } 215 public record Dir(Path path) implements DirPathHolder<Dir> { 216 public static Dir of(Path path){ 217 return new Dir(path); 218 } 219 public static Dir of(String string){ 220 return of (Path.of(string)); 221 } 222 public static Dir current(){ 223 return of(Path.of(System.getProperty("user.dir"))); 224 } 225 public Dir parent(){ 226 return of(path().getParent()); 227 } 228 229 public Dir dir(String subdir){ 230 return Dir.of(path(subdir)); 231 } 232 public Stream<Dir> forEachSubDirectory(String ... dirNames){ 233 return Stream.of(dirNames).map(dirName->path().resolve(dirName)).filter(Files::isDirectory).map(Dir::new); 234 } 235 236 } 237 public record RootDirAndSubPath(DirPathHolder<?> root, Path path) { 238 Path relativize() { 239 return root().path().relativize(path()); 240 } 241 } 242 public record BuildDir(Path path) implements ClassPathEntry, BuildDirHolder<BuildDir> { 243 public static BuildDir of(Path path){ 244 return new BuildDir(path); 245 } 246 247 public JarFile jarFile(String name) { 248 return JarFile.of(path().resolve(name)); 249 } 250 public CMakeBuildDir cMakeBuildDir(String name) { 251 return CMakeBuildDir.of(path().resolve(name)); 252 } 253 public ClassDir classDir(String name) { 254 return ClassDir.of(path().resolve(name)); 255 } 256 257 @Override 258 public BuildDir create() { 259 return BuildDir.of(mkdir(path())); 260 } 261 262 @Override 263 public BuildDir remove() { 264 return BuildDir.of(rmdir(path())); 265 } 266 267 public BuildDir dir(String subdir){ 268 return BuildDir.of(path(subdir)); 269 } 270 271 } 272 273 public record JarFile(Path path) implements ClassPathEntry, FilePathHolder { 274 public static JarFile of(Path path) { 275 return new JarFile(path); 276 } 277 } 278 279 public record SourcePathEntry(Path path) implements DirPathHolder<SourcePathEntry> { 280 } 281 282 public interface TextFile extends FilePathHolder{ 283 284 } 285 286 public interface SourceFile extends TextFile { 287 } 288 289 public static class JavaSourceFile extends SimpleJavaFileObject implements SourceFile { 290 Path path; 291 292 public CharSequence getCharContent(boolean ignoreEncodingErrors) { 293 try { 294 return Files.readString(Path.of(toUri())); 295 } catch (IOException e) { 296 throw new RuntimeException(e); 297 } 298 } 299 300 JavaSourceFile(Path path) { 301 super(path.toUri(), JavaFileObject.Kind.SOURCE); 302 303 } 304 305 @Override 306 public Path path() { 307 return path; 308 } 309 } 310 311 public record CppSourceFile(Path path) implements SourceFile { 312 } 313 314 public record CppHeaderSourceFile(Path path) implements SourceFile { 315 } 316 317 318 public record ClassPath(List<ClassPathEntry> entries) { 319 } 320 321 public record SourcePath(List<SourcePathEntry> entries) { 322 } 323 324 public record XMLFile(Path path) implements TextFile { 325 } 326 327 public interface OS { 328 String arch(); 329 330 String name(); 331 332 String version(); 333 334 static final String MacName = "Mac OS X"; 335 static final String LinuxName = "Linux"; 336 337 338 record Linux(String arch, String name, String version) implements OS { 339 } 340 341 record Mac(String arch, String name, String version) implements OS { 342 public Path appLibFrameworks() { 343 return Path.of("/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/" 344 + "MacOSX.sdk/System/Library/Frameworks"); 345 } 346 347 public Path frameworkHeader(String frameworkName, String headerFileName) { 348 return appLibFrameworks().resolve(frameworkName + ".framework/Headers/" + headerFileName); 349 } 350 351 public Path libFrameworks() { 352 return Path.of("/System/Library/Frameworks"); 353 } 354 355 public Path frameworkLibrary(String frameworkName) { 356 return libFrameworks().resolve(frameworkName + ".framework/" + frameworkName); 357 } 358 } 359 360 static OS get() { 361 String arch = System.getProperty("os.arch"); 362 String name = System.getProperty("os.name"); 363 String version = System.getProperty("os.version"); 364 return switch (name) { 365 366 case "Mac OS X" -> new Mac(arch, name, version); 367 case "Linux" -> new Linux(arch, name, version); 368 default -> throw new IllegalStateException("No os mapping for " + name); 369 }; 370 } 371 } 372 373 374 public static OS os = OS.get(); 375 376 public record Java(String version, File home) { 377 } 378 379 public static Java java = new Java(System.getProperty("java.version"), new File(System.getProperty("java.home"))); 380 381 public record User(File home, File pwd) { 382 } 383 384 public static User user = new User(new File(System.getProperty("user.home")), new File(System.getProperty("user.dir"))); 385 386 387 /* 388 static class POM { 389 static Pattern varPattern = Pattern.compile("\\$\\{([^}]*)\\}"); 390 static public String varExpand(Map<String, String> props, String value) { // recurse 391 String result = value; 392 if (varPattern.matcher(value) instanceof Matcher matcher && matcher.find()) { 393 var v = matcher.groupId(1); 394 result = varExpand(props, value.substring(0, matcher.start()) 395 + (v.startsWith("env") 396 ? System.getenv(v.substring(4)) 397 : props.get(v)) 398 + value.substring(matcher.end())); 399 //out.println("incomming ='"+value+"' v= '"+v+"' value='"+value+"'->'"+result+"'"); 400 } 401 return result; 402 } 403 404 POM(Path dir) throws Throwable { 405 var topPom = new XMLNode(new File(dir.toFile(), "pom.xml")); 406 var babylonDirKey = "babylon.dir"; 407 var spirvDirKey = "beehive.spirv.toolkit.dir"; 408 var hatDirKey = "hat.dir"; 409 var interestingKeys = Set.of(spirvDirKey, babylonDirKey, hatDirKey); 410 var requiredDirKeys = Set.of(babylonDirKey, hatDirKey); 411 var dirKeyToDirMap = new HashMap<String, File>(); 412 var props = new HashMap<String, String>(); 413 414 topPom.children.stream().filter(e -> e.element.getNodeName().equals("properties")).forEach(properties -> 415 properties.children.stream().forEach(property -> { 416 var key = property.element.getNodeName(); 417 var value = varExpand(props, property.element.getTextContent()); 418 props.put(key, value); 419 if (interestingKeys.contains(key)) { 420 var file = new File(value); 421 if (requiredDirKeys.contains(key) && !file.exists()) { 422 System.err.println("ERR pom.xml has property '" + key + "' with value '" + value + "' but that dir does not exists!"); 423 System.exit(1); 424 } 425 dirKeyToDirMap.put(key, file); 426 } 427 }) 428 ); 429 for (var key : requiredDirKeys) { 430 if (!props.containsKey(key)) { 431 System.err.println("ERR pom.xml expected to have property '" + key + "' "); 432 System.exit(1); 433 } 434 } 435 } 436 } 437 */ 438 439 public static String charSeparatedClassPath(List<ClassPathEntry> classPathEntries) { 440 StringBuilder sb = new StringBuilder(); 441 classPathEntries.forEach(classPathEntry -> { 442 if (!sb.isEmpty()) { 443 sb.append(File.pathSeparatorChar); 444 } 445 sb.append(classPathEntry.path()); 446 }); 447 return sb.toString(); 448 } 449 public static String charSeparatedDirPathHolders(List<DirPathHolder<?>> dirPathHolderEntries) { 450 StringBuilder sb = new StringBuilder(); 451 dirPathHolderEntries.forEach(dirPathHolderEntry -> { 452 if (!sb.isEmpty()) { 453 sb.append(File.pathSeparatorChar); 454 } 455 sb.append(dirPathHolderEntry.path()); 456 }); 457 return sb.toString(); 458 } 459 460 public abstract static class Builder<T extends Builder<T>> { 461 @SuppressWarnings("unchecked") T self() { 462 return (T) this; 463 } 464 465 public List<String> opts = new ArrayList<>(); 466 public boolean verbose; 467 public T verbose(boolean verbose) { 468 this.verbose= verbose; 469 return self(); 470 } 471 public T verbose() { 472 verbose(true); 473 return self(); 474 } 475 476 public abstract T show(Consumer<String> stringConsumer); 477 478 public T opts(List<String> opts) { 479 this.opts.addAll(opts); 480 return self(); 481 } 482 483 public T opts(String... opts) { 484 opts(Arrays.asList(opts)); 485 return self(); 486 } 487 488 public T basedOn(T stem) { 489 if (stem != null) { 490 opts.addAll(stem.opts); 491 } 492 return self(); 493 } 494 495 public T when(boolean condition, Consumer<T> consumer) { 496 if (condition) { 497 consumer.accept(self()); 498 } 499 return self(); 500 } 501 502 public T either(boolean condition, Consumer<T> trueConsumer, Consumer<T> falseConsumer) { 503 if (condition) { 504 trueConsumer.accept(self()); 505 } else { 506 falseConsumer.accept(self()); 507 } 508 return self(); 509 } 510 511 } 512 513 public static abstract class ExecBuilder<T extends ExecBuilder<T>> extends Builder<T> { 514 abstract public List<String> execOpts(); 515 516 public void execInheritIO(Path path) { 517 try { 518 var processBuilder = new ProcessBuilder(); 519 520 if (path != null) { 521 processBuilder.directory(path.toFile()); 522 } 523 processBuilder 524 .inheritIO() 525 .command(execOpts()); 526 var process = processBuilder 527 .start(); 528 if (verbose){ 529 print(execOpts()); 530 // show((s)->print(execOpts())); 531 } 532 process.waitFor(); 533 534 } catch (InterruptedException ie) { 535 System.out.println(ie); 536 } catch (IOException ioe) { 537 System.out.println(ioe); 538 } 539 } 540 541 public void execInheritIO() { 542 execInheritIO(null); 543 } 544 } 545 546 public static class JavacBuilder extends Builder<JavacBuilder> { 547 public ClassDir classDir; 548 public List<DirPathHolder<?>> sourcePath ; 549 public List<ClassPathEntry> classPath; 550 551 @Override 552 public JavacBuilder show(Consumer<String> stringConsumer) { 553 return self(); 554 } 555 556 public JavacBuilder basedOn(JavacBuilder stem) { 557 super.basedOn(stem); 558 if (stem != null) { 559 if (stem.classDir != null) { 560 this.classDir = stem.classDir; 561 } 562 if (stem.sourcePath != null) { 563 this.sourcePath = new ArrayList<>(stem.sourcePath); 564 } 565 if (stem.classPath != null) { 566 this.classPath = new ArrayList<>(stem.classPath); 567 } 568 } 569 return this; 570 } 571 572 public JavacBuilder class_dir(Path classDir) { 573 this.classDir = ClassDir.of(classDir); 574 return this; 575 } 576 577 public JavacBuilder class_dir(ClassDir classDir) { 578 this.classDir = classDir; 579 return this; 580 } 581 582 public JavacBuilder source_path(List<DirPathHolder<?>> sourcePaths) { 583 this.sourcePath = this.sourcePath == null ? new ArrayList<>() : this.sourcePath; 584 this.sourcePath.addAll(sourcePaths); 585 return this; 586 } 587 588 public JavacBuilder source_path(DirPathHolder<?>... sourcePaths) { 589 return source_path(List.of(sourcePaths)); 590 } 591 592 public JavacBuilder class_path(ClassPathEntry... classPathEntries) { 593 this.classPath = this.classPath == null ? new ArrayList<>() : this.classPath; 594 this.classPath.addAll(Arrays.asList(classPathEntries)); 595 return this; 596 } 597 598 } 599 600 601 602 603 604 public static JavacBuilder javac(JavacBuilder javacBuilder) { 605 try { 606 if (javacBuilder.classDir == null) { 607 javacBuilder.classDir = ClassDir.temp("javacclasses"); 608 } 609 javacBuilder.opts.addAll(List.of("-d", javacBuilder.classDir.path().toString())); 610 javacBuilder.classDir.clean(); 611 612 613 if (javacBuilder.classPath != null) { 614 javacBuilder.opts.addAll(List.of("--class-path", charSeparatedClassPath(javacBuilder.classPath))); 615 } 616 617 javacBuilder.opts.addAll(List.of("--source-path", charSeparatedDirPathHolders(javacBuilder.sourcePath))); 618 var compilationUnits = new ArrayList<JavaSourceFile>(); 619 javacBuilder.sourcePath.forEach(entry -> 620 entry.findFiles( file -> file.toString().endsWith(".java")) 621 .map(JavaSourceFile::new) 622 .forEach(compilationUnits::add)); 623 624 DiagnosticListener<JavaFileObject> dl = (diagnostic) -> { 625 if (!diagnostic.getKind().equals(Diagnostic.Kind.NOTE)) { 626 System.out.println(diagnostic.getKind() 627 + " " + diagnostic.getLineNumber() + ":" + diagnostic.getColumnNumber() + " " + diagnostic.getMessage(null)); 628 } 629 }; 630 631 // List<RootAndPath> pathsToJar = new ArrayList<>(); 632 JavaCompiler javac = javax.tools.ToolProvider.getSystemJavaCompiler(); 633 JavaCompiler.CompilationTask compilationTask = (javac.getTask( 634 new PrintWriter(System.err), 635 javac.getStandardFileManager(dl, null, null), 636 dl, 637 javacBuilder.opts, 638 null, 639 compilationUnits 640 641 )); 642 ((com.sun.source.util.JavacTask) compilationTask) 643 .generate(); 644 //.forEach(fileObject -> pathsToJar.add(new RootAndPath(javacBuilder.classesDir, Path.of(fileObject.toUri())))); 645 646 647 return javacBuilder; 648 } catch (IOException e) { 649 throw new RuntimeException(e); 650 } 651 } 652 653 public static JavacBuilder javac(Consumer<JavacBuilder> javacBuilderConsumer) { 654 JavacBuilder javacBuilder = new JavacBuilder(); 655 javacBuilderConsumer.accept(javacBuilder); 656 return javac(javacBuilder); 657 } 658 659 public static class JarBuilder extends Builder<JarBuilder> { 660 public JarFile jar; 661 public JavacBuilder javacBuilder; 662 public List<DirPathHolder<?>> dirList; 663 664 public JarBuilder basedOn(JarBuilder stem) { 665 super.basedOn(stem); 666 if (stem != null) { 667 if (stem.jar != null) { 668 this.jar = stem.jar; 669 } 670 if (stem.dirList != null) { 671 this.dirList = new ArrayList<>(stem.dirList); 672 } 673 } 674 return this; 675 } 676 677 public JarBuilder jar(JarFile jar) { 678 this.jar = jar; 679 return this; 680 } 681 public JarBuilder javac( JavacBuilder javacBuilder) { 682 this.javacBuilder = Bldr.javac(javacBuilder); 683 this.dirList = (this.dirList == null) ? new ArrayList<>() : this.dirList; 684 this.dirList.add(this.javacBuilder.classDir); 685 return this; 686 } 687 public JarBuilder javac(Consumer<JavacBuilder> javacBuilderConsumer) { 688 this.javacBuilder = new JavacBuilder(); 689 javacBuilderConsumer.accept(this.javacBuilder); 690 return javac(this.javacBuilder); 691 } 692 public JarBuilder dir_list(Predicate<DirPathHolder<?>> predicate, DirPathHolder<?>... dirs) { 693 Stream.of(dirs).filter(predicate).forEach(optionalDir->{ 694 this.dirList = (this.dirList == null) ? new ArrayList<>() : this.dirList; 695 this.dirList.add(optionalDir); 696 }); 697 return this; 698 } 699 public JarBuilder dir_list(DirPathHolder<?>... dirs) { 700 return dir_list(_->true, dirs); 701 } 702 @Override 703 public JarBuilder show(Consumer<String> stringConsumer) { 704 return self(); 705 } 706 } 707 708 public static JarFile jar(Consumer<JarBuilder> jarBuilderConsumer) { 709 try { 710 JarBuilder jarBuilder = new JarBuilder(); 711 jarBuilderConsumer.accept(jarBuilder); 712 713 List<RootDirAndSubPath> pathsToJar = new ArrayList<>(); 714 var jarStream = new JarOutputStream(Files.newOutputStream(jarBuilder.jar.path())); 715 jarBuilder.dirList.forEach(root -> root 716 .findFiles() 717 .map(path -> new RootDirAndSubPath(root, path)) 718 .forEach(pathsToJar::add)); 719 pathsToJar.stream().sorted(Comparator.comparing(RootDirAndSubPath::path)).forEach(rootAndPath -> { 720 try { 721 var entry = new JarEntry(rootAndPath.relativize().toString()); 722 entry.setTime(Files.getLastModifiedTime(rootAndPath.path()).toMillis()); 723 jarStream.putNextEntry(entry); 724 Files.newInputStream(rootAndPath.path()).transferTo(jarStream); 725 jarStream.closeEntry(); 726 } catch (IOException e) { 727 throw new RuntimeException(e); 728 } 729 }); 730 jarStream.finish(); 731 jarStream.close(); 732 return jarBuilder.jar; 733 } catch (IOException e) { 734 throw new RuntimeException(e); 735 } 736 737 } 738 739 public static class JavaBuilder extends ExecBuilder<JavaBuilder> { 740 public Path jdk = Path.of(System.getProperty("java.home")); 741 public String mainClass; 742 public List<ClassPathEntry> classPath; 743 public List<DirPathHolder<?>> libraryPath; 744 public List<String> vmopts = new ArrayList<>(); 745 public List<String> args = new ArrayList<>(); 746 @Override 747 public JavaBuilder show(Consumer<String> stringConsumer) { 748 return self(); 749 } 750 public JavaBuilder vmopts(List<String> opts) { 751 this.vmopts.addAll(opts); 752 return self(); 753 } 754 755 public JavaBuilder vmopts(String... opts) { 756 vmopts(Arrays.asList(opts)); 757 return self(); 758 } 759 760 761 public JavaBuilder args(List<String> opts) { 762 this.args.addAll(opts); 763 return self(); 764 } 765 766 public JavaBuilder args(String... opts) { 767 args(Arrays.asList(opts)); 768 return self(); 769 } 770 771 772 public JavaBuilder basedOn(JavaBuilder stem) { 773 super.basedOn(stem); 774 if (stem != null) { 775 vmopts.addAll(stem.vmopts); 776 args.addAll(stem.args); 777 if (stem.mainClass != null) { 778 this.mainClass = stem.mainClass; 779 } 780 if (stem.jdk != null) { 781 this.jdk = stem.jdk; 782 } 783 if (stem.classPath != null) { 784 this.classPath = new ArrayList<>(stem.classPath); 785 } 786 787 opts.addAll(stem.opts); 788 789 } 790 return this; 791 } 792 793 public JavaBuilder main_class(String mainClass) { 794 this.mainClass = mainClass; 795 return this; 796 } 797 798 public JavaBuilder jdk(Path jdk) { 799 this.jdk = jdk; 800 return this; 801 } 802 803 public JavaBuilder class_path(List<ClassPathEntry> classPathEntries) { 804 this.classPath = (this.classPath == null) ? new ArrayList<>() : this.classPath; 805 this.classPath.addAll(classPathEntries); 806 return this; 807 } 808 809 public JavaBuilder class_path(ClassPathEntry... classPathEntries) { 810 return this.class_path(List.of(classPathEntries)); 811 } 812 public JavaBuilder library_path(List<DirPathHolder<?>> libraryPathEntries) { 813 this.libraryPath = (this.libraryPath == null) ? new ArrayList<>() : this.libraryPath; 814 this.libraryPath.addAll(libraryPathEntries); 815 return this; 816 } 817 public JavaBuilder library_path(DirPathHolder<?>... libraryPathEntries) { 818 return this.library_path(List.of(libraryPathEntries)); 819 } 820 821 @Override 822 public List<String> execOpts() { 823 List<String> execOpts = new ArrayList<>(); 824 execOpts.add(jdk.resolve("bin/java").toString()); 825 execOpts.addAll(vmopts); 826 if (classPath != null) { 827 execOpts.addAll(List.of("--class-path", charSeparatedClassPath(classPath))); 828 } 829 if (libraryPath!= null) { 830 execOpts.add("-Djava.library.path="+ charSeparatedDirPathHolders(libraryPath)); 831 } 832 execOpts.add(mainClass); 833 execOpts.addAll(args); 834 return execOpts; 835 } 836 } 837 public static JavaBuilder java(JavaBuilder javaBuilder) { 838 javaBuilder.execInheritIO(); 839 return javaBuilder; 840 } 841 842 public static JavaBuilder java(Consumer<JavaBuilder> javaBuilderConsumer) { 843 JavaBuilder javaBuilder = new JavaBuilder(); 844 javaBuilderConsumer.accept(javaBuilder); 845 return java(javaBuilder); 846 } 847 848 public static JavaBuilder javaBuilder() { 849 return new JavaBuilder(); 850 } 851 852 853 public static class CMakeBuilder extends ExecBuilder<CMakeBuilder> { 854 public List<String> libraries = new ArrayList<>(); 855 public CMakeBuildDir cmakeBuildDir; 856 public Dir sourceDir; 857 private Path output; 858 public BuildDir copyToDir; 859 860 public CMakeBuilder() { 861 opts.add("cmake"); 862 } 863 @Override 864 public CMakeBuilder show(Consumer<String> stringConsumer) { 865 return self(); 866 } 867 public CMakeBuilder basedOn(CMakeBuilder stem) { 868 // super.basedOn(stem); you will get two cmakes ;) 869 if (stem != null) { 870 if (stem.output != null) { 871 this.output = stem.output; 872 } 873 if (stem.copyToDir != null) { 874 this.copyToDir = stem.copyToDir; 875 } 876 if (stem.libraries != null) { 877 this.libraries = new ArrayList<>(stem.libraries); 878 } 879 if (stem.cmakeBuildDir != null) { 880 this.cmakeBuildDir = stem.cmakeBuildDir; 881 } 882 if (stem.sourceDir != null) { 883 this.sourceDir = stem.sourceDir; 884 } 885 } 886 return this; 887 } 888 889 public CMakeBuilder build_dir(CMakeBuildDir cmakeBuildDir) { 890 this.cmakeBuildDir = cmakeBuildDir; 891 opts("-B", cmakeBuildDir.path.toString()); 892 return this; 893 } 894 public CMakeBuilder copy_to(BuildDir copyToDir) { 895 this.copyToDir = copyToDir; 896 opts("-DHAT_TARGET=" +this.copyToDir.path().toString()); 897 return this; 898 } 899 900 public CMakeBuilder source_dir(Dir sourceDir) { 901 this.sourceDir = sourceDir; 902 opts("-S", sourceDir.path().toString()); 903 return this; 904 } 905 906 public CMakeBuilder build(CMakeBuildDir cmakeBuildDir) { 907 this.cmakeBuildDir = cmakeBuildDir; 908 opts("--build", cmakeBuildDir.path().toString()); 909 return this; 910 } 911 912 @Override 913 public List<String> execOpts() { 914 return opts; 915 } 916 } 917 918 public static void cmake(Consumer<CMakeBuilder> cmakeBuilderConsumer) { 919 920 CMakeBuilder cmakeBuilder = new CMakeBuilder(); 921 cmakeBuilderConsumer.accept(cmakeBuilder); 922 cmakeBuilder.cmakeBuildDir.create(); 923 cmakeBuilder.execInheritIO(); 924 } 925 926 927 static Path unzip(Path in, Path dir) { 928 try { 929 Files.createDirectories(dir); 930 ZipFile zip = new ZipFile(in.toFile()); 931 zip.entries().asIterator().forEachRemaining(entry -> { 932 try { 933 String currentEntry = entry.getName(); 934 935 Path destFile = dir.resolve(currentEntry); 936 //destFile = new File(newPath, destFile.getName()); 937 Path destinationParent = destFile.getParent(); 938 Files.createDirectories(destinationParent); 939 // create the parent directory structure if needed 940 941 942 if (!entry.isDirectory()) { 943 zip.getInputStream(entry).transferTo(Files.newOutputStream(destFile)); 944 } 945 } catch (IOException ioe) { 946 throw new RuntimeException(ioe); 947 } 948 }); 949 zip.close(); 950 951 } catch (IOException e) { 952 throw new RuntimeException(e); 953 } 954 return dir; 955 } 956 957 public static class JExtractBuilder extends ExecBuilder<JExtractBuilder> { 958 public List<String> compileFlags = new ArrayList<>(); 959 public List<Path> libraries = new ArrayList<>(); 960 public List<Path> headers = new ArrayList<>(); 961 public Path cwd; 962 963 public Path home; 964 private String targetPackage; 965 private Path output; 966 @Override 967 public JExtractBuilder show(Consumer<String> stringConsumer) { 968 return self(); 969 } 970 public JExtractBuilder() { 971 opts.add("jextract"); 972 } 973 974 public JExtractBuilder basedOn(JExtractBuilder stem) { 975 super.basedOn(stem); 976 if (stem != null) { 977 if (stem.output != null) { 978 this.output = stem.output; 979 } 980 if (stem.compileFlags != null) { 981 this.compileFlags = new ArrayList<>(stem.compileFlags); 982 } 983 if (stem.libraries != null) { 984 this.libraries = new ArrayList<>(stem.libraries); 985 } 986 if (stem.home != null) { 987 this.home = stem.home; 988 } 989 if (stem.cwd != null) { 990 this.cwd = stem.cwd; 991 } 992 if (stem.headers != null) { 993 this.headers = new ArrayList<>(stem.headers); 994 } 995 } 996 return this; 997 } 998 999 1000 public JExtractBuilder cwd(Path cwd) { 1001 this.cwd = cwd; 1002 return this; 1003 } 1004 1005 public JExtractBuilder home(Path home) { 1006 this.home = home; 1007 opts.set(0, home.resolve("bin/jextract").toString()); 1008 return this; 1009 } 1010 1011 public JExtractBuilder opts(String... opts) { 1012 this.opts.addAll(Arrays.asList(opts)); 1013 return this; 1014 } 1015 1016 public JExtractBuilder target_package(String targetPackage) { 1017 this.targetPackage = targetPackage; 1018 opts("--target-package", targetPackage); 1019 return this; 1020 } 1021 1022 public JExtractBuilder output(Path output) { 1023 this.output = output; 1024 opts("--output", output.toString()); 1025 return this; 1026 } 1027 1028 public JExtractBuilder library(Path... libraries) { 1029 this.libraries.addAll(Arrays.asList(libraries)); 1030 for (Path library : libraries) { 1031 opts("--library", ":" + library); 1032 } 1033 return this; 1034 } 1035 1036 public JExtractBuilder compile_flag(String... compileFlags) { 1037 this.compileFlags.addAll(Arrays.asList(compileFlags)); 1038 return this; 1039 } 1040 1041 public JExtractBuilder header(Path header) { 1042 this.headers.add(header); 1043 this.opts.add(header.toString()); 1044 return this; 1045 } 1046 1047 @Override 1048 public List<String> execOpts() { 1049 return opts; 1050 } 1051 } 1052 1053 public static void jextract(Consumer<JExtractBuilder> jextractBuilderConsumer) { 1054 JExtractBuilder extractConfig = new JExtractBuilder(); 1055 jextractBuilderConsumer.accept(extractConfig); 1056 System.out.println(extractConfig.opts); 1057 var compilerFlags = extractConfig.cwd.resolve("compiler_flags.txt"); 1058 try { 1059 PrintWriter compilerFlagsWriter = new PrintWriter(Files.newOutputStream(compilerFlags)); 1060 compilerFlagsWriter.println(extractConfig.compileFlags); 1061 compilerFlagsWriter.close(); 1062 Files.createDirectories(extractConfig.output); 1063 extractConfig.execInheritIO(extractConfig.cwd); 1064 Files.deleteIfExists(compilerFlags); 1065 } catch (IOException e) { 1066 throw new RuntimeException(e); 1067 } 1068 } 1069 1070 1071 1072 public record SearchableTextFile(Path path) implements TextFile { 1073 public Stream<Line> lines() { 1074 try { 1075 int num[] = new int[]{1}; 1076 return Files.readAllLines(path(), StandardCharsets.UTF_8).stream().map(line -> new Line(line, num[0]++)); 1077 } catch (IOException ioe) { 1078 System.out.println(ioe); 1079 return new ArrayList<Line>().stream(); 1080 } 1081 } 1082 1083 public boolean grep(Pattern pattern) { 1084 return lines().anyMatch(line -> pattern.matcher(line.line).matches()); 1085 } 1086 1087 public boolean hasSuffix(String... suffixes) { 1088 var suffixSet = Set.of(suffixes); 1089 int dotIndex = path().toString().lastIndexOf('.'); 1090 return dotIndex == -1 || suffixSet.contains(path().toString().substring(dotIndex + 1)); 1091 } 1092 } 1093 1094 public record Line(String line, int num) { 1095 public boolean grep(Pattern pattern) { 1096 return pattern.matcher(line()).matches(); 1097 } 1098 } 1099 1100 public static Path curl(URL url, Path file) { 1101 try { 1102 println("Downloading " + url + "->" + file); 1103 url.openStream().transferTo(Files.newOutputStream(file)); 1104 } catch (IOException e) { 1105 throw new RuntimeException(e); 1106 } 1107 return file; 1108 } 1109 1110 public static Optional<Path> which(String execName) { 1111 // which and whereis had issues. 1112 return Arrays.asList(System.getenv("PATH").split(File.pathSeparator)).stream() 1113 .map(dirName -> Path.of(dirName).resolve(execName).normalize()) 1114 .filter(Files::isExecutable).findFirst(); 1115 } 1116 1117 public static boolean canExecute(String execName) { 1118 // which and whereis had issues. 1119 return which(execName).isPresent(); 1120 } 1121 1122 public static Path untar(Path tarFile, Path dir) { 1123 try { 1124 new ProcessBuilder().inheritIO().command("tar", "xvf", tarFile.toString(), "--directory", tarFile.getParent().toString()).start().waitFor(); 1125 return dir; 1126 } catch ( 1127 InterruptedException e) { // We get IOException if the executable not found, at least on Mac so interuppted means it exists 1128 return null; 1129 } catch (IOException e) { // We get IOException if the executable not found, at least on Mac 1130 //throw new RuntimeException(e); 1131 return null; 1132 } 1133 } 1134 1135 1136 1137 1138 public record Root(Path path) implements DirPathHolder<Root> { 1139 public BuildDir buildDir() { 1140 return BuildDir.of(path("build")).create(); 1141 } 1142 1143 public BuildDir thirdPartyDir() { 1144 return BuildDir.of(path("thirdparty")).create(); 1145 } 1146 1147 public BuildDir repoDir() { 1148 return BuildDir.of(path("repoDir")).create(); 1149 } 1150 1151 public Root() { 1152 this(Path.of(System.getProperty("user.dir"))); 1153 } 1154 } 1155 1156 1157 public static Path requireJExtract(Dir thirdParty) { 1158 var optional = executablesInPath("jextract").findFirst(); 1159 if (optional.isPresent()) { 1160 println("Found jextract in PATH"); 1161 return optional.get().getParent().getParent(); // we want the 'HOME' dir 1162 } 1163 println("No jextract in PATH"); 1164 URL downloadURL = null; 1165 var extractVersionMaj = "22"; 1166 var extractVersionMin = "5"; 1167 var extractVersionPoint = "33"; 1168 1169 1170 var nameArchTuple = switch (os.name()) { 1171 case OS.MacName -> "macos"; 1172 default -> os.name().toLowerCase(); 1173 } + '-' + os.arch(); 1174 1175 try { 1176 downloadURL = new URI("https://download.java.net/java/early_access" 1177 + "/jextract/" + extractVersionMaj + "/" + extractVersionMin 1178 + "/openjdk-" + extractVersionMaj + "-jextract+" + extractVersionMin + "-" + extractVersionPoint + "_" 1179 + nameArchTuple + "_bin.tar.gz").toURL(); 1180 } catch (MalformedURLException e) { 1181 throw new RuntimeException(e); 1182 } catch (URISyntaxException e) { 1183 throw new RuntimeException(e); 1184 } 1185 URL finalDownloadURL = downloadURL; 1186 1187 println("... attempting download from" + downloadURL); 1188 var jextractTar = thirdParty.path("jextract.tar"); 1189 1190 if (!isRegularFile(jextractTar)) { // Have we downloaded already? 1191 jextractTar = curl(finalDownloadURL, jextractTar); // if not 1192 } 1193 1194 var jextractHome = thirdParty.path("jextract-22"); 1195 if (!isDirectory(jextractHome)) { 1196 untar(jextractTar, jextractHome); 1197 } 1198 return jextractHome; 1199 1200 } 1201 1202 1203 1204 public static Stream<Path> executablesInPath(String name) { 1205 return Arrays.asList(System.getenv("PATH").split(File.pathSeparator)).stream() 1206 .map(dirName -> Path.of(dirName).resolve(name).normalize()) 1207 .filter(Files::isExecutable); 1208 1209 } 1210 1211 public static void sanity(Root hatDir) { 1212 var rleParserDir = hatDir.path().resolve("examples/life/src/main/java/io"); 1213 Dir.of(hatDir.path).forEachSubDirectory( "hat", "examples", "backends", "docs").forEach(dir ->{ 1214 dir.findFiles() 1215 .filter((path)->Pattern.matches("^.*\\.(java|cpp|h|hpp|md)", path.toString())) 1216 .forEach(path -> println(path)); 1217 1218 dir.findTextFiles("java", "cpp", "h", "hpp", "md") 1219 .forEach(searchableTextFile -> { 1220 if (!searchableTextFile.path().getFileName().toString().equals("Makefile") && !searchableTextFile.hasSuffix("md") 1221 && !searchableTextFile.path().startsWith(rleParserDir) 1222 && !searchableTextFile.grep(Pattern.compile("^.*Copyright.*202[4-9].*(Intel|Oracle).*$"))) { 1223 System.err.println("ERR MISSING LICENSE " + searchableTextFile.path()); 1224 } 1225 searchableTextFile.lines().forEach(line -> { 1226 if (!searchableTextFile.path().getFileName().toString().startsWith("Makefile") && line.grep(Pattern.compile("^.*\\t.*"))) { 1227 System.err.println("ERR TAB " + searchableTextFile.path() + ":" + line.line() + "#" + line.num()); 1228 } 1229 if (line.grep(Pattern.compile("^.* $"))) { 1230 System.err.println("ERR TRAILING WHITESPACE " + searchableTextFile.path() + ":" + line.line() + "#" + line.num()); 1231 } 1232 }); 1233 });} 1234 ); 1235 } 1236 1237 public static <T> T assertOrThrow(T testme, Predicate<T> predicate, String message){ 1238 if (predicate.test(testme)) { 1239 return testme; 1240 }else{ 1241 throw new IllegalStateException("FAILED: "+message+" "+testme); 1242 } 1243 } 1244 1245 public static <T extends PathHolder> T assertExists(T testme){ 1246 if (Files.exists(testme.path())) { 1247 return testme; 1248 }else{ 1249 throw new IllegalStateException("FAILED: "+testme.path()+" does not exist"); 1250 } 1251 } 1252 public static <T extends Path> T assertExists(T path){ 1253 if (Files.exists(path)) { 1254 return path; 1255 }else{ 1256 throw new IllegalStateException("FAILED: "+path+" does not exist"); 1257 } 1258 } 1259 1260 void main(String[] args) { 1261 var bldrDir = Dir.current().parent().parent().parent(); 1262 var buildDir =BuildDir.of(bldrDir.path("build")).create(); 1263 1264 jar($->$ 1265 .jar(buildDir.jarFile("bldr.jar")) 1266 .javac($$->$$ 1267 .opts( 1268 "--source", "24", 1269 "--enable-preview", 1270 "--add-exports=java.base/jdk.internal=ALL-UNNAMED", 1271 "--add-exports=java.base/jdk.internal.vm.annotation=ALL-UNNAMED" 1272 ) 1273 .class_dir(buildDir.classDir("bld.jar.classes")) 1274 .source_path(bldrDir.dir("src/main/java")) 1275 ) 1276 ); 1277 } 1278 }