package com.mobilesorcery.sdk.builder.android; import java.io.File; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.jface.dialogs.IMessageProvider; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.ui.preferences.ScopedPreferenceStore; import com.mobilesorcery.sdk.builder.android.launch.Android; import com.mobilesorcery.sdk.builder.java.KeystoreCertificateInfo; import com.mobilesorcery.sdk.core.AbstractTool; import com.mobilesorcery.sdk.core.CommandLineBuilder; import com.mobilesorcery.sdk.core.CoreMoSyncPlugin; import com.mobilesorcery.sdk.core.DefaultPackager; 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.MoSyncBuilder; import com.mobilesorcery.sdk.core.MoSyncProject; import com.mobilesorcery.sdk.core.MoSyncTool; import com.mobilesorcery.sdk.core.PackageToolPackager; import com.mobilesorcery.sdk.core.ParameterResolver; import com.mobilesorcery.sdk.core.ParameterResolverException; import com.mobilesorcery.sdk.core.PropertyUtil; import com.mobilesorcery.sdk.core.Util; import com.mobilesorcery.sdk.ui.DefaultMessageProvider; public class AndroidPackager extends PackageToolPackager { public final static String ID = "com.mobilesorcery.sdk.build.android.packager"; public AndroidPackager() { super(); } @Override protected void addPlatformSpecifics(MoSyncProject project, IBuildVariant variant, CommandLineBuilder commandLine) throws Exception { String packageName = project.getProperty(PropertyInitializer.ANDROID_PACKAGE_NAME); String versionCode = project.getProperty(PropertyInitializer.ANDROID_VERSION_CODE); KeystoreCertificateInfo keystoreCertInfo = KeystoreCertificateInfo.loadOne( PropertyInitializer.ANDROID_KEYSTORE_CERT_INFO, PropertyInitializer.ANDROID_PROJECT_SPECIFIC_KEYS, project, Activator.getDefault().getPreferenceStore()); ParameterResolver resolver = MoSyncBuilder.createParameterResolver(project, variant); if (keystoreCertInfo == null || !DefaultMessageProvider.isEmpty(keystoreCertInfo.validate(false, resolver))) { throw new CoreException(new Status(IStatus.OK, Activator.PLUGIN_ID, keystoreCertInfo.validate(false, resolver).getMessage())); } String keystore = Util.replace(keystoreCertInfo.getKeystoreLocation(), resolver); String alias = keystoreCertInfo.getAlias(); String storepass = keystoreCertInfo.getKeystorePassword(); String keypass = keystoreCertInfo.getKeyPassword(); commandLine.flag("--android-package").with(packageName); commandLine.flag("--android-version-code").with(versionCode); commandLine.flag("--android-keystore").with(keystore); commandLine.flag("--android-storepass", true).with(storepass); commandLine.flag("--android-alias").with(alias); commandLine.flag("--android-keypass", true).with(keypass); if (PropertyUtil.getBoolean(project, PropertyInitializer.ANDROID_LARGE_HEAP)) { commandLine.flag("--android-heap").with("large"); } String installLocation = project.getProperty(PropertyInitializer.ANDROID_INSTALL_LOCATION); if (!Util.isEmpty(installLocation)) { commandLine.flag("--android-install-location").with(installLocation); } String manifestTemplate = project.getProperty(PropertyInitializer.ANDROID_MANIFEST_TEMPLATE); if (!Util.isEmpty(manifestTemplate)) { commandLine.flag("--android-manifest-template").with(manifestTemplate); } Android external = Android.getExternal(); if (external.isValid()) { commandLine.flag("--android-sdk-location").with(Activator.getDefault().getExternalAndroidSDKPath().toFile()); } } @Override protected Map<String, List<File>> computeBuildResult(MoSyncProject project, IBuildVariant variant) throws ParameterResolverException { DefaultPackager intern = new DefaultPackager(project, variant); return createBuildResult(new File(intern.get(DefaultPackager.PACKAGE_OUTPUT_DIR), intern.get(DefaultPackager.APP_NAME) + ".apk")); } public void buildNative(MoSyncProject project, IBuildSession session, IBuildVariant variant, IBuildResult result) throws Exception { File location = project.getWrappedProject().getLocation().toFile(); IPath dst = MoSyncBuilder.getPackageOutputPath(project.getWrappedProject(), variant).removeLastSegments(1); if (location.getAbsolutePath().indexOf(' ') != -1 || dst.toFile().getAbsolutePath().indexOf(' ') != -1) { throw new IllegalArgumentException(MessageFormat.format( "Project or output path cannot have spaces: {0}", location.getAbsolutePath())); } super.buildNative(project, session, variant, result); } protected List<File> computeNativeBuildResult(MoSyncProject project, IBuildVariant variant) throws ParameterResolverException, CoreException { // armeabi and armeabi-v7a boolean debug = shouldUseDebugRuntimes(project, variant); ArrayList<File> result = new ArrayList<File>(); File armLib = computeNativeBuildResult(project, variant, "armeabi", debug); File armv7aLib = computeNativeBuildResult(project, variant, "armeabi-v7a", debug); result.add(armLib); result.add(armv7aLib); return result; } public static File computeNativeBuildResult(MoSyncProject project, IBuildVariant variant, String arch, boolean debug) { IPath outputRoot = MoSyncBuilder.getOutputPath(project.getWrappedProject(), variant); // Hm, only shared right now. String cfgQualifier = debug ? "debug" : "release"; IPath lib = outputRoot.append("android_" + arch + "_" + cfgQualifier + "/lib" + project.getName() + ".so"); return lib.toFile(); } public static File computeNativeDebugLib(MoSyncProject project, IBuildVariant variant, String arch) { File tempBuildDir = getTempBuildDir(project, variant); return new File(tempBuildDir, "Debug/obj/local/" + arch + "/lib" + project.getName() + ".so"); } /** * Validates a package name (is it a proper android package name?) * @param packageName * @return */ public static IMessageProvider validatePackageName(String packageName) { String[] packageParts = packageName.split("\\."); if (packageParts.length < 2) { return new DefaultMessageProvider("Android packages must have at least two parts (eg com.test)", IMessageProvider.ERROR); } for (int i = 0; i < packageParts.length; i++) { String error = validatePackagePart(packageParts[i]); if (error != null) { return new DefaultMessageProvider(error, IMessageProvider.ERROR); } } return DefaultMessageProvider.EMPTY; } private static String validatePackagePart(String packagePart) { char[] packagePartCh = packagePart.toCharArray(); if (packagePart.length() == 0) { return "Package segment cannot be empty"; } char invalidChar = Character.isJavaIdentifierStart(packagePartCh[0]) ? '\0' : packagePartCh[0]; for (int i = 1; invalidChar == '\0' && i < packagePartCh.length; i++) { invalidChar = Character.isJavaIdentifierPart(packagePartCh[i]) ? '\0' : packagePartCh[i]; } if (invalidChar != '\0') { return MessageFormat.format("Invalid package character: {0}", invalidChar); } return null; } protected void addNativePlatformSpecifics(MoSyncProject project, IBuildVariant variant, CommandLineBuilder commandLine) throws Exception { IPreferenceStore androidPrefs = Activator.getDefault().getPreferenceStore(); String ndkLocation = androidPrefs.getString(Activator.NDK_PATH); int platformVersion = androidPrefs.getInt(Activator.NDK_PLATFORM_VERSION); if (Util.isEmpty(ndkLocation)) { throw new CoreException(new Status(IStatus.ERROR, CoreMoSyncPlugin.PLUGIN_ID, "Missing NDK location")); } if (platformVersion < 1) { throw new CoreException(new Status(IStatus.ERROR, CoreMoSyncPlugin.PLUGIN_ID, "Missing NDK version")); } commandLine.flag("--android-ndk-location").with(new File(ndkLocation)); commandLine.flag("--android-version").with(Integer.toString(platformVersion)); commandLine.flag("--android-build-dir").with(getTempBuildDir(project, variant)); List<String> argumentList = Arrays.asList(Platform.getApplicationArgs()); boolean useNdkStl = !AbstractTool.isWindows(); if (argumentList.contains("-android-stl-support:true")) { useNdkStl = true; } else if (argumentList.contains("-android-stl-support:false")) { useNdkStl = false; } if (useNdkStl) { // There are situations where this does not work; // if a project has no explicit link to the STL lib, // it will all fail... //commandLine.flag("--android-stl-support"); } } private static File getTempBuildDir(MoSyncProject project, IBuildVariant variant) { IPath dst = MoSyncBuilder.getPackageOutputPath(project.getWrappedProject(), variant).removeLastSegments(1); return dst.append(new Path("temp")).toFile(); } protected boolean supportsOutputType(String outputType) { return super.supportsOutputType(outputType) || MoSyncBuilder.OUTPUT_TYPE_NATIVE_COMPILE.equals(outputType); } protected List<String> getExtensionModules(MoSyncProject project, IBuildVariant variant) { // TODO: This should obviously not be hard coded. Order is important too. ArrayList<String> result = new ArrayList<String>(); if (getOutputType(project).equals(MoSyncBuilder.OUTPUT_TYPE_NATIVE_COMPILE)) { result.addAll(Arrays.asList(new String[] { "mautil", "yajl", "maui", "mafs", "map", "ads", "nativeui", "Facebook", "Purchase", "matest", "testify", "Notification", "Wormhole", "MoGraph" })); } result.addAll(super.getExtensionModules(project, variant)); return result; } }