Index: platform_tools/android/bin/gyp_to_android.py |
diff --git a/platform_tools/android/bin/gyp_to_android.py b/platform_tools/android/bin/gyp_to_android.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..8fc9f43a9b27c119e8bb53ebf9da6c870b070d1d |
--- /dev/null |
+++ b/platform_tools/android/bin/gyp_to_android.py |
@@ -0,0 +1,430 @@ |
+#!/usr/bin/python |
+ |
+# Copyright 2014 Google Inc. |
+# |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+""" |
+ Script for generating the Android framework's verision of Skia from gyp |
epoger
2014/01/23 18:57:58
No need to indent these lines.
verision -> versio
scroggo
2014/01/24 20:09:02
Done.
|
+ files. |
+""" |
+ |
+import os |
+ |
+# Folder containing all gyp files and generated gypd files. |
+GYP_FOLDER = 'gyp' |
+ |
+# FIXME: Currently the x86 specific files are not included. Include them. |
epoger
2014/01/23 18:57:58
I think we more commonly use TODO(username)
scroggo
2014/01/24 20:09:02
Done.
|
+INCLUDE_X86_OPTS = False |
+ |
+def WriteGroup(f, name, items, append): |
epoger
2014/01/23 18:57:58
Throughout:
http://google-styleguide.googlecode.co
scroggo
2014/01/24 20:09:02
I would swear I found some style guide that told m
scroggo
2014/01/31 22:16:24
And thanks to Joe I now see where I found that: ht
|
+ """ |
+ Helper function to list all names passed to a variable. |
+ @param f File open for writing (Android.mk) |
+ @param name Name of the makefile variable (e.g. LOCAL_CFLAGS) |
+ @param items list of strings to be passed to the variable. |
+ @param append Whether to append to the variable or overwrite it. |
+ """ |
+ if len(items) == 0: |
epoger
2014/01/23 18:57:58
mild preference for "if not items:", which will ha
scroggo
2014/01/24 20:09:02
That is clearer, I think. Done.
|
+ return |
+ if append: |
+ f.write('%s += \\\n' % name) |
+ else: |
+ f.write('%s := \\\n' % name) |
+ for item in items: |
+ f.write('\t%s \\\n' % item) |
epoger
2014/01/23 18:57:58
There's probably some easy way to make python do t
scroggo
2014/01/24 20:09:02
I think the choice between '+' and ':' is clearer
|
+ # Add an extra blank line since the last line ended in a \ |
+ f.write('\n') |
+ |
+class VarsDict(dict): |
+ """ |
+ Custom dict of lists for each makefile variable. Does not allow insertion of |
epoger
2014/01/23 18:57:58
I think the Python-y way of doing this is http://d
scroggo
2014/01/24 20:09:02
Added a TODO. Will think on it.
scroggo
2014/01/31 22:16:24
Patch set 13 uses a namedtuple. It has some downsi
|
+ new keys. |
+ """ |
+ def __init__(self): |
+ dict.__init__(self) |
+ self.__allow_insert = True |
+ self['cflags'] = [] |
+ self['cppflags'] = [] |
epoger
2014/01/23 18:57:58
Are these truly lists (ordered, allowing multiple
scroggo
2014/01/24 20:09:02
Thanks for the tip. They're actually neither - the
scroggo
2014/01/31 22:16:24
Now they are OrderedSets. I don't think it was so
|
+ self['defines'] = [] |
+ self['sources'] = [] |
+ self['shared_libraries'] = [] |
+ self['static_libraries'] = [] |
+ self['include_dirs'] = [] |
+ self['export_include_dirs'] = [] |
+ self['known_targets'] = [] |
+ self.__allow_insert = False |
+ self.__allow_convert = True |
+ |
+ def __setitem__(self, k, v): |
+ # Prevent the insertion of arbitrary keys. |
+ assert self.__allow_insert |
+ dict.__setitem__(self, k, v) |
+ |
+ def ConvertDefines(self): |
+ """ |
+ Add each string in 'defines' to 'cflags', prepended with '-D'. Can only |
epoger
2014/01/23 18:57:58
Alternatively, I guess you could add a GetCflags()
scroggo
2014/01/24 20:09:02
Good thinking. I could also add defines directly t
|
+ be done once. |
+ """ |
+ assert self.__allow_convert |
+ self.__allow_convert = False |
+ for define in self['defines']: |
+ define = '-D' + define |
epoger
2014/01/23 18:57:58
I think modifying the iterator is dangerous/confus
scroggo
2014/01/24 20:09:02
Good point. I'll add that, and just know that Conv
scroggo
2014/01/31 22:16:24
As hinted at previously, patch set 13 eliminates C
|
+ self['cflags'].append(define) |
+ |
+def WriteLocalVars(f, var_dict, append): |
+ """ |
+ Helper function to write all the members of var_dict to the makefile. |
+ @param f File open for writing (Android.mk) |
+ @param var_dict VarsDict holding the unique values for one configuration. |
+ @param append Whether to append to each makefile variable or overwrite it. |
+ """ |
+ var_dict.ConvertDefines() |
+ # Always append LOCAL_CFLAGS. This allows us to define some early on in the |
+ # makefile and not overwrite them. |
+ WriteGroup(f, 'LOCAL_CFLAGS', var_dict['cflags'], True) |
epoger
2014/01/23 18:57:58
Maybe we should just use 'LOCAL_CFLAGS' as the key
scroggo
2014/01/24 20:09:02
I could potentially take that further and pass the
scroggo
2014/01/31 22:16:24
Done.
|
+ WriteGroup(f, 'LOCAL_CPPFLAGS', var_dict['cppflags'], append) |
+ WriteGroup(f, 'LOCAL_SRC_FILES', var_dict['sources'], append) |
+ WriteGroup(f, 'LOCAL_SHARED_LIBRARIES', var_dict['shared_libraries'], append) |
+ WriteGroup(f, 'LOCAL_STATIC_LIBRARIES', var_dict['static_libraries'], append) |
+ WriteGroup(f, 'LOCAL_C_INCLUDES', var_dict['include_dirs'], append) |
+ WriteGroup(f, 'LOCAL_EXPORT_C_INCLUDE_DIRS', var_dict['export_include_dirs'], |
+ append) |
+ |
+AUTOGEN_WARNING = ( |
+""" |
+############################################################################### |
+# |
+# THIS FILE IS AUTOGENERATED BY GYP_TO_ANDROID.PY. DO NOT EDIT. |
+# |
+############################################################################### |
+ |
+""" |
+) |
+ |
+DEBUGGING_HELP = ( |
+""" |
+############################################################################### |
+# |
+# PROBLEMS WITH SKIA DEBUGGING?? READ THIS... |
+# |
+# The debug build results in changes to the Skia headers. This means that those |
+# using libskia must also be built with the debug version of the Skia headers. |
+# There are a few scenarios where this comes into play: |
+# |
+# (1) You're building debug code that depends on libskia. |
+# (a) If libskia is built in release, then define SK_RELEASE when building |
+# your sources. |
+# (b) If libskia is built with debugging (see step 2), then no changes are |
+# needed since your sources and libskia have been built with SK_DEBUG. |
+# (2) You're building libskia in debug mode. |
+# (a) RECOMMENDED: You can build the entire system in debug mode. Do this by |
+# updating your build/config.mk to include -DSK_DEBUG on the line that |
+# defines COMMON_GLOBAL_CFLAGS |
+# (b) You can update all the users of libskia to define SK_DEBUG when they are |
+# building their sources. |
+# |
+# NOTE: If neither SK_DEBUG or SK_RELEASE are defined then Skia checks NDEBUG to |
+# determine which build type to use. |
+############################################################################### |
+ |
+""" |
+) |
+ |
+def WriteAndroidMk(common, arm, armNeon, x86, default): |
+ """ |
+ Given all the variables, write the final make file. |
+ @param common VarsDict holding variables definitions common to all |
+ configurations. |
+ @param arm VarsDict holding variable definitions unique to arm. Will be |
+ written to the makefile inside an 'ifeq ($(TARGET_ARCH), arm)' |
+ block. |
+ @param armNeon VarsDict holding variable definitions unique to arm with neon. |
+ Will be written inside an 'ifeq ($(ARCH_ARM_HAVE_NEON),true)' |
+ block nested inside an 'ifeq ($(TARGET_ARCH), arm)' block. |
+ @param x86 VarsDict holding variable definitions unique to x86. Will be |
+ written inside an 'ifeq ($(TARGET_ARCH),x86)' block. |
+ @param default VarsDict holding variable definitions for an architecture |
+ without custom optimizations. |
+ TODO: Add mips. |
+ """ |
+ with open('Android.mk', 'w') as f: |
+ f.write(AUTOGEN_WARNING) |
+ f.write('BASE_PATH := $(call my-dir)\n') |
+ f.write('LOCAL_PATH:= $(call my-dir)\n') |
+ |
+ f.write(DEBUGGING_HELP) |
+ |
+ f.write('include $(CLEAR_VARS)\n') |
+ |
+ f.write('LOCAL_ARM_MODE := thumb\n') |
+ |
+ # need a flag to tell the C side when we're on devices with large memory |
+ # budgets (i.e. larger than the low-end devices that initially shipped) |
+ f.write('ifeq ($(ARCH_ARM_HAVE_VFP),true)\n') |
+ f.write('\tLOCAL_CFLAGS += -DANDROID_LARGE_MEMORY_DEVICE\n') |
+ f.write('endif\n\n') |
+ |
+ f.write('ifeq ($(TARGET_ARCH),x86)\n') |
+ f.write('\tLOCAL_CFLAGS += -DANDROID_LARGE_MEMORY_DEVICE\n') |
+ f.write('endif\n\n') |
+ |
+ f.write('# used for testing\n') |
+ f.write('#LOCAL_CFLAGS += -g -O0\n\n') |
+ |
+ f.write('ifeq ($(NO_FALLBACK_FONT),true)\n') |
+ f.write('\tLOCAL_CFLAGS += -DNO_FALLBACK_FONT\n') |
+ f.write('endif\n\n') |
+ |
+ WriteLocalVars(f, common, False) |
+ |
+ f.write('ifeq ($(TARGET_ARCH),arm)\n') |
+ f.write('ifeq ($(ARCH_ARM_HAVE_NEON),true)\n') |
+ WriteLocalVars(f, armNeon, True) |
+ f.write('endif\n\n') |
+ WriteLocalVars(f, arm, True) |
+ |
+ if INCLUDE_X86_OPTS: |
+ f.write('else ifeq ($(TARGET_ARCH),x86)\n') |
+ WriteLocalVars(f, x86, True) |
+ |
+ f.write('else\n') |
+ WriteLocalVars(f, default, True) |
+ f.write('endif\n\n') |
+ |
+ f.write('include external/stlport/libstlport.mk\n') |
+ f.write('LOCAL_MODULE:= libskia\n') |
+ f.write('include $(BUILD_SHARED_LIBRARY)\n') |
+ |
+def AddToList(li, item): |
+ """ |
+ Add item to li, but only if it is not already in li. |
epoger
2014/01/23 18:57:58
sounds like a set, not a list
scroggo
2014/01/24 20:09:02
As stated above, I do care about order.
|
+ """ |
+ if item not in li: |
+ li.append(item) |
+ |
+def GetMembers(d, name): |
epoger
2014/01/23 18:57:58
I think this function can be replaced by calls to
scroggo
2014/01/24 20:09:02
Done. Thanks!
|
+ """ |
+ Get the list corresponding to name in dictionary d. |
+ """ |
+ if not name in d: |
+ return [] |
+ return d[name] |
+ |
+def ParseDictionary(var_dict, d, current_target_name): |
+ """ |
+ Helper function to get the meaningful entries in a dictionary. |
+ @param var_dict VarsDict object for storing the results of the parsing. |
+ @param d Dictionary object to parse. |
+ @param current_target_name The current target being parsed. If this |
+ dictionary is a target, this will be its entry |
+ 'target_name'. Otherwise, this will be the name of |
+ the target which contains this dictionary. |
+ """ |
+ for source in GetMembers(d, 'sources'): |
+ if source.endswith('.h'): |
+ # Android.mk does not need the header files. |
+ continue |
+ if source.endswith('gypi'): |
+ # The gypi files are included in sources, but the sources they included |
+ # are also included. No need to parse them again. |
+ continue |
+ # The path is relative to the gyp folder, but Android wants the path |
+ # relative to the root. |
+ source = source.replace('../src', 'src', 1) |
+ AddToList(var_dict['sources'], source) |
+ |
+ for lib in GetMembers(d, 'libraries'): |
+ if lib.endswith('.a'): |
+ # Remove the '.a' |
+ lib = lib[:-2] |
+ # Add 'lib', if necessary |
+ if not lib.startswith('lib'): |
+ lib = 'lib' + lib |
+ AddToList(var_dict['static_libraries'], lib) |
+ else: |
+ # lib will be in the form of '-l<name>'. Change it to 'lib<name>' |
+ lib = lib.replace('-l', 'lib', 1) |
+ AddToList(var_dict['shared_libraries'], lib) |
+ |
+ for dependency in GetMembers(d, 'dependencies'): |
+ # Each dependency is listed as |
+ # <path_to_file>:<target>#target |
+ li = dependency.split(':') |
+ assert(len(li) <= 2 and len(li) >= 1) |
+ sub_targets = [] |
+ if len(li) == 2 and li[1] != '*': |
+ sub_targets.append(li[1].split('#')[0]) |
+ sub_path = li[0] |
+ assert(sub_path.endswith('.gyp')) |
+ # Although the original reference is to a .gyp, parse the corresponding |
+ # gypd file, which was constructed by gyp. |
+ sub_path = sub_path + 'd' |
+ ParseGypd(var_dict, sub_path, sub_targets) |
+ |
+ if 'default_configuration' in d: |
+ config_name = d['default_configuration'] |
+ # default_configuration is meaningless without configurations |
+ assert('configurations' in d) |
+ config = d['configurations'][config_name] |
+ ParseDictionary(var_dict, config, current_target_name) |
+ |
+ for flag in GetMembers(d, 'cflags'): |
+ AddToList(var_dict['cflags'], flag) |
+ for flag in GetMembers(d, 'cflags_cc'): |
+ AddToList(var_dict['cppflags'], flag) |
+ |
+ for include in GetMembers(d, 'include_dirs'): |
+ # The input path will be relative to gyp/, but Android wants relative to |
+ # LOCAL_PATH |
+ include = include.replace('..', '$(LOCAL_PATH)', 1) |
+ # Remove a trailing slash, if present. |
+ if include.endswith('/'): |
+ include = include[:-1] |
+ AddToList(var_dict['include_dirs'], include) |
+ # For the top level, libskia, include directories should be exported. |
+ if current_target_name == 'libskia': |
+ AddToList(var_dict['export_include_dirs'], include) |
+ |
+ for define in GetMembers(d, 'defines'): |
+ AddToList(var_dict['defines'], define) |
+ |
+def ParseGypd(var_dict, path, desired_targets=None): |
+ """ |
+ Parse a gypd file. |
+ @param var_dict VarsDict object for storing the result of the parse. |
+ @param path Path to gypd file. |
+ @param desired_targets List of targets to be parsed from this file. If empty, |
+ parse all targets. |
+ """ |
+ d = {} |
+ with open(path, 'r') as f: |
+ # Read the entire file as a dictionary |
+ d = eval(f.read()) |
+ # The gypd file is structured such that the top level dictionary has an entry |
+ # named 'targets' |
+ if 'targets' not in d: |
+ return |
+ targets = d['targets'] |
+ for target in targets: |
+ target_name = target['target_name'] |
+ if target_name in var_dict['known_targets']: |
+ # Avoid circular dependencies |
+ continue |
+ if desired_targets and target_name not in desired_targets: |
+ # Our caller does not depend on this one |
+ continue |
+ # Add it to our known targets so we don't parse it again |
+ var_dict['known_targets'].append(target_name) |
+ |
+ ParseDictionary(var_dict, target, target_name) |
+ |
+def Intersect(var_dict_list): |
epoger
2014/01/23 18:57:58
I think the VarsDict class, and functions that ope
scroggo
2014/01/24 20:09:02
Added a TODO. Will do in a followup patch set.
scroggo
2014/01/31 22:16:24
Done.
|
+ """ |
+ Find the intersection of a list of VarsDicts and trim each input to its |
+ unique entries. |
+ @param var_dict_list list of VarsDicts. |
+ @return VarsDict containing list entries common to all VarsDicts in |
+ var_dict_list |
+ """ |
+ intersection = VarsDict() |
+ # First VarsDict |
+ var_dictA = var_dict_list[0] |
+ # The rest. |
+ otherVarDicts = var_dict_list[1:] |
+ |
+ for key in var_dictA.keys(): |
+ # Copy A's list, so we can continue iterating after modifying the original. |
+ aList = list(var_dictA[key]) |
+ for item in aList: |
+ # If item is in all lists, add to intersection, and remove from all. |
+ in_all_lists = True |
+ for var_dict in otherVarDicts: |
+ # Each dictionary must be a VarsDict, which always contains the same |
+ # set of keys. |
+ if not item in var_dict[key]: |
+ in_all_lists = False |
+ break |
+ if in_all_lists: |
+ intersection[key].append(item) |
+ for var_dict in var_dict_list: |
+ var_dict[key].remove(item) |
+ return intersection |
+ |
+def CleanGypdFiles(): |
+ """ |
+ Remove the gypd files generated by android_framework_gyp.main(). |
+ """ |
+ assert os.path.isdir(GYP_FOLDER) |
+ files = os.listdir(GYP_FOLDER) |
+ for f in files: |
+ if f.endswith('gypd'): |
+ os.remove(os.path.join(GYP_FOLDER, f)) |
+ |
+import android_framework_gyp |
+ |
+def main(): |
+ # Move up to top of trunk |
+ script_dir = os.path.dirname(__file__) |
+ skia_dir = os.path.normpath(os.path.join(script_dir, os.pardir, os.pardir, |
+ os.pardir)) |
+ os.chdir(skia_dir) |
+ |
+ main_gypd_file = os.path.join(GYP_FOLDER, 'android_framework_lib.gypd') |
+ |
+ print 'Creating Android.mk', |
+ |
+ # Call the script to generate gypd files, first using a non-existant archtype. |
+ android_framework_gyp.main('other', False) |
+ # And store its variables in the default dict |
+ default_var_dict = VarsDict() |
+ ParseGypd(default_var_dict, main_gypd_file) |
+ CleanGypdFiles() |
+ |
+ print '.', |
+ |
+ # Now do the same for Arm. arm_var_dict will contain all the variable |
+ # definitions needed to build with arm (meaning that it will share most |
+ # with default_var_dict). |
+ android_framework_gyp.main('arm', False) |
+ arm_var_dict = VarsDict() |
+ ParseGypd(arm_var_dict, main_gypd_file) |
+ CleanGypdFiles() |
+ |
+ print '.', |
+ |
+ # Now do the same for Arm/Neon. |
+ android_framework_gyp.main('arm', True) |
+ arm_neon_var_dict = VarsDict() |
+ ParseGypd(arm_neon_var_dict, main_gypd_file) |
+ CleanGypdFiles() |
+ |
+ print '.' |
+ |
+ # Now do the same for x86. |
+ x86_var_dict = VarsDict() |
+ if INCLUDE_X86_OPTS: |
+ android_framework_gyp.main('x86', False) |
+ ParseGypd(x86_var_dict, main_gypd_file) |
+ CleanGypdFiles() |
+ |
+ # Compute the intersection of all targets. All the files in the intersection |
+ # should be part of the makefile always. Each dict will now contain trimmed |
+ # lists containing only variable definitions specific to that configuration. |
+ var_dict_list = [default_var_dict, arm_var_dict, arm_neon_var_dict] |
+ if INCLUDE_X86_OPTS: |
+ var_dict_list.append(x86_var_dict) |
+ common = Intersect(var_dict_list) |
+ |
+ # Further trim arm_neon_var_dict with arm_var_dict. After this call, |
+ # arm_var_dict (which will now be the intersection) includes all definitions |
+ # used by both arm and arm + neon, and arm_neon_var_dict will only contain |
+ # those specific to arm + neon. |
+ arm_var_dict = Intersect([arm_var_dict, arm_neon_var_dict]) |
+ |
+ WriteAndroidMk(common, arm_var_dict, arm_neon_var_dict, x86_var_dict, |
+ default_var_dict) |
+ |
+if __name__ == '__main__': |
+ main() |