package com.mobilesorcery.sdk.extensionsupport; import java.io.File; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.SortedSet; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; import org.eclipse.ui.IMemento; import com.mobilesorcery.sdk.core.BuildVariant; import com.mobilesorcery.sdk.core.IBuildConfiguration; import com.mobilesorcery.sdk.core.IBuildResult; import com.mobilesorcery.sdk.core.IBuildSession; import com.mobilesorcery.sdk.core.IBuildVariant; import com.mobilesorcery.sdk.core.IFileTreeDiff; import com.mobilesorcery.sdk.core.MoSyncBuilder; import com.mobilesorcery.sdk.core.MoSyncExtensionManager; import com.mobilesorcery.sdk.core.MoSyncProject; import com.mobilesorcery.sdk.core.MoSyncTool; import com.mobilesorcery.sdk.core.PackageToolPackager; import com.mobilesorcery.sdk.core.ProfileManager; import com.mobilesorcery.sdk.core.PropertyUtil; import com.mobilesorcery.sdk.core.Util; import com.mobilesorcery.sdk.core.build.AbstractBuildStep; import com.mobilesorcery.sdk.core.build.AbstractBuildStepFactory; import com.mobilesorcery.sdk.core.build.BuildSequence; import com.mobilesorcery.sdk.core.build.IBuildStep; import com.mobilesorcery.sdk.core.build.NativeLibBuildStep; import com.mobilesorcery.sdk.internal.builder.MoSyncBuilderVisitor; import com.mobilesorcery.sdk.profiles.IVendor; import com.mobilesorcery.sdk.profiles.filter.DeviceCapabilitiesFilter; public class ExtensionSupportBuildStep extends AbstractBuildStep { public static final String IDL_PHASE = "idl"; public static final String PACK_PHASE = "pack"; public static class Factory extends AbstractBuildStepFactory { private boolean shouldUpdateInstallation = true; private boolean generateStubs = false; private String phase = IDL_PHASE; @Override public IBuildStep create() { return new ExtensionSupportBuildStep(this); } @Override public String getId() { return ExtensionSupportBuildStepExtension.ID; } @Override public String getName() { return MessageFormat.format("Native Extensions ({0})", phase); } public void setPhase(String phase) { this.phase = phase; } public String getPhase() { return phase; } public boolean shouldUpdateInstallation() { return shouldUpdateInstallation; } public void shouldUpdateInstallation(boolean shouldUpdateInstallation) { this.shouldUpdateInstallation = shouldUpdateInstallation; } public boolean shouldGenerateStubs() { return generateStubs; } public void shouldGenerateStubs(boolean generateStubs) { this.generateStubs = generateStubs; } public String getPlatformBundleLocation(String platform) { return "%current-project%/" + platform; } @Override public void load(IMemento memento) { phase = memento.getString("phase"); Object shouldUpdateInstallationBool = memento .getBoolean("should.update"); Object shouldGenerateStubsBool = memento .getBoolean("generate.stubs"); shouldUpdateInstallation = PACK_PHASE.equals(phase) && shouldUpdateInstallationBool != Boolean.FALSE; generateStubs = IDL_PHASE.equals(phase) && shouldGenerateStubsBool == Boolean.TRUE; } @Override public void store(IMemento memento) { memento.putString("phase", phase); if (PACK_PHASE.equals(phase)) { memento.putBoolean("should.update", shouldUpdateInstallation); } if (IDL_PHASE.equals(phase)) { memento.putBoolean("generate.stubs", generateStubs); } } } private Factory prototype; private String phase; public ExtensionSupportBuildStep(Factory prototype) { this.prototype = prototype; this.phase = prototype.phase; setName(prototype.getName()); } @Override public int incrementalBuild(MoSyncProject project, IBuildSession session, IBuildVariant variant, IFileTreeDiff diff, IBuildResult result, IProgressMonitor monitor) throws Exception { // First make sure the project is ok... if (!MoSyncBuilder.isExtension(project)) { throw new IllegalStateException( "This is not an extension project."); } // Preparations: IPath libOutput = MoSyncBuilder.computeLibraryOutput( project, MoSyncBuilder.getPropertyOwner(project, variant.getConfigurationId())); String extensionName = Util.getNameWithoutExtension(libOutput.toFile()); IPath extensionOutput = libOutput.removeLastSegments(1).append( Util.replaceExtension(libOutput.lastSegment(), "ext")); File unzippedExtensionOutput = libOutput.removeLastSegments(1) .append(extensionName).toFile(); unzippedExtensionOutput.mkdirs(); // 1. Parse the extension manifest file // 2. Create a library + header files out of the idl file (used by all // platforms) // as well as platform specific stuff (such as android assets, etc). if (IDL_PHASE.equals(phase)) { ExtensionCompiler.getDefault().compile(project, shouldGenerateStubs()); } else { // 3. Gather all platform libs ProfileManager prMgr = MoSyncTool.getDefault().getProfileManager( MoSyncTool.DEFAULT_PROFILE_TYPE); IVendor[] platforms = prMgr.getVendors(); HashSet<String> supportedPlatforms = new HashSet<String>(Arrays.asList(PropertyUtil.getStrings( project, ExtensionSupportPlugin.SUPPORTED_PLATFORMS_PROP))); for (IVendor platform : platforms) { if (supportedPlatforms.contains(platform.getName())) { String platformName = platform.getName(); String bundleLocation = prototype .getPlatformBundleLocation(platformName); // TODO: Error checking, etc. File bundleLocationFile = new File(Util.replace(bundleLocation, getParameterResolver())); File bundleOutput = new File(unzippedExtensionOutput, platformName); if (!bundleLocationFile.exists()) { throw new IllegalArgumentException("No platform specific code for " + platform); } else { bundleOutput.mkdirs(); Util.copy(monitor, bundleLocationFile, bundleOutput, null); } } } // 4. Copy the manifest file Util.copyFile(monitor, project.getWrappedProject().getLocation() .append("extension.mf").toFile(), unzippedExtensionOutput); // 5. Zip it! File libDirectory = new File(unzippedExtensionOutput, "lib"); libDirectory.mkdirs(); File incDirectory = new File(unzippedExtensionOutput, "inc"); incDirectory.mkdirs(); // TODO: Header file should be generated in another way! Util.copyDir( monitor, project.getWrappedProject().getLocation().toFile(), incDirectory, Util.getExtensionFilter(MoSyncBuilderVisitor.C_HEADER_FILE_EXTS), 1); Util.copyFile(monitor, libOutput.toFile(), libDirectory); // If applicable, JavaScript should also go in there. if (PropertyUtil.getBoolean(project, ExtensionSupportPlugin.GENERATE_JS_PROP)) { String jsLibLocation = Util.replace(prototype.getPlatformBundleLocation("js"), getParameterResolver()); Util.copyDir(monitor, new File(jsLibLocation), new File(libDirectory, "js"), Util.getExtensionFilter("js")); } // 5. Build native code. BuildSequence seq = BuildSequence.getCached(project); List<NativeLibBuildStep.Factory> nativeBuild = seq.getBuildStepFactories(NativeLibBuildStep.Factory.class); if (!nativeBuild.isEmpty()) { NativeLibBuildStep nativeBuildStep = (NativeLibBuildStep) nativeBuild.get(0).create(); ArrayList<IBuildVariant> variants = new ArrayList<IBuildVariant>(); ArrayList<String> debugCfgs = new ArrayList<String>(project.getBuildConfigurationsOfType(IBuildConfiguration.DEBUG_TYPE)); ArrayList<String> releaseCfgs = new ArrayList<String>(project.getBuildConfigurationsOfType(IBuildConfiguration.RELEASE_TYPE)); if (debugCfgs.size() != 1 || releaseCfgs.size() != 1) { throw new CoreException(new Status(IStatus.ERROR, ExtensionSupportPlugin.PLUGIN_ID, "Exactly one debug and one release configuration required.")); } //TODO: One for each platform!! BuildVariant dbgVariant = new BuildVariant(variant); dbgVariant.setConfigurationId(debugCfgs.get(0)); BuildVariant relVariant = new BuildVariant(variant); relVariant.setConfigurationId(releaseCfgs.get(0)); variants.add(relVariant); variants.add(dbgVariant); for (IBuildVariant variantToBuild : variants) { nativeBuildStep.build(project, session, variantToBuild, result); List<File> libFiles = result.getBuildResult().get(PackageToolPackager.NATIVE_LIBS); for (File libFile : libFiles) { // Mini-hack to get the lib file name + parent dir name. IPath lastTwo = new Path(libFile.getAbsolutePath()); lastTwo = lastTwo.removeFirstSegments(lastTwo.segmentCount() - 2); File libDst = new File(libDirectory, lastTwo.toOSString()); Util.copy(monitor, libFile, libDst, null); } } } // 6. Zip it all! Util.zip(unzippedExtensionOutput, extensionOutput.toFile()); // 7. If this setting is there, also update the extension library in // the current mosync installation. if (shouldUpdateInstallation()) { MoSyncExtensionManager.getDefault().install( unzippedExtensionOutput, true); } result.setBuildResult(IBuildResult.MAIN, extensionOutput.toFile()); } // MOVE ALL PLATFORM SPEC STUFF LATER ON! return CONTINUE; } private boolean shouldUpdateInstallation() { return prototype.shouldUpdateInstallation(); } private boolean shouldGenerateStubs() { return prototype.shouldGenerateStubs(); } }