diff options
Diffstat (limited to 'python/mozbuild/mozbuild/backend/android_eclipse.py')
-rw-r--r-- | python/mozbuild/mozbuild/backend/android_eclipse.py | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/python/mozbuild/mozbuild/backend/android_eclipse.py b/python/mozbuild/mozbuild/backend/android_eclipse.py new file mode 100644 index 000000000..f17eb8d34 --- /dev/null +++ b/python/mozbuild/mozbuild/backend/android_eclipse.py @@ -0,0 +1,267 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from __future__ import absolute_import, unicode_literals + +import itertools +import os +import time +import types +import xml.dom.minidom as minidom +import xml.etree.ElementTree as ET + +from mozpack.copier import FileCopier +from mozpack.files import (FileFinder, PreprocessedFile) +from mozpack.manifests import InstallManifest +import mozpack.path as mozpath + +from .common import CommonBackend +from ..frontend.data import ( + AndroidEclipseProjectData, + ContextDerived, + ContextWrapped, +) +from ..makeutil import Makefile +from ..util import ensureParentDir +from mozbuild.base import ( + ExecutionSummary, + MachCommandConditions, +) + + +def pretty_print(element): + """Return a pretty-printed XML string for an Element. + """ + s = ET.tostring(element, 'utf-8') + # minidom wraps element in a Document node; firstChild strips it. + return minidom.parseString(s).firstChild.toprettyxml(indent=' ') + + +class AndroidEclipseBackend(CommonBackend): + """Backend that generates Android Eclipse project files. + """ + def __init__(self, environment): + if not MachCommandConditions.is_android(environment): + raise Exception( + 'The Android Eclipse backend is not available with this ' + 'configuration.') + + super(AndroidEclipseBackend, self).__init__(environment) + + def summary(self): + return ExecutionSummary( + 'AndroidEclipse backend executed in {execution_time:.2f}s\n' + 'Wrote {projects:d} Android Eclipse projects to {path:s}; ' + '{created:d} created; {updated:d} updated', + execution_time=self._execution_time, + projects=self._created_count + self._updated_count, + path=mozpath.join(self.environment.topobjdir, 'android_eclipse'), + created=self._created_count, + updated=self._updated_count, + ) + + def consume_object(self, obj): + """Write out Android Eclipse project files.""" + + if not isinstance(obj, ContextDerived): + return False + + if CommonBackend.consume_object(self, obj): + # If CommonBackend acknowledged the object, we're done with it. + return True + + # Handle the one case we care about specially. + if isinstance(obj, ContextWrapped) and isinstance(obj.wrapped, AndroidEclipseProjectData): + self._process_android_eclipse_project_data(obj.wrapped, obj.srcdir, obj.objdir) + + # We don't want to handle most things, so we just acknowledge all objects + return True + + def consume_finished(self): + """The common backend handles WebIDL and test files. We don't handle + these, so we don't call our superclass. + """ + + def _Element_for_classpathentry(self, cpe): + """Turn a ClassPathEntry into an XML Element, like one of: + <classpathentry including="**/*.java" kind="src" path="preprocessed"/> + <classpathentry including="**/*.java" excluding="org/mozilla/gecko/Excluded.java|org/mozilla/gecko/SecondExcluded.java" kind="src" path="src"/> + <classpathentry including="**/*.java" kind="src" path="thirdparty"> + <attributes> + <attribute name="ignore_optional_problems" value="true"/> + </attributes> + </classpathentry> + """ + e = ET.Element('classpathentry') + e.set('kind', 'src') + e.set('including', '**/*.java') + e.set('path', cpe.path) + if cpe.exclude_patterns: + e.set('excluding', '|'.join(sorted(cpe.exclude_patterns))) + if cpe.ignore_warnings: + attrs = ET.SubElement(e, 'attributes') + attr = ET.SubElement(attrs, 'attribute') + attr.set('name', 'ignore_optional_problems') + attr.set('value', 'true') + return e + + def _Element_for_referenced_project(self, name): + """Turn a referenced project name into an XML Element, like: + <classpathentry combineaccessrules="false" kind="src" path="/Fennec"/> + """ + e = ET.Element('classpathentry') + e.set('kind', 'src') + e.set('combineaccessrules', 'false') + # All project directories are in the same root; this + # reference is absolute in the Eclipse namespace. + e.set('path', '/' + name) + return e + + def _Element_for_extra_jar(self, name): + """Turn a referenced JAR name into an XML Element, like: + <classpathentry exported="true" kind="lib" path="/Users/nalexander/Mozilla/gecko-dev/build/mobile/robocop/robotium-solo-4.3.1.jar"/> + """ + e = ET.Element('classpathentry') + e.set('kind', 'lib') + e.set('exported', 'true') + e.set('path', name) + return e + + def _Element_for_filtered_resources(self, filtered_resources): + """Turn a list of filtered resource arguments like + ['1.0-projectRelativePath-matches-false-false-*org/mozilla/gecko/resources/**'] + into an XML Element, like: + <filteredResources> + <filter> + <id>1393009101322</id> + <name></name> + <type>30</type> + <matcher> + <id>org.eclipse.ui.ide.multiFilter</id> + <arguments>1.0-projectRelativePath-matches-false-false-*org/mozilla/gecko/resources/**</arguments> + </matcher> + </filter> + </filteredResources> + + The id is random; the values are magic.""" + + id = int(1000 * time.time()) + filteredResources = ET.Element('filteredResources') + for arg in sorted(filtered_resources): + e = ET.SubElement(filteredResources, 'filter') + ET.SubElement(e, 'id').text = str(id) + id += 1 + ET.SubElement(e, 'name') + ET.SubElement(e, 'type').text = '30' # It's magic! + matcher = ET.SubElement(e, 'matcher') + ET.SubElement(matcher, 'id').text = 'org.eclipse.ui.ide.multiFilter' + ET.SubElement(matcher, 'arguments').text = str(arg) + return filteredResources + + def _manifest_for_project(self, srcdir, project): + manifest = InstallManifest() + + if project.manifest: + manifest.add_copy(mozpath.join(srcdir, project.manifest), 'AndroidManifest.xml') + + if project.res: + manifest.add_symlink(mozpath.join(srcdir, project.res), 'res') + else: + # Eclipse expects a res directory no matter what, so we + # make an empty directory if the project doesn't specify. + res = os.path.abspath(mozpath.join(os.path.dirname(__file__), + 'templates', 'android_eclipse_empty_resource_directory')) + manifest.add_pattern_copy(res, '.**', 'res') + + if project.assets: + manifest.add_symlink(mozpath.join(srcdir, project.assets), 'assets') + + for cpe in project._classpathentries: + manifest.add_symlink(mozpath.join(srcdir, cpe.srcdir), cpe.dstdir) + + # JARs and native libraries go in the same place. For now, we're adding + # class path entries with the full path to required JAR files (which + # makes sense for JARs in the source directory, but probably doesn't for + # JARs in the object directory). This could be a problem because we only + # know the contents of (a subdirectory of) libs/ after a successful + # build and package, which is after build-backend time. At the cost of + # some flexibility, we explicitly copy certain libraries here; if the + # libraries aren't present -- namely, when the tree hasn't been packaged + # -- this fails. That's by design, to avoid crashes on device caused by + # missing native libraries. + for src, dst in project.libs: + manifest.add_copy(mozpath.join(srcdir, src), dst) + + return manifest + + def _process_android_eclipse_project_data(self, data, srcdir, objdir): + # This can't be relative to the environment's topsrcdir, + # because during testing topsrcdir is faked. + template_directory = os.path.abspath(mozpath.join(os.path.dirname(__file__), + 'templates', 'android_eclipse')) + + project_directory = mozpath.join(self.environment.topobjdir, 'android_eclipse', data.name) + manifest_path = mozpath.join(self.environment.topobjdir, 'android_eclipse', '%s.manifest' % data.name) + + manifest = self._manifest_for_project(srcdir, data) + ensureParentDir(manifest_path) + manifest.write(path=manifest_path) + + classpathentries = [] + for cpe in sorted(data._classpathentries, key=lambda x: x.path): + e = self._Element_for_classpathentry(cpe) + classpathentries.append(ET.tostring(e)) + + for name in sorted(data.referenced_projects): + e = self._Element_for_referenced_project(name) + classpathentries.append(ET.tostring(e)) + + for name in sorted(data.extra_jars): + e = self._Element_for_extra_jar(mozpath.join(srcdir, name)) + classpathentries.append(ET.tostring(e)) + + defines = {} + defines['IDE_OBJDIR'] = objdir + defines['IDE_TOPOBJDIR'] = self.environment.topobjdir + defines['IDE_SRCDIR'] = srcdir + defines['IDE_TOPSRCDIR'] = self.environment.topsrcdir + defines['IDE_PROJECT_NAME'] = data.name + defines['IDE_PACKAGE_NAME'] = data.package_name + defines['IDE_PROJECT_DIRECTORY'] = project_directory + defines['IDE_RELSRCDIR'] = mozpath.relpath(srcdir, self.environment.topsrcdir) + defines['IDE_CLASSPATH_ENTRIES'] = '\n'.join('\t' + cpe for cpe in classpathentries) + defines['IDE_RECURSIVE_MAKE_TARGETS'] = ' '.join(sorted(data.recursive_make_targets)) + # Like android.library=true + defines['IDE_PROJECT_LIBRARY_SETTING'] = 'android.library=true' if data.is_library else '' + # Like android.library.reference.1=FennecBrandingResources + defines['IDE_PROJECT_LIBRARY_REFERENCES'] = '\n'.join( + 'android.library.reference.%s=%s' % (i + 1, ref) + for i, ref in enumerate(sorted(data.included_projects))) + if data.filtered_resources: + filteredResources = self._Element_for_filtered_resources(data.filtered_resources) + defines['IDE_PROJECT_FILTERED_RESOURCES'] = pretty_print(filteredResources).strip() + else: + defines['IDE_PROJECT_FILTERED_RESOURCES'] = '' + defines['ANDROID_TARGET_SDK'] = self.environment.substs['ANDROID_TARGET_SDK'] + defines['MOZ_ANDROID_MIN_SDK_VERSION'] = self.environment.defines['MOZ_ANDROID_MIN_SDK_VERSION'] + + copier = FileCopier() + finder = FileFinder(template_directory) + for input_filename, f in itertools.chain(finder.find('**'), finder.find('.**')): + if input_filename == 'AndroidManifest.xml' and not data.is_library: + # Main projects supply their own manifests. + continue + copier.add(input_filename, PreprocessedFile( + mozpath.join(finder.base, input_filename), + depfile_path=None, + marker='#', + defines=defines, + extra_depends={mozpath.join(finder.base, input_filename)})) + + # When we re-create the build backend, we kill everything that was there. + if os.path.isdir(project_directory): + self._updated_count += 1 + else: + self._created_count += 1 + copier.copy(project_directory, skip_if_older=False, remove_unaccounted=True) |