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 }