| # 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 |
| |
| |
| 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 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) |