/* * Strongback * Copyright 2015, Strongback and individual contributors by the @authors tag. * See the COPYRIGHT.txt in the distribution for a full listing of individual * contributors. * * Licensed under the MIT License; you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://opensource.org/licenses/MIT * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.strongback.tools.newproject; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.security.InvalidParameterException; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.function.Function; import org.strongback.tools.utils.FileUtils; import org.strongback.tools.utils.Parser; import org.strongback.tools.utils.PropertiesUtils; import org.strongback.tools.utils.PropertiesUtils.InvalidPropertiesException; import org.strongback.tools.utils.Version; /** * Utility to create a new FRC robot project compatible with the strongback library * If -d and -n are specified, the project will be created in a new directory inside * -d with the name -n. If -r is specified, the project will be created directly inside * -r with the project name being the same as -r. No files will be overwritten unless -o * is specified. * * <pre>usage: strongback newproject [options] -d <directory> -n <project_name> * strongback newproject [options] -r <project_root></pre> * * @author Zach Anderson */ public class NewProject { private static boolean debug; private static void debug( Object msg ) { if ( debug ) System.out.println("DEBUG: " + msg); } public static void main(String[] args) { // Parse the parameters ... Map<String, String> params = null; try { params = Parser.parse(args, "npr", "D|v|h|i|nd!r|r!n!d"); } catch(InvalidParameterException e) { System.err.println(e.getLocalizedMessage()); System.out.println(Strings.HELP); exit("", ExitCodes.BAD_ARGS); } assert params != null; debug = params.containsKey("D"); if(params.containsKey("h")) { System.out.println(Strings.HELP); exit(); } if(params.containsKey("v")) { System.out.println(Strings.VERSION_HEAD); System.out.println(Strings.VERSION); exit(); } // Resolve the Strongback installation directory ... debug("Resolving '~/strongback'"); File strongbackDirectory = FileUtils.resolvePath("~/strongback"); if ( strongbackDirectory == null || !strongbackDirectory.exists()) { exit("Unable to find the 'strongback' installation directory. Check the Strongback installation.", ExitCodes.BAD_ENV); } strongbackDirectory = strongbackDirectory.getAbsoluteFile(); if ( !strongbackDirectory.isDirectory() ) { exit("Expecting '" + strongbackDirectory + "' to be a directory. Check the Strongback installation.",ExitCodes.BAD_ENV); } if ( !strongbackDirectory.canRead() ) { exit("Unable to read the 'strongback' installation directory at " + strongbackDirectory,ExitCodes.BAD_ENV); } // replace the windows backslashes with forward slashes so i) string.replaceAll works, and ii) Eclipse paths are correct final String strongbackPath = strongbackDirectory.getAbsolutePath().replaceAll("\\\\", "/"); debug("Resolved '~/strongback' to '" + strongbackPath + "'"); // Load the strongback properties ... debug("Checking '~/strongback/strongback.properties' file"); File strongbackPropsFile = new File(strongbackDirectory,"/strongback.properties"); if ( !strongbackPropsFile.exists() ) { exit("Unable to find the 'strongback.properties' file in the installation directory. Check the Strongback installation.",ExitCodes.BAD_ENV); } strongbackPropsFile = strongbackPropsFile.getAbsoluteFile(); if ( !strongbackPropsFile.isFile() ) { exit("Expecting '" + strongbackPropsFile + "' to be a file but was a directory. Check the Strongback installation.",ExitCodes.BAD_ENV); } if ( !strongbackPropsFile.canRead() ) { exit("Unable to read the '" + strongbackPropsFile + "' file. Check the Strongback installation.",ExitCodes.BAD_ENV); } Properties strongbackProperties = null; try { debug("Loading '" + strongbackPropsFile + "' file"); strongbackProperties = PropertiesUtils.load(strongbackPropsFile); PropertiesUtils.antify(strongbackProperties); debug("Loaded '" + strongbackPropsFile.getAbsoluteFile() + "' file"); } catch (IOException e) { exit("Unable to load the '" + strongbackPropsFile + "' file: " + e.getMessage(),ExitCodes.BAD_ENV); } catch (InvalidPropertiesException e) { exit("Invalid property field in '" + strongbackPropsFile + "' file: " + e.getMessage(),ExitCodes.BAD_ENV); } // Resolve the WPILib installation directory ... String wpiPath = strongbackProperties.getProperty("wpilib.home"); debug("Checking for the WPILib installation directory " + wpiPath); if ( wpiPath == null ) { exit("Strongback properties file '" + strongbackPropsFile + "' must specify the WPILIb directory in 'wpilib.home'",ExitCodes.BAD_ENV); } File wpiLibDir = new File(wpiPath).getAbsoluteFile(); if ( !wpiLibDir.exists() ) { exit("Unable to find the '" + wpiLibDir.getName() + "' installation directory. Make sure the 'wpilib.home' property in '" + strongbackPropsFile + "' points to a valid version of WPILib installation.",ExitCodes.BAD_ENV); } if ( !wpiLibDir.isDirectory() ) { exit("Expecting '" + wpiLibDir + "' to be a directory but was a file. Make sure the 'wpilib.home' property in '" + strongbackPropsFile + "' points to a valid version of WPILib installation.",ExitCodes.BAD_ENV); } if ( !wpiLibDir.canRead() ) { exit("Unable to read the '" + wpiLibDir + "' file. Check the WPILib version and file permissions.",ExitCodes.BAD_ENV); } debug("Found valid WPILib installation directory: " + wpiLibDir); // Load the WPILib properties (which may not exist anymore) ... debug("Looking for WPILib properties file"); String wpiLibPropsPath = strongbackProperties.getProperty("wpilib.props", new File(wpiLibDir,"wpilib.properties").getAbsolutePath()); debug("Checking '" + wpiLibPropsPath + "' file"); File wpiLibPropsFile = new File(wpiLibPropsPath); Properties wpi = new Properties(); if ( wpiLibPropsFile.exists() && wpiLibPropsFile.isFile() && wpiLibPropsFile.canRead() ) { wpiLibPropsFile = wpiLibPropsFile.getAbsoluteFile(); try { debug("Loading '" + wpiLibPropsFile + "' file"); wpi = PropertiesUtils.load(wpiLibPropsFile); PropertiesUtils.antify(wpi); debug("Loaded '" + wpiLibPropsFile.getAbsoluteFile() + "' file"); } catch (IOException e) { exit("Unable to load the '" + wpiLibPropsFile + "' file: " + e.getMessage(),ExitCodes.BAD_PROPS); } catch (InvalidPropertiesException e) { exit("Invalid property field in '" + wpiLibPropsFile + "' file: " + e.getMessage(),ExitCodes.BAD_PROPS); } } else { debug("WPILib installation does not contain a properties file, so skipping this step"); } final Properties strongback = PropertiesUtils.concat(strongbackProperties, wpi); debug("The Strongback properties are: " + strongback); File projectRoot; String projectName; String mainPackage; if(params.containsKey("r")) { projectRoot = FileUtils.resolvePath(params.get("r")); projectName = projectRoot.getName(); } else { projectName = params.get("n"); projectRoot = FileUtils.resolvePath(params.get("d") + File.separator + projectName); } debug("The project root will be: " + projectRoot); debug("The project name will be: " + projectName); if(params.containsKey("p")) { mainPackage = params.get("p"); } else { mainPackage = "org.usfirst.frc.team"+ strongback.getProperty("team-number") +".robot"; } debug("The main package for the robot will be '" + mainPackage + "'"); /* Application Begins */ // Source folders File src = new File(projectRoot, "src" + File.separator + mainPackage.replace('.', File.separatorChar)); File testsrc = new File(projectRoot, "testsrc" + File.separator + mainPackage.replace('.', File.separatorChar)); // Source files to copy File buildTemplate = new File(strongback.getProperty("strongback.templates.dir"), "build.xml.template"); File propsTemplate = new File(strongback.getProperty("strongback.templates.dir"), "build.properties.template"); File robotTemplate = new File(strongback.getProperty("strongback.templates.dir"), "Robot.java.template"); File testTemplate = new File(strongback.getProperty("strongback.templates.dir"), "TestRobot.java.template"); // Eclipse specific File projectTemplate = new File(strongback.getProperty("strongback.templates.dir"), "project.template"); File classpathTemplate = new File(strongback.getProperty("strongback.templates.dir"), "classpath.template"); File userlibTemplate = new File(strongback.getProperty("strongback.templates.dir"), "Strongback.userlibraries.template"); File userlibImportTemplate = new File(strongback.getProperty("strongback.templates.dir"), "Strongback.userlibraries.import.template"); // Verify templates exist if(!buildTemplate.exists()) exit(Strings.MISSING_TEMPLATE + buildTemplate.getPath(), ExitCodes.MISSING_FILE); if(!propsTemplate.exists()) exit(Strings.MISSING_TEMPLATE + propsTemplate.getPath(), ExitCodes.MISSING_FILE); if(!robotTemplate.exists()) exit(Strings.MISSING_TEMPLATE + robotTemplate.getPath(), ExitCodes.MISSING_FILE); if(!testTemplate.exists()) exit(Strings.MISSING_TEMPLATE + testTemplate.getPath(), ExitCodes.MISSING_FILE); // Destination files File buildProps = new File(projectRoot, "build.properties"); File buildXML = new File(projectRoot, "build.xml"); File robotJava = new File(src, "Robot.java"); File testJava = new File(testsrc, "TestRobot.java"); // Eclipse specific File project = new File(projectRoot, ".project"); File classpath = new File(projectRoot, ".classpath"); File metadataDir = new File(projectRoot.getParentFile(), ".metadata"); // Be sure to always generate the Eclipse files that are part of the installation ... try { File eclipseDir = FileUtils.resolvePath("~/strongback/java/eclipse"); eclipseDir.mkdirs(); debug("Created the '" + eclipseDir + "' directory to hold generated files."); // user libraries importable file ... File userlibraries = new File(eclipseDir, "Strongback.userlibraries"); if(!userlibraries.exists()) { // don't overwrite copyTo(userlibImportTemplate, userlibraries, (line) -> line.replaceAll("STRONGBACKHOME", strongbackPath)); } debug("Created the '" + userlibraries + "' file for manually importing the Strongback user libraries."); } catch (IOException e) { exit(Strings.IO_EXCEPTION + e.getLocalizedMessage(), ExitCodes.IO_EXCEPT); } // -------------------------- // PROJECT-SPECIFIC FILES ... // -------------------------- // If any of the files to write to already exist, give up and write message about the overwrite flag if(!params.containsKey("o")) { if(buildProps.exists()) exit(Strings.OVERWRITE_WARN + buildProps.getPath(), ExitCodes.OVERWRITE); if(buildXML.exists()) exit(Strings.OVERWRITE_WARN + buildXML.getPath(), ExitCodes.OVERWRITE); if(robotJava.exists()) exit(Strings.OVERWRITE_WARN + robotJava.getPath(), ExitCodes.OVERWRITE); if(testJava.exists()) exit(Strings.OVERWRITE_WARN + testJava.getPath(), ExitCodes.OVERWRITE); // Eclipse specific if(params.containsKey("e")) { if(project.exists()) exit(Strings.OVERWRITE_WARN + project.getPath(), ExitCodes.OVERWRITE); if(classpath.exists()) exit(Strings.OVERWRITE_WARN + classpath.getPath(), ExitCodes.OVERWRITE); } } // Eclipse specific if(params.containsKey("e")) { if(!projectTemplate.exists()) exit(Strings.MISSING_TEMPLATE + projectTemplate.getPath(), ExitCodes.MISSING_FILE); if(!classpathTemplate.exists()) exit(Strings.MISSING_TEMPLATE + classpathTemplate.getPath(), ExitCodes.MISSING_FILE); } // Make the directories if(!projectRoot.exists() & !projectRoot.mkdirs()) exit(Strings.FAILED_MKDIR + projectRoot.getPath(), ExitCodes.FAILED_MKDIR); if(!src.exists() & !src.mkdirs()) exit(Strings.FAILED_MKDIR + src.getPath(), ExitCodes.FAILED_MKDIR); if(!testsrc.exists() & !testsrc.mkdirs()) exit(Strings.FAILED_MKDIR + testsrc.getPath(), ExitCodes.FAILED_MKDIR); // Copy templates boolean eclipseProject = false; boolean foundMetadata = false; boolean updatedMetadata = false; try { copyTo(buildTemplate, buildXML, (line) -> line.replace("PROJECT_NAME", projectName)); copyTo(propsTemplate, buildProps, (line) -> line.replace("PACKAGE", mainPackage)); copyTo(robotTemplate, robotJava, (line) -> line.replace("PACKAGE", mainPackage)); copyTo(testTemplate, testJava, (line) -> line.replace("PACKAGE", mainPackage)); // Eclipse specific if(params.containsKey("e")) { copyTo(projectTemplate, project, (line) -> line.replace("PROJECT_NAME", projectName)); copyTo(classpathTemplate, classpath, (line) -> line); // See if the `Strongback` user library is in the workspace ... if (metadataDir.exists()) { foundMetadata = true; File jdtPrefsFile = new File(metadataDir,".plugins/org.eclipse.core.runtime/.settings/org.eclipse.jdt.core.prefs"); if (jdtPrefsFile.exists()) { Properties jdtPrefs = new Properties(); try ( InputStream is = new FileInputStream(jdtPrefsFile) ) { jdtPrefs.load(is); } if (!jdtPrefs.isEmpty() && !jdtPrefs.containsKey("org.eclipse.jdt.core.userLibrary.Strongback")) { debug("Adding the Strongback user library to the Eclipse workspace at " + metadataDir.getParent()); // Make a backup of the original preferences file ... File jdtPrefsFileCopy = new File(jdtPrefsFile.getParentFile(),"org.eclipse.jdt.core.prefs.backup"); copyTo(jdtPrefsFile, jdtPrefsFileCopy, (line) -> line); debug("Created backup of " + jdtPrefsFile); // Read in the userlibrary file and escape all the required characters ... List<String> lines = Files.readAllLines(userlibTemplate.toPath(), StandardCharsets.UTF_8); String escapedContents = combineAndEscape(lines, strongbackPath); debug("Escaped contents of the preference file:" + System.lineSeparator() + escapedContents); // Set the property and output the file ... jdtPrefs.setProperty("org.eclipse.jdt.core.userLibrary.Strongback", escapedContents); try ( OutputStream os = new FileOutputStream(jdtPrefsFile) ) { jdtPrefs.store(os,""); debug("Updated preference file"); updatedMetadata = true; } } } } eclipseProject = true; } } catch (IOException e) { exit(Strings.IO_EXCEPTION + e.getLocalizedMessage(), ExitCodes.IO_EXCEPT); } // Print success System.out.print(Strings.SUCCESS); try { System.out.println(projectRoot.getCanonicalPath()); } catch (IOException e) { System.out.println(projectRoot.getPath()); } if ( foundMetadata && updatedMetadata ) { System.out.print(Strings.UPDATED_WORKSPACE); System.out.println(metadataDir.getParentFile().getAbsolutePath()); System.out.print(Strings.RESTART_ECLIPSE); } else if ( eclipseProject ) { System.out.println(Strings.IMPORT_ECLIPSE); if ( !foundMetadata ) { System.out.print(Strings.IMPORT_USERLIB); System.out.println(strongbackDirectory.getAbsolutePath() + "/java/eclipse/Strongback.userlibraries"); } } } protected static String combineAndEscape( List<String> lines, String strongbackHome ) throws IOException { StringBuilder sb = new StringBuilder(); lines.forEach(str->{ debug("Pre-escaped line: " + str); String replaced = str.replaceAll("STRONGBACKHOME",strongbackHome).replaceAll(" ", "\t"); sb.append(replaced).append("\n"); debug("Escaped line: " + replaced); }); return sb.toString(); } protected static void copyTo(File input, File output, Function<String, String> each) throws IOException { BufferedReader testReader = new BufferedReader(new FileReader(input)); BufferedWriter testWriter = new BufferedWriter(new FileWriter(output)); String now = new Date().toString(); try { while(testReader.ready()) { testWriter.write(each.apply(testReader.readLine().replace("DATE", now))); testWriter.newLine(); } } finally { testWriter.close(); testReader.close(); } } private static void exit(String exitMessage, int exitCode) { System.err.println(exitMessage); System.exit(exitCode); } private static void exit() { System.exit(ExitCodes.NORMAL); } private static final class Strings { public static final String LS = System.lineSeparator(); /* Error Text */ public static final String FAILED_MKDIR = "Failed to create project directory at: "; public static final String OVERWRITE_WARN = "The file already exists, aborting job. To overwrite exisiting files run this" + " application with the -o option. "; public static final String MISSING_TEMPLATE = "Cannot locate template file. Double check that the strongback folder is in" + " the same directory as your wpilib folder. "; public static final String IO_EXCEPTION = "An IO Exception occured. "; public static final String SUCCESS = "Successfully created new project at: "; public static final String UPDATED_WORKSPACE= "\nAdded the Strongback user library to your Eclipse workspace at "; public static final String RESTART_ECLIPSE = "Restart this Eclipse workspace and import the project.\n"; public static final String IMPORT_ECLIPSE = "\nProject ready for importing into Eclipse workspace."; public static final String IMPORT_USERLIB = "\nEnsure that the Strongback user library is defined in workspace; if not then import from "; public static final String HELP = "usage: strongback newproject [options] -d <directory> -n <project_name>" + LS + " strongback newproject [options] -r <project_root>" + LS + "" + LS + "Description" + LS + " Utility to create a new FRC robot project compatible with the strongback library" + LS + " If -d and -n are specified, the project will be created in a new directory inside" + LS + " -d with the name -n. If -r is specified, the project will be created directly inside" + LS + " -r with the project name being the same as -r. No files will be overwritten unless -o" + LS + " is specified." + LS + "" + LS + "Options" + LS + " -d <parent_directory>" + LS + " The directory to create the new project directory in" + LS + "" + LS + " -e" + LS + " Adds project metadata for the Eclipse Java IDE, and looks for the Eclipse workspace" + LS + " .metadata folder to add the Strongback user library if not already defined." + LS + "" + LS + " -h" + LS + " Displays help information" + LS + "" + LS + " -n <project_name>" + LS + " The name of the new project" + LS + "" + LS + " -o" + LS + " Forces overwriting of exisiting files" + LS + "" + LS + " -p <package_name>" + LS + " Specifies a custom initial package for Robot.java" + LS + "" + LS + " -v" + LS + " Displays version information" ; /* Version Information */ // TODO Can we update this with ant? public static final String VERSION_HEAD = "Strongback New Project Utility"; public static final String VERSION = Version.versionNumber() + " compiled on " + Version.buildDate(); } private static final class ExitCodes { public static final int NORMAL = 0; public static final int BAD_PROPS = 1; public static final int BAD_ARGS = 2; public static final int IO_EXCEPT = 3; public static final int FAILED_MKDIR = 4; public static final int OVERWRITE = 5; public static final int MISSING_FILE = 6; public static final int BAD_ENV = 7; } }