def GenerateCommandLineFile(app_info, xwalk_command_line): if xwalk_command_line == '': return assets_path = os.path.join(GetBuildDir(app_info.android_name), 'assets') file_path = os.path.join(assets_path, 'xwalk-command-line') command_line_file = open(file_path, 'w') command_line_file.write('xwalk ' + xwalk_command_line)
def MakeApk(options, app_info, manifest): CheckSystemRequirements() Customize(options, app_info, manifest) name = app_info.android_name app_dir = GetBuildDir(name) packaged_archs = [] if options.mode == 'shared': MakeSharedApk(options, app_info, app_dir) else: # default MakeEmbeddedApk(options, app_info, app_dir, packaged_archs) # if project_dir, save build directory if options.project_dir: print ('\nCreating project directory') save_dir = os.path.join(options.project_dir, name) if CreateAndCopyDir(app_dir, save_dir, True): print (' A project directory was created successfully in:\n %s' % os.path.abspath(save_dir)) print (' To manually generate an APK, run the following in that ' 'directory:') print (' ant release -f build.xml') print (' For more information, see:\n' ' http://developer.android.com/tools/building/' 'building-cmdline.html') else: print ('Error: Unable to create a project directory during the build. ' 'Please check the directory passed in --project-dir, ' 'available disk space, and write permission.') if not options.project_only: PrintPackageInfo(options, name, packaged_archs)
def CustomizeIconByDict(name, app_root, icon_dict): app_dir = GetBuildDir(name) icon_name = None drawable_dict = {'ldpi': [1, 37], 'mdpi': [37, 72], 'hdpi': [72, 96], 'xhdpi': [96, 120], 'xxhdpi': [120, 144], 'xxxhdpi': [144, 168]} if not icon_dict: return icon_name try: icon_dict = dict((int(k), v) for k, v in icon_dict.items()) except ValueError: print('The key of icon in the manifest file should be a number.') if len(icon_dict) > 0: icon_list = sorted(icon_dict.items(), key=lambda d: d[0]) for kd, vd in drawable_dict.items(): for item in icon_list: if item[0] >= vd[0] and item[0] < vd[1]: drawable_path = os.path.join(app_dir, 'res', 'drawable-' + kd) if not os.path.exists(drawable_path): os.makedirs(drawable_path) icon = os.path.join(app_root, item[1]) if icon and os.path.isfile(icon): icon_name = os.path.basename(icon) icon_suffix = icon_name.split('.')[-1] shutil.copyfile(icon, os.path.join(drawable_path, 'icon.' + icon_suffix)) icon_name = 'icon' elif icon and (not os.path.isfile(icon)): print('Error: "%s" does not exist.' % icon) sys.exit(6) break return icon_name
def CustomizeXML(app_info, description, icon_dict, manifest, permissions): app_version = app_info.app_version app_versionCode = app_info.app_versionCode name = app_info.android_name orientation = app_info.orientation package = app_info.package app_name = app_info.app_name app_dir = GetBuildDir(name) # Chinese character with unicode get from 'manifest.json' will cause # 'UnicodeEncodeError' when finally wrote to 'AndroidManifest.xml'. app_name = EncodingUnicodeValue(app_name) # If string start with '@' or '?', it will be treated as Android resource, # which will cause 'No resource found' error, # append a space before '@' or '?' to fix that. if app_name.startswith('@') or app_name.startswith('?'): app_name = ' ' + app_name manifest_path = os.path.join(app_dir, 'AndroidManifest.xml') if not os.path.isfile(manifest_path): print('Please make sure AndroidManifest.xml' ' exists under template folder.') sys.exit(6) CustomizeStringXML(name, description) CustomizeThemeXML(name, app_info.fullscreen_flag, manifest) xmldoc = minidom.parse(manifest_path) EditElementAttribute(xmldoc, 'manifest', 'package', package) if app_versionCode: EditElementAttribute(xmldoc, 'manifest', 'android:versionCode', str(app_versionCode)) if app_version: EditElementAttribute(xmldoc, 'manifest', 'android:versionName', app_version) if description: EditElementAttribute(xmldoc, 'manifest', 'android:description', "@string/description") HandlePermissions(permissions, xmldoc) EditElementAttribute(xmldoc, 'application', 'android:label', app_name) activity_name = package + '.' + name + 'Activity' EditElementAttribute(xmldoc, 'activity', 'android:name', activity_name) EditElementAttribute(xmldoc, 'activity', 'android:label', app_name) if orientation: EditElementAttribute(xmldoc, 'activity', 'android:screenOrientation', orientation) icon_name = CustomizeIcon(name, app_info.app_root, app_info.icon, icon_dict) if icon_name: EditElementAttribute(xmldoc, 'application', 'android:icon', '@drawable/%s' % icon_name) file_handle = open(os.path.join(app_dir, 'AndroidManifest.xml'), 'w') xmldoc.writexml(file_handle, encoding='utf-8') file_handle.close()
def CustomizeIconByOption(name, icon): if os.path.isfile(icon): drawable_path = os.path.join(GetBuildDir(name), 'res', 'drawable') if not os.path.exists(drawable_path): os.makedirs(drawable_path) icon_file = os.path.basename(icon) icon_file = ReplaceInvalidChars(icon_file) shutil.copyfile(icon, os.path.join(drawable_path, icon_file)) icon_name = os.path.splitext(icon_file)[0] return icon_name else: print('Error: "%s" does not exist.' % icon) sys.exit(6)
def CustomizeJava(app_info, app_url, app_local_path, keep_screen_on, manifest): name = app_info.android_name package = app_info.package app_dir = GetBuildDir(name) app_pkg_dir = os.path.join(app_dir, 'src', package.replace('.', os.path.sep)) dest_activity = os.path.join(app_pkg_dir, name + 'Activity.java') ReplaceString(dest_activity, 'org.xwalk.app.template', package) ReplaceString(dest_activity, 'AppTemplate', name) if manifest: manifest_name = os.path.basename(manifest.input_path) manifest_in_asset = 'file:///android_asset/www/' + manifest_name load_from_manifest = 'loadAppFromManifest("' + manifest_in_asset + '")' ReplaceString( dest_activity, 'loadAppFromUrl("file:///android_asset/www/index.html")', load_from_manifest) else: if app_url: if re.search(r'^http(|s)', app_url): ReplaceString(dest_activity, 'file:///android_asset/www/index.html', app_url) elif app_local_path: if os.path.isfile( os.path.join(app_dir, 'assets', 'www', app_local_path)): ReplaceString(dest_activity, 'file:///android_asset/www/index.html', 'app://' + package + '/' + app_local_path) else: print('Please make sure that the relative path of entry file' ' is correct.') sys.exit(8) if app_info.remote_debugging: SetVariable(dest_activity, 'public void onCreate(Bundle savedInstanceState)', 'RemoteDebugging', 'true') if app_info.use_animatable_view: SetVariable(dest_activity, 'public void onCreate(Bundle savedInstanceState)', 'UseAnimatableView', 'true') if app_info.fullscreen_flag: SetVariable(dest_activity, 'super.onCreate(savedInstanceState)', 'IsFullscreen', 'true') if keep_screen_on: ReplaceString( dest_activity, 'super.onCreate(savedInstanceState);', 'super.onCreate(savedInstanceState);\n ' + 'getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);' )
def CustomizeStringXML(name, description): strings_path = os.path.join(GetBuildDir(name), 'res', 'values', 'strings.xml') if not os.path.isfile(strings_path): print('Please make sure strings_xml' ' exists under template folder.') sys.exit(6) if description: description = EncodingUnicodeValue(description) xmldoc = minidom.parse(strings_path) AddElementAttributeAndText(xmldoc, 'string', 'name', 'description', description) strings_file = open(strings_path, 'w') xmldoc.writexml(strings_file, encoding='utf-8') strings_file.close()
def Prepare(app_info, compressor): """Copy the Android template project to a new app project named app_info.app_name """ # create new app_dir in temp dir app_name = app_info.android_name app_dir = GetBuildDir(app_name) app_package = app_info.package app_root = app_info.app_root template_app_dir = os.path.join(xwalk_dir, TEMPLATE_DIR_NAME) # 1) copy template project to app_dir CleanDir(app_dir) if not os.path.isdir(template_app_dir): print('Error: The template directory could not be found (%s).' % template_app_dir) sys.exit(7) shutil.copytree(template_app_dir, app_dir) # 2) replace app_dir 'src' dir with template 'src' dir CleanDir(os.path.join(app_dir, 'src')) template_src_root = os.path.join(template_app_dir, 'src', 'org', 'xwalk', 'app', 'template') # 3) Create directory tree from app package (org.xyz.foo -> src/org/xyz/foo) # and copy AppTemplateActivity.java to <app_name>Activity.java template_activity_file = os.path.join(template_src_root, 'AppTemplateActivity.java') if not os.path.isfile(template_activity_file): print('Error: The template file %s was not found. ' 'Please make sure this file exists.' % template_activity_file) sys.exit(7) app_pkg_dir = os.path.join(app_dir, 'src', app_package.replace('.', os.path.sep)) if not os.path.exists(app_pkg_dir): os.makedirs(app_pkg_dir) app_activity_file = app_name + 'Activity.java' shutil.copyfile(template_activity_file, os.path.join(app_pkg_dir, app_activity_file)) # 4) Copy all HTML source from app_root to app_dir if app_root: app_assets_dir = os.path.join(app_dir, 'assets', 'www') CleanDir(app_assets_dir) shutil.copytree(app_root, app_assets_dir) if compressor: CompressSourceFiles(app_assets_dir, compressor)
def CustomizeThemeXML(name, fullscreen, manifest): theme_path = os.path.join(GetBuildDir(name), 'res', 'values-v14', 'theme.xml') if not os.path.isfile(theme_path): print('Error: theme.xml is missing in the build tool.') sys.exit(6) theme_xmldoc = minidom.parse(theme_path) if fullscreen: EditElementValueByNodeName(theme_xmldoc, 'item', 'android:windowFullscreen', 'true') has_background = CustomizeLaunchScreen(manifest, name) if has_background: EditElementValueByNodeName(theme_xmldoc, 'item', 'android:windowBackground', '@drawable/launchscreen_bg') theme_file = open(theme_path, 'w') theme_xmldoc.writexml(theme_file, encoding='utf-8') theme_file.close()
def CustomizeManifest(app_info): app_versionCode = app_info.app_versionCode app_dir = GetBuildDir(app_info.android_name) app_name = EncodingUnicodeValue(app_info.app_name) if app_name.startswith('@') or app_name.startswith('?'): app_name = ' ' + app_name manifest_path = os.path.join(app_dir, 'AndroidManifest.xml') if not os.path.isfile(manifest_path): print('Please make sure AndroidManifest.xml' ' exists under template folder.') sys.exit(6) xmldoc = minidom.parse(manifest_path) if app_versionCode: EditElementAttribute(xmldoc, 'manifest', 'android:versionCode', str(app_versionCode)) EditElementAttribute(xmldoc, 'application', 'android:label', app_name) EditElementAttribute(xmldoc, 'activity', 'android:label', app_name) file_handle = open(os.path.join(app_dir, 'AndroidManifest.xml'), 'w') xmldoc.writexml(file_handle, encoding='utf-8') file_handle.close()
def Execution(options, app_info): # Now we've got correct app_version and correct ABI value, # start to generate suitable versionCode app_info.app_versionCode = MakeVersionCode(options, app_info.app_version) # Write generated versionCode into AndroidManifest.xml. # Later if we have other customization, # we can put them together into CustomizeManifest func. CustomizeManifest(app_info) name = app_info.android_name arch_string = (' (' + options.arch + ')' if options.arch else '') print('\nStarting application build' + arch_string) app_dir = GetBuildDir(name) android_path = Which('android') api_level = GetAndroidApiLevel(android_path) target_string = 'android-%d' % api_level print(' * Checking keystore for signing') if options.keystore_path: key_store = os.path.expanduser(options.keystore_path) if options.keystore_alias: key_alias = options.keystore_alias else: print('Please provide an alias name of the developer key.') sys.exit(6) if options.keystore_passcode: key_code = options.keystore_passcode else: key_code = None if options.keystore_alias_passcode: key_alias_code = options.keystore_alias_passcode else: key_alias_code = None else: print(' No keystore provided for signing. Using xwalk\'s keystore ' 'for debugging.\n Please use a valid keystore when ' 'distributing to the app market.') key_store = os.path.join(xwalk_dir, 'xwalk-debug.keystore') key_alias = 'xwalkdebugkey' key_code = 'xwalkdebug' key_alias_code = 'xwalkdebug' # Update android project for app and xwalk_core_library. update_project_cmd = [ android_path, 'update', 'project', '--path', app_dir, '--target', target_string, '--name', name ] if options.mode == 'embedded': print(' * Updating project with xwalk_core_library') RunCommand([ android_path, 'update', 'lib-project', '--path', os.path.join(app_dir, EMBEDDED_LIBRARY), '--target', target_string ]) update_project_cmd.extend(['-l', EMBEDDED_LIBRARY]) elif options.mode == 'shared': print(' * Updating project with xwalk_shared_library') RunCommand([ android_path, 'update', 'lib-project', '--path', os.path.join(app_dir, SHARED_LIBRARY), '--target', target_string ]) update_project_cmd.extend(['-l', SHARED_LIBRARY]) else: print(' * Updating project') RunCommand(update_project_cmd) # Check whether external extensions are included. print(' * Checking for external extensions') extensions_string = 'xwalk-extensions' extensions_dir = os.path.join(app_dir, extensions_string) external_extension_jars = FindExtensionJars(extensions_dir) for external_extension_jar in external_extension_jars: shutil.copyfile( external_extension_jar, os.path.join(app_dir, 'libs', os.path.basename(external_extension_jar))) if options.mode == 'embedded': print(' * Copying native libraries for %s' % options.arch) # Remove existing native libraries in xwalk_core_library, they are probably # for the last execution to make apk for another CPU arch. # And then copy the native libraries for the specified arch into # xwalk_core_library. arch = ConvertArchNameToArchFolder(options.arch) if not arch: print('Invalid CPU arch: %s.' % arch) sys.exit(10) library_lib_path = os.path.join(app_dir, EMBEDDED_LIBRARY, 'libs') for dir_name in os.listdir(library_lib_path): lib_dir = os.path.join(library_lib_path, dir_name) if ContainsNativeLibrary(lib_dir): shutil.rmtree(lib_dir) native_lib_path = os.path.join(app_dir, 'native_libs', arch) if ContainsNativeLibrary(native_lib_path): shutil.copytree(native_lib_path, os.path.join(library_lib_path, arch)) else: print( 'No %s native library has been found for creating a Crosswalk ' 'embedded APK.' % arch) sys.exit(10) if options.project_only: print(' (Skipping apk package creation)') return # Build the APK if options.mode == 'embedded': print(' * Building Android apk package with Crosswalk embedded' + arch_string) else: print(' * Building Android apk package') ant_path = Which('ant') ant_cmd = [ant_path, 'release', '-f', os.path.join(app_dir, 'build.xml')] ant_cmd.extend(['-Dkey.store=%s' % os.path.abspath(key_store)]) ant_cmd.extend(['-Dkey.alias=%s' % key_alias]) if key_code: ant_cmd.extend(['-Dkey.store.password=%s' % key_code]) if key_alias_code: ant_cmd.extend(['-Dkey.alias.password=%s' % key_alias_code]) cmd_display = ' '.join([str(item) for item in ant_cmd]) if options.verbose: print('Executing:\n %s\n' % cmd_display) else: ant_cmd.extend(['-quiet']) ant_result = subprocess.call(ant_cmd) if ant_result != 0: print('Command "%s" exited with non-zero exit code %d' % (cmd_display, ant_result)) sys.exit(ant_result) src_file = os.path.join(app_dir, 'bin', '%s-release.apk' % name) package_name = name if options.app_version: package_name += ('_' + options.app_version) if options.mode == 'shared': dst_file = os.path.join(options.target_dir, '%s.apk' % package_name) elif options.mode == 'embedded': dst_file = os.path.join(options.target_dir, '%s_%s.apk' % (package_name, options.arch)) shutil.copyfile(src_file, dst_file) print(' (Location: %s)' % dst_file)
def main(): parser = optparse.OptionParser() info = ('The package name. Such as: ' '--package=com.example.YourPackage') parser.add_option('--package', help=info) info = ('The apk name. Such as: --name="Your Application Name"') parser.add_option('--name', help=info) info = ('The version of the app. Such as: --app-version=TheVersionNumber') parser.add_option('--app-version', help=info) info = ('The versionCode of the app. Such as: --app-versionCode=24') parser.add_option('--app-versionCode', type='int', help=info) info = ('The application description. Such as:' '--description=YourApplicationdDescription') parser.add_option('--description', help=info) info = ('The permission list. Such as: --permissions="geolocation"' 'For more permissions, such as:' '--permissions="geolocation:permission2"') parser.add_option('--permissions', help=info) info = ('The url of application. ' 'This flag allows to package website as apk. Such as: ' '--app-url=http://www.intel.com') parser.add_option('--app-url', help=info) info = ('The root path of the web app. ' 'This flag allows to package local web app as apk. Such as: ' '--app-root=/root/path/of/the/web/app') parser.add_option('--app-root', help=info) info = ('The reletive path of entry file based on |app_root|. ' 'This flag should work with "--app-root" together. ' 'Such as: --app-local-path=/reletive/path/of/entry/file') parser.add_option('--app-local-path', help=info) parser.add_option('--enable-remote-debugging', action='store_true', dest='enable_remote_debugging', default=False, help='Enable remote debugging.') parser.add_option('--use-animatable-view', action='store_true', dest='use_animatable_view', default=False, help='Enable using animatable view (TextureView).') parser.add_option('-f', '--fullscreen', action='store_true', dest='fullscreen', default=False, help='Make application fullscreen.') parser.add_option('--keep-screen-on', action='store_true', default=False, help='Support keeping screen on') info = ('The path list for external extensions separated by os separator.' 'On Linux and Mac, the separator is ":". On Windows, it is ";".' 'Such as: --extensions="/path/to/extension1:/path/to/extension2"') parser.add_option('--extensions', help=info) info = ( 'The orientation of the web app\'s display on the device. ' 'Such as: --orientation=landscape. The default value is "unspecified"' 'The value options are the same as those on the Android: ' 'http://developer.android.com/guide/topics/manifest/' 'activity-element.html#screen') parser.add_option('--orientation', help=info) parser.add_option('--manifest', help='The manifest path') info = ( 'Use command lines.' 'Crosswalk is powered by Chromium and supports Chromium command line.' 'For example, ' '--xwalk-command-line=\'--chromium-command-1 --xwalk-command-2\'') info = ('Create an Android project directory at this location. ') parser.add_option('--project-dir', help=info) parser.add_option('--xwalk-command-line', default='', help=info) info = ('Minify and obfuscate javascript and css.' '--compressor: compress javascript and css.' '--compressor=js: compress javascript.' '--compressor=css: compress css.') parser.add_option('--compressor', dest='compressor', action='callback', callback=ParseParameterForCompressor, type='string', nargs=0, help=info) options, _ = parser.parse_args() try: icon_dict = { 144: 'icons/icon_144.png', 72: 'icons/icon_72.png', 96: 'icons/icon_96.png', 48: 'icons/icon_48.png' } app_info = AppInfo() if options.name is not None: app_info.android_name = options.name if options.app_root is None: app_info.app_root = os.path.join(xwalk_dir, 'test_data', 'manifest') else: app_info.app_root = options.app_root if options.package is not None: app_info.package = options.package if options.orientation is not None: app_info.orientation = options.orientation if options.app_version is not None: app_info.app_version = options.app_version if options.enable_remote_debugging is not None: app_info.remote_debugging = options.enable_remote_debugging if options.fullscreen is not None: app_info.fullscreen_flag = options.fullscreen app_info.icon = os.path.join('test_data', 'manifest', 'icons', 'icon_96.png') CustomizeAll(app_info, options.description, icon_dict, options.permissions, options.app_url, options.app_local_path, options.keep_screen_on, options.extensions, None, options.xwalk_command_line, options.compressor) # build project is now in /tmp/<name>. Copy to project_dir if options.project_dir: src_dir = GetBuildDir(app_info.android_name) dest_dir = os.path.join(options.project_dir, app_info.android_name) CreateAndCopyDir(src_dir, dest_dir, True) except SystemExit as ec: print('Exiting with error code: %d' % ec.code) return ec.code finally: CleanDir(GetBuildDir(app_info.android_name)) return 0
def CustomizeExtensions(app_info, extensions): """Copy the files from external extensions and merge them into APK. The directory of one external extension should be like: myextension/ myextension.jar myextension.js myextension.json That means the name of the internal files should be the same as the directory name. For .jar files, they'll be copied to xwalk-extensions/ and then built into classes.dex in make_apk.py. For .js files, they'll be copied into assets/xwalk-extensions/. For .json files, the'll be merged into one file called extensions-config.json and copied into assets/. """ if not extensions: return name = app_info.android_name app_dir = GetBuildDir(name) apk_assets_path = os.path.join(app_dir, 'assets') extensions_string = 'xwalk-extensions' # Set up the target directories and files. dest_jar_path = os.path.join(app_dir, extensions_string) os.mkdir(dest_jar_path) dest_js_path = os.path.join(apk_assets_path, extensions_string) os.mkdir(dest_js_path) apk_extensions_json_path = os.path.join(apk_assets_path, 'extensions-config.json') # Split the paths into a list. extension_paths = extensions.split(os.pathsep) extension_json_list = [] for source_path in extension_paths: if not os.path.exists(source_path): print('Error: can not find the extension directory \'%s\'.' % source_path) sys.exit(9) # Remove redundant separators to avoid empty basename. source_path = os.path.normpath(source_path) extension_name = os.path.basename(source_path) # Copy .jar file into xwalk-extensions. CopyExtensionFile(extension_name, '.jar', source_path, dest_jar_path) # Copy .js file into assets/xwalk-extensions. CopyExtensionFile(extension_name, '.js', source_path, dest_js_path) # Merge .json file into assets/xwalk-extensions. file_name = extension_name + '.json' src_file = os.path.join(source_path, file_name) if not os.path.isfile(src_file): print('Error: %s was not found in %s.' % (file_name, source_path)) sys.exit(9) else: src_file_handle = open(src_file) src_file_content = src_file_handle.read() json_output = json.JSONDecoder().decode(src_file_content) # Below 3 properties are used by runtime. See extension manager. # And 'permissions' will be merged. if not ('name' in json_output and 'class' in json_output and 'jsapi' in json_output): print( 'Error: properties \'name\', \'class\' and \'jsapi\' in a json ' 'file are mandatory.') sys.exit(9) # Reset the path for JavaScript. js_path_prefix = extensions_string + '/' + extension_name + '/' json_output['jsapi'] = js_path_prefix + json_output['jsapi'] extension_json_list.append(json_output) # Merge the permissions of extensions into AndroidManifest.xml. manifest_path = os.path.join(app_dir, 'AndroidManifest.xml') xmldoc = minidom.parse(manifest_path) if ('permissions' in json_output): # Get used permission list to avoid repetition as "--permissions" # option can also be used to declare used permissions. existingList = [] usedPermissions = xmldoc.getElementsByTagName( "uses-permission") for used in usedPermissions: existingList.append(used.getAttribute("android:name")) # Add the permissions to manifest file if not used yet. for p in json_output['permissions']: if p in existingList: continue AddElementAttribute(xmldoc, 'uses-permission', 'android:name', p) existingList.append(p) # Write to the manifest file to save the update. file_handle = open(manifest_path, 'w') xmldoc.writexml(file_handle, encoding='utf-8') file_handle.close() if 'manifest' in json_output: manifest_merge_path = os.path.join(source_path, json_output['manifest']) if not os.path.isfile(manifest_merge_path): print('Error: %s specified in the extension\'s JSON ' 'could not be found.' % manifest_merge_path) sys.exit(9) xmldoc_merge = minidom.parse(manifest_merge_path) manifest_nodes = xmldoc.getElementsByTagName('manifest') manifest_nodes_merge = xmldoc_merge.getElementsByTagName( 'manifest') if not manifest_nodes: print('Error: %s does not have a <manifest> node.' % manifest_path) sys.exit(9) if not manifest_nodes_merge: print('Error: %s does not have a <manifest> node.' % manifest_merge_path) sys.exit(9) MergeNodes(manifest_nodes[0], manifest_nodes_merge[0]) with open(manifest_path, 'w') as file_handle: xmldoc.writexml(file_handle, encoding='utf-8') # Write configuration of extensions into the target extensions-config.json. if extension_json_list: extensions_string = json.JSONEncoder().encode(extension_json_list) extension_json_file = open(apk_extensions_json_path, 'w') extension_json_file.write(extensions_string) extension_json_file.close()
def Execution(options, app_info): # Now we've got correct app_version and correct ABI value, # start to generate suitable versionCode app_info.app_versionCode = MakeVersionCode(options, app_info.app_version) # Write generated versionCode into AndroidManifest.xml. # Later if we have other customization, # we can put them together into CustomizeManifest func. CustomizeManifest(app_info) name = app_info.android_name arch_string = (' ('+options.arch+')' if options.arch else '') print('\nStarting application build' + arch_string) app_dir = GetBuildDir(name) android_path = Which('android') api_level = GetAndroidApiLevel(android_path) target_string = 'android-%d' % api_level print (' * Checking keystore for signing') if options.keystore_path: key_store = os.path.expanduser(options.keystore_path) if options.keystore_alias: key_alias = options.keystore_alias else: print('Please provide an alias name of the developer key.') sys.exit(6) if options.keystore_passcode: key_code = options.keystore_passcode else: key_code = None if options.keystore_alias_passcode: key_alias_code = options.keystore_alias_passcode else: key_alias_code = None else: print(' No keystore provided for signing. Using xwalk\'s keystore ' 'for debugging.\n Please use a valid keystore when ' 'distributing to the app market.') key_store = os.path.join(xwalk_dir, 'xwalk-debug.keystore') key_alias = 'xwalkdebugkey' key_code = 'xwalkdebug' key_alias_code = 'xwalkdebug' # Update android project for app and xwalk_core_library. update_project_cmd = [android_path, 'update', 'project', '--path', app_dir, '--target', target_string, '--name', name] if options.mode == 'embedded': print(' * Updating project with xwalk_core_library') RunCommand([android_path, 'update', 'lib-project', '--path', os.path.join(app_dir, EMBEDDED_LIBRARY), '--target', target_string]) update_project_cmd.extend(['-l', EMBEDDED_LIBRARY]) elif options.mode == 'shared': print(' * Updating project with xwalk_shared_library') RunCommand([android_path, 'update', 'lib-project', '--path', os.path.join(app_dir, SHARED_LIBRARY), '--target', target_string]) update_project_cmd.extend(['-l', SHARED_LIBRARY]) else: print(' * Updating project') RunCommand(update_project_cmd) # Enable proguard if options.mode == 'embedded' and options.enable_proguard: print(' * Enabling proguard config files') # Enable proguard in project.properies. if not os.path.exists(os.path.join(app_dir, 'project.properties')): print('Error, project.properties file not found!') sys.exit(14) file_prop = file(os.path.join(app_dir, 'project.properties'), 'a') file_prop.write('proguard.config=${sdk.dir}/tools/proguard/' 'proguard-android.txt:proguard-xwalk.txt') file_prop.close() # Add proguard cfg file. if not os.path.exists(os.path.join(app_dir, 'proguard-xwalk.txt')): print('Error, proguard config file for Crosswalk not found!') sys.exit(14) # Check whether external extensions are included. print(' * Checking for external extensions') extensions_string = 'xwalk-extensions' extensions_dir = os.path.join(app_dir, extensions_string) external_extension_jars = FindExtensionJars(extensions_dir) for external_extension_jar in external_extension_jars: shutil.copyfile(external_extension_jar, os.path.join(app_dir, 'libs', os.path.basename(external_extension_jar))) if options.mode == 'embedded': print (' * Copying native libraries for %s' % options.arch) # Remove existing native libraries in xwalk_core_library, they are probably # for the last execution to make apk for another CPU arch. # And then copy the native libraries for the specified arch into # xwalk_core_library. arch = ConvertArchNameToArchFolder(options.arch) if not arch: print ('Invalid CPU arch: %s.' % arch) sys.exit(10) native_path = os.path.join(app_dir, 'native_libs', arch) library_path = os.path.join(app_dir, EMBEDDED_LIBRARY, 'libs') raw_path = os.path.join(app_dir, EMBEDDED_LIBRARY, 'res', 'raw') if options.enable_lzma: contains_library = ContainsCompressedLibrary clean_library = CleanCompressedLibrary copy_library = CopyCompressedLibrary else: contains_library = ContainsNativeLibrary clean_library = CleanNativeLibrary copy_library = CopyNativeLibrary # cleanup previous build's library first. for dir_name in os.listdir(library_path): clean_library(library_path, dir_name) if options.native_extensions: CheckValidationOfExpectedLibraryArch(options.native_extensions, options.arch) CopyNativeExtensionFile('.so', os.path.join(options.native_extensions, arch), native_path) if contains_library(native_path): copy_library(native_path, library_path, raw_path, arch) else: print('No %s native library has been found for creating a Crosswalk ' 'embedded APK.' % arch) sys.exit(10) else: if options.native_extensions: for arch_name in ALL_ARCHITECTURES: arch = ConvertArchNameToArchFolder(arch_name) extension_path = os.path.join(app_dir, SHARED_LIBRARY, 'libs', arch) library_path = os.path.join(options.native_extensions, arch) CheckValidationOfExpectedLibraryArch(library_path, arch_name) os.mkdir(extension_path) CopyNativeExtensionFile('.so', os.path.join(options.native_extensions, arch), extension_path) if options.project_only: print (' (Skipping apk package creation)') return # Build the APK if options.mode == 'embedded': print(' * Building Android apk package with Crosswalk embedded' + arch_string) else: print(' * Building Android apk package') ant_path = Which('ant') ant_cmd = [ant_path, 'release', '-f', os.path.join(app_dir, 'build.xml')] ant_cmd.extend(['-Dkey.store=%s' % os.path.abspath(key_store)]) ant_cmd.extend(['-Dkey.alias=%s' % key_alias]) if key_code: ant_cmd.extend(['-Dkey.store.password=%s' % key_code]) if key_alias_code: ant_cmd.extend(['-Dkey.alias.password=%s' % key_alias_code]) ignore_properties = "!.svn:!.git:.*:!CVS:!thumbs.db:!picasa.ini:!*.scc:*~" ant_cmd.extend(['-Daapt.ignore.assets=%s' % ignore_properties]) cmd_display = ' '.join([str(item) for item in ant_cmd]) if options.verbose: print('Executing:\n %s\n' % cmd_display) else: ant_cmd.extend(['-quiet']) ant_result = subprocess.call(ant_cmd) if ant_result != 0: print('Command "%s" exited with non-zero exit code %d' % (cmd_display, ant_result)) sys.exit(ant_result) src_file = os.path.join(app_dir, 'bin', '%s-release.apk' % name) package_name = name if options.app_version: package_name += ('_' + options.app_version) if options.mode == 'shared': dst_file = os.path.join(options.target_dir, '%s.apk' % package_name) elif options.mode == 'embedded': dst_file = os.path.join(options.target_dir, '%s_%s.apk' % (package_name, options.arch)) shutil.copyfile(src_file, dst_file) print(' (Location: %s)' % dst_file) #Copy proguard dumping files if options.mode == 'embedded' and options.enable_proguard \ and not options.project_dir: proguard_dir = os.path.join(app_dir, 'bin/proguard/') if os.path.exists(proguard_dir): for afile in os.listdir(proguard_dir): if afile.endswith('.txt'): shutil.copy(os.path.join(proguard_dir,afile), xwalk_dir) else: print('Warning:Cannot find proguard dumping directory!')
def MakeApk(options, app_info, manifest): CheckSystemRequirements() Customize(options, app_info, manifest) name = app_info.android_name app_dir = GetBuildDir(name) packaged_archs = [] if options.mode == 'shared': # Copy xwalk_shared_library into app folder target_library_path = os.path.join(app_dir, SHARED_LIBRARY) shutil.copytree(os.path.join(xwalk_dir, SHARED_LIBRARY), target_library_path) Execution(options, app_info) elif options.mode == 'embedded': # Copy xwalk_core_library into app folder and move the native libraries # out. # When making apk for specified CPU arch, will only include the # corresponding native library by copying it back into xwalk_core_library. target_library_path = os.path.join(app_dir, EMBEDDED_LIBRARY) shutil.copytree(os.path.join(xwalk_dir, EMBEDDED_LIBRARY), target_library_path) library_lib_path = os.path.join(target_library_path, 'libs') native_lib_path = os.path.join(app_dir, 'native_libs') os.makedirs(native_lib_path) available_archs = [] for dir_name in os.listdir(library_lib_path): lib_dir = os.path.join(library_lib_path, dir_name) if ContainsNativeLibrary(lib_dir): shutil.move(lib_dir, os.path.join(native_lib_path, dir_name)) available_archs.append(dir_name) if options.arch: Execution(options, app_info) packaged_archs.append(options.arch) else: # If the arch option is unspecified, all of available platform APKs # will be generated. for arch in AllArchitectures(): if ConvertArchNameToArchFolder(arch) in available_archs: options.arch = arch Execution(options, app_info) packaged_archs.append(options.arch) else: print('Warning: failed to create package for arch "%s" ' 'due to missing native library' % arch) if len(packaged_archs) == 0: print('No packages created, aborting') sys.exit(13) # if project_dir, save build directory if options.project_dir: print('\nCreating project directory') save_dir = os.path.join(options.project_dir, name) if CreateAndCopyDir(app_dir, save_dir, True): print(' A project directory was created successfully in:\n %s' % os.path.abspath(save_dir)) print(' To manually generate an APK, run the following in that ' 'directory:') print(' ant release -f build.xml') print(' For more information, see:\n' ' http://developer.android.com/tools/building/' 'building-cmdline.html') else: print( 'Error: Unable to create a project directory during the build. ' 'Please check the directory passed in --project-dir, ' 'available disk space, and write permission.') if not options.project_only: PrintPackageInfo(options, name, packaged_archs)
def main(argv): parser = optparse.OptionParser() parser.add_option('-v', '--version', action='store_true', dest='version', default=False, help='The version of this python tool.') parser.add_option('--verbose', action="store_true", dest='verbose', default=False, help='Print debug messages.') info = ( 'The packaging mode of the web application. The value \'shared\' ' 'means that the runtime is shared across multiple application ' 'instances and that the runtime needs to be distributed separately. ' 'The value \'embedded\' means that the runtime is embedded into the ' 'application itself and distributed along with it.' 'Set the default mode as \'embedded\'. For example: --mode=embedded') parser.add_option('--mode', choices=('embedded', 'shared'), default='embedded', help=info) info = ( 'The target architecture of the embedded runtime. Supported values: ' '%s. If not specified, APKs for all available architectures will be ' 'generated.' % ', '.join(AllArchitectures())) parser.add_option('--arch', choices=AllArchitectures(), help=info) group = optparse.OptionGroup( parser, 'Application Source Options', 'This packaging tool supports 3 kinds of web application source: ' '1) XPK package; 2) manifest.json; 3) various command line options, ' 'for example, \'--app-url\' for website, \'--app-root\' and ' '\'--app-local-path\' for local web application.') info = ( 'The path of the XPK package. For example, --xpk=/path/to/xpk/file') group.add_option('--xpk', help=info) info = ( 'The manifest file with the detail description of the application. ' 'For example, --manifest=/path/to/your/manifest/file') group.add_option('--manifest', help=info) info = ('The url of application. ' 'This flag allows to package website as apk. For example, ' '--app-url=http://www.intel.com') group.add_option('--app-url', help=info) info = ('The root path of the web app. ' 'This flag allows to package local web app as apk. For example, ' '--app-root=/root/path/of/the/web/app') group.add_option('--app-root', help=info) info = ( 'The relative path of entry file based on the value from ' '\'app_root\'. This flag should work with \'--app-root\' together. ' 'For example, --app-local-path=/relative/path/of/entry/file') group.add_option('--app-local-path', help=info) parser.add_option_group(group) # Mandatory options group group = optparse.OptionGroup( parser, 'Mandatory arguments', 'They are used for describing the APK information through ' 'command line options.') info = ('The apk name. For example, --name="Your Application Name"') group.add_option('--name', help=info) info = ('The package name. For example, ' '--package=com.example.YourPackage') group.add_option('--package', help=info) parser.add_option_group(group) # Optional options group (alphabetical) group = optparse.OptionGroup( parser, 'Optional arguments', 'They are used for various settings for applications through ' 'command line options.') info = ('The version name of the application. ' 'For example, --app-version=1.0.0') group.add_option('--app-version', help=info) info = ('The version code of the application. ' 'For example, --app-versionCode=24') group.add_option('--app-versionCode', type='int', help=info) info = ('The version code base of the application. Version code will ' 'be made by adding a prefix based on architecture to the version ' 'code base. For example, --app-versionCodeBase=24') group.add_option('--app-versionCodeBase', type='int', help=info) info = ('The description of the application. For example, ' '--description=YourApplicationDescription') group.add_option('--description', help=info) group.add_option('--enable-remote-debugging', action='store_true', dest='enable_remote_debugging', default=False, help='Enable remote debugging.') group.add_option('--use-animatable-view', action='store_true', dest='use_animatable_view', default=False, help='Enable using animatable view (TextureView).') info = ('The list of external extension paths splitted by OS separators. ' 'The separators are \':\' , \';\' and \':\' on Linux, Windows and ' 'Mac OS respectively. For example, ' '--extensions=/path/to/extension1:/path/to/extension2.') group.add_option('--extensions', help=info) group.add_option('-f', '--fullscreen', action='store_true', dest='fullscreen', default=False, help='Make application fullscreen.') group.add_option('--keep-screen-on', action='store_true', default=False, help='Support keeping screen on') info = ('The path of application icon. ' 'Such as: --icon=/path/to/your/customized/icon') group.add_option('--icon', help=info) info = ('The orientation of the web app\'s display on the device. ' 'For example, --orientation=landscape. The default value is ' '\'unspecified\'. The permitted values are from Android: ' 'http://developer.android.com/guide/topics/manifest/' 'activity-element.html#screen') group.add_option('--orientation', help=info) info = ( 'The list of permissions to be used by web application. For example, ' '--permissions=geolocation:webgl') group.add_option('--permissions', help=info) info = ( 'Create an Android project directory with Crosswalk at this location.' ' (See project-only option below)') group.add_option('--project-dir', help=info) info = ('Must be used with project-dir option. Create an Android project ' 'directory with Crosswalk but do not build the APK package') group.add_option('--project-only', action='store_true', default=False, dest='project_only', help=info) info = ('Packaging tool will move the output APKs to the target directory') group.add_option('--target-dir', default=os.getcwd(), help=info) info = ( 'Use command lines.' 'Crosswalk is powered by Chromium and supports Chromium command line.' 'For example, ' '--xwalk-command-line=\'--chromium-command-1 --xwalk-command-2\'') group.add_option('--xwalk-command-line', default='', help=info) parser.add_option_group(group) # Keystore options group group = optparse.OptionGroup( parser, 'Keystore Options', 'The keystore is a signature from web developer, it\'s used when ' 'developer wants to distribute the applications.') info = ('The path to the developer keystore. For example, ' '--keystore-path=/path/to/your/developer/keystore') group.add_option('--keystore-path', help=info) info = ('The alias name of keystore. For example, --keystore-alias=name') group.add_option('--keystore-alias', help=info) info = ('The passcode of keystore. For example, --keystore-passcode=code') group.add_option('--keystore-passcode', help=info) info = ('Passcode for alias\'s private key in the keystore, ' 'For example, --keystore-alias-passcode=alias-code') group.add_option('--keystore-alias-passcode', help=info) info = ('Minify and obfuscate javascript and css.' '--compressor: compress javascript and css.' '--compressor=js: compress javascript.' '--compressor=css: compress css.') group.add_option('--compressor', dest='compressor', action='callback', callback=ParseParameterForCompressor, type='string', nargs=0, help=info) parser.add_option_group(group) options, _ = parser.parse_args() if len(argv) == 1: parser.print_help() return 0 if options.version: if os.path.isfile('VERSION'): print(GetVersion('VERSION')) return 0 else: parser.error( 'VERSION was not found, so Crosswalk\'s version could not ' 'be determined.') xpk_temp_dir = '' if options.xpk: xpk_name = os.path.splitext(os.path.basename(options.xpk))[0] xpk_temp_dir = tempfile.mkdtemp(prefix="%s-" % xpk_name + '_xpk') CleanDir(xpk_temp_dir) ParseXPK(options, xpk_temp_dir) if options.manifest: options.manifest = os.path.abspath(options.manifest) if not os.path.isfile(options.manifest): print('Error: The manifest file does not exist.') sys.exit(8) if options.app_root and not options.manifest: manifest_path = os.path.join(options.app_root, 'manifest.json') if os.path.exists(manifest_path): print('Using manifest.json distributed with the application.') options.manifest = manifest_path app_info = AppInfo() manifest = None if not options.manifest: # The checks here are really convoluted, but at the moment make_apk # misbehaves any of the following conditions is true. if options.app_url: # 1) --app-url must be passed without either --app-local-path or # --app-root. if options.app_root or options.app_local_path: parser.error( 'You must pass either "--app-url" or "--app-local-path" ' 'with "--app-root", but not all.') else: # 2) --app-url is not passed but only one of --app-local-path and # --app-root is set. if bool(options.app_root) != bool(options.app_local_path): parser.error('You must specify both "--app-local-path" and ' '"--app-root".') # 3) None of --app-url, --app-local-path and --app-root are passed. elif not options.app_root and not options.app_local_path: parser.error( 'You must pass either "--app-url" or "--app-local-path" ' 'with "--app-root".') if options.permissions: permission_list = options.permissions.split(':') else: print( 'Warning: all supported permissions on Android port are added. ' 'Refer to https://github.com/crosswalk-project/' 'crosswalk-website/wiki/Crosswalk-manifest') permission_list = permission_mapping_table.keys() options.permissions = HandlePermissionList(permission_list) options.icon_dict = {} else: try: manifest = ParseManifest(options) except SystemExit as ec: return ec.code if not options.name: parser.error( 'An APK name is required. Please use the "--name" option.') if not options.package: parser.error('A package name is required. Please use the "--package" ' 'option.') VerifyPackageName(options.package) if (options.app_root and options.app_local_path and not os.path.isfile( os.path.join(options.app_root, options.app_local_path))): print('Please make sure that the local path file of launching app ' 'does exist.') sys.exit(7) if options.target_dir: target_dir = os.path.abspath(os.path.expanduser(options.target_dir)) options.target_dir = target_dir if not os.path.isdir(target_dir): os.makedirs(target_dir) if options.project_only and not options.project_dir: print('\nmake_apk.py error: Option --project-only must be used ' 'with --project-dir') sys.exit(8) try: MakeApk(options, app_info, manifest) except SystemExit as ec: return ec.code finally: CleanDir(GetBuildDir(app_info.android_name)) CleanDir(xpk_temp_dir) return 0
def CustomizeXML(app_info, description, icon_dict, manifest, permissions): app_version = app_info.app_version app_versionCode = app_info.app_versionCode name = app_info.android_name orientation = app_info.orientation package = app_info.package app_name = app_info.app_name app_dir = GetBuildDir(name) # Chinese character with unicode get from 'manifest.json' will cause # 'UnicodeEncodeError' when finally wrote to 'AndroidManifest.xml'. app_name = EncodingUnicodeValue(app_name) # If string start with '@' or '?', it will be treated as Android resource, # which will cause 'No resource found' error, # append a space before '@' or '?' to fix that. if app_name.startswith('@') or app_name.startswith('?'): app_name = ' ' + app_name manifest_path = os.path.join(app_dir, 'AndroidManifest.xml') if not os.path.isfile(manifest_path): print('Please make sure AndroidManifest.xml' ' exists under template folder.') sys.exit(6) CustomizeStringXML(name, description) CustomizeThemeXML(name, app_info.fullscreen_flag, manifest) xmldoc = minidom.parse(manifest_path) EditElementAttribute(xmldoc, 'manifest', 'package', package) if app_versionCode: EditElementAttribute(xmldoc, 'manifest', 'android:versionCode', str(app_versionCode)) if app_version: EditElementAttribute(xmldoc, 'manifest', 'android:versionName', app_version) if description: EditElementAttribute(xmldoc, 'manifest', 'android:description', "@string/description") HandlePermissions(permissions, xmldoc) if app_info.mode == 'download': AddElementAttribute( xmldoc, 'uses-permission', 'android:name', 'android.permission.DOWNLOAD_WITHOUT_NOTIFICATION') EditElementAttribute(xmldoc, 'application', 'android:label', app_name) activity_name = package + '.' + name + 'Activity' EditElementAttribute(xmldoc, 'activity', 'android:name', activity_name) EditElementAttribute(xmldoc, 'activity', 'android:label', app_name) if orientation: EditElementAttribute(xmldoc, 'activity', 'android:screenOrientation', orientation) icon_name = CustomizeIcon(name, app_info.app_root, app_info.icon, icon_dict) if icon_name: EditElementAttribute(xmldoc, 'application', 'android:icon', '@drawable/%s' % icon_name) if app_info.xwalk_apk_url: meta_data = xmldoc.createElement('meta-data') meta_data.setAttribute('android:name', 'xwalk_apk_url') meta_data.setAttribute('android:value', app_info.xwalk_apk_url) app_node = xmldoc.getElementsByTagName('application')[0] comment = 'The download URL of Crosswalk runtime library APK. \n\ Default updater use the Android download manager to fetch the url' app_node.appendChild(xmldoc.createComment(comment)) app_node.appendChild(meta_data) if app_info.mode == 'download': meta_data = xmldoc.createElement('meta-data') meta_data.setAttribute('android:name', 'xwalk_enable_download_mode') meta_data.setAttribute('android:value', 'enable') comment = 'Make application run in silent download mode.' app_node.appendChild(xmldoc.createComment(comment)) app_node.appendChild(meta_data) file_handle = open(os.path.join(app_dir, 'AndroidManifest.xml'), 'w') xmldoc.writexml(file_handle, encoding='utf-8') file_handle.close()