diff a/hat/mkpoms b/hat/mkpoms
--- /dev/null
+++ b/hat/mkpoms
@@ -0,0 +1,334 @@
+/* vim: set ft=java: 
+ *
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import static bldr.Bldr.*;
+import static bldr.Bldr.Capabilities.*;
+
+void main(String[] argv) {
+   var pomComment = """
+      Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+      DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+
+      This code is free software; you can redistribute it and/or modify it
+      under the terms of the GNU General Public License version 2 only, as
+      published by the Free Software Foundation.  Oracle designates this
+      particular file as subject to the "Classpath" exception as provided
+      by Oracle in the LICENSE file that accompanied this code.
+
+      This code is distributed in the hope that it will be useful, but WITHOUT
+      ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+      FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+      version 2 for more details (a copy is included in the LICENSE file that
+      accompanied this code).
+
+      You should have received a copy of the GNU General Public License version
+      2 along with this work; if not, write to the Free Software Foundation,
+      Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+
+      Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+      or visit www.oracle.com if you need additional information or have any
+      questions.                
+      """;
+
+  var hatDir = DirEntry.current();
+  var hatCore = hatDir.existingDir("hat");
+  var backends = hatDir.existingDir("backends");
+  var examples = hatDir.existingDir("examples");
+  var extractions = hatDir.existingDir("extractions");
+  var buildDir = hatDir.existingBuildDir("build");
+
+  var opencl = OpenCL.of();
+  var opengl = OpenGL.of();
+  var cuda = CUDA.of();
+  var hip = HIP.of();
+  var jextract = JExtract.of();
+  var cmake = CMake.of();
+
+  var capabilities = Capabilities.of(opencl, opengl, cuda, hip, jextract, cmake);
+
+  if (cmake.available()) {
+    cmake.probe(buildDir, capabilities);
+  }
+  capabilities.capabilities().forEach(fw -> println((fw.available() ? "we have " : "no ") + fw.name));
+
+  hatDir.pom(pomComment, pom -> pom
+      .comment("Auto generated by mkpoms")
+      .pom("oracle.code", "hat-root", "1.0")
+      .properties(properties -> properties
+          .property("project.build.sourceEncoding", "UTF-8")
+          .property("hat.root", "${env.PWD}")
+          .property("hat.build", "${hat.root}/build")
+          .property("hat.stage", "${hat.root}/stage")
+          .property("hat.stage.repo", "${hat.stage}/repo")
+          .property("hat.stage.jextract", "${hat.stage}/jextract")
+          .property("mac.app.frameworks", "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks")
+          .property("mac.lib.frameworks", "/System/Library/Frameworks")
+      )
+      .modules("hat", "extractions", "backends", "examples")
+      .build(build -> build
+          .plugins(plugins -> plugins
+              .compilerPlugin(plugin -> plugin
+                  .configuration(configuration -> configuration
+                      .compilerArgs(
+                          "--add-modules=jdk.incubator.code",
+                          "--enable-preview",
+                          "--add-exports=java.base/jdk.internal=ALL-UNNAMED",
+                          "--add-exports=java.base/jdk.internal.vm.annotation=ALL-UNNAMED"
+                      )
+                      .source("24")
+                      .target("24")
+                      .showDeprecation("true")
+                      .failOnError("true")
+                      .showWarnings("true")
+                  )
+              )
+          )
+      )
+  );
+
+  // Now hat/pom.xml
+  hatCore.pom(pomComment, pom -> pom
+      .comment("Auto generated by mkpoms")
+      .jar("oracle.code", "hat", "1.0")
+      .parent("oracle.code", "hat-root", "1.0")
+      .dependsOn("org.junit.jupiter", "junit-jupiter-engine", "5.10.0", "test")
+      .build(build -> build
+          .pluginManagement(pluginManagement -> pluginManagement
+              .plugins(plugins -> plugins
+                  .surefirePlugin(plugin -> plugin
+                      .configuration(configuration -> configuration
+                          .argLine("-enable-preview")
+                      )
+                  )
+              )
+          )
+          .plugins(plugins -> plugins
+              .antPlugin(plugin -> plugin
+                  .executions(executions -> executions
+                      .ant("1", "install", "run", ant -> ant
+                          .copy("target/${project.artifactId}-1.0.jar", "${hat.build}")
+                      )
+                  )
+              )
+          )
+      )
+  );
+
+  // Note that we filter by capability here. So we only create poms (and include modules in parent) if capability is available
+  var extractionDirs = extractions
+      .subDirs(extraction -> !extraction.matches("^.*(target)$")).filter(dir -> capabilities.capabilityIsAvailable(dir.fileName())).toList();
+
+  extractions.pom(pomComment, pom -> pom
+      .comment("Auto generated by mkpoms")
+      .pom("oracle.code", "hat-extractions", "1.0")
+      .parent("oracle.code", "hat-root", "1.0")
+      .modules(extractionDirs.stream().map(PathHolder::fileName).toList())
+      .build(build -> build
+          .plugins(plugins -> plugins
+              .antPlugin(plugin -> plugin
+                  .executions(executions -> executions
+                      .ant("createDir", "compile", "run", ant -> ant
+                          .mkdir("${hat.stage.jextract}")
+                          .echo("${hat.stage.jextract}/compile_flags.txt", "-F${mac.app.frameworks}")
+                      )
+                  )
+              )
+          )
+
+      )
+  );
+
+  extractionDirs.forEach(extraction -> extraction
+      .pom(pomComment, examplePom -> examplePom
+          .comment("Auto generated by mkpoms")
+          .pom("oracle.code", "hat-extraction-" + extraction.fileName(), "1.0")
+          .parent("oracle.code", "hat-extractions", "1.0")
+          .build(build -> build
+              .plugins(plugins -> plugins
+                  .execPlugin(plugin -> plugin
+                      .executions(execution -> execution
+                          .execIdPhaseConf("2", "compile", configuration -> configuration
+                              .executable("jextract")
+                              .workingDirectory("${hat.stage.jextract}")
+                              .arguments(arguments -> {
+                                arguments
+                                    .argument("--output").argument("${hat.stage.jextract}")
+                                    .argument("-t").argument(extraction.fileName());
+                                switch (extraction.fileName()) {
+                                  case "opencl" -> arguments
+                                      .argument("-l").argument("${mac.lib.frameworks}/OpenCL.framework/OpenCL")
+                                      .argument("${mac.app.frameworks}/OpenCL.framework/Headers/opencl.h");
+                                  case "opengl" -> arguments
+                                      .argument("-l").argument("${mac.lib.frameworks}/GLUT.framework/GLUT")
+                                      .argument("-l").argument("${mac.lib.frameworks}/OpenGL.framework/OpenGL")
+                                      .argument("${mac.app.frameworks}/GLUT.framework/Headers/glut.h");
+                                  default ->
+                                      throw new IllegalStateException("Unexpected value: " + extraction.fileName());
+                                }
+                              })
+                          )
+                      )
+                  )
+                  .antPlugin(plugin -> plugin
+                      .executions(executions -> executions
+                          .ant("1", "install", "run", ant -> ant
+                                   .antjar("${hat.stage.jextract}", extraction.fileName(), "${hat.build}/jextracted-" + extraction.fileName() + "-1.0.jar")
+                               //.copy("target/jextracted" + extraction.fileName() + "-1.0.jar", "${hat.build}")
+                          )
+                      )
+                  )
+              )
+          )
+      )
+  );
+
+  var exampleDirs = examples.subDirs(example -> !example.matches("^.*(experiments|target|nbody|.idea)$")).toList();
+
+  // Here we create examples/pom.xml and nested inside we create each example/*/pom.xml
+  examples.pom(pomComment, examplesPom -> examplesPom
+      .comment("Auto generated by mkpoms")
+      .pom("oracle.code", "hat-examples", "1.0")
+      .parent("oracle.code", "hat-root", "1.0")
+      .dependsOn("oracle.code", "hat", "1.0")
+      .modules(exampleDirs.stream().map(PathHolder::fileName).toList())
+  );
+
+  exampleDirs.forEach(example -> example
+      .pom(pomComment, examplePom -> examplePom
+          .comment("Auto generated by mkpoms")
+          .jar("oracle.code", "hat-example-" + example.fileName(), "1.0")
+          .parent("oracle.code", "hat-examples", "1.0")
+          .dependsOn("oracle.code", "hat", "1.0")
+          .build(build -> build
+              .plugins(plugins -> plugins
+                  .antPlugin(plugin -> plugin
+                      .executions(executions -> executions
+                          .ant("1", "install", "run", ant -> ant
+                              .copy("target/hat-example-" + example.fileName() + "-1.0.jar", "${hat.build}")
+                          )
+                      )
+                  )
+              )
+          )
+      )
+  );
+  var backendDirs = backends.subDirs(backend -> !backend.matches("^.*(jextracted|target|.idea)$")).toList();
+
+  // Now backends/pom.xml and backends/*/pom.xml
+  backends.pom(pomComment, backendsPom -> backendsPom
+      .comment("Auto generated by mkpoms")
+      .pom("oracle.code", "hat-backends", "1.0")
+      .parent("oracle.code", "hat-root", "1.0")
+      .dependsOn("oracle.code", "hat", "1.0")
+      .modules("java", "ffi")
+  );
+
+  {
+    var ffiBackend = backends.dir("ffi");
+
+
+    var ffiBackendDirs = ffiBackend.subDirs(backend -> !backend.matches("^.*(hip|spirv|shared|openclx|target|.idea)$")).toList();
+
+    ffiBackend.pom(pomComment, backendsPom -> backendsPom
+        .comment("Auto generated by mkpoms")
+        .pom("oracle.code", "hat-backends-ffi", "1.0")
+        .parent("oracle.code", "hat-backends", "1.0")
+        .dependsOn("oracle.code", "hat", "1.0")
+        .modules(ffiBackendDirs.stream().map(PathHolder::fileName).toList())
+        .build(build -> build
+            .plugins(plugins -> plugins
+                .execPlugin(plugin -> plugin
+                    .executions(executions -> executions
+                        .cmake("1", "compile", "-DHAT_TARGET=${hat.build}", "-B", "${hat.build}/cmake-build-debug")
+                        .cmake("2", "install", "--build", "${hat.build}/cmake-build-debug")
+                    )
+                )
+            )
+        )
+    );
+
+    ffiBackendDirs.forEach(backend -> backend
+        .pom(pomComment, backendPom -> backendPom
+            .comment("Auto generated by mkpoms")
+            .jar("oracle.code", "hat-backend-ffi-" + backend.fileName(), "1.0")
+            .parent("oracle.code", "hat-backends-ffi", "1.0")
+            .dependsOn("oracle.code", "hat", "1.0")
+            .build(build -> build
+                .plugins(plugins -> plugins
+                    .execPlugin(plugin -> plugin
+                        .configuration(configuration -> configuration
+                            .skip("true")
+                        )
+                    )
+                    .antPlugin(plugin -> plugin
+                        .executions(executions -> executions
+                            .ant("1", "install", "run", ant -> ant
+                                .copy("target/hat-backend-ffi-" + backend.fileName() + "-1.0.jar", "${hat.build}")
+                            )
+                        )
+                    )
+                )
+            )
+        )
+    );
+  }
+  {
+    var javaBackend = backends.dir("java");
+    var javaBackendDirs = javaBackend.subDirs(backend -> !backend.matches("^.*(target|.idea)$")).toList();
+
+    javaBackend.pom(pomComment, backendsPom -> backendsPom
+        .comment("Auto generated by mkpoms")
+        .pom("oracle.code", "hat-backends-java", "1.0")
+        .parent("oracle.code", "hat-backends", "1.0")
+        .dependsOn("oracle.code", "hat", "1.0")
+        .modules(javaBackendDirs.stream().map(PathHolder::fileName).toList())
+    );
+
+    javaBackendDirs.forEach(backend -> backend
+        .pom(pomComment, backendPom -> backendPom
+            .comment("Auto generated by mkpoms")
+            .jar("oracle.code", "hat-backend-java-" + backend.fileName(), "1.0")
+            .parent("oracle.code", "hat-backends-java", "1.0")
+            .dependsOn("oracle.code", "hat", "1.0")
+            .build(build -> build
+                .plugins(plugins -> plugins
+                    .antPlugin(plugin -> plugin
+                        .executions(executions -> executions
+                            .ant("1", "install", "run", ant -> ant
+                                .copy("target/hat-backend-java-" + backend.fileName() + "-1.0.jar", "${hat.build}")
+                            )
+                        )
+                    )
+                )
+            )
+        )
+    );
+  }
+
+
+  var backendJExtracted = backends.dir("jextracted");
+
+}
