summaryrefslogtreecommitdiffstats
path: root/python/mozbuild/mozbuild/backend/android_eclipse.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/mozbuild/mozbuild/backend/android_eclipse.py')
-rw-r--r--python/mozbuild/mozbuild/backend/android_eclipse.py267
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)