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 import job.*;
 27 
 28 static String logo = """
 29         ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 30         ⠀⠀⠀⠀⠀⠀⠀⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣤⣀⠀⠀⠀⠀⠀⠀⠀
 31         ⠀⠀⠀⠀⠀⠀⠀ ⠙⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠀⠀⠀⠀⠀⠀⠀
 32         ⠀⠀⠀⠀⠀⠀⠀⠀⣷⣶⣤⣄⣈⣉⣉⣉⣉⣉⣉⣉⣁⣤⡄⠀⠀⠀⠀⠀⠀⠀
 33         ⠀⠀⠀⠀⠀⠀ ⠀⣿⣿⣿⣿⣿ HAT ⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀
 34         ⠀⠀⢀⣠⣶⣾⡏⢀⡈⠛⠻⠿⢿⣿⣿⣿⣿⣿⠿⠿⠟⠛⢁⠀⢶⣤⣀⠀⠀⠀
 35         ⠀⢠⣿⣿⣿⣿⡇⠸⣿⣿⣶⣶⣤⣤⣤⣤⣤⣤⣤⣶⣶⣿⡿⠂⣸⣿⣿⣷⡄⠀
 36         ⠀⢸⣿⣿⣿⣿⣿⣦⣄⡉⠛⠛⠛⠿⠿⠿⠿⠛⠛⠛⢉⣁⣤⣾⣿⣿⣿⣿⡷⠀
 37         ⠀⠀⠙⢿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣶⣶⣶⣶⣶⣾⣿⣿⣿⣿⣿⣿⣿⡿⠛⠁⠀
 38         ⠀⠀⠀⠀⠈⠙⠛⠿⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠿⠿⠛⠛⠉⠁⠀⠀⠀⠀
 39         """;
 40 static String help = """
 41         Usage  bld|clean|run ...
 42              bld:
 43                    Compile all buildable (based on capabilities) available jars and native code.
 44 
 45              dot:
 46                    Create dot graph  (bld.dot) of buildable dependencies (based on capabilities)
 47                       dot bld.dot -Tsvg > bld.svg  && chrome bld.svg
 48            clean:
 49                    Removes build directory entirely
 50                    conf dir and jextracted artifacts (opencl/cuda/opengl) remain
 51 
 52 
 53              run:  [ffi|my|seq]-[opencl|java|cuda|mock|hip] [-DXXX ...] runnable  args
 54                       run ffi-opencl mandel
 55                       run ffi-opencl nbody 4096
 56                       run ffi-opencl -DHAT=SHOW_CODE nbody 4096
 57                       run ffi-opencl -DHAT=SHOW_KERNEL_MODEL heal
 58                       run ffi-opencl -DHAT=MINIMIZE_BUFFERS life
 59 
 60              exp:  [ffi|my|seq]-[opencl|java|cuda|mock|hip] [-DXXX ... ] experimentClassName  args
 61                       exp ffi-opencl QuotedConstantArgs
 62 
 63              test:  [ffi|my|seq]-[opencl|java|cuda|mock|hip]
 64                       test ffi-opencl
 65 
 66           sanity:  Check source files for copyright and WS issues (tabs and trailing EOL WS)
 67         """;
 68 
 69 
 70 public static void main(String[] argArr) throws IOException, InterruptedException {
 71     var args = new ArrayList<>(List.of(argArr));
 72     if (args.isEmpty()) {
 73         System.out.println(help);
 74     } else {
 75         Path hatDir = Path.of(System.getProperty("user.dir"));
 76         var project = new Project(hatDir, Reporter.progressAndErrors);
 77         var cmake = project.isAvailable("cmake", "--version");
 78         if (!cmake.isAvailable()) {
 79             System.out.println("We need cmake, to check the availability of opencl, cuda etc so we wont be able to build much  ");
 80         }
 81         var jextract = project.isAvailable("jextract", "--version");
 82         if (!jextract.isAvailable()) {
 83             System.out.println("We will need jextract to create jextracted backends and for examples requiring opengl ");
 84         }
 85 
 86         // A user defined optional dependency.  Anything depending on this will only build if it is true
 87         // In our case we pull the value from the headless system property
 88         var ui = new Opt(project.id("ui"), !Boolean.getBoolean("headless"));
 89 
 90         // These dependencies are only 'true' on the appropriate platform.
 91         // So any target that depends on one of these, will only build on that platform
 92         var mac = new Mac(project.id("os-mac"));
 93         var linux = new Linux(project.id("os-linux"));
 94         // var windows = new Windows(project.id("os-windows")); maybe one day
 95 
 96         // These next three 'optional' dependencies use cmake to determine availability.  We delegate to cmake which
 97         //    a) determines if capability is available,
 98         //    b) if they are, they extract from cmake vars (see conf/cmake-info/OpenCL/properties for example) information export headers and libs needed by JExtract
 99         var openclCmakeInfo = new OpenCL(project.id("cmake-info-opencl"), cmake);
100         var openglCmakeInfo = new OpenGL(project.id("cmake-info-opengl"), cmake);
101         var cudaCmakeInfo = new Cuda(project.id("cmake-info-cuda"), cmake);
102 
103         // Now we just create jars and shared libs and declare dependencies
104         var core = Jar.of(project.id("core"));
105         var tools = Jar.of(project.id("tools"), core);
106         var tests = Jar.of(project.id("tests"), core, tools);
107         var backend_ffi_native = CMake.of(project.id("backend{s}-ffi"), core, cmake);
108         var ffiSharedBackend = Jar.of(project.id("backend{s}-ffi-shared"), backend_ffi_native);
109         var backend_ffi_cuda = Jar.of(project.id("backend{s}-ffi-cuda"), ffiSharedBackend);
110         var backend_ffi_opencl = Jar.of(project.id("backend{s}-ffi-opencl"), ffiSharedBackend);
111         var backend_ffi_mock = Jar.of(project.id("backend{s}-ffi-mock"), ffiSharedBackend);
112 
113         // These examples just rely on core
114         var backend_mt_java = Jar.of(project.id("backend{s}-java-mt"), core);
115         var backend_seq_java = Jar.of(project.id("backend{s}-java-seq"), core);
116         var example_squares = Jar.of(project.id("example{s}-squares"), core);
117         var example_matmul = Jar.of(project.id("example{s}-matmul"), core);
118         var example_arrayview = Jar.of(project.id("example{s}-arrayview"), core);
119         var example_blackscholes = Jar.of(project.id("example{s}-blackscholes"), core);
120         var example_normmap = Jar.of(project.id("example{s}-normmap"), core); // will probabvly need shared when we hatify
121 
122         // example_shared allows us to break out common UI functions, views, even loops etc
123         var example_shared = Jar.of(project.id("example{s}-shared"), ui, core);
124 
125         // These examples use example_shared, so they are UI based
126         var example_mandel = Jar.of(project.id("example{s}-mandel"), example_shared);
127         var example_life = Jar.of(project.id("example{s}-life"), example_shared);
128         var example_heal = Jar.of(project.id("example{s}-heal"), example_shared);
129         var example_violajones = Jar.of(project.id("example{s}-violajones"), example_shared);
130 
131         // experiments include code that expects an opencl backend, this is not idea, but we can accomodate
132         var example_experiments = Jar.of(project.id("example{s}-experiments"), backend_ffi_opencl);
133 
134         // Now we have the more complex nonsense for nbody (which needs opengl and opencl extracted)
135         var wrapped_shared = Jar.of(project.id("wrap{s}-shared"));
136         var jextracted_opencl = JExtract.extract(project.id("extract{ions|ed}-opencl"), jextract, openclCmakeInfo, core);
137         var wrapped_jextracted_opencl = Jar.of(project.id("wrap{s}-opencl"), jextracted_opencl, wrapped_shared);
138         var jextracted_opengl = JExtract.extract(project.id("extract{ions|ed}-opengl"), jextract, ui, openglCmakeInfo, core);
139 
140         // Sigh... We have different src exclusions for wrapped opengl depending on the OS
141         var excludedOpenGLWrapSrc = project.rootPath().resolve(
142                 "wraps/opengl/src/main/java/wrap/opengl/GL" + (mac.isAvailable() ? "Callback" : "Func") + "EventHandler.java");
143 
144         var wrapped_jextracted_opengl = Jar.of(project.id("wrap{s}-opengl"), Set.of(excludedOpenGLWrapSrc), jextracted_opengl, wrapped_shared);
145 
146         // Finally we have everything needed for nbody
147         var example_nbody = Jar.of(project.id("example{s}-nbody"), ui, wrapped_jextracted_opengl, wrapped_jextracted_opencl);
148 
149         while (!args.isEmpty()) {
150             var arg = args.removeFirst();
151             switch (arg) {
152                 case "help" -> System.out.println(logo + "\n" + help);
153                 case "clean" -> project.clean();
154                 case "dot" -> {
155                     var dag = project.all();
156                     var available = dag.available();
157                     Files.writeString(Path.of("bld.dot") , available.toDot());
158                     System.out.println("Consider...\n    dot bld.dot -Tsvg > bld.svg");
159                 }
160                 case "bld" -> {
161                     var dag = project.all();
162                     var available = dag.available();
163                     project.build(available);
164                 }
165                 case "sanity" -> {
166                     final  var copyrightPattern = Pattern.compile("^.*Copyright.*202[0-9].*(Intel|Oracle).*$");
167                     final  var copyrightExemptPattern = Pattern.compile("^(robertograham|CMakeFiles|hip)");
168                     final  var tabOrEolWsPattern = Pattern.compile("^(.*\\t.*|.* )$");
169                     final  var textSuffix  = Pattern.compile("^(.*\\.(java|cpp|h|hpp|md)|pom.xml)$");
170                     final  var sourceSuffix  = Pattern.compile("^(.*\\.(java|cpp|h|hpp)|pom.xml)$");
171 
172                     Stream.of("core","tools","examples","backends","docs","wraps")
173                             .map(hatDir::resolve)
174                             .forEach(dir-> {
175                                 Util.recurse(dir,
176                                    (d)-> true, // we do this for all subdirs
177                                    (f)-> textSuffix.matcher(f.getFileName().toString()).matches() && Util.grepLines(tabOrEolWsPattern, f),
178                                    (c)-> System.out.println("File contains WS issue (TAB or EOLWs) " + c)
179                                 );
180                                 Util.recurse(dir,
181                                    (d)-> !copyrightExemptPattern.matcher(d.getFileName().toString()).matches(), // we skip these subdirs
182                                    (f)-> sourceSuffix.matcher(f.getFileName().toString()).matches() && !Util.grepLines(copyrightPattern, f),
183                                    (c)-> System.out.println("File does not contain copyright " + c)
184                                 );
185                             });
186                     args.clear();
187                 }
188                 case "run" -> {
189                     if (args.size() > 1) {
190                         var backendName = args.removeFirst();
191                         var vmOpts = new ArrayList<String>(List.of());
192                         while (args.getFirst() instanceof String  possibleVmOpt &&  possibleVmOpt.startsWith("-")){
193                             vmOpts.add(args.removeFirst());
194                         }
195                         var runnableName = args.removeFirst();
196                         if (project.get(backendName) instanceof Jar backend) {
197                             if (project.get(runnableName) instanceof Jar runnable) {
198                                 if (runnableName.equals("nbody") && mac.isAvailable()) {  // nbody (anything on mac using OpenGL
199                                     vmOpts.add("-XstartOnFirstThread");
200                                 }
201                                 runnable.run(runnableName + ".Main", new job.Dag(runnable, backend).ordered(), vmOpts,args);
202                             } else {
203                                 System.err.println("Failed to find runnable " + runnableName);
204                             }
205                         } else {
206                             System.err.println("Failed to find " + backendName);
207                         }
208                     } else {
209                         System.err.println("For run we expect 'run backend runnable' ");
210                     }
211                     args.clear(); //!! :)
212                 }
213                 case "test" -> {
214                     if (args.size() > 0) {
215                         var backendName = args.removeFirst();
216                         if (project.get(backendName) instanceof Jar backend) {
217                            class Stats {
218                                int passed = 0;
219                                int failed = 0;
220                            }
221                            var test_reports_txt = Paths.get("test_report.txt");
222                            Files.deleteIfExists(test_reports_txt); // because we will append to it in the next loop
223                            var suiteRe = Pattern.compile("(oracle/code/hat/Test[a-zA-Z0-9]*).class");
224                            var jarFile = new JarFile(tests.jarFile().toString());
225                            var testEngine = "oracle.code.hat.engine.HatTestEngine";
226                            var entries = jarFile.entries();
227                            var orderedDag  = new job.Dag(tests, backend).ordered();
228                            while (entries.hasMoreElements()) {
229                               if (suiteRe.matcher(entries.nextElement().getName()) instanceof Matcher matched && matched.matches()){
230                                   tests.run(testEngine, orderedDag, List.of(),List.of(matched.group(1).replace('/','.')));
231                               }
232                            }
233                            System.out.println("\n\n"+logo+"                 HAT Test Report ");
234                            System.out.println("************************************************");
235                            var pattern = Pattern.compile( "passed: (\\d+), failed: (\\d+)");
236                            var stats = new Stats();
237                            Files.readAllLines(test_reports_txt).forEach(line->{
238                               System.out.println(line);
239                               if (pattern.matcher(line) instanceof Matcher matcher && matcher.find()){
240                                  stats.passed+=Integer.parseInt(matcher.group(1));
241                                  stats.failed+=Integer.parseInt(matcher.group(2));
242                               }
243                           });
244                           System.out.printf("Global passed: %d, failed: %d, pass-rate: %.2f%%",
245                                 stats.passed, stats.failed, ((float)(stats.passed * 100 / (stats.passed + stats.failed))));
246                         } else {
247                            System.err.println("Failed to find backend   " + backendName);
248                         }
249                     } else {
250                         System.err.println("For test we require a backend ");
251                     }
252                     args.clear(); //!! :)
253                 }
254                 case "exp" -> {
255                     if (args.size() > 1) {
256                         var backendName = args.removeFirst();
257                         var runnableName = "experiments";
258                         var vmOpts = new ArrayList<String>(List.of());
259                         while (args.getFirst() instanceof String  possibleVmOpt &&  possibleVmOpt.startsWith("-")){
260                             vmOpts.add(args.removeFirst());
261                         }
262                         var className = args.removeFirst();
263                         if (project.get(backendName) instanceof Jar backend) {
264                             if (project.get(runnableName) instanceof Jar runnable) {
265                                 runnable.run(runnableName + "."+className, new job.Dag(runnable, backend).ordered(), vmOpts,args);
266                             } else {
267                                 System.err.println("Failed to find runnable " + runnableName);
268                             }
269                         } else {
270                             System.err.println("Failed to find " + backendName);
271                         }
272                     } else {
273                         System.err.println("For exp we expect 'exp backend [-DXXX ...] testclass' ");
274                     }
275                     args.clear(); //!! :)
276                 }
277                 default -> {
278                     System.out.println("'" + arg + "' was unexpected ");
279                     System.out.println(help);
280                     args.clear();
281                 }
282             }
283         }
284     }
285 }