| # Copyright (c) 2012 Google Inc. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| # Notes: |
| # |
| # This generates makefiles suitable for inclusion into the Android build system |
| # via an Android.mk file. It is based on make.py, the standard makefile |
| # generator. |
| # |
| # The code below generates a separate .mk file for each target, but |
| # all are sourced by the top-level GypAndroid.mk. This means that all |
| # variables in .mk-files clobber one another, and furthermore that any |
| # variables set potentially clash with other Android build system variables. |
| # Try to avoid setting global variables where possible. |
| |
| import gyp |
| import gyp.common |
| import gyp.generator.make as make # Reuse global functions from make backend. |
| import os |
| import re |
| |
| generator_default_variables = { |
| 'OS': 'android', |
| 'EXECUTABLE_PREFIX': '', |
| 'EXECUTABLE_SUFFIX': '', |
| 'STATIC_LIB_PREFIX': 'lib', |
| 'SHARED_LIB_PREFIX': 'lib', |
| 'STATIC_LIB_SUFFIX': '.a', |
| 'SHARED_LIB_SUFFIX': '.so', |
| 'INTERMEDIATE_DIR': '$(gyp_intermediate_dir)', |
| 'SHARED_INTERMEDIATE_DIR': '$(gyp_shared_intermediate_dir)', |
| 'PRODUCT_DIR': '$(gyp_shared_intermediate_dir)', |
| 'SHARED_LIB_DIR': '$(builddir)/lib.$(TOOLSET)', |
| 'LIB_DIR': '$(obj).$(TOOLSET)', |
| 'RULE_INPUT_ROOT': '%(INPUT_ROOT)s', # This gets expanded by Python. |
| 'RULE_INPUT_DIRNAME': '%(INPUT_DIRNAME)s', # This gets expanded by Python. |
| 'RULE_INPUT_PATH': '$(RULE_SOURCES)', |
| 'RULE_INPUT_EXT': '$(suffix $<)', |
| 'RULE_INPUT_NAME': '$(notdir $<)', |
| 'CONFIGURATION_NAME': 'NOT_USED_ON_ANDROID', |
| } |
| |
| # Make supports multiple toolsets |
| generator_supports_multiple_toolsets = True |
| |
| |
| # Generator-specific gyp specs. |
| generator_additional_non_configuration_keys = [ |
| # Boolean to declare that this target does not want its name mangled. |
| 'android_unmangled_name', |
| ] |
| generator_additional_path_sections = [] |
| generator_extra_sources_for_rules = [] |
| |
| |
| SHARED_FOOTER = """\ |
| # "gyp_all_modules" is a concatenation of the "gyp_all_modules" targets from |
| # all the included sub-makefiles. This is just here to clarify. |
| gyp_all_modules: |
| """ |
| |
| header = """\ |
| # This file is generated by gyp; do not edit. |
| |
| """ |
| |
| android_standard_include_paths = set([ |
| # JNI_H_INCLUDE in build/core/binary.mk |
| 'dalvik/libnativehelper/include/nativehelper', |
| # from SRC_HEADERS in build/core/config.mk |
| 'system/core/include', |
| 'hardware/libhardware/include', |
| 'hardware/libhardware_legacy/include', |
| 'hardware/ril/include', |
| 'dalvik/libnativehelper/include', |
| 'frameworks/native/include', |
| 'frameworks/native/opengl/include', |
| 'frameworks/base/include', |
| 'frameworks/base/opengl/include', |
| 'frameworks/base/native/include', |
| 'external/skia/include', |
| # TARGET_C_INCLUDES in build/core/combo/TARGET_linux-arm.mk |
| 'bionic/libc/arch-arm/include', |
| 'bionic/libc/include', |
| 'bionic/libstdc++/include', |
| 'bionic/libc/kernel/common', |
| 'bionic/libc/kernel/arch-arm', |
| 'bionic/libm/include', |
| 'bionic/libm/include/arm', |
| 'bionic/libthread_db/include', |
| ]) |
| |
| |
| # Map gyp target types to Android module classes. |
| MODULE_CLASSES = { |
| 'static_library': 'STATIC_LIBRARIES', |
| 'shared_library': 'SHARED_LIBRARIES', |
| 'executable': 'EXECUTABLES', |
| } |
| |
| |
| def IsCPPExtension(ext): |
| return make.COMPILABLE_EXTENSIONS.get(ext) == 'cxx' |
| |
| |
| def Sourceify(path): |
| """Convert a path to its source directory form. The Android backend does not |
| support options.generator_output, so this function is a noop.""" |
| return path |
| |
| |
| # Map from qualified target to path to output. |
| # For Android, the target of these maps is a tuple ('static', 'modulename'), |
| # ('dynamic', 'modulename'), or ('path', 'some/path') instead of a string, |
| # since we link by module. |
| target_outputs = {} |
| # Map from qualified target to any linkable output. A subset |
| # of target_outputs. E.g. when mybinary depends on liba, we want to |
| # include liba in the linker line; when otherbinary depends on |
| # mybinary, we just want to build mybinary first. |
| target_link_deps = {} |
| |
| |
| class AndroidMkWriter(object): |
| """AndroidMkWriter packages up the writing of one target-specific Android.mk. |
| |
| Its only real entry point is Write(), and is mostly used for namespacing. |
| """ |
| |
| def __init__(self, android_top_dir): |
| self.android_top_dir = android_top_dir |
| |
| def Write(self, qualified_target, base_path, output_filename, spec, configs, |
| part_of_all): |
| """The main entry point: writes a .mk file for a single target. |
| |
| Arguments: |
| qualified_target: target we're generating |
| base_path: path relative to source root we're building in, used to resolve |
| target-relative paths |
| output_filename: output .mk file name to write |
| spec, configs: gyp info |
| part_of_all: flag indicating this target is part of 'all' |
| """ |
| make.ensure_directory_exists(output_filename) |
| |
| self.fp = open(output_filename, 'w') |
| |
| self.fp.write(header) |
| |
| self.qualified_target = qualified_target |
| self.path = base_path |
| self.target = spec['target_name'] |
| self.type = spec['type'] |
| self.toolset = spec['toolset'] |
| |
| deps, link_deps = self.ComputeDeps(spec) |
| |
| # Some of the generation below can add extra output, sources, or |
| # link dependencies. All of the out params of the functions that |
| # follow use names like extra_foo. |
| extra_outputs = [] |
| extra_sources = [] |
| |
| self.android_class = MODULE_CLASSES.get(self.type, 'GYP') |
| self.android_module = self.ComputeAndroidModule(spec) |
| (self.android_stem, self.android_suffix) = self.ComputeOutputParts(spec) |
| self.output = self.output_binary = self.ComputeOutput(spec) |
| |
| # Standard header. |
| self.WriteLn('include $(CLEAR_VARS)\n') |
| |
| # Module class and name. |
| self.WriteLn('LOCAL_MODULE_CLASS := ' + self.android_class) |
| self.WriteLn('LOCAL_MODULE := ' + self.android_module) |
| # Only emit LOCAL_MODULE_STEM if it's different to LOCAL_MODULE. |
| # The library module classes fail if the stem is set. ComputeOutputParts |
| # makes sure that stem == modulename in these cases. |
| if self.android_stem != self.android_module: |
| self.WriteLn('LOCAL_MODULE_STEM := ' + self.android_stem) |
| self.WriteLn('LOCAL_MODULE_SUFFIX := ' + self.android_suffix) |
| self.WriteLn('LOCAL_MODULE_TAGS := optional') |
| if self.toolset == 'host': |
| self.WriteLn('LOCAL_IS_HOST_MODULE := true') |
| |
| # Grab output directories; needed for Actions and Rules. |
| self.WriteLn('gyp_intermediate_dir := $(call local-intermediates-dir)') |
| self.WriteLn('gyp_shared_intermediate_dir := ' |
| '$(call intermediates-dir-for,GYP,shared)') |
| self.WriteLn() |
| |
| # List files this target depends on so that actions/rules/copies/sources |
| # can depend on the list. |
| # TODO: doesn't pull in things through transitive link deps; needed? |
| target_dependencies = [x[1] for x in deps if x[0] == 'path'] |
| self.WriteLn('# Make sure our deps are built first.') |
| self.WriteList(target_dependencies, 'GYP_TARGET_DEPENDENCIES', |
| local_pathify=True) |
| |
| # Actions must come first, since they can generate more OBJs for use below. |
| if 'actions' in spec: |
| self.WriteActions(spec['actions'], extra_sources, extra_outputs) |
| |
| # Rules must be early like actions. |
| if 'rules' in spec: |
| self.WriteRules(spec['rules'], extra_sources, extra_outputs) |
| |
| if 'copies' in spec: |
| self.WriteCopies(spec['copies'], extra_outputs) |
| |
| # GYP generated outputs. |
| self.WriteList(extra_outputs, 'GYP_GENERATED_OUTPUTS', local_pathify=True) |
| |
| # Set LOCAL_ADDITIONAL_DEPENDENCIES so that Android's build rules depend |
| # on both our dependency targets and our generated files. |
| self.WriteLn('# Make sure our deps and generated files are built first.') |
| self.WriteLn('LOCAL_ADDITIONAL_DEPENDENCIES := $(GYP_TARGET_DEPENDENCIES) ' |
| '$(GYP_GENERATED_OUTPUTS)') |
| self.WriteLn() |
| |
| # Sources. |
| if spec.get('sources', []) or extra_sources: |
| self.WriteSources(spec, configs, extra_sources) |
| |
| self.WriteTarget(spec, configs, deps, link_deps, part_of_all) |
| |
| # Update global list of target outputs, used in dependency tracking. |
| target_outputs[qualified_target] = ('path', self.output_binary) |
| |
| # Update global list of link dependencies. |
| if self.type == 'static_library': |
| target_link_deps[qualified_target] = ('static', self.android_module) |
| elif self.type == 'shared_library': |
| target_link_deps[qualified_target] = ('shared', self.android_module) |
| |
| self.fp.close() |
| return self.android_module |
| |
| |
| def WriteActions(self, actions, extra_sources, extra_outputs): |
| """Write Makefile code for any 'actions' from the gyp input. |
| |
| extra_sources: a list that will be filled in with newly generated source |
| files, if any |
| extra_outputs: a list that will be filled in with any outputs of these |
| actions (used to make other pieces dependent on these |
| actions) |
| """ |
| for action in actions: |
| name = make.StringToMakefileVariable('%s_%s' % (self.qualified_target, |
| action['action_name'])) |
| self.WriteLn('### Rules for action "%s":' % action['action_name']) |
| inputs = action['inputs'] |
| outputs = action['outputs'] |
| |
| # Build up a list of outputs. |
| # Collect the output dirs we'll need. |
| dirs = set() |
| for out in outputs: |
| if not out.startswith('$'): |
| print ('WARNING: Action for target "%s" writes output to local path ' |
| '"%s".' % (self.target, out)) |
| dir = os.path.split(out)[0] |
| if dir: |
| dirs.add(dir) |
| if int(action.get('process_outputs_as_sources', False)): |
| extra_sources += outputs |
| |
| # Prepare the actual command. |
| command = gyp.common.EncodePOSIXShellList(action['action']) |
| if 'message' in action: |
| quiet_cmd = 'Gyp action: %s ($@)' % action['message'] |
| else: |
| quiet_cmd = 'Gyp action: %s ($@)' % name |
| if len(dirs) > 0: |
| command = 'mkdir -p %s' % ' '.join(dirs) + '; ' + command |
| |
| cd_action = 'cd $(gyp_local_path)/%s; ' % self.path |
| command = cd_action + command |
| |
| # The makefile rules are all relative to the top dir, but the gyp actions |
| # are defined relative to their containing dir. This replaces the gyp_* |
| # variables for the action rule with an absolute version so that the |
| # output goes in the right place. |
| # Only write the gyp_* rules for the "primary" output (:1); |
| # it's superfluous for the "extra outputs", and this avoids accidentally |
| # writing duplicate dummy rules for those outputs. |
| main_output = make.QuoteSpaces(self.LocalPathify(outputs[0])) |
| self.WriteLn('%s: gyp_local_path := $(LOCAL_PATH)' % main_output) |
| self.WriteLn('%s: gyp_intermediate_dir := ' |
| '$(GYP_ABS_ANDROID_TOP_DIR)/$(gyp_intermediate_dir)' % |
| main_output) |
| self.WriteLn('%s: gyp_shared_intermediate_dir := ' |
| '$(GYP_ABS_ANDROID_TOP_DIR)/$(gyp_shared_intermediate_dir)' % |
| main_output) |
| |
| for input in inputs: |
| assert ' ' not in input, ( |
| "Spaces in action input filenames not supported (%s)" % input) |
| for output in outputs: |
| assert ' ' not in output, ( |
| "Spaces in action output filenames not supported (%s)" % output) |
| |
| self.WriteLn('%s: %s $(GYP_TARGET_DEPENDENCIES)' % |
| (main_output, ' '.join(map(self.LocalPathify, inputs)))) |
| self.WriteLn('\t@echo "%s"' % quiet_cmd) |
| self.WriteLn('\t$(hide)%s\n' % command) |
| for output in outputs[1:]: |
| # Make each output depend on the main output, with an empty command |
| # to force make to notice that the mtime has changed. |
| self.WriteLn('%s: %s ;' % (self.LocalPathify(output), main_output)) |
| |
| extra_outputs += outputs |
| self.WriteLn() |
| |
| self.WriteLn() |
| |
| |
| def WriteRules(self, rules, extra_sources, extra_outputs): |
| """Write Makefile code for any 'rules' from the gyp input. |
| |
| extra_sources: a list that will be filled in with newly generated source |
| files, if any |
| extra_outputs: a list that will be filled in with any outputs of these |
| rules (used to make other pieces dependent on these rules) |
| """ |
| if len(rules) == 0: |
| return |
| rule_trigger = '%s_rule_trigger' % self.android_module |
| |
| did_write_rule = False |
| for rule in rules: |
| if len(rule.get('rule_sources', [])) == 0: |
| continue |
| did_write_rule = True |
| name = make.StringToMakefileVariable('%s_%s' % (self.qualified_target, |
| rule['rule_name'])) |
| self.WriteLn('\n### Generated for rule "%s":' % name) |
| self.WriteLn('# "%s":' % rule) |
| |
| inputs = rule.get('inputs') |
| for rule_source in rule.get('rule_sources', []): |
| (rule_source_dirname, rule_source_basename) = os.path.split(rule_source) |
| (rule_source_root, rule_source_ext) = \ |
| os.path.splitext(rule_source_basename) |
| |
| outputs = [self.ExpandInputRoot(out, rule_source_root, |
| rule_source_dirname) |
| for out in rule['outputs']] |
| |
| dirs = set() |
| for out in outputs: |
| if not out.startswith('$'): |
| print ('WARNING: Rule for target %s writes output to local path %s' |
| % (self.target, out)) |
| dir = os.path.dirname(out) |
| if dir: |
| dirs.add(dir) |
| extra_outputs += outputs |
| if int(rule.get('process_outputs_as_sources', False)): |
| extra_sources.extend(outputs) |
| |
| components = [] |
| for component in rule['action']: |
| component = self.ExpandInputRoot(component, rule_source_root, |
| rule_source_dirname) |
| if '$(RULE_SOURCES)' in component: |
| component = component.replace('$(RULE_SOURCES)', |
| rule_source) |
| components.append(component) |
| |
| command = gyp.common.EncodePOSIXShellList(components) |
| cd_action = 'cd $(gyp_local_path)/%s; ' % self.path |
| command = cd_action + command |
| if dirs: |
| command = 'mkdir -p %s' % ' '.join(dirs) + '; ' + command |
| |
| # We set up a rule to build the first output, and then set up |
| # a rule for each additional output to depend on the first. |
| outputs = map(self.LocalPathify, outputs) |
| main_output = outputs[0] |
| self.WriteLn('%s: gyp_local_path := $(LOCAL_PATH)' % main_output) |
| self.WriteLn('%s: gyp_intermediate_dir := ' |
| '$(GYP_ABS_ANDROID_TOP_DIR)/$(gyp_intermediate_dir)' |
| % main_output) |
| self.WriteLn('%s: gyp_shared_intermediate_dir := ' |
| '$(GYP_ABS_ANDROID_TOP_DIR)/$(gyp_shared_intermediate_dir)' |
| % main_output) |
| |
| main_output_deps = self.LocalPathify(rule_source) |
| if inputs: |
| main_output_deps += ' ' |
| main_output_deps += ' '.join([self.LocalPathify(f) for f in inputs]) |
| |
| self.WriteLn('%s: %s $(GYP_TARGET_DEPENDENCIES)' % |
| (main_output, main_output_deps)) |
| self.WriteLn('\t%s\n' % command) |
| for output in outputs[1:]: |
| self.WriteLn('%s: %s' % (output, main_output)) |
| self.WriteLn('.PHONY: %s' % (rule_trigger)) |
| self.WriteLn('%s: %s' % (rule_trigger, main_output)) |
| self.WriteLn('') |
| if did_write_rule: |
| extra_sources.append(rule_trigger) # Force all rules to run. |
| self.WriteLn('### Finished generating for all rules') |
| self.WriteLn('') |
| |
| |
| def WriteCopies(self, copies, extra_outputs): |
| """Write Makefile code for any 'copies' from the gyp input. |
| |
| extra_outputs: a list that will be filled in with any outputs of this action |
| (used to make other pieces dependent on this action) |
| """ |
| self.WriteLn('### Generated for copy rule.') |
| |
| variable = make.StringToMakefileVariable(self.qualified_target + '_copies') |
| outputs = [] |
| for copy in copies: |
| for path in copy['files']: |
| # The Android build system does not allow generation of files into the |
| # source tree. The destination should start with a variable, which will |
| # typically be $(gyp_intermediate_dir) or |
| # $(gyp_shared_intermediate_dir). Note that we can't use an assertion |
| # because some of the gyp tests depend on this. |
| if not copy['destination'].startswith('$'): |
| print ('WARNING: Copy rule for target %s writes output to ' |
| 'local path %s' % (self.target, copy['destination'])) |
| |
| # LocalPathify() calls normpath, stripping trailing slashes. |
| path = Sourceify(self.LocalPathify(path)) |
| filename = os.path.split(path)[1] |
| output = Sourceify(self.LocalPathify(os.path.join(copy['destination'], |
| filename))) |
| |
| self.WriteLn('%s: %s $(GYP_TARGET_DEPENDENCIES) | $(ACP)' % |
| (output, path)) |
| self.WriteLn('\t@echo Copying: $@') |
| self.WriteLn('\t$(hide) mkdir -p $(dir $@)') |
| self.WriteLn('\t$(hide) $(ACP) -r $< $@') |
| self.WriteLn() |
| outputs.append(output) |
| self.WriteLn('%s = %s' % (variable, |
| ' '.join(map(make.QuoteSpaces, outputs)))) |
| extra_outputs.append('$(%s)' % variable) |
| self.WriteLn() |
| |
| |
| def WriteSourceFlags(self, spec, configs): |
| """Write out the flags and include paths used to compile source files for |
| the current target. |
| |
| Args: |
| spec, configs: input from gyp. |
| """ |
| config = configs[spec['default_configuration']] |
| extracted_includes = [] |
| |
| self.WriteLn('\n# Flags passed to both C and C++ files.') |
| cflags, includes_from_cflags = self.ExtractIncludesFromCFlags( |
| config.get('cflags')) |
| extracted_includes.extend(includes_from_cflags) |
| self.WriteList(cflags, 'MY_CFLAGS') |
| |
| cflags_c, includes_from_cflags_c = self.ExtractIncludesFromCFlags( |
| config.get('cflags_c')) |
| extracted_includes.extend(includes_from_cflags_c) |
| self.WriteList(cflags_c, 'MY_CFLAGS_C') |
| |
| self.WriteList(config.get('defines'), 'MY_DEFS', prefix='-D', |
| quoter=make.EscapeCppDefine) |
| self.WriteLn('LOCAL_CFLAGS := $(MY_CFLAGS_C) $(MY_CFLAGS) $(MY_DEFS)') |
| |
| # Undefine ANDROID for host modules |
| # TODO: the source code should not use macro ANDROID to tell if it's host or |
| # target module. |
| if self.toolset == 'host': |
| self.WriteLn('# Undefine ANDROID for host modules') |
| self.WriteLn('LOCAL_CFLAGS += -UANDROID') |
| |
| self.WriteLn('\n# Include paths placed before CFLAGS/CPPFLAGS') |
| includes = list(config.get('include_dirs', [])) |
| includes.extend(extracted_includes) |
| includes = map(Sourceify, map(self.LocalPathify, includes)) |
| includes = self.NormalizeIncludePaths(includes) |
| self.WriteList(includes, 'LOCAL_C_INCLUDES') |
| self.WriteLn('LOCAL_C_INCLUDES := $(GYP_COPIED_SOURCE_ORIGIN_DIRS) ' |
| '$(LOCAL_C_INCLUDES)') |
| |
| self.WriteLn('\n# Flags passed to only C++ (and not C) files.') |
| self.WriteList(config.get('cflags_cc'), 'LOCAL_CPPFLAGS') |
| |
| |
| def WriteSources(self, spec, configs, extra_sources): |
| """Write Makefile code for any 'sources' from the gyp input. |
| These are source files necessary to build the current target. |
| We need to handle shared_intermediate directory source files as |
| a special case by copying them to the intermediate directory and |
| treating them as a genereated sources. Otherwise the Android build |
| rules won't pick them up. |
| |
| Args: |
| spec, configs: input from gyp. |
| extra_sources: Sources generated from Actions or Rules. |
| """ |
| sources = filter(make.Compilable, spec.get('sources', [])) |
| generated_not_sources = [x for x in extra_sources if not make.Compilable(x)] |
| extra_sources = filter(make.Compilable, extra_sources) |
| |
| # Determine and output the C++ extension used by these sources. |
| # We simply find the first C++ file and use that extension. |
| all_sources = sources + extra_sources |
| local_cpp_extension = '.cpp' |
| for source in all_sources: |
| (root, ext) = os.path.splitext(source) |
| if IsCPPExtension(ext): |
| local_cpp_extension = ext |
| break |
| if local_cpp_extension != '.cpp': |
| self.WriteLn('LOCAL_CPP_EXTENSION := %s' % local_cpp_extension) |
| |
| # We need to move any non-generated sources that are coming from the |
| # shared intermediate directory out of LOCAL_SRC_FILES and put them |
| # into LOCAL_GENERATED_SOURCES. We also need to move over any C++ files |
| # that don't match our local_cpp_extension, since Android will only |
| # generate Makefile rules for a single LOCAL_CPP_EXTENSION. |
| local_files = [] |
| for source in sources: |
| (root, ext) = os.path.splitext(source) |
| if '$(gyp_shared_intermediate_dir)' in source: |
| extra_sources.append(source) |
| elif '$(gyp_intermediate_dir)' in source: |
| extra_sources.append(source) |
| elif IsCPPExtension(ext) and ext != local_cpp_extension: |
| extra_sources.append(source) |
| else: |
| local_files.append(os.path.normpath(os.path.join(self.path, source))) |
| |
| # For any generated source, if it is coming from the shared intermediate |
| # directory then we add a Make rule to copy them to the local intermediate |
| # directory first. This is because the Android LOCAL_GENERATED_SOURCES |
| # must be in the local module intermediate directory for the compile rules |
| # to work properly. If the file has the wrong C++ extension, then we add |
| # a rule to copy that to intermediates and use the new version. |
| final_generated_sources = [] |
| # If a source file gets copied, we still need to add the orginal source |
| # directory as header search path, for GCC searches headers in the |
| # directory that contains the source file by default. |
| origin_src_dirs = [] |
| for source in extra_sources: |
| local_file = source |
| if not '$(gyp_intermediate_dir)/' in local_file: |
| basename = os.path.basename(local_file) |
| local_file = '$(gyp_intermediate_dir)/' + basename |
| (root, ext) = os.path.splitext(local_file) |
| if IsCPPExtension(ext) and ext != local_cpp_extension: |
| local_file = root + local_cpp_extension |
| if local_file != source: |
| self.WriteLn('%s: %s' % (local_file, self.LocalPathify(source))) |
| self.WriteLn('\tmkdir -p $(@D); cp $< $@') |
| origin_src_dirs.append(os.path.dirname(source)) |
| final_generated_sources.append(local_file) |
| |
| # We add back in all of the non-compilable stuff to make sure that the |
| # make rules have dependencies on them. |
| final_generated_sources.extend(generated_not_sources) |
| self.WriteList(final_generated_sources, 'LOCAL_GENERATED_SOURCES') |
| |
| origin_src_dirs = gyp.common.uniquer(origin_src_dirs) |
| origin_src_dirs = map(Sourceify, map(self.LocalPathify, origin_src_dirs)) |
| self.WriteList(origin_src_dirs, 'GYP_COPIED_SOURCE_ORIGIN_DIRS') |
| |
| self.WriteList(local_files, 'LOCAL_SRC_FILES') |
| |
| # Write out the flags used to compile the source; this must be done last |
| # so that GYP_COPIED_SOURCE_ORIGIN_DIRS can be used as an include path. |
| self.WriteSourceFlags(spec, configs) |
| |
| |
| def ComputeAndroidModule(self, spec): |
| """Return the Android module name used for a gyp spec. |
| |
| We use the complete qualified target name to avoid collisions between |
| duplicate targets in different directories. We also add a suffix to |
| distinguish gyp-generated module names. |
| """ |
| |
| if int(spec.get('android_unmangled_name', 0)): |
| assert self.type != 'shared_library' or self.target.startswith('lib') |
| return self.target |
| |
| if self.type == 'shared_library': |
| # For reasons of convention, the Android build system requires that all |
| # shared library modules are named 'libfoo' when generating -l flags. |
| prefix = 'lib_' |
| else: |
| prefix = '' |
| |
| if spec['toolset'] == 'host': |
| suffix = '_host_gyp' |
| else: |
| suffix = '_gyp' |
| |
| if self.path: |
| name = '%s%s_%s%s' % (prefix, self.path, self.target, suffix) |
| else: |
| name = '%s%s%s' % (prefix, self.target, suffix) |
| |
| return make.StringToMakefileVariable(name) |
| |
| |
| def ComputeOutputParts(self, spec): |
| """Return the 'output basename' of a gyp spec, split into filename + ext. |
| |
| Android libraries must be named the same thing as their module name, |
| otherwise the linker can't find them, so product_name and so on must be |
| ignored if we are building a library, and the "lib" prepending is |
| not done for Android. |
| """ |
| assert self.type != 'loadable_module' # TODO: not supported? |
| |
| target = spec['target_name'] |
| target_prefix = '' |
| target_ext = '' |
| if self.type == 'static_library': |
| target = self.ComputeAndroidModule(spec) |
| target_ext = '.a' |
| elif self.type == 'shared_library': |
| target = self.ComputeAndroidModule(spec) |
| target_ext = '.so' |
| elif self.type == 'none': |
| target_ext = '.stamp' |
| elif self.type != 'executable': |
| print ("ERROR: What output file should be generated?", |
| "type", self.type, "target", target) |
| |
| if self.type != 'static_library' and self.type != 'shared_library': |
| target_prefix = spec.get('product_prefix', target_prefix) |
| target = spec.get('product_name', target) |
| product_ext = spec.get('product_extension') |
| if product_ext: |
| target_ext = '.' + product_ext |
| |
| target_stem = target_prefix + target |
| return (target_stem, target_ext) |
| |
| |
| def ComputeOutputBasename(self, spec): |
| """Return the 'output basename' of a gyp spec. |
| |
| E.g., the loadable module 'foobar' in directory 'baz' will produce |
| 'libfoobar.so' |
| """ |
| return ''.join(self.ComputeOutputParts(spec)) |
| |
| |
| def ComputeOutput(self, spec): |
| """Return the 'output' (full output path) of a gyp spec. |
| |
| E.g., the loadable module 'foobar' in directory 'baz' will produce |
| '$(obj)/baz/libfoobar.so' |
| """ |
| if self.type == 'executable' and self.toolset == 'host': |
| # We install host executables into shared_intermediate_dir so they can be |
| # run by gyp rules that refer to PRODUCT_DIR. |
| path = '$(gyp_shared_intermediate_dir)' |
| elif self.type == 'shared_library': |
| if self.toolset == 'host': |
| path = '$(HOST_OUT_INTERMEDIATE_LIBRARIES)' |
| else: |
| path = '$(TARGET_OUT_INTERMEDIATE_LIBRARIES)' |
| else: |
| # Other targets just get built into their intermediate dir. |
| if self.toolset == 'host': |
| path = '$(call intermediates-dir-for,%s,%s,true)' % (self.android_class, |
| self.android_module) |
| else: |
| path = '$(call intermediates-dir-for,%s,%s)' % (self.android_class, |
| self.android_module) |
| |
| assert spec.get('product_dir') is None # TODO: not supported? |
| return os.path.join(path, self.ComputeOutputBasename(spec)) |
| |
| |
| def NormalizeLdFlags(self, ld_flags): |
| """ Clean up ldflags from gyp file. |
| Remove any ldflags that contain android_top_dir. |
| |
| Args: |
| ld_flags: ldflags from gyp files. |
| |
| Returns: |
| clean ldflags |
| """ |
| clean_ldflags = [] |
| for flag in ld_flags: |
| if self.android_top_dir in flag: |
| continue |
| clean_ldflags.append(flag) |
| return clean_ldflags |
| |
| def NormalizeIncludePaths(self, include_paths): |
| """ Normalize include_paths. |
| Convert absolute paths to relative to the Android top directory; |
| filter out include paths that are already brought in by the Android build |
| system. |
| |
| Args: |
| include_paths: A list of unprocessed include paths. |
| Returns: |
| A list of normalized include paths. |
| """ |
| normalized = [] |
| for path in include_paths: |
| if path[0] == '/': |
| path = gyp.common.RelativePath(path, self.android_top_dir) |
| |
| # Filter out the Android standard search path. |
| if path not in android_standard_include_paths: |
| normalized.append(path) |
| return normalized |
| |
| def ExtractIncludesFromCFlags(self, cflags): |
| """Extract includes "-I..." out from cflags |
| |
| Args: |
| cflags: A list of compiler flags, which may be mixed with "-I.." |
| Returns: |
| A tuple of lists: (clean_clfags, include_paths). "-I.." is trimmed. |
| """ |
| clean_cflags = [] |
| include_paths = [] |
| if cflags: |
| for flag in cflags: |
| if flag.startswith('-I'): |
| include_paths.append(flag[2:]) |
| else: |
| clean_cflags.append(flag) |
| |
| return (clean_cflags, include_paths) |
| |
| def ComputeAndroidLibraryModuleNames(self, libraries): |
| """Compute the Android module names from libraries, ie spec.get('libraries') |
| |
| Args: |
| libraries: the value of spec.get('libraries') |
| Returns: |
| A tuple (static_lib_modules, dynamic_lib_modules) |
| """ |
| static_lib_modules = [] |
| dynamic_lib_modules = [] |
| for libs in libraries: |
| # Libs can have multiple words. |
| for lib in libs.split(): |
| # Filter the system libraries, which are added by default by the Android |
| # build system. |
| if (lib == '-lc' or lib == '-lstdc++' or lib == '-lm' or |
| lib.endswith('libgcc.a')): |
| continue |
| match = re.search(r'([^/]+)\.a$', lib) |
| if match: |
| static_lib_modules.append(match.group(1)) |
| continue |
| match = re.search(r'([^/]+)\.so$', lib) |
| if match: |
| dynamic_lib_modules.append(match.group(1)) |
| continue |
| # "-lstlport" -> libstlport |
| if lib.startswith('-l'): |
| if lib.endswith('_static'): |
| static_lib_modules.append('lib' + lib[2:]) |
| else: |
| dynamic_lib_modules.append('lib' + lib[2:]) |
| return (static_lib_modules, dynamic_lib_modules) |
| |
| |
| def ComputeDeps(self, spec): |
| """Compute the dependencies of a gyp spec. |
| |
| Returns a tuple (deps, link_deps), where each is a list of |
| filenames that will need to be put in front of make for either |
| building (deps) or linking (link_deps). |
| """ |
| deps = [] |
| link_deps = [] |
| if 'dependencies' in spec: |
| deps.extend([target_outputs[dep] for dep in spec['dependencies'] |
| if target_outputs[dep]]) |
| for dep in spec['dependencies']: |
| if dep in target_link_deps: |
| link_deps.append(target_link_deps[dep]) |
| deps.extend(link_deps) |
| return (gyp.common.uniquer(deps), gyp.common.uniquer(link_deps)) |
| |
| |
| def WriteTargetFlags(self, spec, configs, link_deps): |
| """Write Makefile code to specify the link flags and library dependencies. |
| |
| spec, configs: input from gyp. |
| link_deps: link dependency list; see ComputeDeps() |
| """ |
| config = configs[spec['default_configuration']] |
| |
| # LDFLAGS |
| ldflags = list(config.get('ldflags', [])) |
| static_flags, dynamic_flags = self.ComputeAndroidLibraryModuleNames( |
| ldflags) |
| self.WriteLn('') |
| self.WriteList(self.NormalizeLdFlags(ldflags), 'LOCAL_LDFLAGS') |
| |
| # Libraries (i.e. -lfoo) |
| libraries = gyp.common.uniquer(spec.get('libraries', [])) |
| static_libs, dynamic_libs = self.ComputeAndroidLibraryModuleNames( |
| libraries) |
| |
| # Link dependencies (i.e. libfoo.a, libfoo.so) |
| static_link_deps = [x[1] for x in link_deps if x[0] == 'static'] |
| shared_link_deps = [x[1] for x in link_deps if x[0] == 'shared'] |
| self.WriteLn('') |
| self.WriteList(static_flags + static_libs + static_link_deps, |
| 'LOCAL_STATIC_LIBRARIES') |
| self.WriteLn('# Enable grouping to fix circular references') |
| self.WriteLn('LOCAL_GROUP_STATIC_LIBRARIES := true') |
| self.WriteLn('') |
| self.WriteList(dynamic_flags + dynamic_libs + shared_link_deps, |
| 'LOCAL_SHARED_LIBRARIES') |
| |
| |
| def WriteTarget(self, spec, configs, deps, link_deps, part_of_all): |
| """Write Makefile code to produce the final target of the gyp spec. |
| |
| spec, configs: input from gyp. |
| deps, link_deps: dependency lists; see ComputeDeps() |
| part_of_all: flag indicating this target is part of 'all' |
| """ |
| self.WriteLn('### Rules for final target.') |
| |
| if self.type != 'none': |
| self.WriteTargetFlags(spec, configs, link_deps) |
| |
| # Add to the set of targets which represent the gyp 'all' target. We use the |
| # name 'gyp_all_modules' as the Android build system doesn't allow the use |
| # of the Make target 'all' and because 'all_modules' is the equivalent of |
| # the Make target 'all' on Android. |
| if part_of_all: |
| self.WriteLn('# Add target alias to "gyp_all_modules" target.') |
| self.WriteLn('.PHONY: gyp_all_modules') |
| self.WriteLn('gyp_all_modules: %s' % self.android_module) |
| self.WriteLn('') |
| |
| # Add an alias from the gyp target name to the Android module name. This |
| # simplifies manual builds of the target, and is required by the test |
| # framework. |
| if self.target != self.android_module: |
| self.WriteLn('# Alias gyp target name.') |
| self.WriteLn('.PHONY: %s' % self.target) |
| self.WriteLn('%s: %s' % (self.target, self.android_module)) |
| self.WriteLn('') |
| |
| # Add the command to trigger build of the target type depending |
| # on the toolset. Ex: BUILD_STATIC_LIBRARY vs. BUILD_HOST_STATIC_LIBRARY |
| # NOTE: This has to come last! |
| modifier = '' |
| if self.toolset == 'host': |
| modifier = 'HOST_' |
| if self.type == 'static_library': |
| self.WriteLn('include $(BUILD_%sSTATIC_LIBRARY)' % modifier) |
| elif self.type == 'shared_library': |
| self.WriteLn('LOCAL_PRELINK_MODULE := false') |
| self.WriteLn('include $(BUILD_%sSHARED_LIBRARY)' % modifier) |
| elif self.type == 'executable': |
| if self.toolset == 'host': |
| self.WriteLn('LOCAL_MODULE_PATH := $(gyp_shared_intermediate_dir)') |
| else: |
| # Don't install target executables for now, as it results in them being |
| # included in ROM. This can be revisited if there's a reason to install |
| # them later. |
| self.WriteLn('LOCAL_UNINSTALLABLE_MODULE := true') |
| self.WriteLn('include $(BUILD_%sEXECUTABLE)' % modifier) |
| else: |
| self.WriteLn('LOCAL_MODULE_PATH := $(PRODUCT_OUT)/gyp_stamp') |
| self.WriteLn('LOCAL_UNINSTALLABLE_MODULE := true') |
| self.WriteLn() |
| self.WriteLn('include $(BUILD_SYSTEM)/base_rules.mk') |
| self.WriteLn() |
| self.WriteLn('$(LOCAL_BUILT_MODULE): $(LOCAL_ADDITIONAL_DEPENDENCIES)') |
| self.WriteLn('\t$(hide) echo "Gyp timestamp: $@"') |
| self.WriteLn('\t$(hide) mkdir -p $(dir $@)') |
| self.WriteLn('\t$(hide) touch $@') |
| |
| |
| def WriteList(self, value_list, variable=None, prefix='', |
| quoter=make.QuoteIfNecessary, local_pathify=False): |
| """Write a variable definition that is a list of values. |
| |
| E.g. WriteList(['a','b'], 'foo', prefix='blah') writes out |
| foo = blaha blahb |
| but in a pretty-printed style. |
| """ |
| values = '' |
| if value_list: |
| value_list = [quoter(prefix + l) for l in value_list] |
| if local_pathify: |
| value_list = [self.LocalPathify(l) for l in value_list] |
| values = ' \\\n\t' + ' \\\n\t'.join(value_list) |
| self.fp.write('%s :=%s\n\n' % (variable, values)) |
| |
| |
| def WriteLn(self, text=''): |
| self.fp.write(text + '\n') |
| |
| |
| def LocalPathify(self, path): |
| """Convert a subdirectory-relative path into a normalized path which starts |
| with the make variable $(LOCAL_PATH) (i.e. the top of the project tree). |
| Absolute paths, or paths that contain variables, are just normalized.""" |
| if '$(' in path or os.path.isabs(path): |
| # path is not a file in the project tree in this case, but calling |
| # normpath is still important for trimming trailing slashes. |
| return os.path.normpath(path) |
| local_path = os.path.join('$(LOCAL_PATH)', self.path, path) |
| local_path = os.path.normpath(local_path) |
| # Check that normalizing the path didn't ../ itself out of $(LOCAL_PATH) |
| # - i.e. that the resulting path is still inside the project tree. The |
| # path may legitimately have ended up containing just $(LOCAL_PATH), though, |
| # so we don't look for a slash. |
| assert local_path.startswith('$(LOCAL_PATH)'), ( |
| 'Path %s attempts to escape from gyp path %s !)' % (path, self.path)) |
| return local_path |
| |
| |
| def ExpandInputRoot(self, template, expansion, dirname): |
| if '%(INPUT_ROOT)s' not in template and '%(INPUT_DIRNAME)s' not in template: |
| return template |
| path = template % { |
| 'INPUT_ROOT': expansion, |
| 'INPUT_DIRNAME': dirname, |
| } |
| return path |
| |
| |
| def WriteAutoRegenerationRule(params, root_makefile, makefile_name, |
| build_files): |
| """Write the target to regenerate the Makefile.""" |
| options = params['options'] |
| # Sort to avoid non-functional changes to makefile. |
| build_files = sorted([os.path.join('$(LOCAL_PATH)', f) for f in build_files]) |
| build_files_args = [gyp.common.RelativePath(filename, options.toplevel_dir) |
| for filename in params['build_files_arg']] |
| build_files_args = [os.path.join('$(PRIVATE_LOCAL_PATH)', f) |
| for f in build_files_args] |
| gyp_binary = gyp.common.FixIfRelativePath(params['gyp_binary'], |
| options.toplevel_dir) |
| makefile_path = os.path.join('$(LOCAL_PATH)', makefile_name) |
| if not gyp_binary.startswith(os.sep): |
| gyp_binary = os.path.join('.', gyp_binary) |
| root_makefile.write('GYP_FILES := \\\n %s\n\n' % |
| '\\\n '.join(map(Sourceify, build_files))) |
| root_makefile.write('%s: PRIVATE_LOCAL_PATH := $(LOCAL_PATH)\n' % |
| makefile_path) |
| root_makefile.write('%s: $(GYP_FILES)\n' % makefile_path) |
| root_makefile.write('\techo ACTION Regenerating $@\n\t%s\n\n' % |
| gyp.common.EncodePOSIXShellList([gyp_binary, '-fandroid'] + |
| gyp.RegenerateFlags(options) + |
| build_files_args)) |
| |
| |
| def GenerateOutput(target_list, target_dicts, data, params): |
| options = params['options'] |
| generator_flags = params.get('generator_flags', {}) |
| builddir_name = generator_flags.get('output_dir', 'out') |
| limit_to_target_all = generator_flags.get('limit_to_target_all', False) |
| android_top_dir = os.environ.get('ANDROID_BUILD_TOP') |
| assert android_top_dir, '$ANDROID_BUILD_TOP not set; you need to run lunch.' |
| |
| def CalculateMakefilePath(build_file, base_name): |
| """Determine where to write a Makefile for a given gyp file.""" |
| # Paths in gyp files are relative to the .gyp file, but we want |
| # paths relative to the source root for the master makefile. Grab |
| # the path of the .gyp file as the base to relativize against. |
| # E.g. "foo/bar" when we're constructing targets for "foo/bar/baz.gyp". |
| base_path = gyp.common.RelativePath(os.path.dirname(build_file), |
| options.depth) |
| # We write the file in the base_path directory. |
| output_file = os.path.join(options.depth, base_path, base_name) |
| assert not options.generator_output, ( |
| 'The Android backend does not support options.generator_output.') |
| base_path = gyp.common.RelativePath(os.path.dirname(build_file), |
| options.toplevel_dir) |
| return base_path, output_file |
| |
| # TODO: search for the first non-'Default' target. This can go |
| # away when we add verification that all targets have the |
| # necessary configurations. |
| default_configuration = None |
| toolsets = set([target_dicts[target]['toolset'] for target in target_list]) |
| for target in target_list: |
| spec = target_dicts[target] |
| if spec['default_configuration'] != 'Default': |
| default_configuration = spec['default_configuration'] |
| break |
| if not default_configuration: |
| default_configuration = 'Default' |
| |
| srcdir = '.' |
| makefile_name = 'GypAndroid' + options.suffix + '.mk' |
| makefile_path = os.path.join(options.toplevel_dir, makefile_name) |
| assert not options.generator_output, ( |
| 'The Android backend does not support options.generator_output.') |
| make.ensure_directory_exists(makefile_path) |
| root_makefile = open(makefile_path, 'w') |
| |
| root_makefile.write(header) |
| |
| # We set LOCAL_PATH just once, here, to the top of the project tree. This |
| # allows all the other paths we use to be relative to the Android.mk file, |
| # as the Android build system expects. |
| root_makefile.write('\nLOCAL_PATH := $(call my-dir)\n') |
| |
| # Find the list of targets that derive from the gyp file(s) being built. |
| needed_targets = set() |
| for build_file in params['build_files']: |
| for target in gyp.common.AllTargets(target_list, target_dicts, build_file): |
| needed_targets.add(target) |
| |
| build_files = set() |
| include_list = set() |
| android_modules = {} |
| for qualified_target in target_list: |
| build_file, target, toolset = gyp.common.ParseQualifiedTarget( |
| qualified_target) |
| build_files.add(gyp.common.RelativePath(build_file, options.toplevel_dir)) |
| included_files = data[build_file]['included_files'] |
| for included_file in included_files: |
| # The included_files entries are relative to the dir of the build file |
| # that included them, so we have to undo that and then make them relative |
| # to the root dir. |
| relative_include_file = gyp.common.RelativePath( |
| gyp.common.UnrelativePath(included_file, build_file), |
| options.toplevel_dir) |
| abs_include_file = os.path.abspath(relative_include_file) |
| # If the include file is from the ~/.gyp dir, we should use absolute path |
| # so that relocating the src dir doesn't break the path. |
| if (params['home_dot_gyp'] and |
| abs_include_file.startswith(params['home_dot_gyp'])): |
| build_files.add(abs_include_file) |
| else: |
| build_files.add(relative_include_file) |
| |
| base_path, output_file = CalculateMakefilePath(build_file, |
| target + '.' + toolset + options.suffix + '.mk') |
| |
| spec = target_dicts[qualified_target] |
| configs = spec['configurations'] |
| |
| part_of_all = (qualified_target in needed_targets and |
| not int(spec.get('suppress_wildcard', False))) |
| if limit_to_target_all and not part_of_all: |
| continue |
| writer = AndroidMkWriter(android_top_dir) |
| android_module = writer.Write(qualified_target, base_path, output_file, |
| spec, configs, part_of_all=part_of_all) |
| if android_module in android_modules: |
| print ('ERROR: Android module names must be unique. The following ' |
| 'targets both generate Android module name %s.\n %s\n %s' % |
| (android_module, android_modules[android_module], |
| qualified_target)) |
| return |
| android_modules[android_module] = qualified_target |
| |
| # Our root_makefile lives at the source root. Compute the relative path |
| # from there to the output_file for including. |
| mkfile_rel_path = gyp.common.RelativePath(output_file, |
| os.path.dirname(makefile_path)) |
| include_list.add(mkfile_rel_path) |
| |
| # Some tools need to know the absolute path of the top directory. |
| root_makefile.write('GYP_ABS_ANDROID_TOP_DIR := $(shell pwd)\n') |
| |
| # Write out the sorted list of includes. |
| root_makefile.write('\n') |
| for include_file in sorted(include_list): |
| root_makefile.write('include $(LOCAL_PATH)/' + include_file + '\n') |
| root_makefile.write('\n') |
| |
| if generator_flags.get('auto_regeneration', True): |
| WriteAutoRegenerationRule(params, root_makefile, makefile_name, build_files) |
| |
| root_makefile.write(SHARED_FOOTER) |
| |
| root_makefile.close() |