Sun, 28 Jan 2024 13:26:47 +0100
validation errors contain line and column number
package de.unixwork.uwproj; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import org.xml.sax.SAXParseException; import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.SchemaFactory; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.nio.file.attribute.PosixFilePermissions; import java.util.Arrays; import java.util.Objects; public class Main { public final static String IN_FILE_DEFAULT = "make/project.xml"; public final static String TPL_FILE_DEFAULT = "make/configure.vm"; public final static String OUT_FILE_DEFAULT = "configure"; static Project loadProjectFile(String fileName) { try (final var xsdResource = Objects.requireNonNull(Main.class.getClassLoader().getResourceAsStream("make/uwproj.xsd"))) { // Create the XSD validator final var factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); final var schema = factory.newSchema(new StreamSource(xsdResource)); final var validator = schema.newValidator(); // Validate the XML as stream (DOM validation cannot output line numbers in error message) validator.validate(new StreamSource(new File(fileName))); // Load the DOM from the input file final var dom = DocumentBuilderFactory. newDefaultNSInstance(). newDocumentBuilder(). parse(fileName). getDocumentElement(); // Parse the XML return new Project(dom); } catch (SAXParseException saxerror) { System.err.printf("Parse error in line %d, column %d: %s", saxerror.getLineNumber(), saxerror.getColumnNumber(), saxerror.getMessage()); throw new RuntimeException("XML validation failed."); } catch (Exception e) { throw new RuntimeException(e); } } static void writeConfigureScript(Writer out, String tplFileName, Project project) { var context = new VelocityContext(); context.put("targets", project.getTargets()); context.put("namedDependencies", project.getNamedDependencies()); context.put("dependencies", project.getDependencies()); context.put("options", project.getOptions()); context.put("features", project.getFeatures()); context.put("project", project); context.put("vars", project.getVars()); context.put("languages", project.getLang()); new VelocityEngine().getTemplate(tplFileName).merge(context, out); } private static void abort() { System.err.println("Abort."); System.exit(1); } private static void printUsage() { System.err.println("Usage: uwproj [OPTIONS]"); System.err.println(); System.err.println("OPTIONS:"); System.err.println(" -h --help Print help text and exit"); System.err.println(" --init Creates default files in directory 'make'"); System.err.println(" -o --output <file> Path to the output file"); System.err.printf(" (default: %s)\n", OUT_FILE_DEFAULT); System.err.println(" -p --project <file> Path to the project.xml file"); System.err.printf(" (default: %s)\n", IN_FILE_DEFAULT); System.err.println(" -t --template <file> Path to the velocity template"); System.err.printf(" (default: %s)\n", TPL_FILE_DEFAULT); System.err.println(" --update Same as --init, but overwrites existing files"); } private static void extract(Path path) { try (final var res = Objects.requireNonNull(Main.class.getClassLoader().getResourceAsStream(path.toString()))) { Files.copy(res, path, StandardCopyOption.REPLACE_EXISTING); } catch (Throwable t) { System.err.printf("Cannot extract '%s': %s\n", path, t.getMessage()); abort(); } } private static void init(boolean force) { final var files = new String[]{ "cc.mk", "gcc.mk", "clang.mk", "suncc.mk", "configure.vm", "toolchain.sh", "uwproj.xsd" }; // (1) create the make dir if it does not exist final var make = Paths.get("make"); if (Files.exists(make)) { if (!Files.isDirectory(make)) { System.err.println("A file with name 'make' already exists, but it's not a directory."); abort(); } if (!force) { System.err.println("uwproj files already exist. You can use --update instead."); abort(); } } else { try { Files.createDirectory(make); } catch (Throwable t) { System.err.printf("Creating directory failed: %s\n", t.getMessage()); abort(); } } // (2) create project.xml ONLY if it does not exist final var project = make.resolve("project.xml"); if (Files.notExists(project)) { extract(project); } // (3) create or update remaining uwproj files Arrays.stream(files).map(make::resolve).forEach(Main::extract); // (4) stop System.out.printf("uwproj files %s.\n", force ? "updated" : "created"); System.exit(0); } private static void checkOneMoreArg(int i, int length) { if (i+1 >= length) { printUsage(); System.exit(1); } } public static void main(String[] args) { var inFileName = IN_FILE_DEFAULT; var tplFileName = TPL_FILE_DEFAULT; var outFileName = OUT_FILE_DEFAULT; boolean opsOptionsUsed = false; boolean doInit = false; boolean forceInit = false; for (int i = 0 ; i < args.length ; i++) { switch (args[i]) { case "-o": case "--output": checkOneMoreArg(i, args.length); outFileName = args[++i]; opsOptionsUsed = true; break; case "-p": case "--project": checkOneMoreArg(i, args.length); inFileName = args[++i]; opsOptionsUsed = true; break; case "-t": case "--template": checkOneMoreArg(i, args.length); tplFileName = args[++i]; opsOptionsUsed = true; break; case "--init": doInit = true; break; case "--update": doInit = true; forceInit = true; break; case "--help": case "-h": printUsage(); System.exit(0); default: printUsage(); System.exit(1); } } if (opsOptionsUsed && doInit) { System.err.println("Cannot use --init or --update with other options."); abort(); } if (doInit) { init(forceInit); } System.out.println("In: " + inFileName); System.out.println("Tpl: " + tplFileName); final var outFilePath = Path.of(outFileName); try { final var tmp = File.createTempFile("uwproj", null); try (var out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(tmp)))) { writeConfigureScript(out, tplFileName, loadProjectFile(inFileName)); } Files.copy(tmp.toPath(), outFilePath, StandardCopyOption.REPLACE_EXISTING); } catch (Throwable t) { System.err.printf("Unexpected error: %s\n", t.getMessage()); abort(); } System.out.println("Out: " + outFileName); try { Files.setPosixFilePermissions(outFilePath, PosixFilePermissions.fromString("rwxr-xr-x")); } catch (Throwable t) { System.err.printf("WARN: Setting file permissions failed: %s\n", t.getMessage()); } } }