From 89335631749b29ea92e55ed710f030692aa13297 Mon Sep 17 00:00:00 2001 From: "commit-bot@chromium.org" Date: Thu, 6 Feb 2014 16:13:00 +0000 Subject: Scripts to generate Android.mk for framework Skia. In order to create Android.mk, run >> python platform_tools/android/bin/gyp_to_android.py For the change in the Android.mk file, see https://googleplex-android-review.git.corp.google.com/#/c/408170/ (SkipBuildbotRuns) BUG=skia:1975 R=djsollen@google.com, epoger@google.com Author: scroggo@google.com Review URL: https://codereview.chromium.org/140503007 git-svn-id: http://skia.googlecode.com/svn/trunk@13344 2bbb7eff-a529-9590-31e7-b0007b416f81 --- platform_tools/android/gyp_gen/gypd_parser.py | 122 +++++++++++++++ platform_tools/android/gyp_gen/makefile_writer.py | 175 ++++++++++++++++++++++ platform_tools/android/gyp_gen/variables.py | 11 ++ platform_tools/android/gyp_gen/vars_dict_lib.py | 133 ++++++++++++++++ 4 files changed, 441 insertions(+) create mode 100644 platform_tools/android/gyp_gen/gypd_parser.py create mode 100644 platform_tools/android/gyp_gen/makefile_writer.py create mode 100644 platform_tools/android/gyp_gen/variables.py create mode 100644 platform_tools/android/gyp_gen/vars_dict_lib.py (limited to 'platform_tools/android/gyp_gen') diff --git a/platform_tools/android/gyp_gen/gypd_parser.py b/platform_tools/android/gyp_gen/gypd_parser.py new file mode 100644 index 0000000000..b547c42525 --- /dev/null +++ b/platform_tools/android/gyp_gen/gypd_parser.py @@ -0,0 +1,122 @@ +#!/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. + +""" +Functions for parsing the gypd output from gyp. +""" + +import vars_dict_lib + +def parse_dictionary(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 d.get('sources', []): + # Compare against a lowercase version, in case files are named .H or .GYPI + lowercase_source = source.lower() + if lowercase_source.endswith('.h'): + # Android.mk does not need the header files. + continue + if lowercase_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) + var_dict.LOCAL_SRC_FILES.add(source) + + for lib in d.get('libraries', []): + if lib.endswith('.a'): + # Remove the '.a' + lib = lib[:-2] + # Add 'lib', if necessary + if not lib.startswith('lib'): + lib = 'lib' + lib + var_dict.LOCAL_STATIC_LIBRARIES.add(lib) + else: + # lib will be in the form of '-l'. Change it to 'lib' + lib = lib.replace('-l', 'lib', 1) + var_dict.LOCAL_SHARED_LIBRARIES.add(lib) + + for dependency in d.get('dependencies', []): + # Each dependency is listed as + # :#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' + parse_gypd(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] + parse_dictionary(var_dict, config, current_target_name) + + for flag in d.get('cflags', []): + var_dict.LOCAL_CFLAGS.add(flag) + for flag in d.get('cflags_cc', []): + var_dict.LOCAL_CPPFLAGS.add(flag) + + for include in d.get('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] + var_dict.LOCAL_C_INCLUDES.add(include) + # For the top level, libskia, include directories should be exported. + if current_target_name == 'libskia': + var_dict.LOCAL_EXPORT_C_INCLUDE_DIRS.add(include) + + for define in d.get('defines', []): + var_dict.LOCAL_CFLAGS.add('-D' + define) + + +def parse_gypd(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' + for target in d['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.add(target_name) + + parse_dictionary(var_dict, target, target_name) + diff --git a/platform_tools/android/gyp_gen/makefile_writer.py b/platform_tools/android/gyp_gen/makefile_writer.py new file mode 100644 index 0000000000..ea3a27ff1e --- /dev/null +++ b/platform_tools/android/gyp_gen/makefile_writer.py @@ -0,0 +1,175 @@ +#!/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. + +""" +Functions for creating an Android.mk from already created dictionaries. +""" + +import os +import variables + +def write_group(f, name, items, append): + """ + 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 not items: + return + + # Copy the list so we can prepend it with its name. + items_to_write = list(items) + + if append: + items_to_write.insert(0, '%s +=' % name) + else: + items_to_write.insert(0, '%s :=' % name) + + f.write(' \\\n\t'.join(items_to_write)) + + f.write('\n\n') + + +def write_local_vars(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. + """ + for key in var_dict.keys(): + if key == 'LOCAL_CFLAGS': + # Always append LOCAL_CFLAGS. This allows us to define some early on in + # the makefile and not overwrite them. + _append = True + elif key == 'KNOWN_TARGETS': + # KNOWN_TARGETS are not needed in the final make file. + continue + else: + _append = append + write_group(f, key, var_dict[key], _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. +############################################################################### + +""" +) + + +# TODO (scroggo): Currently write_android_mk has intimate knowledge about its +# parameters: e.g. arm_neon keeps track of differences from arm, whereas the +# others keep track of differences from common. Consider reworking this. +def write_android_mk(target_dir, common, arm, arm_neon, x86, default): + """ + Given all the variables, write the final make file. + @param target_dir The full path to the directory to write Android.mk, or None + to use the current working directory. + @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 arm_neon 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. + """ + target_file = 'Android.mk' + if target_dir: + target_file = os.path.join(target_dir, target_file) + with open(target_file, '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') + + write_local_vars(f, common, False) + + f.write('ifeq ($(TARGET_ARCH),arm)\n') + f.write('ifeq ($(ARCH_ARM_HAVE_NEON),true)\n') + write_local_vars(f, arm_neon, True) + f.write('endif\n\n') + write_local_vars(f, arm, True) + + if variables.INCLUDE_X86_OPTS: + f.write('else ifeq ($(TARGET_ARCH),x86)\n') + write_local_vars(f, x86, True) + + f.write('else\n') + write_local_vars(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') + + + diff --git a/platform_tools/android/gyp_gen/variables.py b/platform_tools/android/gyp_gen/variables.py new file mode 100644 index 0000000000..326254a86e --- /dev/null +++ b/platform_tools/android/gyp_gen/variables.py @@ -0,0 +1,11 @@ +#!/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. + +# TODO (scroggo): Currently the x86 specific files are not included. Include +# them. +INCLUDE_X86_OPTS = False + diff --git a/platform_tools/android/gyp_gen/vars_dict_lib.py b/platform_tools/android/gyp_gen/vars_dict_lib.py new file mode 100644 index 0000000000..eedb8a36b9 --- /dev/null +++ b/platform_tools/android/gyp_gen/vars_dict_lib.py @@ -0,0 +1,133 @@ +#!/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. + +import collections +import types + +class OrderedSet(object): + """ + Ordered set of unique items that supports addition and removal. + """ + + def __init__(self): + self.__li = [] + + def add(self, item): + """ + Add item, if it is not already in the set. + @param item The item to add. + """ + if item not in self.__li: + self.__li.append(item) + + def __contains__(self, item): + """ + Whether the set contains item. + @param item The item to search for in the set. + @return bool Whether the item is in the set. + """ + return item in self.__li + + def __iter__(self): + """ + Iterator for the set. + """ + return self.__li.__iter__() + + def remove(self, item): + """ + Remove item from the set. + @param item Item to be removed. + """ + return self.__li.remove(item) + + def __len__(self): + """ + Number of items in the set. + """ + return len(self.__li) + + def __getitem__(self, index): + """ + Return item at index. + """ + return self.__li[index] + +VAR_NAMES = ['LOCAL_CFLAGS', + 'LOCAL_CPPFLAGS', + 'LOCAL_SRC_FILES', + 'LOCAL_SHARED_LIBRARIES', + 'LOCAL_STATIC_LIBRARIES', + 'LOCAL_C_INCLUDES', + 'LOCAL_EXPORT_C_INCLUDE_DIRS', + 'KNOWN_TARGETS'] + +class VarsDict(collections.namedtuple('VarsDict', VAR_NAMES)): + """ + Custom class for storing the arguments to Android.mk variables. Can be + treated as a dictionary with fixed keys. + """ + + __slots__ = () + + def __new__(cls): + lists = [] + # TODO (scroggo): Is there a better way add N items? + for __unused__ in range(len(VAR_NAMES)): + lists.append(OrderedSet()) + return tuple.__new__(cls, lists) + + def keys(self): + """ + Return the field names as strings. + """ + return self._fields + + def __getitem__(self, index): + """ + Return an item, indexed by a number or a string. + """ + if type(index) == types.IntType: + # Treat the index as an array index into a tuple. + return tuple.__getitem__(self, index) + if type(index) == types.StringType: + # Treat the index as a key into a dictionary. + return eval('self.%s' % index) + return None + + +def intersect(var_dict_list): + """ + Find the intersection of a list of VarsDicts and trim each input to its + unique entries. + @param var_dict_list list of VarsDicts. WARNING: each VarsDict will be + modified in place, to remove the common elements! + @return VarsDict containing list entries common to all VarsDicts in + var_dict_list + """ + intersection = VarsDict() + # First VarsDict + var_dict_a = var_dict_list[0] + # The rest. + other_var_dicts = var_dict_list[1:] + + for key in var_dict_a.keys(): + # Copy A's list, so we can continue iterating after modifying the original. + a_list = list(var_dict_a[key]) + for item in a_list: + # If item is in all lists, add to intersection, and remove from all. + in_all_lists = True + for var_dict in other_var_dicts: + if not item in var_dict[key]: + in_all_lists = False + break + if in_all_lists: + intersection[key].add(item) + for var_dict in var_dict_list: + var_dict[key].remove(item) + return intersection + -- cgit v1.2.3