# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+import compress_js_and_css
+import fnmatch
import json
import optparse
import os
import re
import shutil
+import stat
import sys
+from customize_launch_screen import CustomizeLaunchScreen
from handle_xml import AddElementAttribute
from handle_xml import AddElementAttributeAndText
from handle_xml import EditElementAttribute
from handle_permissions import HandlePermissions
from xml.dom import minidom
+
+def VerifyAppName(value, mode='default'):
+ descrpt = 'The app'
+ sample = 'helloworld, hello_world, hello_world1'
+ regex = r'^([a-zA-Z](\w)*)+$'
+
+ if len(value) >= 128:
+ print('To be safe, the length of package name or app name '
+ 'should be less than 128.')
+ sys.exit(6)
+ if mode == 'packagename':
+ regex = r'^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$'
+ descrpt = 'Each part of package'
+ sample = 'org.xwalk.example, org.xwalk.example_'
+
+ if not re.match(regex, value):
+ print('Error: %s name should be started with letters and should not '
+ 'conatin invalid characters.\n'
+ 'It may conatin letters, numbers and underscores\n'
+ 'Sample: %s' % (descrpt, sample))
+ sys.exit(6)
+
+
def ReplaceInvalidChars(value, mode='default'):
""" Replace the invalid chars with '_' for input string.
Args:
if mode == 'default':
invalid_chars = '\/:*?"<>|- '
elif mode == 'apkname':
- invalid_chars = '\/:.*?"<>|-'
+ invalid_chars = '\/:.*?"<>|- '
for c in invalid_chars:
if mode == 'apkname' and c in value:
print("Illegal character: '%s' is replaced with '_'" % c)
- value = value.replace(c,'_')
+ value = value.replace(c, '_')
return value
-def Prepare(options, sanitized_name):
- if os.path.exists(sanitized_name):
- shutil.rmtree(sanitized_name)
- shutil.copytree('app_src', sanitized_name)
- shutil.rmtree(os.path.join(sanitized_name, 'src'))
+def GetFilesByExt(path, ext, sub_dir=True):
+ if os.path.exists(path):
+ file_list = []
+ for name in os.listdir(path):
+ full_name = os.path.join(path, name)
+ st = os.lstat(full_name)
+ if stat.S_ISDIR(st.st_mode) and sub_dir:
+ file_list += GetFilesByExt(full_name, ext)
+ elif os.path.isfile(full_name):
+ if fnmatch.fnmatch(full_name, ext):
+ file_list.append(full_name)
+ return file_list
+ else:
+ return []
+
+
+def ParseParameterForCompressor(option, value, values, parser):
+ if ((not values or values.startswith('-'))
+ and value.find('--compressor') != -1):
+ values = 'all'
+ val = values
+ if parser.rargs and not parser.rargs[0].startswith('-'):
+ val = parser.rargs[0]
+ parser.rargs.pop(0)
+ setattr(parser.values, option.dest, val)
+
+
+def CompressSourceFiles(app_root, compressor):
+ js_list = []
+ css_list = []
+ js_ext = '*.js'
+ css_ext = '*.css'
+
+ if compressor == 'all' or compressor == 'js':
+ js_list = GetFilesByExt(app_root, js_ext)
+ compress_js_and_css.CompressJavaScript(js_list)
+
+ if compressor == 'all' or compressor == 'css':
+ css_list = GetFilesByExt(app_root, css_ext)
+ compress_js_and_css.CompressCss(css_list)
+
+
+def Prepare(name, package, app_root, compressor):
+ if os.path.exists(name):
+ shutil.rmtree(name)
+ shutil.copytree('app_src', name)
+ shutil.rmtree(os.path.join(name, 'src'))
src_root = os.path.join('app_src', 'src', 'org', 'xwalk', 'app', 'template')
src_activity = os.path.join(src_root, 'AppTemplateActivity.java')
if not os.path.isfile(src_activity):
print ('Please make sure that the java file'
' of activity does exist.')
sys.exit(7)
- root_path = os.path.join(sanitized_name, 'src',
- options.package.replace('.', os.path.sep))
+ root_path = os.path.join(name, 'src', package.replace('.', os.path.sep))
if not os.path.exists(root_path):
os.makedirs(root_path)
- dest_activity = sanitized_name + 'Activity.java'
+ dest_activity = name + 'Activity.java'
shutil.copyfile(src_activity, os.path.join(root_path, dest_activity))
- if options.app_root:
- assets_path = os.path.join(sanitized_name, 'assets')
+ if app_root:
+ assets_path = os.path.join(name, 'assets')
shutil.rmtree(assets_path)
os.makedirs(assets_path)
app_src_path = os.path.join(assets_path, 'www')
- shutil.copytree(options.app_root, app_src_path)
+ shutil.copytree(app_root, app_src_path)
+ if compressor:
+ CompressSourceFiles(app_src_path, compressor)
-def CustomizeStringXML(options, sanitized_name):
- strings_path = os.path.join(sanitized_name, 'res', 'values', 'strings.xml')
+def CustomizeStringXML(name, description):
+ strings_path = os.path.join(name, 'res', 'values', 'strings.xml')
if not os.path.isfile(strings_path):
print ('Please make sure strings_xml'
' exists under app_src folder.')
sys.exit(6)
- if options.description:
+ if description:
xmldoc = minidom.parse(strings_path)
AddElementAttributeAndText(xmldoc, 'string', 'name', 'description',
- options.description)
+ description)
strings_file = open(strings_path, 'w')
- xmldoc.writexml(strings_file)
+ xmldoc.writexml(strings_file, encoding='utf-8')
strings_file.close()
-def CustomizeThemeXML(options, sanitized_name):
- theme_path = os.path.join(sanitized_name, 'res', 'values', 'theme.xml')
+def CustomizeThemeXML(name, fullscreen, app_manifest):
+ theme_path = os.path.join(name, 'res', 'values', 'theme.xml')
if not os.path.isfile(theme_path):
print('Error: theme.xml is missing in the build tool.')
sys.exit(6)
- xmldoc = minidom.parse(theme_path)
- if options.fullscreen:
- EditElementValueByNodeName(xmldoc, 'item',
+ theme_xmldoc = minidom.parse(theme_path)
+ if fullscreen:
+ EditElementValueByNodeName(theme_xmldoc, 'item',
'android:windowFullscreen', 'true')
- if options.launch_screen_img:
- EditElementValueByNodeName(xmldoc, 'item',
+ has_background = CustomizeLaunchScreen(app_manifest, name)
+ if has_background:
+ EditElementValueByNodeName(theme_xmldoc, 'item',
'android:windowBackground',
- '@drawable/launchscreen')
- default_image = options.launch_screen_img
- if os.path.isfile(default_image):
- drawable_path = os.path.join(sanitized_name, 'res', 'drawable')
- if not os.path.exists(drawable_path):
- os.makedirs(drawable_path)
- # Get the extension of default_image.
- # Need to take care of special case, such as 'img.9.png'
- name = os.path.basename(default_image)
- extlist = name.split('.')
- # Remove the file name from the list.
- extlist.pop(0)
- ext = '.' + '.'.join(extlist)
- final_launch_screen_path = os.path.join(drawable_path,
- 'launchscreen' + ext)
- shutil.copyfile(default_image, final_launch_screen_path)
- else:
- print('Error: Please make sure \"' + default_image + '\" exists!')
- sys.exit(6)
+ '@drawable/launchscreen_bg')
theme_file = open(theme_path, 'wb')
- xmldoc.writexml(theme_file)
+ theme_xmldoc.writexml(theme_file, encoding='utf-8')
theme_file.close()
-def CustomizeXML(options, sanitized_name):
- manifest_path = os.path.join(sanitized_name, 'AndroidManifest.xml')
+def CustomizeXML(package, app_versionCode, app_version, description, name,
+ orientation, icon_dict, fullscreen, icon, app_manifest,
+ permissions, app_root):
+ manifest_path = os.path.join(name, 'AndroidManifest.xml')
if not os.path.isfile(manifest_path):
print ('Please make sure AndroidManifest.xml'
' exists under app_src folder.')
sys.exit(6)
- CustomizeStringXML(options, sanitized_name)
- CustomizeThemeXML(options, sanitized_name)
+ CustomizeStringXML(name, description)
+ CustomizeThemeXML(name, fullscreen, app_manifest)
xmldoc = minidom.parse(manifest_path)
- EditElementAttribute(xmldoc, 'manifest', 'package', options.package)
- if options.app_versionCode:
+ EditElementAttribute(xmldoc, 'manifest', 'package', package)
+ if app_versionCode:
EditElementAttribute(xmldoc, 'manifest', 'android:versionCode',
- str(options.app_versionCode))
- if options.app_version:
+ str(app_versionCode))
+ if app_version:
EditElementAttribute(xmldoc, 'manifest', 'android:versionName',
- options.app_version)
- if options.description:
+ app_version)
+ if description:
EditElementAttribute(xmldoc, 'manifest', 'android:description',
"@string/description")
- HandlePermissions(options, xmldoc)
- EditElementAttribute(xmldoc, 'application', 'android:label', options.name)
- activity_name = options.package + '.' + sanitized_name + 'Activity'
+ HandlePermissions(permissions, xmldoc)
+ EditElementAttribute(xmldoc, 'application', 'android:label', name)
+ activity_name = package + '.' + name + 'Activity'
EditElementAttribute(xmldoc, 'activity', 'android:name', activity_name)
- EditElementAttribute(xmldoc, 'activity', 'android:label', options.name)
- if options.orientation:
+ EditElementAttribute(xmldoc, 'activity', 'android:label', name)
+ if orientation:
EditElementAttribute(xmldoc, 'activity', 'android:screenOrientation',
- options.orientation)
- if options.icon and os.path.isfile(options.icon):
- drawable_path = os.path.join(sanitized_name, 'res', 'drawable')
- if not os.path.exists(drawable_path):
- os.makedirs(drawable_path)
- icon_file = os.path.basename(options.icon)
- icon_file = ReplaceInvalidChars(icon_file)
- shutil.copyfile(options.icon, os.path.join(drawable_path, icon_file))
- icon_name = os.path.splitext(icon_file)[0]
- EditElementAttribute(xmldoc, 'application',
- 'android:icon', '@drawable/%s' % icon_name)
- elif options.icon and (not os.path.isfile(options.icon)):
- print ('Please make sure the icon file does exist!')
- sys.exit(6)
-
- file_handle = open(os.path.join(sanitized_name, 'AndroidManifest.xml'), 'w')
- xmldoc.writexml(file_handle)
+ orientation)
+ icon_name = CustomizeIcon(name, app_root, icon, icon_dict)
+ if icon_name:
+ EditElementAttribute(xmldoc, 'application', 'android:icon',
+ '@drawable/%s' % icon_name)
+
+ file_handle = open(os.path.join(name, 'AndroidManifest.xml'), 'w')
+ xmldoc.writexml(file_handle, encoding='utf-8')
file_handle.close()
file_handle.close()
-def SetVariable(file_path, variable, value):
+def SetVariable(file_path, string_line, variable, value):
function_string = ('%sset%s(%s);\n' %
(' ', variable, value))
temp_file_path = file_path + '.backup'
file_handle = open(temp_file_path, 'w+')
for line in open(file_path):
file_handle.write(line)
- if (line.find('public void onCreate(Bundle savedInstanceState)') >= 0):
+ if (line.find(string_line) >= 0):
file_handle.write(function_string)
file_handle.close()
shutil.move(temp_file_path, file_path)
-def CustomizeJava(options, sanitized_name):
- root_path = os.path.join(sanitized_name, 'src',
- options.package.replace('.', os.path.sep))
- dest_activity = os.path.join(root_path, sanitized_name + 'Activity.java')
- ReplaceString(dest_activity, 'org.xwalk.app.template', options.package)
- ReplaceString(dest_activity, 'AppTemplate', sanitized_name)
- manifest_file = os.path.join(sanitized_name, 'assets/www', 'manifest.json')
+def CustomizeJava(name, package, app_url, app_local_path,
+ enable_remote_debugging, display_as_fullscreen,
+ keep_screen_on):
+ root_path = os.path.join(name, 'src', package.replace('.', os.path.sep))
+ dest_activity = os.path.join(root_path, name + 'Activity.java')
+ ReplaceString(dest_activity, 'org.xwalk.app.template', package)
+ ReplaceString(dest_activity, 'AppTemplate', name)
+ manifest_file = os.path.join(name, 'assets/www', 'manifest.json')
if os.path.isfile(manifest_file):
ReplaceString(
dest_activity,
'loadAppFromUrl("file:///android_asset/www/index.html")',
'loadAppFromManifest("file:///android_asset/www/manifest.json")')
else:
- if options.app_url:
- if re.search(r'^http(|s)', options.app_url):
+ if app_url:
+ if re.search(r'^http(|s)', app_url):
ReplaceString(dest_activity, 'file:///android_asset/www/index.html',
- options.app_url)
- elif options.app_local_path:
- if os.path.isfile(os.path.join(sanitized_name, 'assets/www',
- options.app_local_path)):
+ app_url)
+ elif app_local_path:
+ if os.path.isfile(os.path.join(name, 'assets/www', app_local_path)):
ReplaceString(dest_activity, 'file:///android_asset/www/index.html',
- 'app://' + options.package + '/' + options.app_local_path)
+ 'app://' + package + '/' + app_local_path)
else:
print ('Please make sure that the relative path of entry file'
' is correct.')
sys.exit(8)
- if options.enable_remote_debugging:
- SetVariable(dest_activity, 'RemoteDebugging', 'true')
+ if enable_remote_debugging:
+ SetVariable(dest_activity,
+ 'public void onCreate(Bundle savedInstanceState)',
+ 'RemoteDebugging', 'true')
+ if display_as_fullscreen:
+ 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 CopyExtensionFile(extension_name, suffix, src_path, dest_path):
shutil.copyfile(src_file, dest_file)
-def CustomizeExtensions(options, sanitized_name):
+def CustomizeExtensions(name, extensions):
"""Copy the files from external extensions and merge them into APK.
The directory of one external extension should be like:
For .json files, the'll be merged into one file called
extensions-config.json and copied into assets/.
"""
- if not options.extensions:
+ if not extensions:
return
- apk_path = options.name
+ apk_path = name
apk_assets_path = os.path.join(apk_path, 'assets')
extensions_string = 'xwalk-extensions'
'extensions-config.json')
# Split the paths into a list.
- extension_paths = options.extensions.split(os.pathsep)
+ extension_paths = extensions.split(os.pathsep)
extension_json_list = []
for source_path in extension_paths:
if not os.path.exists(source_path):
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) or (not 'class' in json_output)
- or (not 'jsapi' in json_output)):
+ 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)
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(sanitized_name, 'AndroidManifest.xml')
+ manifest_path = os.path.join(name, 'AndroidManifest.xml')
xmldoc = minidom.parse(manifest_path)
if ('permissions' in json_output):
# Get used permission list to avoid repetition as "--permissions"
# Write to the manifest file to save the update.
file_handle = open(manifest_path, 'w')
- xmldoc.writexml(file_handle)
+ xmldoc.writexml(file_handle, encoding='utf-8')
file_handle.close()
# Write configuration of extensions into the target extensions-config.json.
extension_json_file.close()
+def GenerateCommandLineFile(name, xwalk_command_line):
+ if xwalk_command_line == '':
+ return
+ assets_path = os.path.join(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 CustomizeIconByDict(name, app_root, icon_dict):
+ 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.iteritems(), key=lambda d: d[0])
+ for kd, vd in drawable_dict.iteritems():
+ for item in icon_list:
+ if item[0] >= vd[0] and item[0] < vd[1]:
+ drawable_path = os.path.join(name, '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: Please make sure \"' + icon + '\" does exist!')
+ sys.exit(6)
+ break
+ return icon_name
+
+
+def CustomizeIconByOption(name, icon):
+ if os.path.isfile(icon):
+ drawable_path = os.path.join(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: Please make sure \"' + icon + '\" is a file!')
+ sys.exit(6)
+
+
+def CustomizeIcon(name, app_root, icon, icon_dict):
+ icon_name = None
+ if icon:
+ icon_name = CustomizeIconByOption(name, icon)
+ else:
+ icon_name = CustomizeIconByDict(name, app_root, icon_dict)
+ return icon_name
+
+
+def CustomizeAll(app_versionCode, description, icon_dict, permissions, app_url,
+ app_root, app_local_path, enable_remote_debugging,
+ display_as_fullscreen, keep_screen_on, extensions,
+ app_manifest, icon, package='org.xwalk.app.template',
+ name='AppTemplate', app_version='1.0.0',
+ orientation='unspecified', xwalk_command_line='',
+ compressor=None):
+ try:
+ Prepare(name, package, app_root, compressor)
+ CustomizeXML(package, app_versionCode, app_version, description, name,
+ orientation, icon_dict, display_as_fullscreen, icon,
+ app_manifest, permissions, app_root)
+ CustomizeJava(name, package, app_url, app_local_path,
+ enable_remote_debugging, display_as_fullscreen,
+ keep_screen_on)
+ CustomizeExtensions(name, extensions)
+ GenerateCommandLineFile(name, xwalk_command_line)
+ except SystemExit as ec:
+ print('Exiting with error code: %d' % ec.code)
+ sys.exit(ec.code)
+
+
def main():
parser = optparse.OptionParser()
info = ('The package name. Such as: '
info = ('The application description. Such as:'
'--description=YourApplicationdDescription')
parser.add_option('--description', help=info)
- info = ('The path of icon. Such as: --icon=/path/to/your/customized/icon')
- parser.add_option('--icon', help=info)
info = ('The permission list. Such as: --permissions="geolocation"'
'For more permissions, such as:'
'--permissions="geolocation:permission2"')
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.')
+ help='Enable remote debugging.')
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"')
'http://developer.android.com/guide/topics/manifest/'
'activity-element.html#screen')
parser.add_option('--orientation', help=info)
- parser.add_option('--launch-screen-img',
- help='The fallback image for launch_screen')
+ 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\'')
+ 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()
- sanitized_name = ReplaceInvalidChars(options.name, 'apkname')
try:
- Prepare(options, sanitized_name)
- CustomizeXML(options, sanitized_name)
- CustomizeJava(options, sanitized_name)
- CustomizeExtensions(options, sanitized_name)
+ icon_dict = {144: 'icons/icon_144.png',
+ 72: 'icons/icon_72.png',
+ 96: 'icons/icon_96.png',
+ 48: 'icons/icon_48.png'}
+ if options.name is None:
+ options.name = 'Example'
+ if options.app_root is None:
+ options.app_root = os.path.join('test_data', 'manifest')
+ if options.package is None:
+ options.package = 'org.xwalk.app.template'
+ if options.orientation is None:
+ options.orientation = 'unspecified'
+ if options.app_version is None:
+ options.app_version = '1.0.0'
+ icon = os.path.join('test_data', 'manifest', 'icons', 'icon_96.png')
+ CustomizeAll(options.app_versionCode, options.description, icon_dict,
+ options.permissions, options.app_url, options.app_root,
+ options.app_local_path, options.enable_remote_debugging,
+ options.fullscreen, options.keep_screen_on,
+ options.extensions, options.manifest,
+ icon, options.package, options.name,
+ options.app_version, options.orientation,
+ options.xwalk_command_line, options.compressor)
except SystemExit as ec:
print('Exiting with error code: %d' % ec.code)
return ec.code