/* * Copyright (C) 2015 drrb * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.github.drrb.javarust.build; import java.io.File; import java.io.IOException; import static java.nio.charset.StandardCharsets.UTF_8; import java.nio.file.FileVisitResult; import java.nio.file.FileVisitor; 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.BasicFileAttributes; import static java.util.Arrays.asList; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; /** * */ public class CompileRustCrates { private static final Date EPOCH = new Date(0); private static final Path RUST_OUTPUT_DIR = Paths.get("target", "rust-libs"); public static void main(String[] args) throws Exception { Paths.get("target", "rust-libs").toFile().mkdirs(); if (changesDetected()) { System.out.println("Changes detected. Compiling all Rust crates!"); for (Path crate : crates()) { compile(crate); } } else { System.out.println("No changes detected. Not recompiling Rust crates."); } } private static boolean changesDetected() throws IOException { Date lastSourceChange = newestChange(rustSources()); Date lastCompilation = newestChange(compiledRustLibraries()); return lastSourceChange.getTime() > lastCompilation.getTime(); } private static void compile(Path sourceFile) { System.out.format("Compiling crate %s%n", sourceFile); try { Process process = rustcProcess(sourceFile).inheritIO().start(); process.waitFor(); if (process.exitValue() != 0) { throw new RuntimeException(String.format("rustc exited nonzero (status code = %s)", process.exitValue())); } for (Path compiledRustLibrary : compiledRustLibraries()) { moveLibIntoClasspath(compiledRustLibrary); } } catch (IOException | InterruptedException ex) { throw new RuntimeException(ex); } } private static ProcessBuilder rustcProcess(Path crateFile) { List<String> commandParts; if (inNetbeans() && new File("/bin/bash").isFile()) { System.out.println("(running rustc via bash because we're in NetBeans)"); commandParts = asList("/bin/bash", "-lc", String.format("rustc --out-dir %s %s", RUST_OUTPUT_DIR, crateFile)); } else { commandParts = asList("rustc", "--out-dir", RUST_OUTPUT_DIR.toString(), crateFile.toString()); } System.out.format("Running command: %s%n", commandParts); return new ProcessBuilder(commandParts); } private static void moveLibIntoClasspath(Path library) { try { Path outputDir = outputDir(); outputDir.toFile().mkdirs(); System.out.format("Installing %s into %s%n", library, outputDir); Files.copy(library, outputDir.resolve(library.getFileName()), StandardCopyOption.REPLACE_EXISTING); } catch (IOException ex) { throw new RuntimeException(ex); } } private static Path outputDir() { return Paths.get("target", "classes", osArchName()); } private static String osArchName() { return Os.getCurrent().jnaArchString(); } private static List<Path> crates() throws IOException { List<Path> crates = new LinkedList<>(); for (Path rustSource : rustSources()) { if (isCrate(rustSource)) { crates.add(rustSource); } } return crates; } private static List<Path> rustSources() throws IOException { return findFiles(Paths.get("src", "main", "rust"), new FileFinder() { @Override protected boolean accept(Path file, BasicFileAttributes attrs) { return isRustSource(file, attrs); } }); } private static List<Path> compiledRustLibraries() throws IOException { return findFiles(RUST_OUTPUT_DIR, new FileFinder() { @Override protected boolean accept(Path file, BasicFileAttributes attrs) { return isDylib(file, attrs); } }); } private static List<Path> findFiles(Path startPath, FileFinder finder) throws IOException { Files.walkFileTree(startPath, finder); return finder.getFound(); } private static boolean inNetbeans() { for (Map.Entry<String, String> envVars : System.getenv().entrySet()) { String key = envVars.getKey(); String value = envVars.getValue(); if (key.matches("JAVA_MAIN_CLASS_\\d+") && value.equals("org.netbeans.Main")) { return true; } } return false; } private static boolean isRustSource(Path path, BasicFileAttributes attributes) { return attributes.isRegularFile() && path.toString().endsWith(".rs"); } private static boolean isCrate(Path path) { try { return path.toFile().isFile() && path.toString().endsWith(".rs") && new String(Files.readAllBytes(path), UTF_8).contains("#![crate_type"); } catch (IOException ex) { throw new RuntimeException(ex); } } private static boolean isDylib(Path path, BasicFileAttributes attributes) { String pathString = path.toString(); String pathExtension = pathString.substring(pathString.lastIndexOf(".")); List<String> dylibExtensions = asList(".dylib", ".so", ".dll"); return attributes.isRegularFile() && dylibExtensions.contains(pathExtension); } private static Date newestChange(List<Path> paths) { Date lastChange = EPOCH; for (Path path : paths) { Date change = mtime(path); if (change.getTime() > lastChange.getTime()) { lastChange = change; } } return lastChange; } @SuppressWarnings("CallToPrintStackTrace") private static Date mtime(Path path) { try { return new Date(Files.getLastModifiedTime(path).toMillis()); } catch (IOException ex) { ex.printStackTrace(); return EPOCH; } } private enum Os { MAC_OS("mac", "darwin") { @Override public String jnaArchString() { return "darwin"; } }, WINDOWS("win") { @Override public String jnaArchString() { if (currentIs64Bit()) { return "win32-x86-64"; } else { return "win32-x86"; } } }, GNU_SLASH_LINUX("nux") { @Override public String jnaArchString() { if (currentIs64Bit()) { return "linux-x86-64"; } else { return "linux-x86"; } } }, UNKNOWN() { @Override public String jnaArchString() { throw new RuntimeException("Unknown platform. Can't tell what platform we're running on!"); } }; private final String[] substrings; private Os(String... substrings) { this.substrings = substrings; } public abstract String jnaArchString(); public static Os getCurrent() { for (Os os : values()) { if (os.isCurrent()) { return os; } } return UNKNOWN; } public boolean isCurrent() { for (String substring : substrings) { if (currentOsString().contains(substring)) { return true; } } return false; } private static boolean currentIs64Bit() { return System.getProperty("os.arch").contains("64"); } private static String currentOsString() { return System.getProperty("os.name", "unknown").toLowerCase(Locale.ENGLISH); } } private static abstract class FileFinder implements FileVisitor<Path> { private final List<Path> found = new LinkedList<>(); public List<Path> getFound() { return found; } @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (accept(file, attrs)) { found.add(file); } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } protected abstract boolean accept(Path file, BasicFileAttributes attrs); } }