Generate tests/Android.mk from gyp
authorcommit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>
Mon, 28 Apr 2014 16:00:30 +0000 (16:00 +0000)
committercommit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>
Mon, 28 Apr 2014 16:00:30 +0000 (16:00 +0000)
gyp/apptype_console.gypi:
Don't include console app dependencies when building for Android framework.

gyp/tests.gyp:
Add/remove libraries for framework build.

platform_tools/android/bin/android_framework_gyp.py:
Moved to gyp_gen.
clean_up_gypd_files moved to this script.

platform_tools/android/bin/gyp_to_android.py:
Call new function for generating tool makefile.
Set LOCAL_MODULE.

platform_tools/android/gyp_gen/gypd_parser.py:
Support relative paths.

platform_tools/android/gyp_gen/makefile_writer.py:
Factor out helper functions to be used by tool writer.
LOCAL_MODULE is set elsewhere.

platform_tools/android/gyp_gen/tool_makefile_writer.py:
Multipurpose file for writing makefiles for tools. Should be able to use
it mostly unchanged for bench, gm, etc.

platform_tools/android/gyp_gen/vars_dict_lib.py:
Make the comments follow the style guide.
Add set().
Rename __li to __ordered_set

More/update tests:
platform_tools/android/tests/android_framework_gyp_tests.py
platform_tools/android/tests/expectations/Android.mk
platform_tools/android/tests/expectations/tool/Android.mk
platform_tools/android/tests/expectations/write_local_vars_append_arm
platform_tools/android/tests/expectations/write_local_vars_append_foo
platform_tools/android/tests/expectations/write_local_vars_append_no_name
platform_tools/android/tests/expectations/write_local_vars_no_append_arm
platform_tools/android/tests/expectations/write_local_vars_no_append_foo
platform_tools/android/tests/expectations/write_local_vars_no_append_no_name
platform_tools/android/tests/gyp_to_android_tests.py
platform_tools/android/tests/makefile_writer_tests.py
platform_tools/android/tests/ordered_set_tests.py
platform_tools/android/tests/test_variables.py

BUG=skia:2447

May require an update to the bot to remove pyc files.

R=halcanary@google.com, djsollen@google.com

Author: scroggo@google.com

Review URL: https://codereview.chromium.org/235883015

git-svn-id: http://skia.googlecode.com/svn/trunk@14408 2bbb7eff-a529-9590-31e7-b0007b416f81

21 files changed:
gyp/apptype_console.gypi
gyp/tests.gyp
platform_tools/android/bin/gyp_to_android.py
platform_tools/android/gyp_gen/android_framework_gyp.py [moved from platform_tools/android/bin/android_framework_gyp.py with 74% similarity]
platform_tools/android/gyp_gen/gypd_parser.py
platform_tools/android/gyp_gen/makefile_writer.py
platform_tools/android/gyp_gen/tool_makefile_writer.py [new file with mode: 0644]
platform_tools/android/gyp_gen/vars_dict_lib.py
platform_tools/android/tests/android_framework_gyp_tests.py [new file with mode: 0644]
platform_tools/android/tests/expectations/Android.mk
platform_tools/android/tests/expectations/tool/Android.mk [new file with mode: 0644]
platform_tools/android/tests/expectations/write_local_vars_append_arm
platform_tools/android/tests/expectations/write_local_vars_append_foo
platform_tools/android/tests/expectations/write_local_vars_append_no_name
platform_tools/android/tests/expectations/write_local_vars_no_append_arm
platform_tools/android/tests/expectations/write_local_vars_no_append_foo
platform_tools/android/tests/expectations/write_local_vars_no_append_no_name
platform_tools/android/tests/gyp_to_android_tests.py
platform_tools/android/tests/makefile_writer_tests.py
platform_tools/android/tests/ordered_set_tests.py
platform_tools/android/tests/test_variables.py

