/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * 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 com.bladecoder.engineeditor.setup; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.util.HashMap; import java.util.List; import java.util.Map; import com.bladecoder.engineeditor.common.EditorLogger; import com.bladecoder.engineeditor.common.RunProccess; import com.bladecoder.engineeditor.common.Versions; import com.bladecoder.engineeditor.setup.DependencyBank.ProjectType; /** Command line tool to generate libgdx projects * @author badlogic * @author Tomski * @author rgarcia*/ public class BladeEngineSetup { public static boolean isSdkLocationValid (String sdkLocation) { return new File(sdkLocation, "tools").exists() && new File(sdkLocation, "platforms").exists(); } public static boolean isEmptyDirectory (String destination) { if (new File(destination).exists()) { return new File(destination).list().length == 0; } else { return true; } } public static boolean isSdkUpToDate (String sdkLocation) { File buildTools = new File(sdkLocation, "build-tools"); if (!buildTools.exists()) { EditorLogger.error("You have no build tools!\nUpdate your Android SDK with build tools version: " + Versions.getBuildToolsVersion()); return false; } File apis = new File(sdkLocation, "platforms"); if (!apis.exists()) { EditorLogger.error("You have no Android APIs!\nUpdate your Android SDK with API level: " + Versions.getAndroidAPILevel()); return false; } String newestLocalTool = getLatestTools(buildTools); int[] localToolVersion = convertTools(newestLocalTool); int[] targetToolVersion = convertTools(Versions.getBuildToolsVersion()); if (compareVersions(targetToolVersion, localToolVersion)) { // ALWAYS USE THE CURRENT BUILD TOOLS Versions.setBuildToolsVersion(newestLocalTool); EditorLogger.error("Using build tools: " + Versions.getBuildToolsVersion()); } else { if (!hasFileInDirectory(buildTools, Versions.getBuildToolsVersion())) { EditorLogger.error("Please update your Android SDK, you need build tools: " + Versions.getBuildToolsVersion()); return false; } } int newestLocalApi = getLatestApi(apis); if (newestLocalApi > Integer.valueOf(Versions.getAndroidAPILevel())) { // ALWAYS USE THE CURRENT API Versions.setAndroidAPILevel(Integer.toString(newestLocalApi)); EditorLogger.error("Using API level: " + Versions.getAndroidAPILevel()); } else { if (!hasFileInDirectory(apis, "android-" + Versions.getAndroidAPILevel())) { EditorLogger.error("Please update your Android SDK, you need the Android API: " + Versions.getAndroidAPILevel()); return false; } } return true; } private static boolean hasFileInDirectory (File file, String fileName) { for (String name : file.list()) { if (name.equals(fileName)) return true; } return false; } private static int getLatestApi (File apis) { int apiLevel = 0; for (File api : apis.listFiles()) { int level = readAPIVersion(api); if (level > apiLevel) apiLevel = level; } return apiLevel; } private static String getLatestTools (File buildTools) { String version = null; int[] versionSplit = new int[3]; int[] testSplit = new int[3]; for (File toolsVersion : buildTools.listFiles()) { if (version == null) { version = readBuildToolsVersion(toolsVersion); versionSplit = convertTools(version); continue; } testSplit = convertTools(readBuildToolsVersion(toolsVersion)); if (compareVersions(versionSplit, testSplit)) { version = readBuildToolsVersion(toolsVersion); } } if (version != null) { return version; } else { return "0.0.0"; } } private static int readAPIVersion (File parentFile) { File properties = new File(parentFile, "source.properties"); FileReader reader; BufferedReader buffer; try { reader = new FileReader(properties); buffer = new BufferedReader(reader); String line = null; while ((line = buffer.readLine()) != null) { if (line.contains("AndroidVersion.ApiLevel")) { String versionString = line.split("\\=")[1]; int apiLevel = Integer.parseInt(versionString); buffer.close(); reader.close(); return apiLevel; } } } catch (IOException| NumberFormatException e) { EditorLogger.printStackTrace(e); } return 0; } private static String readBuildToolsVersion (File parentFile) { File properties = new File(parentFile, "source.properties"); FileReader reader; BufferedReader buffer; try { reader = new FileReader(properties); buffer = new BufferedReader(reader); String line = null; while ((line = buffer.readLine()) != null) { if (line.contains("Pkg.Revision")) { String versionString = line.split("\\=")[1]; int count = versionString.split("\\.").length; for (int i = 0; i < 3 - count; i++) { versionString += ".0"; } buffer.close(); reader.close(); return versionString; } } } catch (IOException e) { EditorLogger.printStackTrace(e); } return "0.0.0"; } private static boolean compareVersions(int[] version, int[] testVersion) { if (testVersion[0] > version[0]) { return true; } else if (testVersion[0] == version[0]) { if (testVersion[1] > version[1]) { return true; } else if (testVersion[1] == version[1]) { return testVersion[2] > version[2]; } } return false; } private static int[] convertTools (String toolsVersion) { String[] stringSplit = toolsVersion.split("\\."); int[] versionSplit = new int[3]; if (stringSplit.length == 3) { try { versionSplit[0] = Integer.parseInt(stringSplit[0]); versionSplit[1] = Integer.parseInt(stringSplit[1]); versionSplit[2] = Integer.parseInt(stringSplit[2]); return versionSplit; } catch (NumberFormatException nfe) { return new int[] {0, 0, 0}; } } else { return new int[] {0, 0, 0}; } } public void build (ProjectBuilder builder, String outputDir, String appName, String packageName, String mainClass, String sdkLocation, List<String> gradleArgs) throws IOException { ProjectSetup project = new ProjectSetup(); String packageDir = packageName.replace('.', '/'); String sdkPath = null; if(sdkLocation != null && !sdkLocation.isEmpty()) sdkPath = sdkLocation.replace('\\', '/'); if (!isSdkLocationValid(sdkLocation)) { EditorLogger.error("Android SDK location '" + sdkLocation + "' doesn't contain an SDK"); } else if (!isSdkUpToDate(sdkLocation)) { // SHOW THE ANDROID SDK MANAGER?? } // root dir/gradle files project.files.add(new ProjectFile("gitignore", ".gitignore", false)); project.files.add(new TemporaryProjectFile(builder.settingsFile, "settings.gradle", false)); project.files.add(new TemporaryProjectFile(builder.buildFile, "build.gradle", true)); project.files.add(new ProjectFile("gradlew", false)); project.files.add(new ProjectFile("gradlew.bat", false)); project.files.add(new ProjectFile("gradle/wrapper/gradle-wrapper.jar", false)); project.files.add(new ProjectFile("gradle/wrapper/gradle-wrapper.properties", false)); project.files.add(new ProjectFile("gradle.properties")); // core project project.files.add(new ProjectFile("core/build.gradle")); // project.files.add(new ProjectFile("core/src/MainClass", "core/src/" + packageDir + "/" + mainClass + ".java", true)); if (builder.modules.contains(ProjectType.HTML)) { project.files.add(new ProjectFile("core/CoreGdxDefinition", "core/src/" + mainClass + ".gwt.xml", true)); } // desktop project if (builder.modules.contains(ProjectType.DESKTOP)) { project.files.add(new ProjectFile("desktop/build.gradle")); project.files.add(new ProjectFile("desktop/src/DesktopLauncher", "desktop/src/" + packageDir + "/desktop/DesktopLauncher.java", true)); project.files.add(new ProjectFile("desktop/src/icons/icon16.png", false)); project.files.add(new ProjectFile("desktop/src/icons/icon32.png", false)); project.files.add(new ProjectFile("desktop/src/icons/icon128.png", false)); } // Assets String assetPath = builder.modules.contains(ProjectType.ANDROID) ? "android/assets" : "core/assets"; // CREATE ASSETS EMPTY FOLDERS new File(outputDir + "/" + assetPath + "/3d").mkdirs(); new File(outputDir + "/" + assetPath + "/atlases/1").mkdirs(); new File(outputDir + "/" + assetPath + "/images/1").mkdirs(); new File(outputDir + "/" + assetPath + "/model").mkdirs(); new File(outputDir + "/" + assetPath + "/music").mkdirs(); new File(outputDir + "/" + assetPath + "/sounds").mkdirs(); new File(outputDir + "/" + assetPath + "/spine").mkdirs(); new File(outputDir + "/" + assetPath + "/voices").mkdirs(); new File(outputDir + "/" + assetPath + "/particles").mkdirs(); new File(outputDir + "/" + assetPath + "/tests").mkdirs(); new File(outputDir + "/" + assetPath + "/ui/1").mkdirs(); new File(outputDir + "/" + assetPath + "/ui/fonts").mkdirs(); project.files.add(new ProjectFile("android/assets/model/00.chapter.json", assetPath + "/model/00.chapter.json", false)); project.files.add(new ProjectFile("android/assets/model/world.properties", assetPath + "/model/world.properties", false)); project.files.add(new ProjectFile("android/assets/model/world.json", assetPath + "/model/world.json", false)); project.files.add(new ProjectFile("android/assets/model/world_es.properties", assetPath + "/model/world_es.properties", false)); project.files.add(new ProjectFile("android/assets/ui/credits.txt", assetPath + "/ui/credits.txt", false)); project.files.add(new ProjectFile("android/assets/ui/fonts/PaytoneOne.ttf", assetPath + "/ui/fonts/PaytoneOne.ttf", false)); project.files.add(new ProjectFile("android/assets/ui/fonts/ArchitectsDaughter.ttf", assetPath + "/ui/fonts/ArchitectsDaughter.ttf", false)); project.files.add(new ProjectFile("android/assets/ui/fonts/Roboto-Black.ttf", assetPath + "/ui/fonts/Roboto-Black.ttf", false)); project.files.add(new ProjectFile("android/assets/ui/fonts/Roboto-Regular.ttf", assetPath + "/ui/fonts/Roboto-Regular.ttf", false)); project.files.add(new ProjectFile("android/assets/ui/fonts/Roboto-Thin.ttf", assetPath + "/ui/fonts/Roboto-Thin.ttf", false)); project.files.add(new ProjectFile("android/assets/ui/fonts/Ubuntu-M.ttf", assetPath + "/ui/fonts/Ubuntu-M.ttf", false)); project.files.add(new ProjectFile("android/assets/ui/ui.json", assetPath + "/ui/ui.json", false)); project.files.add(new ProjectFile("android/assets/ui/1/blade_logo.png", assetPath + "/ui/1/blade_logo.png", false)); project.files.add(new ProjectFile("android/assets/ui/1/helpDesktop.png", assetPath + "/ui/1/helpDesktop.png", false)); project.files.add(new ProjectFile("android/assets/ui/1/helpDesktop_es.png", assetPath + "/ui/1/helpDesktop_es.png", false)); project.files.add(new ProjectFile("android/assets/ui/1/helpPie.png", assetPath + "/ui/1/helpPie.png", false)); project.files.add(new ProjectFile("android/assets/ui/1/helpPie_es.png", assetPath + "/ui/1/helpPie_es.png", false)); project.files.add(new ProjectFile("android/assets/ui/1/libgdx_logo.png", assetPath + "/ui/1/libgdx_logo.png", false)); project.files.add(new ProjectFile("android/assets/ui/1/ui.atlas", assetPath + "/ui/1/ui.atlas", false)); project.files.add(new ProjectFile("android/assets/ui/1/ui.png", assetPath + "/ui/1/ui.png", false)); project.files.add(new ProjectFile("android/assets/BladeEngine.properties", assetPath + "/BladeEngine.properties", false)); // android project if (builder.modules.contains(ProjectType.ANDROID)) { project.files.add(new ProjectFile("android/res/values/strings.xml")); project.files.add(new ProjectFile("android/res/values/styles.xml", false)); project.files.add(new ProjectFile("android/res/drawable-hdpi/ic_launcher.png", false)); project.files.add(new ProjectFile("android/res/drawable-mdpi/ic_launcher.png", false)); project.files.add(new ProjectFile("android/res/drawable-xhdpi/ic_launcher.png", false)); project.files.add(new ProjectFile("android/res/drawable-xxhdpi/ic_launcher.png", false)); project.files.add(new ProjectFile("android/res/drawable-xxxhdpi/ic_launcher.png", false)); project.files.add(new ProjectFile("android/src/AndroidLauncher", "android/src/" + packageDir + "/AndroidLauncher.java", true)); project.files.add(new ProjectFile("android/AndroidManifest.xml")); project.files.add(new ProjectFile("android/build.gradle", true)); project.files.add(new ProjectFile("android/ic_launcher-web.png", false)); project.files.add(new ProjectFile("android/proguard-project.txt", false)); project.files.add(new ProjectFile("android/project.properties", true)); if(sdkLocation != null) project.files.add(new ProjectFile("local.properties", true)); } // html project if (builder.modules.contains(ProjectType.HTML)) { project.files.add(new ProjectFile("html/build.gradle")); project.files.add(new ProjectFile("html/src/HtmlLauncher", "html/src/" + packageDir + "/client/HtmlLauncher.java", true)); project.files.add(new ProjectFile("html/GdxDefinition", "html/src/" + packageDir + "/GdxDefinition.gwt.xml", true)); project.files.add(new ProjectFile("html/GdxDefinitionSuperdev", "html/src/" + packageDir + "/GdxDefinitionSuperdev.gwt.xml", true)); project.files.add(new ProjectFile("html/war/index", "html/webapp/index.html", true)); project.files.add(new ProjectFile("html/war/styles.css", "html/webapp/styles.css", false)); project.files.add(new ProjectFile("html/war/refresh.png", "html/webapp/refresh.png", false)); project.files.add(new ProjectFile("html/war/soundmanager2-jsmin.js", "html/webapp/soundmanager2-jsmin.js", false)); project.files.add(new ProjectFile("html/war/soundmanager2-setup.js", "html/webapp/soundmanager2-setup.js", false)); project.files.add(new ProjectFile("html/war/WEB-INF/web.xml", "html/webapp/WEB-INF/web.xml", true)); } // ios robovm if (builder.modules.contains(ProjectType.IOS)) { project.files.add(new ProjectFile("ios/src/IOSLauncher", "ios/src/" + packageDir + "/IOSLauncher.java", true)); project.files.add(new ProjectFile("ios/data/Default.png", false)); project.files.add(new ProjectFile("ios/data/Default@2x.png", false)); project.files.add(new ProjectFile("ios/data/Default@2x~ipad.png", false)); project.files.add(new ProjectFile("ios/data/Default-568h@2x.png", false)); project.files.add(new ProjectFile("ios/data/Default~ipad.png", false)); project.files.add(new ProjectFile("ios/data/Default-375w-667h@2x.png", false)); project.files.add(new ProjectFile("ios/data/Default-414w-736h@3x.png", false)); project.files.add(new ProjectFile("ios/data/Default-1024w-1366h@2x~ipad.png", false)); project.files.add(new ProjectFile("ios/data/Icon.png", false)); project.files.add(new ProjectFile("ios/data/Icon@2x.png", false)); project.files.add(new ProjectFile("ios/data/Icon-72.png", false)); project.files.add(new ProjectFile("ios/data/Icon-72@2x.png", false)); project.files.add(new ProjectFile("ios/data/Icon-76.png", false)); project.files.add(new ProjectFile("ios/data/Icon-76@2x.png", false)); project.files.add(new ProjectFile("ios/data/Icon-120.png", false)); project.files.add(new ProjectFile("ios/build.gradle", true)); project.files.add(new ProjectFile("ios/Info.plist.xml", false)); project.files.add(new ProjectFile("ios/robovm.properties")); project.files.add(new ProjectFile("ios/robovm.xml", true)); } Map<String, String> values = new HashMap<String, String>(); values.put("%APP_NAME%", appName); values.put("%APP_NAME_ESCAPED%", appName.replace("'", "\\'")); values.put("%PACKAGE%", packageName); values.put("%PACKAGE_DIR%", packageDir); values.put("%MAIN_CLASS%", mainClass); if(sdkPath != null) values.put("%ANDROID_SDK%", sdkPath); values.put("%ASSET_PATH%", assetPath); values.put("%BUILD_TOOLS_VERSION%", Versions.getBuildToolsVersion()); values.put("%API_LEVEL%", Versions.getAndroidAPILevel()); values.put("%GWT_VERSION%", Versions.getGwtVersion()); values.put("%BLADE_ENGINE_VERSION%", Versions.getVersion()); values.put("%LIBGDX_VERSION%", Versions.getLibgdxVersion()); values.put("%ROBOVM_VERSION%", Versions.getRoboVMVersion()); values.put("%ANDROID_GRADLE_PLUGIN_VERSION%", Versions.getAndroidGradlePluginVersion()); values.put("%ROBOVM_GRADLE_PLUGIN_VERSION%", Versions.getROBOVMGradlePluginVersion()); values.put("%BLADE_INK_VERSION%", Versions.getBladeInkVersion()); if (builder.modules.contains(ProjectType.HTML)) { values.put("%GWT_INHERITS%", parseGwtInherits(builder)); } copyAndReplace(outputDir, project, values); copyLibs(outputDir); builder.cleanUp(); // HACK executable flag isn't preserved for whatever reason... new File(outputDir, "gradlew").setExecutable(true); // RunProccess.runGradle(new File(outputDir), "clean" + parseGradleArgs(builder.modules, gradleArgs)); RunProccess.runGradle(new File(outputDir), "desktop:clean" + parseGradleArgs(builder.modules, gradleArgs)); } /** * NOTE: Not necessary now. * Copy the blade-engine.jar and libgdx-spine.jar to the output folder * @param outputDir * @throws IOException */ private void copyLibs(String outputDir) throws IOException { // File bladeEngineFile = new File(BladeEngine.class.getProtectionDomain().getCodeSource().getLocation().getPath()); // File libSpineFile = new File(Skeleton.class.getProtectionDomain().getCodeSource().getLocation().getPath()); // FileUtils.copyFileToDirectory(libSpineFile, new File(outputDir + "/libs")); // blade-engine.jar is in Maven Central now // if(bladeEngineFile.isDirectory()) { // EditorLogger.error("Editor is running inside IDE. Trying to build blade-engine.jar"); // RunProccess.runGradle(bladeEngineFile.getParentFile().getParentFile(), ":blade-engine:jar"); // // FileUtils.copyDirectory(new File(bladeEngineFile.getParent() + "/build/libs"), new File(outputDir + "/libs")); // } else { // FileUtils.copyFileToDirectory(bladeEngineFile, new File(outputDir + "/libs")); // } } private void copyAndReplace (String outputDir, ProjectSetup project, Map<String, String> values) throws IOException { File out = new File(outputDir); if (!out.exists() && !out.mkdirs()) { throw new RuntimeException("Couldn't create output directory '" + out.getAbsolutePath() + "'"); } for (ProjectFile file : project.files) { copyFile(file, out, values); } } private byte[] readResource (String resource, String path) { InputStream in = null; try { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); byte[] buffer = new byte[1024 * 10]; in = BladeEngineSetup.class.getResourceAsStream(path + resource); if (in == null) throw new RuntimeException("Couldn't read resource '" + resource + "'"); int read = 0; while ((read = in.read(buffer)) > 0) { bytes.write(buffer, 0, read); } return bytes.toByteArray(); } catch (IOException e) { throw new RuntimeException("Couldn't read resource '" + resource + "'", e); } finally { if (in != null) try { in.close(); } catch (IOException e) { } } } private byte[] readResource (File file) throws IOException { InputStream in = null; try { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); byte[] buffer = new byte[1024 * 10]; in = new FileInputStream(file); int read = 0; while ((read = in.read(buffer)) > 0) { bytes.write(buffer, 0, read); } return bytes.toByteArray(); } catch (Exception e) { throw new IOException("Couldn't read resource '" + file.getAbsoluteFile() + "'", e); } finally { if (in != null) try { in.close(); } catch (IOException e) { } } } private String readResourceAsString (String resource, String path) { try { return new String(readResource(resource, path), "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } private String readResourceAsString (File file) throws IOException { try { return new String(readResource(file), "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } private void writeFile (File outFile, byte[] bytes) { OutputStream out = null; try { out = new BufferedOutputStream(new FileOutputStream(outFile)); out.write(bytes); } catch (IOException e) { throw new RuntimeException("Couldn't write file '" + outFile.getAbsolutePath() + "'", e); } finally { if (out != null) try { out.close(); } catch (IOException e) { } } } private void writeFile (File outFile, String text) { try { writeFile(outFile, text.getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } private void copyFile (ProjectFile file, File out, Map<String, String> values) throws IOException { File outFile = new File(out, file.outputName); if (!outFile.getParentFile().exists() && !outFile.getParentFile().mkdirs()) { throw new RuntimeException("Couldn't create dir '" + outFile.getAbsolutePath() + "'"); } boolean isTemp = file instanceof TemporaryProjectFile ? true : false; if (file.isTemplate) { String txt; if (isTemp) { txt = readResourceAsString(((TemporaryProjectFile)file).file); } else { txt = readResourceAsString(file.resourceName, file.resourceLoc); } txt = replace(txt, values); writeFile(outFile, txt); } else { if (isTemp) { writeFile(outFile, readResource(((TemporaryProjectFile)file).file)); } else { writeFile(outFile, readResource(file.resourceName, file.resourceLoc)); } } } private String replace (String txt, Map<String, String> values) { for (String key : values.keySet()) { String value = values.get(key); txt = txt.replace(key, value); } return txt; } private String parseGwtInherits (ProjectBuilder builder) { String parsed = ""; for (Dependency dep : builder.dependencies) { if (dep.getGwtInherits() != null) { for (String inherit : dep.getGwtInherits()) { parsed += "\t<inherits name='" + inherit + "' />\n"; } } } return parsed; } private String parseGradleArgs (List<ProjectType> modules, List<String> args) { String argString = ""; if (args == null) return argString; for (String argument : args) { if (argument.equals("afterEclipseImport") && !modules.contains(ProjectType.DESKTOP)) continue; argString += " " + argument; } return argString; } }