Java tutorial
/* * Copyright (C) 2015 RoboVM AB * * 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 2 * 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/gpl-2.0.html>. */ package org.robovm.idea.compilation; import com.intellij.compiler.options.CompileStepBeforeRun; import com.intellij.execution.configurations.RunConfiguration; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.compiler.CompileContext; import com.intellij.openapi.compiler.CompileTask; import com.intellij.openapi.compiler.CompilerMessageCategory; import com.intellij.openapi.compiler.CompilerPaths; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.roots.OrderEnumerator; import com.intellij.openapi.roots.OrderRootsEnumerator; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.util.Computable; import org.apache.commons.exec.CommandLine; import org.apache.commons.io.FileUtils; import org.robovm.compiler.AppCompiler; import org.robovm.compiler.config.Arch; import org.robovm.compiler.config.Config; import org.robovm.compiler.config.OS; import org.robovm.compiler.plugin.PluginArgument; import org.robovm.compiler.target.ConsoleTarget; import org.robovm.compiler.target.ios.IOSTarget; import org.robovm.compiler.target.ios.ProvisioningProfile; import org.robovm.compiler.target.ios.SigningIdentity; import org.robovm.idea.RoboVmPlugin; import org.robovm.idea.actions.CreateIpaAction; import org.robovm.idea.running.RoboVmRunConfiguration; import org.robovm.idea.running.RoboVmIOSRunConfigurationSettingsEditor; import java.io.File; import java.io.IOException; import java.net.ServerSocket; import java.util.*; /** * Registered by {@link org.robovm.idea.RoboVmPlugin} on startup. Responsible * for compiling an app in case there's a run configuration in the {@link com.intellij.openapi.compiler.CompileContext} * or if we perform an ad-hoc/IPA build from the RoboVM menu. */ public class RoboVmCompileTask implements CompileTask { @Override public boolean execute(CompileContext context) { if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { RoboVmPlugin.logError(context.getProject(), "Can't compile application due to previous compilation errors"); return false; } RunConfiguration c = context.getCompileScope().getUserData(CompileStepBeforeRun.RUN_CONFIGURATION); if (c == null || !(c instanceof RoboVmRunConfiguration)) { CreateIpaAction.IpaConfig ipaConfig = context.getCompileScope() .getUserData(CreateIpaAction.IPA_CONFIG_KEY); if (ipaConfig != null) { return compileForIpa(context, ipaConfig); } else { return true; } } else { return compileForRunConfiguration(context, (RoboVmRunConfiguration) c); } } private boolean compileForIpa(CompileContext context, final CreateIpaAction.IpaConfig ipaConfig) { try { ProgressIndicator progress = context.getProgressIndicator(); context.getProgressIndicator().pushState(); RoboVmPlugin.focusToolWindow(context.getProject()); progress.setText("Creating IPA"); RoboVmPlugin.logInfo(context.getProject(), "Creating package in " + ipaConfig.getDestinationDir().getAbsolutePath() + " ..."); Config.Builder builder = new Config.Builder(); builder.logger(RoboVmPlugin.getLogger(context.getProject())); File moduleBaseDir = new File( ModuleRootManager.getInstance(ipaConfig.getModule()).getContentRoots()[0].getPath()); // load the robovm.xml file loadConfig(context.getProject(), builder, moduleBaseDir, false); builder.os(OS.ios); builder.archs(ipaConfig.getArchs()); builder.installDir(ipaConfig.getDestinationDir()); builder.iosSignIdentity(SigningIdentity.find(SigningIdentity.list(), ipaConfig.getSigningIdentity())); if (ipaConfig.getProvisioningProfile() != null) { builder.iosProvisioningProfile( ProvisioningProfile.find(ProvisioningProfile.list(), ipaConfig.getProvisioningProfile())); } configureClassAndSourcepaths(context, builder, ipaConfig.getModule()); builder.home(RoboVmPlugin.getRoboVmHome()); Config config = builder.build(); progress.setFraction(0.5); AppCompiler compiler = new AppCompiler(config); RoboVmCompilerThread thread = new RoboVmCompilerThread(compiler, progress) { protected void doCompile() throws Exception { compiler.build(); compiler.archive(); } }; thread.compile(); if (progress.isCanceled()) { RoboVmPlugin.logInfo(context.getProject(), "Build canceled"); return false; } progress.setFraction(1); RoboVmPlugin.logInfo(context.getProject(), "Package successfully created in " + ipaConfig.getDestinationDir().getAbsolutePath()); } catch (Throwable t) { RoboVmPlugin.logErrorThrowable(context.getProject(), "Couldn't create IPA", t, false); return false; } finally { context.getProgressIndicator().popState(); } return true; } private boolean compileForRunConfiguration(CompileContext context, final RoboVmRunConfiguration runConfig) { try { ProgressIndicator progress = context.getProgressIndicator(); context.getProgressIndicator().pushState(); RoboVmPlugin.focusToolWindow(context.getProject()); progress.setText("Compiling RoboVM app"); Config.Builder builder = new Config.Builder(); builder.logger(RoboVmPlugin.getLogger(context.getProject())); // get the module we are about to compile ModuleManager moduleManager = ModuleManager.getInstance(runConfig.getProject()); Module module = ApplicationManager.getApplication().runReadAction(new Computable<Module>() { @Override public Module compute() { return ModuleManager.getInstance(runConfig.getProject()) .findModuleByName(runConfig.getModuleName()); } }); if (module == null) { RoboVmPlugin.logBalloon(context.getProject(), MessageType.ERROR, "Couldn't find Module '" + runConfig.getModuleName() + "'"); return false; } File moduleBaseDir = new File(ModuleRootManager.getInstance(module).getContentRoots()[0].getPath()); // load the robovm.xml file loadConfig(context.getProject(), builder, moduleBaseDir, false); // set OS and arch OS os = null; Arch arch = null; if (runConfig.getTargetType() == RoboVmRunConfiguration.TargetType.Device) { os = OS.ios; arch = runConfig.getDeviceArch(); } else if (runConfig.getTargetType() == RoboVmRunConfiguration.TargetType.Simulator) { os = OS.ios; arch = runConfig.getSimArch(); } else { os = OS.getDefaultOS(); arch = Arch.getDefaultArch(); } builder.os(os); builder.arch(arch); // set the plugin args List<String> args = splitArgs(runConfig.getArguments()); applyPluginArguments(args, builder); // set build dir and install dir, pattern // module-basedir/robovm-build/tmp/module-name/runconfig-name/os/arch. // module-basedir/robovm-build/app/module-name/runconfig-name/os/arch. File buildDir = RoboVmPlugin.getModuleBuildDir(module, runConfig.getName(), os, arch); builder.tmpDir(buildDir); builder.skipInstall(true); RoboVmPlugin.logInfo(context.getProject(), "Building executable in %s", buildDir.getAbsolutePath()); RoboVmPlugin.logInfo(context.getProject(), "Installation of app in %s", buildDir.getAbsolutePath()); // setup classpath entries, debug build parameters and target // parameters, e.g. signing identity etc. configureClassAndSourcepaths(context, builder, module); configureDebugging(builder, runConfig, module); configureTarget(builder, runConfig); // clean build dir RoboVmPlugin.logInfo(context.getProject(), "Cleaning output dir " + buildDir.getAbsolutePath()); FileUtils.deleteDirectory(buildDir); buildDir.mkdirs(); // Set the Home to be used, create the Config and AppCompiler Config.Home home = RoboVmPlugin.getRoboVmHome(); if (home.isDev()) { builder.useDebugLibs(true); builder.dumpIntermediates(true); builder.addPluginArgument("debug:logconsole=true"); } builder.home(home); Config config = builder.build(); AppCompiler compiler = new AppCompiler(config); if (progress.isCanceled()) { RoboVmPlugin.logInfo(context.getProject(), "Build canceled"); return false; } progress.setFraction(0.5); // Start the build in a separate thread, check if // user canceled it. RoboVmCompilerThread thread = new RoboVmCompilerThread(compiler, progress); thread.compile(); if (progress.isCanceled()) { RoboVmPlugin.logInfo(context.getProject(), "Build canceled"); return false; } RoboVmPlugin.logInfo(context.getProject(), "Build done"); // set the config and compiler on the run configuration so // it knows where to find things. runConfig.setConfig(config); runConfig.setCompiler(compiler); runConfig.setProgramArguments(args); } catch (Throwable t) { RoboVmPlugin.logErrorThrowable(context.getProject(), "Couldn't compile app", t, false); return false; } finally { context.getProgressIndicator().popState(); } return true; } private void configureClassAndSourcepaths(CompileContext context, Config.Builder builder, Module module) { // gather the boot and user classpaths. RoboVM RT libs may be // specified in a Maven/Gradle build file, in which case they'll // turn up as order entries. We filter them out here. // FIXME junit needs to include test classes OrderEnumerator classes = ModuleRootManager.getInstance(module).orderEntries().recursively().withoutSdk() .compileOnly().productionOnly(); Set<File> classPaths = new HashSet<File>(); Set<File> bootClassPaths = new HashSet<File>(); for (String path : classes.getPathsList().getPathList()) { if (!RoboVmPlugin.isSdkLibrary(path)) { classPaths.add(new File(path)); } } // add the output dirs of all affected modules to the // classpath. IDEA will make the output directory // of a module an order entry after the first compile // so we add the path twice. Fixed by using a set. // FIXME junit needs to include test output directories for (Module mod : context.getCompileScope().getAffectedModules()) { String path = CompilerPaths.getModuleOutputPath(mod, false); if (path != null && !path.isEmpty()) { classPaths.add(new File(path)); } else { RoboVmPlugin.logWarn(context.getProject(), "Output path of module %s not defined", mod.getName()); } } // set the user classpath entries for (File path : classPaths) { RoboVmPlugin.logInfo(context.getProject(), "classpath entry: %s", path.getAbsolutePath()); builder.addClasspathEntry(path); } // Use the RT from the SDK RoboVmPlugin.logInfo(context.getProject(), "Using SDK boot classpath"); for (File path : RoboVmPlugin.getSdkLibrariesWithoutSources()) { if (RoboVmPlugin.isBootClasspathLibrary(path)) { builder.addBootClasspathEntry(path); } else { builder.addClasspathEntry(path); } } } private void configureDebugging(Config.Builder builder, RoboVmRunConfiguration runConfig, Module module) { // setup debug configuration if necessary if (runConfig.isDebug()) { Set<String> sourcesPaths = new HashSet<String>(); // source paths of dependencies and modules OrderRootsEnumerator sources = ModuleRootManager.getInstance(module).orderEntries().recursively() .withoutSdk().sources(); for (String path : sources.getPathsList().getPathList()) { RoboVmPlugin.logInfo(module.getProject(), "source path entry: %s", path); sourcesPaths.add(path); } StringBuilder b = new StringBuilder(); // SDK sourcepaths for (File path : RoboVmPlugin.getSdkLibrarySources()) { b.append(path.getAbsolutePath()); b.append(":"); } for (String path : sourcesPaths) { b.append(path); b.append(":"); } // set arguments for debug plugin runConfig.setDebugPort(findFreePort()); builder.debug(true); builder.addPluginArgument("debug:sourcepath=" + b.toString()); builder.addPluginArgument("debug:jdwpport=" + runConfig.getDebugPort()); builder.addPluginArgument("debug:clientmode=true"); builder.addPluginArgument("debug:logdir=" + RoboVmPlugin.getModuleLogDir(module).getAbsolutePath()); } } private void configureTarget(Config.Builder builder, RoboVmRunConfiguration runConfig) { if (runConfig.getTargetType() == RoboVmRunConfiguration.TargetType.Device) { // configure device build builder.targetType(IOSTarget.TYPE); String signingId = runConfig.getSigningIdentity(); String profile = runConfig.getProvisioningProfile(); if (RoboVmIOSRunConfigurationSettingsEditor.SKIP_SIGNING.equals(signingId)) { builder.iosSkipSigning(true); } else { if (signingId != null && !RoboVmIOSRunConfigurationSettingsEditor.AUTO_SIGNING_IDENTITY.equals(signingId)) { builder.iosSignIdentity(SigningIdentity.find(SigningIdentity.list(), signingId)); } if (profile != null && !RoboVmIOSRunConfigurationSettingsEditor.AUTO_PROVISIONING_PROFILE.equals(profile)) { builder.iosProvisioningProfile(ProvisioningProfile.find(ProvisioningProfile.list(), profile)); } } } else if (runConfig.getTargetType() == RoboVmRunConfiguration.TargetType.Simulator) { builder.targetType(IOSTarget.TYPE); } else if (runConfig.getTargetType() == RoboVmRunConfiguration.TargetType.Console) { builder.targetType(ConsoleTarget.TYPE); } else { throw new RuntimeException("Unsupported target type: " + runConfig.getTargetType()); } } public static Config.Builder loadConfig(Project project, Config.Builder configBuilder, File projectRoot, boolean isTest) { try { configBuilder.readProjectProperties(projectRoot, isTest); configBuilder.readProjectConfig(projectRoot, isTest); } catch (IOException e) { RoboVmPlugin.logErrorThrowable(project, "Couldn't load robovm.xml", e, true); throw new RuntimeException(e); } // Ignore classpath entries in config XML file. configBuilder.clearBootClasspathEntries(); configBuilder.clearClasspathEntries(); return configBuilder; } public int findFreePort() { ServerSocket socket = null; try { socket = new ServerSocket(0); return socket.getLocalPort(); } catch (IOException localIOException2) { } finally { if (socket != null) { try { socket.close(); } catch (IOException localIOException4) { } } } return -1; } private static String unquoteArg(String arg) { if (arg.startsWith("\"") && arg.endsWith("\"")) { return arg.substring(1, arg.length() - 1); } return arg; } public static List<String> splitArgs(String args) { if (args == null || args.trim().length() == 0) { return new ArrayList<String>(); } String[] parts = CommandLine.parse("foo " + args).toStrings(); if (parts.length <= 1) { return Collections.emptyList(); } List<String> result = new ArrayList<String>(parts.length - 1); for (int i = 1; i < parts.length; i++) { result.add(unquoteArg(parts[i])); } return result; } /** * Filters any plugin arguments and sets them on the provided builder * @param args * @param configBuilder builder or null to filter the args list */ public static void applyPluginArguments(List<String> args, Config.Builder configBuilder) { Map<String, PluginArgument> pluginArguments = configBuilder.fetchPluginArguments(); Iterator<String> iter = args.iterator(); while (iter.hasNext()) { String arg = iter.next(); if (!arg.startsWith("-rvm") && arg.startsWith("-")) { String argName = arg.substring(1); if (argName.contains("=")) { argName = argName.substring(0, argName.indexOf('=')); } PluginArgument pluginArg = pluginArguments.get(argName); if (pluginArg != null) { if (configBuilder != null) { configBuilder.addPluginArgument(arg.substring(1)); iter.remove(); } } } } } }