index 85cc502..cde8779 100644 (file)
@@ -12,7 +12,7 @@
       },
     },
     'conditions': [
-      [ 'skia_os == "android"', {
+      [ 'skia_os == "android" and not skia_android_framework', {
         'dependencies': [
           'android_deps.gyp:Android_EntryPoint',
           'android_system.gyp:skia_launcher',
index d0a898c..0d6ed5a 100644 (file)
         '../tests/skia_test.cpp',
       ],
       'conditions': [
+        [ 'skia_android_framework == 1', {
+          'libraries': [
+            '-lskia',
+            '-lcutils',
+          ],
+          'libraries!': [
+            '-lz',
+            '-llog',
+          ],
+        }],
         [ 'skia_gpu == 1', {
           'include_dirs': [
             '../src/gpu',
index f7199b8..f43d597 100755 (executable)
@@ -10,7 +10,6 @@ Script for generating the Android framework's version of Skia from gyp
 files.
 """
 
-import android_framework_gyp
 import os
 import shutil
 import sys
@@ -25,53 +24,48 @@ SKIA_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, os.pardir, os.pardir,
 GYP_GEN_DIR = os.path.join(SKIA_DIR, 'platform_tools', 'android', 'gyp_gen')
 sys.path.append(GYP_GEN_DIR)
 
+import android_framework_gyp
 import gypd_parser
 import generate_user_config
 import makefile_writer
+import tool_makefile_writer
 import vars_dict_lib
 
 # Folder containing all gyp files and generated gypd files.
 GYP_FOLDER = 'gyp'
 
-# TODO(scroggo): Update the docstrings to match the style guide:
-# http://google-styleguide.googlecode.com/svn/trunk/pyguide.html#Comments
-def clean_gypd_files(folder):
-  """
-  Remove the gypd files generated by android_framework_gyp.main().
-  @param folder Folder in which to delete all files ending with 'gypd'.
-  """
-  assert os.path.isdir(folder)
-  files = os.listdir(folder)
-  for f in files:
-    if f.endswith('gypd'):
-      os.remove(os.path.join(folder, f))
 
 def generate_var_dict(target_dir, target_file, skia_arch_type, have_neon):
-  """
-  Create a VarsDict for a particular arch type. Each paramater is passed
-  directly to android_framework_gyp.main().
-  @param target_dir Directory containing gyp files.
-  @param target_file Target gyp file.
-  @param skia_arch_type Target architecture.
-  @param have_neon Whether the target should build for neon.
-  @return a VarsDict containing the variable definitions determined by gyp.
+  """Create a VarsDict for a particular arch type.
+
+  Each paramater is passed directly to android_framework_gyp.main().
+
+  Args:
+    target_dir: Directory containing gyp files.
+    target_file: Target gyp file.
+    skia_arch_type: Target architecture.
+    have_neon: Whether the target should build for neon.
+  Returns:
+    A VarsDict containing the variable definitions determined by gyp.
   """
   result_file = android_framework_gyp.main(target_dir, target_file,
                                            skia_arch_type, have_neon)
   var_dict = vars_dict_lib.VarsDict()
-  gypd_parser.parse_gypd(var_dict, result_file)
-  clean_gypd_files(target_dir)
+  gypd_parser.parse_gypd(var_dict, result_file, '.')
+  android_framework_gyp.clean_gypd_files(target_dir)
   print '.',
   return var_dict
 
 def main(target_dir=None, require_sk_user_config=False):
-  """
-  Read gyp files and create Android.mk for the Android framework's
-  external/skia.
-  @param target_dir Directory in which to place 'Android.mk'. If None, the file
-                    will be placed in skia's root directory.
-  @param require_sk_user_config If True, raise an AssertionError if
-                                SkUserConfig.h does not exist.
+  """Create Android.mk for the Android framework's external/skia.
+
+  Builds Android.mk using Skia's gyp files.
+
+  Args:
+    target_dir: Directory in which to place 'Android.mk'. If None, the file
+      will be placed in skia's root directory.
+    require_sk_user_config: If True, raise an AssertionError if
+      SkUserConfig.h does not exist.
   """
   # Create a temporary folder to hold gyp and gypd files. Create it in SKIA_DIR
   # so that it is a sibling of gyp/, so the relationships between gyp files and
@@ -119,6 +113,8 @@ def main(target_dir=None, require_sk_user_config=False):
                      x86_var_dict, mips_var_dict, arm64_var_dict]
     common = vars_dict_lib.intersect(var_dict_list)
 
+    common.LOCAL_MODULE.add('libskia')
+
     # Create SkUserConfig
     user_config = os.path.join(SKIA_DIR, 'include', 'config', 'SkUserConfig.h')
     if target_dir:
@@ -131,8 +127,26 @@ def main(target_dir=None, require_sk_user_config=False):
         require_sk_user_config=require_sk_user_config, target_dir=dst_dir,
         ordered_set=common.DEFINES)
 
-    # Now that the defines have been written to SkUserConfig, they are not
-    # needed in Android.mk.
+    tool_makefile_writer.generate_tool(gyp_dir=tmp_folder,
+                                       target_file='tests.gyp',
+                                       skia_trunk=target_dir,
+                                       dest_dir='tests',
+                                       skia_lib_var_dict=common,
+                                       local_module_name='skia_test',
+                                       local_module_tags=['eng', 'tests'])
+
+    # TODO (scroggo): Generate bench/Android.mk. See skbug.com/2448
+    #tool_makefile_writer.generate_tool(gyp_dir=tmp_folder,
+    #                                   target_file='bench.gyp',
+    #                                   skia_trunk=target_dir,
+    #                                   dest_dir='bench',
+    #                                   skia_lib_var_dict=common,
+    #                                   local_module_name='skia_bench',
+    #                                   local_module_tags=['tests'])
+
+    # Now that the defines have been written to SkUserConfig and they've been
+    # used to skip adding them to the tools makefiles, they are not needed in
+    # Android.mk. Reset DEFINES.
     common.DEFINES.reset()
 
     # Further trim arm_neon_var_dict with arm_var_dict. After this call,
@@ -16,7 +16,7 @@ SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
 
 # Unlike gyp_skia, this file is nested deep inside Skia. Find Skia's trunk dir.
 # This line depends on the fact that the script is three levels deep
-# (specifically, it is in platform_tools/android/bin).
+# (specifically, it is in platform_tools/android/gyp_gen).
 SKIA_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, os.pardir, os.pardir,
                                          os.pardir))
 DIR_CONTENTS = os.listdir(SKIA_DIR)
@@ -40,15 +40,18 @@ sys.path.insert(0, os.path.join(GYP_SOURCE_DIR, 'pylib'))
 import gyp
 
 def main(target_dir, target_file, skia_arch_type, have_neon):
-  """
-  Create gypd files based on target_file.
-  @param target_dir Directory containing all gyp files, including common.gypi
-  @param target_file Gyp file to start on. Other files within target_dir will
-                     be read if target_file depends on them.
-  @param skia_arch_type Target architecture to pass to gyp.
-  @param have_neon Whether to generate files including neon optimizations.
-                   Only meaningful if skia_arch_type is 'arm'.
-  @return path Path to root gypd file created by running gyp.
+  """Create gypd files based on target_file.
+
+  Args:
+    target_dir: Directory containing all gyp files, including common.gypi
+    target_file: Gyp file to start on. Other files within target_dir will
+      be read if target_file depends on them.
+    skia_arch_type: Target architecture to pass to gyp.
+    have_neon: Whether to generate files including neon optimizations.
+      Only meaningful if skia_arch_type is 'arm'.
+
+  Returns:
+    path: Path to root gypd file created by running gyp.
   """
   # Set GYP_DEFINES for building for the android framework.
   gyp_defines = ('skia_android_framework=1 OS=android skia_arch_type=%s '
@@ -85,3 +88,16 @@ def main(target_dir, target_file, skia_arch_type, have_neon):
     raise Exception("gyp failed to produce gypd file!")
 
   return gypd_file
+
+
+def clean_gypd_files(folder):
+  """Remove the gypd files generated by main().
+
+  Args:
+    folder: Folder in which to delete all files ending with 'gypd'.
+  """
+  assert os.path.isdir(folder)
+  files = os.listdir(folder)
+  for f in files:
+    if f.endswith('gypd'):
+      os.remove(os.path.join(folder, f))
index fc4dc68..b0e1417 100644 (file)
@@ -5,19 +5,32 @@
 # 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.
+"""Functions for parsing the gypd output from gyp.
 """
 
-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.
+
+import os
+
+
+def parse_dictionary(var_dict, d, current_target_name, dest_dir):
+  """Helper function to get the meaningful entries in a dictionary.
+
+  Parse dictionary d, and store unique relevant entries in var_dict.
+  Recursively parses internal dictionaries and files that are referenced.
+  When parsing the 'libraries' list from gyp, entries in the form
+  '-l<name>' get assigned to var_dict.LOCAL_SHARED_LIBRARIES as 'lib<name>',
+  and entries in the form '[lib]<name>.a' get assigned to
+  var_dict.LOCAL_STATIC_LIBRARIES as 'lib<name>'.
+
+  Args:
+    var_dict: VarsDict object for storing the results of the parsing.
+    d: Dictionary object to parse.
+    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.
+    dest_dir: Destination for the eventual Android.mk that will be created from
+      this parse, relative to Skia trunk. Used to determine path for source
+      files.
   """
   for source in d.get('sources', []):
     # Compare against a lowercase version, in case files are named .H or .GYPI
@@ -30,9 +43,10 @@ def parse_dictionary(var_dict, d, current_target_name):
       # 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)
+    # relative to dest_dir.
+    rel_source = os.path.relpath(source, os.pardir)
+    rel_source = os.path.relpath(rel_source, dest_dir)
+    var_dict.LOCAL_SRC_FILES.add(rel_source)
 
   for lib in d.get('libraries', []):
     if lib.endswith('.a'):
@@ -60,14 +74,14 @@ def parse_dictionary(var_dict, d, current_target_name):
     # 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)
+    parse_gypd(var_dict, sub_path, dest_dir, 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)
+    parse_dictionary(var_dict, config, current_target_name, dest_dir)
 
   for flag in d.get('cflags', []):
     var_dict.LOCAL_CFLAGS.add(flag)
@@ -75,28 +89,44 @@ def parse_dictionary(var_dict, d, current_target_name):
     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)
+    if include.startswith('external'):
+      # This path is relative to the Android root. Leave it alone.
+      rel_include = include
+    else:
+      # As with source, the input path will be relative to gyp/, but Android
+      # wants relative to dest_dir.
+      rel_include = os.path.relpath(include, os.pardir)
+      rel_include = os.path.relpath(rel_include, dest_dir)
+      rel_include = os.path.join('$(LOCAL_PATH)', rel_include)
+
     # Remove a trailing slash, if present.
-    if include.endswith('/'):
-      include = include[:-1]
-    var_dict.LOCAL_C_INCLUDES.add(include)
+    if rel_include.endswith('/'):
+      rel_include = rel_include[:-1]
+    var_dict.LOCAL_C_INCLUDES.add(rel_include)
     # For the top level, libskia, include directories should be exported.
+    # FIXME (scroggo): Do not hard code this.
     if current_target_name == 'libskia':
-      var_dict.LOCAL_EXPORT_C_INCLUDE_DIRS.add(include)
+      var_dict.LOCAL_EXPORT_C_INCLUDE_DIRS.add(rel_include)
 
   for define in d.get('defines', []):
     var_dict.DEFINES.add(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.
+def parse_gypd(var_dict, path, dest_dir, desired_targets=None):
+  """Parse a gypd file.
+
+  Open a file that consists of python dictionaries representing build targets.
+  Parse those dictionaries using parse_dictionary. Recursively parses
+  referenced files.
+
+  Args:
+    var_dict: VarsDict object for storing the result of the parse.
+    path: Path to gypd file.
+    dest_dir: Destination for the eventual Android.mk that will be created from
+      this parse, relative to Skia trunk. Used to determine path for source
+      files and include directories.
+    desired_targets: List of targets to be parsed from this file. If empty,
+      parse all targets.
   """
   d = {}
   with open(path, 'r') as f:
@@ -116,5 +146,5 @@ def parse_gypd(var_dict, path, desired_targets=None):
     # 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)
+    parse_dictionary(var_dict, target, target_name, dest_dir)
 
index 1b977ca..2129799 100644 (file)
@@ -12,12 +12,13 @@ Functions for creating an Android.mk from already created dictionaries.
 import os
 
 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.
+  """Helper function to list all names passed to a variable.
+
+  Args:
+    f: File open for writing (Android.mk)
+    name: Name of the makefile variable (e.g. LOCAL_CFLAGS)
+    items: list of strings to be passed to the variable.
+    append: Whether to append to the variable or overwrite it.
   """
   if not items:
     return
@@ -36,12 +37,13 @@ def write_group(f, name, items, append):
 
 
 def write_local_vars(f, var_dict, append, name):
-  """
-  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.
-  @param name If not None, a string to be appended to each key.
+  """Helper function to write all the members of var_dict to the makefile.
+
+  Args:
+    f: File open for writing (Android.mk)
+    var_dict: VarsDict holding the unique values for one configuration.
+    append: Whether to append to each makefile variable or overwrite it.
+    name: If not None, a string to be appended to each key.
   """
   for key in var_dict.keys():
     _key = key
@@ -121,46 +123,65 @@ SKIA_TOOLS = (
 #include $(BASE_PATH)/gm/Android.mk
 
 # unit-tests
-#include $(BASE_PATH)/tests/Android.mk
-
-# pathOps unit-tests
-# TODO include those sources!
+include $(BASE_PATH)/tests/Android.mk
 """
 )
 
 
 class VarsDictData(object):
-  """
-  Helper class for keeping a VarsDict along with a name and an optional
-  condition.
+  """Helper class to keep a VarsDict along with a name and optional condition.
   """
   def __init__(self, vars_dict, name, condition=None):
-    """
-    Create a new VarsDictData.
-    @param vars_dict A VarsDict. Can be accessed via self.vars_dict.
-    @param name Name associated with the VarsDict. Can be accessed via
-                self.name.
-    @param condition Optional string representing a condition. If not None,
-                     used to create a conditional inside the makefile.
+    """Create a new VarsDictData.
+
+    Args:
+      vars_dict: A VarsDict. Can be accessed via self.vars_dict.
+      name: Name associated with the VarsDict. Can be accessed via
+        self.name.
+      condition: Optional string representing a condition. If not None,
+        used to create a conditional inside the makefile.
     """
     self.vars_dict = vars_dict
     self.condition = condition
     self.name = name
 
+def write_local_path(f):
+    """Add the LOCAL_PATH line to the makefile.
+
+    Args:
+      f: File open for writing.
+    """
+    f.write('LOCAL_PATH:= $(call my-dir)\n')
+
+def write_clear_vars(f):
+    """Add the CLEAR_VARS line to the makefile.
+
+    Args:
+      f: File open for writing.
+    """
+    f.write('include $(CLEAR_VARS)\n')
+
+def write_include_stlport(f):
+    """Add a line to include stlport.
+
+    Args:
+      f: File open for writing.
+    """
+    f.write('include external/stlport/libstlport.mk\n')
+
 def write_android_mk(target_dir, common, deviations_from_common):
-  """
-  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 deviations_from_common List of VarsDictData, one for each possible
-                                configuration. VarsDictData.name will be
-                                appended to each key before writing it to the
-                                makefile. VarsDictData.condition, if not None,
-                                will be written to the makefile as a condition
-                                to determine whether to include
-                                VarsDictData.vars_dict.
+  """Given all the variables, write the final make file.
+
+  Args:
+    target_dir: The full path to the directory to write Android.mk, or None
+      to use the current working directory.
+    common: VarsDict holding variables definitions common to all
+      configurations.
+    deviations_from_common: List of VarsDictData, one for each possible
+      configuration. VarsDictData.name will be appended to each key before
+      writing it to the makefile. VarsDictData.condition, if not None, will be
+      written to the makefile as a condition to determine whether to include
+      VarsDictData.vars_dict.
   """
   target_file = 'Android.mk'
   if target_dir:
@@ -168,12 +189,11 @@ def write_android_mk(target_dir, common, deviations_from_common):
   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')
+    write_local_path(f)
 
     f.write(DEBUGGING_HELP)
 
-    f.write('include $(CLEAR_VARS)\n')
-
+    write_clear_vars(f)
     f.write('LOCAL_ARM_MODE := thumb\n')
 
     # need a flag to tell the C side when we're on devices with large memory
@@ -204,8 +224,7 @@ def write_android_mk(target_dir, common, deviations_from_common):
       if data.condition:
         f.write('endif\n\n')
 
-    f.write('include external/stlport/libstlport.mk\n')
-    f.write('LOCAL_MODULE:= libskia\n')
+    write_include_stlport(f)
     f.write('include $(BUILD_SHARED_LIBRARY)\n')
     f.write(SKIA_TOOLS)
 
diff --git a/platform_tools/android/gyp_gen/tool_makefile_writer.py b/platform_tools/android/gyp_gen/tool_makefile_writer.py
new file mode 100644 (file)
index 0000000..bdcd884
--- /dev/null
@@ -0,0 +1,92 @@
+#!/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.
+
+"""Code for generating Android.mk for a tool."""
+
+
+import android_framework_gyp
+import gypd_parser
+import makefile_writer
+import os
+import vars_dict_lib
+
+
+def write_tool_android_mk(target_dir, var_dict):
+  """Write Android.mk for a Skia tool.
+
+  Args:
+    target_dir: Destination for the makefile. Must not be None.
+    var_dict: VarsDict containing variables for the makefile.
+  """
+  target_file = os.path.join(target_dir, 'Android.mk')
+  with open(target_file, 'w') as f:
+    f.write(makefile_writer.AUTOGEN_WARNING)
+    makefile_writer.write_local_path(f)
+    makefile_writer.write_clear_vars(f)
+
+    makefile_writer.write_local_vars(f, var_dict, False, None)
+
+    makefile_writer.write_include_stlport(f)
+    f.write('include $(BUILD_EXECUTABLE)\n')
+
+
+def generate_tool(gyp_dir, target_file, skia_trunk, dest_dir,
+                  skia_lib_var_dict, local_module_name, local_module_tags):
+  """Common steps for building one of the skia tools.
+
+  Parse a gyp file and create an Android.mk for this tool.
+
+  Args:
+    gyp_dir: Directory containing gyp files.
+    target_file: gyp file for the project to be built, contained in gyp_dir.
+    skia_trunk: Trunk of Skia, used for determining the destination to write
+      'Android.mk'.
+    dest_dir: Destination for 'Android.mk', relative to skia_trunk. Used for
+      both writing relative paths in the makefile and for determining the
+      destination to write the it.
+    skia_lib_var_dict: VarsDict representing libskia. Used as a reference to
+      ensure we do not duplicate anything in this Android.mk.
+    local_module_name: Name for this tool, to set as LOCAL_MODULE.
+    local_module_tags: Tags to pass to LOCAL_MODULE_TAG.
+  """
+  result_file = android_framework_gyp.main(target_dir=gyp_dir,
+                                           target_file=target_file,
+                                           skia_arch_type='other',
+                                           have_neon=False)
+
+  var_dict = vars_dict_lib.VarsDict()
+
+  # Add known targets from skia_lib, so we do not reparse them.
+  var_dict.KNOWN_TARGETS.set(skia_lib_var_dict.KNOWN_TARGETS)
+
+  gypd_parser.parse_gypd(var_dict, result_file, dest_dir)
+
+  android_framework_gyp.clean_gypd_files(gyp_dir)
+
+  var_dict.LOCAL_MODULE.add(local_module_name)
+  for tag in local_module_tags:
+    var_dict.LOCAL_MODULE_TAGS.add(tag)
+
+  # No need for defines that are already in skia_lib.
+  for define in skia_lib_var_dict.DEFINES:
+    try:
+      var_dict.DEFINES.remove(define)
+    except ValueError:
+      # Okay if the define was not part of the parse for our tool.
+      pass
+
+  if skia_trunk:
+    full_dest = os.path.join(skia_trunk, dest_dir)
+  else:
+    full_dest = dest_dir
+
+  # If the path does not exist, create it. This will happen during testing,
+  # where there is no subdirectory for each tool (just a temporary folder).
+  if not os.path.exists(full_dest):
+    os.mkdir(full_dest)
+
+  write_tool_android_mk(target_dir=full_dest, var_dict=var_dict)
index a4ef67e..6be2402 100644 (file)
@@ -13,59 +13,76 @@ import types
 # we want to make sure the image decoders are in a particular order. See
 # images.gyp for more information.
 class OrderedSet(object):
-  """
-  Ordered set of unique items that supports addition and removal.
+  """Ordered set of unique items that supports addition and removal.
+
+  Retains the order in which items are inserted.
   """
 
   def __init__(self):
-    self.__li = []
+    self.__ordered_set = []
 
   def add(self, item):
+    """Add item, if it is not already in the set.
+
+    item is appended to the end if it is not already in the set.
+
+    Args:
+      item: The item to add.
     """
-    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)
+    if item not in self.__ordered_set:
+      self.__ordered_set.append(item)
 
   def __contains__(self, item):
+    """Whether the set contains item.
+
+    Args:
+      item: The item to search for in the set.
+
+    Returns:
+      bool: Whether the item is in the set.
     """
-    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
+    return item in self.__ordered_set
 
   def __iter__(self):
+    """Iterator for the set.
     """
-    Iterator for the set.
-    """
-    return self.__li.__iter__()
+    return self.__ordered_set.__iter__()
 
   def remove(self, item):
     """
     Remove item from the set.
-    @param item Item to be removed.
+
+    Args:
+      item: Item to be removed.
+
+    Raises:
+      ValueError if item is not in the set.
     """
-    return self.__li.remove(item)
+    self.__ordered_set.remove(item)
 
   def __len__(self):
+    """Number of items in the set.
     """
-    Number of items in the set.
-    """
-    return len(self.__li)
+    return len(self.__ordered_set)
 
   def __getitem__(self, index):
+    """Return item at index.
     """
-    Return item at index.
-    """
-    return self.__li[index]
+    return self.__ordered_set[index]
 
   def reset(self):
+    """Reset to empty.
     """
-    Reset to empty.
+    self.__ordered_set = []
+
+  def set(self, other):
+    """Replace this ordered set with another.
+
+    Args:
+      other: OrderedSet to replace this one. After this call, this OrderedSet
+        will contain exactly the same elements as other.
     """
-    self.__li = []
+    self.__ordered_set = list(other.__ordered_set)
 
 VAR_NAMES = ['LOCAL_CFLAGS',
              'LOCAL_CPPFLAGS',
@@ -75,12 +92,15 @@ VAR_NAMES = ['LOCAL_CFLAGS',
              'LOCAL_C_INCLUDES',
              'LOCAL_EXPORT_C_INCLUDE_DIRS',
              'DEFINES',
-             'KNOWN_TARGETS']
+             'KNOWN_TARGETS',
+             # These are not parsed by gyp, but set manually.
+             'LOCAL_MODULE_TAGS',
+             'LOCAL_MODULE']
 
 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.
+  """Custom class for storing the arguments to Android.mk variables.
+
+  Can also be treated as a dictionary with fixed keys.
   """
 
   __slots__ = ()
@@ -93,14 +113,12 @@ class VarsDict(collections.namedtuple('VarsDict', VAR_NAMES)):
     return tuple.__new__(cls, lists)
 
   def keys(self):
-    """
-    Return the field names as strings.
+    """Return the field names as strings.
     """
     return self._fields
 
   def __getitem__(self, index):
-    """
-    Return an item, indexed by a number or a string.
+    """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.
@@ -112,13 +130,17 @@ class VarsDict(collections.namedtuple('VarsDict', VAR_NAMES)):
 
 
 def intersect(var_dict_list):
-  """
+  """Compute intersection of VarsDicts.
+
   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
+
+  Args:
+    var_dict_list: list of VarsDicts. WARNING: each VarsDict will be
+      modified in place, to remove the common elements!
+  Returns:
+    VarsDict containing list entries common to all VarsDicts in
+      var_dict_list
   """
   intersection = VarsDict()
   # First VarsDict
diff --git a/platform_tools/android/tests/android_framework_gyp_tests.py b/platform_tools/android/tests/android_framework_gyp_tests.py
new file mode 100644 (file)
index 0000000..0b52c6b
--- /dev/null
@@ -0,0 +1,82 @@
+#!/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.
+
+"""
+Test gyp_to_android.py
+"""
+
+import os
+import shutil
+import sys
+import tempfile
+import test_variables
+import unittest
+
+# Path to android_framework_gyp
+sys.path.append(test_variables.GYP_GEN_DIR)
+
+import android_framework_gyp
+
+GYPD_SUFFIX = ".gypd"
+GYP_SUFFIX = ".gyp"
+GYPI_SUFFIX = ".gypi"
+OTHER_SUFFIX = ".txt"
+
+class CleanGypdTest(unittest.TestCase):
+
+  def setUp(self):
+    self.__tmp_dir = tempfile.mkdtemp()
+    self.__num_files = 10
+    # Fill the dir with four types of files. .gypd files should be deleted by
+    # clean_gypd_files(), while the rest should be left alone.
+    for i in range(self.__num_files):
+      self.create_file('%s%s' % (str(i), GYPD_SUFFIX))
+      self.create_file('%s%s' % (str(i), GYPI_SUFFIX))
+      self.create_file('%s%s' % (str(i), GYP_SUFFIX))
+      self.create_file('%s%s' % (str(i), OTHER_SUFFIX))
+
+  def create_file(self, basename):
+    """Create a file named 'basename' in self.__tmp_dir.
+    """
+    f = tempfile.mkstemp(dir=self.__tmp_dir)
+    os.rename(f[1], os.path.join(self.__tmp_dir, basename))
+    self.assert_file_exists(basename)
+
+  def assert_file_exists(self, basename):
+    """Assert that 'basename' exists in self.__tmp_dir.
+    """
+    full_name = os.path.join(self.__tmp_dir, basename)
+    self.assertTrue(os.path.exists(full_name))
+
+  def assert_file_does_not_exist(self, basename):
+    """Assert that 'basename' does not exist in self.__tmp_dir.
+    """
+    full_name = os.path.join(self.__tmp_dir, basename)
+    self.assertFalse(os.path.exists(full_name))
+
+  def test_clean(self):
+    """Test that clean_gypd_files() deletes .gypd files, and leaves others.
+    """
+    android_framework_gyp.clean_gypd_files(self.__tmp_dir)
+    for i in range(self.__num_files):
+      self.assert_file_exists('%s%s' % (str(i), GYPI_SUFFIX))
+      self.assert_file_exists('%s%s' % (str(i), GYP_SUFFIX))
+      self.assert_file_exists('%s%s' % (str(i), OTHER_SUFFIX))
+      # Only the GYPD files should have been deleted.
+      self.assert_file_does_not_exist('%s%s' % (str(i), GYPD_SUFFIX))
+
+  def tearDown(self):
+    shutil.rmtree(self.__tmp_dir)
+
+
+def main():
+  loader = unittest.TestLoader()
+  suite = loader.loadTestsFromTestCase(CleanGypdTest)
+  unittest.TextTestRunner(verbosity=2).run(suite)
+
+if __name__ == "__main__":
+  main()
index 9fab0ac..bba6824 100644 (file)
@@ -73,6 +73,12 @@ LOCAL_EXPORT_C_INCLUDE_DIRS := \
 LOCAL_CFLAGS += \
        -Ddefines
 
+LOCAL_MODULE_TAGS := \
+       local_module_tags
+
+LOCAL_MODULE := \
+       local_module
+
 ifeq ($(COND), true)
 LOCAL_CFLAGS_foo += \
        local_cflags_foo
@@ -98,6 +104,12 @@ LOCAL_EXPORT_C_INCLUDE_DIRS_foo += \
 LOCAL_CFLAGS_foo += \
        -Ddefines_foo
 
+LOCAL_MODULE_TAGS_foo += \
+       local_module_tags_foo
+
+LOCAL_MODULE_foo += \
+       local_module_foo
+
 endif
 
 LOCAL_CFLAGS_bar += \
@@ -124,8 +136,13 @@ LOCAL_EXPORT_C_INCLUDE_DIRS_bar += \
 LOCAL_CFLAGS_bar += \
        -Ddefines_bar
 
+LOCAL_MODULE_TAGS_bar += \
+       local_module_tags_bar
+
+LOCAL_MODULE_bar += \
+       local_module_bar
+
 include external/stlport/libstlport.mk
-LOCAL_MODULE:= libskia
 include $(BUILD_SHARED_LIBRARY)
 
 #############################################################
@@ -139,7 +156,4 @@ include $(BUILD_SHARED_LIBRARY)
 #include $(BASE_PATH)/gm/Android.mk
 
 # unit-tests
-#include $(BASE_PATH)/tests/Android.mk
-
-# pathOps unit-tests
-# TODO include those sources!
+include $(BASE_PATH)/tests/Android.mk
diff --git a/platform_tools/android/tests/expectations/tool/Android.mk b/platform_tools/android/tests/expectations/tool/Android.mk
new file mode 100644 (file)
index 0000000..faac45d
--- /dev/null
@@ -0,0 +1,41 @@
+
+###############################################################################
+#
+# THIS FILE IS AUTOGENERATED BY GYP_TO_ANDROID.PY. DO NOT EDIT.
+#
+###############################################################################
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+LOCAL_CFLAGS += \
+       local_cflags
+
+LOCAL_CPPFLAGS := \
+       local_cppflags
+
+LOCAL_SRC_FILES := \
+       local_src_files
+
+LOCAL_SHARED_LIBRARIES := \
+       local_shared_libraries
+
+LOCAL_STATIC_LIBRARIES := \
+       local_static_libraries
+
+LOCAL_C_INCLUDES := \
+       local_c_includes
+
+LOCAL_EXPORT_C_INCLUDE_DIRS := \
+       local_export_c_include_dirs
+
+LOCAL_CFLAGS += \
+       -Ddefines
+
+LOCAL_MODULE_TAGS := \
+       local_module_tags
+
+LOCAL_MODULE := \
+       local_module
+
+include external/stlport/libstlport.mk
+include $(BUILD_EXECUTABLE)
index 3e5f180..60c2bce 100644 (file)
@@ -22,3 +22,9 @@ LOCAL_EXPORT_C_INCLUDE_DIRS_arm += \
 LOCAL_CFLAGS_arm += \
        -Ddefines
 
+LOCAL_MODULE_TAGS_arm += \
+       local_module_tags
+
+LOCAL_MODULE_arm += \
+       local_module
+
index 66c4f91..eef5a6e 100644 (file)
@@ -22,3 +22,9 @@ LOCAL_EXPORT_C_INCLUDE_DIRS_foo += \
 LOCAL_CFLAGS_foo += \
        -Ddefines
 
+LOCAL_MODULE_TAGS_foo += \
+       local_module_tags
+
+LOCAL_MODULE_foo += \
+       local_module
+
index e0b31c5..e51ede9 100644 (file)
@@ -22,3 +22,9 @@ LOCAL_EXPORT_C_INCLUDE_DIRS += \
 LOCAL_CFLAGS += \
        -Ddefines
 
+LOCAL_MODULE_TAGS += \
+       local_module_tags
+
+LOCAL_MODULE += \
+       local_module
+
index c77a9bb..79a5d09 100644 (file)
@@ -22,3 +22,9 @@ LOCAL_EXPORT_C_INCLUDE_DIRS_arm := \
 LOCAL_CFLAGS_arm += \
        -Ddefines
 
+LOCAL_MODULE_TAGS_arm := \
+       local_module_tags
+
+LOCAL_MODULE_arm := \
+       local_module
+
index d93b242..cef7b8d 100644 (file)
@@ -22,3 +22,9 @@ LOCAL_EXPORT_C_INCLUDE_DIRS_foo := \
 LOCAL_CFLAGS_foo += \
        -Ddefines
 
+LOCAL_MODULE_TAGS_foo := \
+       local_module_tags
+
+LOCAL_MODULE_foo := \
+       local_module
+
index e7caec4..59cd2e0 100644 (file)
@@ -22,3 +22,9 @@ LOCAL_EXPORT_C_INCLUDE_DIRS := \
 LOCAL_CFLAGS += \
        -Ddefines
 
+LOCAL_MODULE_TAGS := \
+       local_module_tags
+
+LOCAL_MODULE := \
+       local_module
+
index cb2a3e0..512ce36 100644 (file)
@@ -21,6 +21,8 @@ sys.path.append(test_variables.BIN_DIR)
 
 import gyp_to_android
 
+
+
 class AndroidMkCreationTest(unittest.TestCase):
 
   def setUp(self):
@@ -31,74 +33,23 @@ class AndroidMkCreationTest(unittest.TestCase):
     gyp_to_android.main(self.__tmp_dir)
 
     # Now there should be a file named 'Android.mk' inside __tmp_dir
-    path_to_android_mk = os.path.join(self.__tmp_dir, 'Android.mk')
+    path_to_android_mk = os.path.join(self.__tmp_dir,
+                                      test_variables.ANDROID_MK)
     self.assertTrue(os.path.exists(path_to_android_mk))
 
-  def tearDown(self):
-    # Remove self.__tmp_dir, which is no longer needed.
-    shutil.rmtree(self.__tmp_dir)
-
-
-GYPD_SUFFIX = ".gypd"
-GYP_SUFFIX = ".gyp"
-GYPI_SUFFIX = ".gypi"
-OTHER_SUFFIX = ".txt"
-
-class CleanGypdTest(unittest.TestCase):
-
-  def setUp(self):
-    self.__tmp_dir = tempfile.mkdtemp()
-    self.__num_files = 10
-    # Fill the dir with four types of files. .gypd files should be deleted by
-    # clean_gypd_files(), while the rest should be left alone.
-    for i in range(self.__num_files):
-      self.create_file('%s%s' % (str(i), GYPD_SUFFIX))
-      self.create_file('%s%s' % (str(i), GYPI_SUFFIX))
-      self.create_file('%s%s' % (str(i), GYP_SUFFIX))
-      self.create_file('%s%s' % (str(i), OTHER_SUFFIX))
-
-  def create_file(self, basename):
-    """
-    Create a file named 'basename' in self.__tmp_dir.
-    """
-    f = tempfile.mkstemp(dir=self.__tmp_dir)
-    os.rename(f[1], os.path.join(self.__tmp_dir, basename))
-    self.assert_file_exists(basename)
-
-  def assert_file_exists(self, basename):
-    """
-    Assert that 'basename' exists in self.__tmp_dir.
-    """
-    full_name = os.path.join(self.__tmp_dir, basename)
-    self.assertTrue(os.path.exists(full_name))
-
-  def assert_file_does_not_exist(self, basename):
-    """
-    Assert that 'basename' does not exist in self.__tmp_dir.
-    """
-    full_name = os.path.join(self.__tmp_dir, basename)
-    self.assertFalse(os.path.exists(full_name))
-
-  def test_clean(self):
-    """
-    Test that clean_gypd_files() deletes .gypd files, and leaves others.
-    """
-    gyp_to_android.clean_gypd_files(self.__tmp_dir)
-    for i in range(self.__num_files):
-      self.assert_file_exists('%s%s' % (str(i), GYPI_SUFFIX))
-      self.assert_file_exists('%s%s' % (str(i), GYP_SUFFIX))
-      self.assert_file_exists('%s%s' % (str(i), OTHER_SUFFIX))
-      # Only the GYPD files should have been deleted.
-      self.assert_file_does_not_exist('%s%s' % (str(i), GYPD_SUFFIX))
+    # In addition, there should be an 'Android.mk' inside /tests/
+    path_to_tests_android_mk = os.path.join(self.__tmp_dir, 'tests',
+                                            test_variables.ANDROID_MK)
+    self.assertTrue(os.path.exists(path_to_tests_android_mk))
 
   def tearDown(self):
+    # Remove self.__tmp_dir, which is no longer needed.
     shutil.rmtree(self.__tmp_dir)
 
 
 def main():
   loader = unittest.TestLoader()
   suite = loader.loadTestsFromTestCase(AndroidMkCreationTest)
-  suite.addTest(loader.loadTestsFromTestCase(CleanGypdTest))
   unittest.TextTestRunner(verbosity=2).run(suite)
 
 if __name__ == "__main__":
index 1b42bf3..6b72657 100644 (file)
@@ -21,11 +21,13 @@ import utils
 sys.path.append(test_variables.GYP_GEN_DIR)
 
 import makefile_writer
+import tool_makefile_writer
 import vars_dict_lib
 
-MAKEFILE_NAME = 'Android.mk'
+MAKEFILE_NAME = test_variables.ANDROID_MK
 REBASELINE_MSG = ('If you\'ve modified makefile_writer.py, run '
                   '"makefile_writer_tests.py --rebaseline" to rebaseline')
+TOOL_DIR = 'tool'
 
 def generate_dummy_vars_dict(name):
   """Create a VarsDict and fill it with dummy entries.
@@ -109,6 +111,16 @@ def generate_dummy_makefile(target_dir):
                                    common=common_vars_dict,
                                    deviations_from_common=deviations)
 
+def generate_dummy_tool_makefile(target_dir):
+  """Create a dummy makefile for a tool.
+
+  Args:
+      target_dir: directory in which to write the resulting Android.mk
+  """
+  vars_dict = generate_dummy_vars_dict(None)
+  tool_makefile_writer.write_tool_android_mk(target_dir=target_dir,
+                                             var_dict=vars_dict)
+
 
 class MakefileWriterTest(unittest.TestCase):
 
@@ -169,6 +181,15 @@ class MakefileWriterTest(unittest.TestCase):
 
     shutil.rmtree(outdir)
 
+  def test_tool_writer(self):
+    outdir = tempfile.mkdtemp()
+    tool_dir = os.path.join(outdir, TOOL_DIR)
+    os.mkdir(tool_dir)
+    generate_dummy_tool_makefile(tool_dir)
+
+    utils.compare_to_expectation(os.path.join(tool_dir, MAKEFILE_NAME),
+                                 os.path.join(TOOL_DIR, MAKEFILE_NAME),
+                                 self.assertTrue, REBASELINE_MSG)
 
 def main():
   loader = unittest.TestLoader()
@@ -187,6 +208,8 @@ def rebaseline():
     with open(os.path.join(utils.EXPECTATIONS_DIR, filename), 'w') as f:
       makefile_writer.write_local_vars(f, vars_dict, append, name)
 
+  generate_dummy_tool_makefile(os.path.join(utils.EXPECTATIONS_DIR, TOOL_DIR))
+
 
 if __name__ == '__main__':
   parser = argparse.ArgumentParser()
index 01016d9..5ec4597 100644 (file)
@@ -28,8 +28,7 @@ class OrderedSetTest(unittest.TestCase):
     self.__set = OrderedSet()
 
   def test_methods(self):
-    """
-    Test methods on OrderedSet.
+    """Test methods on OrderedSet.
     """
     RANGE = 10
     for i in range(RANGE):
@@ -67,6 +66,38 @@ class OrderedSetTest(unittest.TestCase):
       self.__set.reset()
       self.assertEqual(len(self.__set), 0)
 
+  def test_set(self):
+    """Test OrderedSet.set().
+    """
+    # Create a set with dummy values.
+    my_set = OrderedSet()
+    RANGE = 10
+    for i in range(RANGE):
+      my_set.add(create_dummy_var(i))
+    my_len = len(my_set)
+    self.assertEqual(my_len, RANGE)
+
+    # Copy it to another set.
+    other_set = OrderedSet()
+    self.assertEqual(len(other_set), 0)
+    other_set.set(my_set)
+
+    # Both sets should contain the same values, in the same order.
+    iterator = iter(my_set)
+    for item in other_set:
+      self.assertTrue(item == iterator.next())
+    with self.assertRaises(StopIteration):
+      iterator.next()
+    self.assertEqual(my_len, len(other_set))
+
+    # But the sets are different. Changing one will not affect the other.
+    self.assertFalse(other_set is my_set)
+    other_var = 'something_else'
+    other_set.add(other_var)
+    self.assertEqual(my_len + 1, len(other_set))
+    self.assertEqual(my_len, len(my_set))
+    self.assertNotIn(other_var, my_set)
+
 
 def main():
   loader = unittest.TestLoader()
index dd49a7e..cc0bf1c 100644 (file)
@@ -20,3 +20,5 @@ BIN_DIR = os.path.join(ANDROID_DIR, 'bin')
 
 # Path to generator files.
 GYP_GEN_DIR = os.path.join(ANDROID_DIR, 'gyp_gen')
+
+ANDROID_MK = 'Android.mk'