Bug 853045 - Part 3: Add AndroidEclipseBackend. r=gps

test_recursive_make.py depends on the test data added by this commit, so
it's here rather than in the earlier commit.
This commit is contained in:
Nick Alexander
2014-02-13 22:19:49 -08:00
parent c8a1c6486b
commit 1da09f1f95
17 changed files with 436 additions and 0 deletions

View File

@@ -0,0 +1,190 @@
# 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 unicode_literals
import itertools
import os
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,
SandboxDerived,
SandboxWrapped,
)
from ..makeutil import Makefile
from ..util import ensureParentDir
class AndroidEclipseBackend(CommonBackend):
"""Backend that generates Android Eclipse project files.
"""
def _init(self):
CommonBackend._init(self)
def consume_object(self, obj):
"""Write out Android Eclipse project files."""
if not isinstance(obj, SandboxDerived):
return
CommonBackend.consume_object(self, obj)
# If CommonBackend acknowledged the object, we're done with it.
if obj._ack:
return
# We don't want to handle most things, so we just acknowledge all objects...
obj.ack()
# ... and handle the one case we care about specially.
if isinstance(obj, SandboxWrapped) and isinstance(obj.wrapped, AndroidEclipseProjectData):
self._process_android_eclipse_project_data(obj.wrapped, obj.srcdir, obj.objdir)
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 kind="lib" path="libs/robotium-solo-4.3.1.jar"/>
"""
e = ET.Element('classpathentry')
e.set('kind', 'lib')
# All project directories are in the same root; this
# reference is absolute in the Eclipse namespace.
e.set('path', 'libs/' + name)
return e
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')
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. This
# wouldn't be a problem, except we only know the contents of
# (a subdirectory of) libs/ after a successful build and
# package, which is after build-backend time. So we use a
# pattern symlink that is resolved at manifest install time.
if project.libs:
manifest.add_pattern_copy(mozpath.join(srcdir, project.libs), '**', 'libs')
for extra_jar in sorted(project.extra_jars):
manifest.add_copy(mozpath.join(srcdir, extra_jar), mozpath.join('libs', os.path.basename(extra_jar)))
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(os.path.basename(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)))
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.
copier.copy(project_directory, skip_if_older=False, remove_unaccounted=True)

View File

@@ -0,0 +1 @@
<string name="label">library1</string>

View File

@@ -0,0 +1 @@
<!-- Placeholder. -->

View File

@@ -0,0 +1 @@
<!-- Placeholder. -->

View File

@@ -0,0 +1 @@
# Placeholder.

View File

@@ -0,0 +1 @@
# Placeholder.

View File

@@ -0,0 +1 @@
<string name="label">main1</string>

View File

@@ -0,0 +1 @@
<!-- Placeholder. -->

View File

@@ -0,0 +1 @@
package a.a;

View File

@@ -0,0 +1 @@
package b;

View File

@@ -0,0 +1 @@
package d.e;

View File

@@ -0,0 +1 @@
<!-- Placeholder. -->

View File

@@ -0,0 +1,37 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# Any copyright is dedicated to the Public Domain.
# http://creativecommons.org/publicdomain/zero/1.0/
p = add_android_eclipse_library_project('library1')
p.package_name = 'org.mozilla.test.library1'
p.res = 'library1/resources'
p = add_android_eclipse_library_project('library2')
p.package_name = 'org.mozilla.test.library2'
p = add_android_eclipse_project('main1', 'main1/AndroidManifest.xml')
p.package_name = 'org.mozilla.test.main1'
p.recursive_make_targets += ['target1', 'target2']
p = add_android_eclipse_project('main2', 'main2/AndroidManifest.xml')
p.package_name = 'org.mozilla.test.main2'
p.res = 'main2/res'
p.assets = 'main2/assets'
p.extra_jars = ['main2/extra.jar']
p = add_android_eclipse_project('main3', 'main3/AndroidManifest.xml')
p.package_name = 'org.mozilla.test.main3'
cpe = p.add_classpathentry('a', 'main3/a', dstdir='a/a')
cpe = p.add_classpathentry('b', 'main3/b', dstdir='b')
cpe.exclude_patterns += ['b/Excludes.java', 'b/Excludes2.java']
cpe = p.add_classpathentry('c', 'main3/c', dstdir='d/e')
cpe.ignore_warnings = True
p = add_android_eclipse_project('main4', 'main3/AndroidManifest.xml')
p.package_name = 'org.mozilla.test.main3'
p.referenced_projects += ['library1']
p.included_projects += ['library2']
p.recursive_make_targets += ['target3', 'target4']
DIRS += ['subdir']

View File

@@ -0,0 +1,13 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# Any copyright is dedicated to the Public Domain.
# http://creativecommons.org/publicdomain/zero/1.0/
DEFINES['FOO'] = 'FOO'
p = add_android_eclipse_library_project('sublibrary')
p.package_name = 'org.mozilla.test.sublibrary'
p.is_library = True
p = add_android_eclipse_project('submain', 'submain/AndroidManifest.xml')
p.package_name = 'org.mozilla.test.submain'
p.recursive_make_targets += ['subtarget1', 'subtarget2']

View File

@@ -0,0 +1 @@
<!-- Placeholder. -->

View File

@@ -0,0 +1,151 @@
# 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 unicode_literals
import json
import os
import unittest
from mozbuild.backend.android_eclipse import AndroidEclipseBackend
from mozbuild.frontend.emitter import TreeMetadataEmitter
from mozbuild.frontend.reader import BuildReader
from mozbuild.test.backend.common import BackendTester
from mozpack.manifests import InstallManifest
from mozunit import main
import mozpack.path as mozpath
class TestAndroidEclipseBackend(BackendTester):
def __init__(self, *args, **kwargs):
BackendTester.__init__(self, *args, **kwargs)
self.env = None
def assertExists(self, *args):
p = mozpath.join(self.env.topobjdir, 'android_eclipse', *args)
self.assertTrue(os.path.exists(p), "Path %s exists" % p)
def assertNotExists(self, *args):
p = mozpath.join(self.env.topobjdir, 'android_eclipse', *args)
self.assertFalse(os.path.exists(p), "Path %s does not exist" % p)
def test_library_project_files(self):
"""Ensure we generate reasonable files for library projects."""
self.env = self._consume('android_eclipse', AndroidEclipseBackend)
for f in ['.classpath',
'.project',
'.settings',
'AndroidManifest.xml',
'project.properties']:
self.assertExists('library1', f)
def test_main_project_files(self):
"""Ensure we generate reasonable files for main (non-library) projects."""
self.env = self._consume('android_eclipse', AndroidEclipseBackend)
for f in ['.classpath',
'.externalToolBuilders',
'.project',
'.settings',
'build.xml',
'gen',
'lint.xml',
'project.properties']:
self.assertExists('main1', f)
def test_library_manifest(self):
"""Ensure we generate manifest for library projects."""
self.env = self._consume('android_eclipse', AndroidEclipseBackend)
self.assertExists('library1', 'AndroidManifest.xml')
def test_classpathentries(self):
"""Ensure we produce reasonable classpathentries."""
self.env = self._consume('android_eclipse', AndroidEclipseBackend)
self.assertExists('main3', '.classpath')
# This is brittle but simple.
with open(mozpath.join(self.env.topobjdir, 'android_eclipse', 'main3', '.classpath'), 'rt') as fh:
lines = fh.readlines()
lines = [line.strip() for line in lines]
self.assertIn('<classpathentry including="**/*.java" kind="src" path="a" />', lines)
self.assertIn('<classpathentry excluding="b/Excludes.java|b/Excludes2.java" including="**/*.java" kind="src" path="b" />', lines)
self.assertIn('<classpathentry including="**/*.java" kind="src" path="c"><attributes><attribute name="ignore_optional_problems" value="true" /></attributes></classpathentry>', lines)
def test_library_project_setting(self):
"""Ensure we declare a library project correctly."""
self.env = self._consume('android_eclipse', AndroidEclipseBackend)
self.assertExists('library1', 'project.properties')
with open(mozpath.join(self.env.topobjdir, 'android_eclipse', 'library1', 'project.properties'), 'rt') as fh:
lines = fh.readlines()
lines = [line.strip() for line in lines]
self.assertIn('android.library=true', lines)
self.assertExists('main1', 'project.properties')
with open(mozpath.join(self.env.topobjdir, 'android_eclipse', 'main1', 'project.properties'), 'rt') as fh:
lines = fh.readlines()
lines = [line.strip() for line in lines]
self.assertNotIn('android.library=true', lines)
def test_referenced_projects(self):
"""Ensure we reference another project correctly."""
self.env = self._consume('android_eclipse', AndroidEclipseBackend)
self.assertExists('main4', '.classpath')
# This is brittle but simple.
with open(mozpath.join(self.env.topobjdir, 'android_eclipse', 'main4', '.classpath'), 'rt') as fh:
lines = fh.readlines()
lines = [line.strip() for line in lines]
self.assertIn('<classpathentry combineaccessrules="false" kind="src" path="/library1" />', lines)
def test_included_projects(self):
"""Ensure we include another project correctly."""
self.env = self._consume('android_eclipse', AndroidEclipseBackend)
self.assertExists('main4', 'project.properties')
# This is brittle but simple.
with open(mozpath.join(self.env.topobjdir, 'android_eclipse', 'main4', 'project.properties'), 'rt') as fh:
lines = fh.readlines()
lines = [line.strip() for line in lines]
self.assertIn('android.library.reference.1=library2', lines)
def assertInManifest(self, project_name, *args):
manifest_path = mozpath.join(self.env.topobjdir, 'android_eclipse', '%s.manifest' % project_name)
manifest = InstallManifest(manifest_path)
for arg in args:
self.assertIn(arg, manifest, '%s in manifest for project %s' % (arg, project_name))
def assertNotInManifest(self, project_name, *args):
manifest_path = mozpath.join(self.env.topobjdir, 'android_eclipse', '%s.manifest' % project_name)
manifest = InstallManifest(manifest_path)
for arg in args:
self.assertNotIn(arg, manifest, '%s not in manifest for project %s' % (arg, project_name))
def test_manifest_main_manifest(self):
"""Ensure we symlink manifest if asked to for main projects."""
self.env = self._consume('android_eclipse', AndroidEclipseBackend)
self.assertInManifest('main1', 'AndroidManifest.xml')
def test_manifest_res(self):
"""Ensure we symlink res/ iff asked to."""
self.env = self._consume('android_eclipse', AndroidEclipseBackend)
self.assertInManifest('library1', 'res')
self.assertNotInManifest('library2', 'res')
def test_manifest_classpathentries(self):
"""Ensure we symlink classpathentries correctly."""
self.env = self._consume('android_eclipse', AndroidEclipseBackend)
self.assertInManifest('main3', 'a/a', 'b', 'd/e')
def test_manifest_assets(self):
"""Ensure we symlink assets/ iff asked to."""
self.env = self._consume('android_eclipse', AndroidEclipseBackend)
self.assertNotInManifest('main1', 'assets')
self.assertInManifest('main2', 'assets')
def test_manifest_extra_jars(self):
"""Ensure we symlink extra jars iff asked to."""
self.env = self._consume('android_eclipse', AndroidEclipseBackend)
self.assertNotInManifest('main1', 'libs')
self.assertInManifest('main2', 'libs/extra.jar')
if __name__ == '__main__':
main()

View File

@@ -616,6 +616,39 @@ class TestRecursiveMakeBackend(BackendTester):
self.assertIn('JAR_MANIFEST := %s/jar.mn' % env.topsrcdir, lines)
def test_android_eclipse(self):
env = self._consume('android_eclipse', RecursiveMakeBackend)
with open(mozpath.join(env.topobjdir, 'backend.mk'), 'rb') as fh:
lines = fh.readlines()
lines = [line.rstrip() for line in lines]
# Dependencies first.
self.assertIn('ANDROID_ECLIPSE_PROJECT_main1: target1 target2', lines)
self.assertIn('ANDROID_ECLIPSE_PROJECT_main4: target3 target4', lines)
command_template = '\t$(call py_action,process_install_manifest,' + \
'--no-remove --no-remove-all-directory-symlinks ' + \
'--no-remove-empty-directories %s %s.manifest)'
# Commands second.
for project_name in ['main1', 'main2', 'library1', 'library2']:
stem = '%s/android_eclipse/%s' % (env.topobjdir, project_name)
self.assertIn(command_template % (stem, stem), lines)
# Projects declared in subdirectories.
with open(mozpath.join(env.topobjdir, 'subdir', 'backend.mk'), 'rb') as fh:
lines = fh.readlines()
lines = [line.rstrip() for line in lines]
self.assertIn('ANDROID_ECLIPSE_PROJECT_submain: subtarget1 subtarget2', lines)
for project_name in ['submain', 'sublibrary']:
# Destination and install manifest are relative to topobjdir.
stem = '%s/android_eclipse/%s' % (env.topobjdir, project_name)
self.assertIn(command_template % (stem, stem), lines)
if __name__ == '__main__':
main()